KHtml

kjs_range.cpp
1 /*
2  * This file is part of the KDE libraries
3  * Copyright (C) 2001 Peter Kelly ([email protected])
4  * Copyright (C) 2003 Apple Computer, Inc.
5  * Copyright (C) 2009 Maksim Orlovich ([email protected])
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 #include "kjs_range.h"
23 #include "kjs_range.lut.h"
24 #include "khtml_part.h"
25 #include "dom/dom_exception.h"
26 #include "dom/dom2_range.h"
27 #include "khtml_debug.h"
28 
29 using DOM::DOMException;
30 
31 namespace KJS
32 {
33 // -------------------------------------------------------------------------
34 
35 const ClassInfo DOMRange::info = { "Range", nullptr, &DOMRangeTable, nullptr };
36 /*
37 @begin DOMRangeTable 7
38  startContainer DOMRange::StartContainer DontDelete|ReadOnly
39  startOffset DOMRange::StartOffset DontDelete|ReadOnly
40  endContainer DOMRange::EndContainer DontDelete|ReadOnly
41  endOffset DOMRange::EndOffset DontDelete|ReadOnly
42  collapsed DOMRange::Collapsed DontDelete|ReadOnly
43  commonAncestorContainer DOMRange::CommonAncestorContainer DontDelete|ReadOnly
44 @end
45 @begin DOMRangeProtoTable 17
46 setStart DOMRange::SetStart DontDelete|Function 2
47  setEnd DOMRange::SetEnd DontDelete|Function 2
48  setStartBefore DOMRange::SetStartBefore DontDelete|Function 1
49  setStartAfter DOMRange::SetStartAfter DontDelete|Function 1
50  setEndBefore DOMRange::SetEndBefore DontDelete|Function 1
51  setEndAfter DOMRange::SetEndAfter DontDelete|Function 1
52  collapse DOMRange::Collapse DontDelete|Function 1
53  selectNode DOMRange::SelectNode DontDelete|Function 1
54  selectNodeContents DOMRange::SelectNodeContents DontDelete|Function 1
55  compareBoundaryPoints DOMRange::CompareBoundaryPoints DontDelete|Function 2
56  deleteContents DOMRange::DeleteContents DontDelete|Function 0
57  extractContents DOMRange::ExtractContents DontDelete|Function 0
58  cloneContents DOMRange::CloneContents DontDelete|Function 0
59  insertNode DOMRange::InsertNode DontDelete|Function 1
60  surroundContents DOMRange::SurroundContents DontDelete|Function 1
61  cloneRange DOMRange::CloneRange DontDelete|Function 0
62  toString DOMRange::ToString DontDelete|Function 0
63  detach DOMRange::Detach DontDelete|Function 0
64  createContextualFragment DOMRange::CreateContextualFragment DontDelete|Function 1
65 @end
66 */
67 KJS_DEFINE_PROTOTYPE(DOMRangeProto)
68 KJS_IMPLEMENT_PROTOFUNC(DOMRangeProtoFunc)
69 KJS_IMPLEMENT_PROTOTYPE("DOMRange", DOMRangeProto, DOMRangeProtoFunc, ObjectPrototype)
70 
71 DOMRange::DOMRange(ExecState *exec, DOM::RangeImpl *r)
72  : m_impl(r)
73 {
74  assert(r);
75  setPrototype(DOMRangeProto::self(exec));
76 }
77 
78 DOMRange::~DOMRange()
79 {
80  ScriptInterpreter::forgetDOMObject(m_impl.get());
81 }
82 
83 bool DOMRange::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
84 {
85  return getStaticValueSlot<DOMRange, DOMObject>(exec, &DOMRangeTable, this, propertyName, slot);
86 }
87 
88 JSValue *DOMRange::getValueProperty(ExecState *exec, int token) const
89 {
90  DOMExceptionTranslator exception(exec);
91  DOM::RangeImpl &range = *m_impl;
92 
93  switch (token) {
94  case StartContainer:
95  return getDOMNode(exec, range.startContainer(exception));
96  case StartOffset:
97  return jsNumber(range.startOffset(exception));
98  case EndContainer:
99  return getDOMNode(exec, range.endContainer(exception));
100  case EndOffset:
101  return jsNumber(range.endOffset(exception));
102  case Collapsed:
103  return jsBoolean(range.collapsed(exception));
104  case CommonAncestorContainer: {
105  return getDOMNode(exec, range.commonAncestorContainer(exception));
106  }
107  default:
108  // qCDebug(KHTML_LOG) << "WARNING: Unhandled token in DOMRange::getValueProperty : " << token;
109  return jsNull();
110  }
111 }
112 
113 JSValue *DOMRangeProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
114 {
115  KJS_CHECK_THIS(KJS::DOMRange, thisObj);
116  DOMExceptionTranslator exception(exec);
117  DOM::RangeImpl &range = *static_cast<DOMRange *>(thisObj)->impl();
118 
119  JSValue *result = jsUndefined();
120 
121  switch (id) {
122  case DOMRange::SetStart:
123  range.setStart(toNode(args[0]), args[1]->toInteger(exec), exception);
124  break;
125  case DOMRange::SetEnd:
126  range.setEnd(toNode(args[0]), args[1]->toInteger(exec), exception);
127  break;
128  case DOMRange::SetStartBefore:
129  range.setStartBefore(toNode(args[0]), exception);
130  break;
131  case DOMRange::SetStartAfter:
132  range.setStartAfter(toNode(args[0]), exception);
133  break;
134  case DOMRange::SetEndBefore:
135  range.setEndBefore(toNode(args[0]), exception);
136  break;
137  case DOMRange::SetEndAfter:
138  range.setEndAfter(toNode(args[0]), exception);
139  break;
140  case DOMRange::Collapse:
141  range.collapse(args[0]->toBoolean(exec), exception);
142  break;
143  case DOMRange::SelectNode:
144  range.selectNode(toNode(args[0]), exception);
145  break;
146  case DOMRange::SelectNodeContents:
147  range.selectNodeContents(toNode(args[0]), exception);
148  break;
149  case DOMRange::CompareBoundaryPoints:
150  result = jsNumber(range.compareBoundaryPoints(static_cast<DOM::Range::CompareHow>(args[0]->toInt32(exec)), toRange(args[1]), exception));
151  break;
152  case DOMRange::DeleteContents:
153  range.deleteContents(exception);
154  break;
155  case DOMRange::ExtractContents:
156  result = getDOMNode(exec, range.extractContents(exception));
157  break;
158  case DOMRange::CloneContents:
159  result = getDOMNode(exec, range.cloneContents(exception));
160  break;
161  case DOMRange::InsertNode:
162  range.insertNode(toNode(args[0]), exception);
163  break;
164  case DOMRange::SurroundContents:
165  range.surroundContents(toNode(args[0]), exception);
166  break;
167  case DOMRange::CloneRange:
168  result = getDOMRange(exec, range.cloneRange(exception));
169  break;
170  case DOMRange::ToString:
171  result = jsString(UString(range.toString(exception)));
172  break;
173  case DOMRange::Detach:
174  range.detach(exception);
175  break;
176  case DOMRange::CreateContextualFragment:
177  JSValue *value = args[0];
178  DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString();
179  DOM::DocumentFragment frag = range.createContextualFragment(str, exception);
180  result = getDOMNode(exec, frag.handle());
181  break;
182  };
183 
184  return result;
185 }
186 
187 JSValue *getDOMRange(ExecState *exec, DOM::RangeImpl *r)
188 {
189  return cacheDOMObject<DOM::RangeImpl, KJS::DOMRange>(exec, r);
190 }
191 
192 // -------------------------------------------------------------------------
193 
194 const ClassInfo RangeConstructor::info = { "RangeConstructor", nullptr, &RangeConstructorTable, nullptr };
195 /*
196 @begin RangeConstructorTable 5
197  START_TO_START DOM::Range::START_TO_START DontDelete|ReadOnly
198  START_TO_END DOM::Range::START_TO_END DontDelete|ReadOnly
199  END_TO_END DOM::Range::END_TO_END DontDelete|ReadOnly
200  END_TO_START DOM::Range::END_TO_START DontDelete|ReadOnly
201 @end
202 */
203 
204 RangeConstructor::RangeConstructor(ExecState *exec)
205  : DOMObject(exec->lexicalInterpreter()->builtinObjectPrototype())
206 {
207  putDirect(exec->propertyNames().prototype, DOMRangeProto::self(exec), DontEnum | DontDelete | ReadOnly);
208 }
209 
210 bool RangeConstructor::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
211 {
212  return getStaticValueSlot<RangeConstructor, DOMObject>(exec, &RangeConstructorTable, this, propertyName, slot);
213 }
214 
215 JSValue *RangeConstructor::getValueProperty(ExecState *, int token) const
216 {
217  return jsNumber(token);
218 }
219 
220 JSValue *getRangeConstructor(ExecState *exec)
221 {
222  return cacheGlobalObject<RangeConstructor>(exec, "[[range.constructor]]");
223 }
224 
225 DOM::RangeImpl *toRange(JSValue *val)
226 {
227  JSObject *obj = val->getObject();
228  if (!obj || !obj->inherits(&DOMRange::info)) {
229  return nullptr;
230  }
231 
232  const DOMRange *dobj = static_cast<const DOMRange *>(obj);
233  return dobj->impl();
234 }
235 
236 /* Source for RangeExceptionProtoTable.
237 @begin RangeExceptionProtoTable 2
238 BAD_BOUNDARYPOINTS_ERR DOM::RangeException::BAD_BOUNDARYPOINTS_ERR DontDelete|ReadOnly
239 INVALID_NODE_TYPE_ERR DOM::RangeException::INVALID_NODE_TYPE_ERR DontDelete|ReadOnly
240 @end
241 */
242 
243 DEFINE_CONSTANT_TABLE(RangeExceptionProto)
244 IMPLEMENT_CONSTANT_TABLE(RangeExceptionProto, "RangeException")
245 
246 IMPLEMENT_PSEUDO_CONSTRUCTOR_WITH_PARENT(RangeExceptionPseudoCtor, "RangeException",
247  RangeExceptionProto, RangeExceptionProto)
248 
249 RangeException::RangeException(ExecState *exec)
250  : DOMObject(RangeExceptionProto::self(exec))
251 {
252 }
253 
254 const ClassInfo RangeException::info = { "RangeException", nullptr, nullptr, nullptr };
255 
256 // -------------------------------------------------------------------------
257 
258 const ClassInfo DOMSelection::info = { "Selection", nullptr, &DOMSelectionTable, nullptr };
259 /*
260 @begin DOMSelectionTable 7
261  anchorNode DOMSelection::AnchorNode DontDelete|ReadOnly
262  anchorOffset DOMSelection::AnchorOffset DontDelete|ReadOnly
263  focusNode DOMSelection::FocusNode DontDelete|ReadOnly
264  focusOffset DOMSelection::FocusOffset DontDelete|ReadOnly
265  isCollapsed DOMSelection::IsCollapsed DontDelete|ReadOnly
266  rangeCount DOMSelection::RangeCount DontDelete|ReadOnly
267 @end
268 @begin DOMSelectionProtoTable 13
269  collapsed DOMSelection::Collapsed DontDelete|Function 2
270  collapseToStart DOMSelection::CollapseToStart DontDelete|Function 0
271  collapseToEnd DOMSelection::CollapseToEnd DontDelete|Function 0
272  selectAllChildren DOMSelection::SelectAllChildren DontDelete|Function 1
273  deleteFromDocument DOMSelection::DeleteFromDocument DontDelete|Function 0
274  getRangeAt DOMSelection::GetRangeAt DontDelete|Function 1
275  addRange DOMSelection::AddRange DontDelete|Function 1
276  removeRange DOMSelection::RemoveRange DontDelete|Function 1
277  removeAllRanges DOMSelection::RemoveAllRanges DontDelete|Function 0
278  toString DOMSelection::ToString DontDelete|Function 0
279 @end
280 */
281 KJS_DEFINE_PROTOTYPE(DOMSelectionProto)
282 KJS_IMPLEMENT_PROTOFUNC(DOMSelectionProtoFunc)
283 KJS_IMPLEMENT_PROTOTYPE("Selection", DOMSelectionProto, DOMSelectionProtoFunc, ObjectPrototype)
284 
285 DOMSelection::DOMSelection(ExecState *exec, DOM::DocumentImpl *parentDocument):
286  JSObject(DOMSelectionProto::self(exec)), m_document(parentDocument)
287 {}
288 
289 bool DOMSelection::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
290 {
291  // qCDebug(KHTML_LOG) << propertyName.ustring().qstring();
292  return getStaticValueSlot<DOMSelection, JSObject>(exec, &DOMSelectionTable, this, propertyName, slot);
293 }
294 
295 JSValue *DOMSelection::getValueProperty(ExecState *exec, int token) const
296 {
297  // qCDebug(KHTML_LOG) << token;
298  DOMExceptionTranslator exception(exec);
299  DOM::Selection sel = currentSelection();
300  // ### TODO: below doesn't really distinguish anchor and focus properly.
301  switch (token) {
302  case DOMSelection::AnchorNode:
303  return sel.notEmpty() ? getDOMNode(exec, sel.base().node()) : jsNull();
304  case DOMSelection::AnchorOffset:
305  return jsNumber(sel.notEmpty() ? sel.base().offset() : 0L);
306  case DOMSelection::FocusNode:
307  return sel.notEmpty() ? getDOMNode(exec, sel.extent().node()) : jsNull();
308  case DOMSelection::FocusOffset:
309  return jsNumber(sel.notEmpty() ? sel.extent().offset() : 0L);
310  case DOMSelection::IsCollapsed:
311  return jsBoolean(sel.isCollapsed() || sel.isEmpty());
312  case DOMSelection::RangeCount:
313  return sel.notEmpty() ? jsNumber(1) : jsNumber(0);
314  }
315 
316  assert(false);
317  return jsUndefined();
318 }
319 
320 JSValue *DOMSelectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
321 {
322  KJS_CHECK_THIS(KJS::DOMSelection, thisObj);
323 
324  DOMSelection *self = static_cast<DOMSelection *>(thisObj);
325  if (!self->attached()) {
326  return jsUndefined();
327  }
328 
329  DOM::Selection sel = self->currentSelection();
330 
331  DOMExceptionTranslator exception(exec);
332  switch (id) {
333  case DOMSelection::Collapsed: {
334  DOM::NodeImpl *node = toNode(args[0]);
335  int offset = args[1]->toInt32(exec);
336  if (node && node->document() == self->m_document) {
337  self->m_document->part()->setCaret(DOM::Selection(DOM::Position(node, offset)));
338  } else {
339  setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
340  }
341  break;
342  }
343 
344  case DOMSelection::CollapseToStart:
345  if (sel.notEmpty()) {
346  sel.moveTo(sel.start());
347  self->m_document->part()->setCaret(sel);
348  } else {
349  setDOMException(exec, DOMException::INVALID_STATE_ERR);
350  }
351  break;
352 
353  case DOMSelection::CollapseToEnd:
354  if (sel.notEmpty()) {
355  sel.moveTo(sel.end());
356  self->m_document->part()->setCaret(sel);
357  } else {
358  setDOMException(exec, DOMException::INVALID_STATE_ERR);
359  }
360  break;
361 
362  case DOMSelection::SelectAllChildren: {
363  DOM::NodeImpl *node = toNode(args[0]);
364  if (node && node->document() == self->m_document) {
365  DOM::RangeImpl *range = new DOM::RangeImpl(self->m_document);
366  range->selectNodeContents(node, exception);
367  self->m_document->part()->setCaret(DOM::Selection(DOM::Range(range)));
368  } else {
369  setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
370  }
371  break;
372  }
373 
374  case DOMSelection::DeleteFromDocument: {
375  self->m_document->part()->setCaret(DOM::Selection());
376  DOM::Range r = sel.toRange();
377  DOM::RangeImpl *ri = r.handle();
378  if (ri) {
379  ri->deleteContents(exception);
380  }
381  break;
382  }
383 
384  case DOMSelection::GetRangeAt: {
385  int i = args[0]->toInt32(exec);
386  if (sel.isEmpty() || i != 0) {
387  setDOMException(exec, DOMException::INDEX_SIZE_ERR);
388  } else {
389  DOM::Range r = sel.toRange();
390  return getDOMRange(exec, r.handle());
391  }
392  break;
393  }
394 
395  case DOMSelection::AddRange: {
396  // We only support a single range, so we merge the two.
397  // This does violate HTML5, though, as it's actually supposed to report the
398  // overlap twice. Perhaps this shouldn't be live?
399  DOM::RangeImpl *range = toRange(args[0]);
400 
401  if (!range) {
402  return jsUndefined();
403  }
404  if (range->ownerDocument() != self->m_document) {
405  setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
406  return jsUndefined();
407  }
408 
409  if (sel.isEmpty()) {
410  self->m_document->part()->setCaret(DOM::Selection(range));
411  return jsUndefined();
412  }
413 
414  DOM::Range ourRange = sel.toRange();
415  DOM::RangeImpl *ourRangeImpl = ourRange.handle();
416 
417  bool startExisting = ourRangeImpl->compareBoundaryPoints(DOM::Range::START_TO_START, range, exception) == -1;
418  bool endExisting = ourRangeImpl->compareBoundaryPoints(DOM::Range::END_TO_END, range, exception) == -1;
419 
420  DOM::RangeImpl *rangeForStart = startExisting ? ourRangeImpl : range;
421  DOM::RangeImpl *rangeForEnd = endExisting ? ourRangeImpl : range;
422  DOM::Position start = DOM::Position(rangeForStart->startContainer(exception), rangeForStart->startOffset(exception));
423  DOM::Position end = DOM::Position(rangeForEnd->endContainer(exception), rangeForEnd->endOffset(exception));
424 
425  self->m_document->part()->setCaret(DOM::Selection(start, end));
426  break;
427  }
428 
429  case DOMSelection::RemoveRange: {
430  // This actually take a /Range/. How brittle.
431  if (sel.isEmpty()) {
432  return jsUndefined();
433  }
434 
435  DOM::RangeImpl *range = toRange(args[0]);
436  DOM::Range ourRange = sel.toRange();
437  DOM::RangeImpl *ourRangeImpl = ourRange.handle();
438  if (range && range->startContainer(exception) == ourRangeImpl->startContainer(exception)
439  && range->startOffset(exception) == ourRangeImpl->startOffset(exception)
440  && range->endContainer(exception) == ourRangeImpl->endContainer(exception)
441  && range->endOffset(exception) == ourRangeImpl->endOffset(exception)) {
442  self->m_document->part()->setCaret(DOM::Selection());
443  }
444  break;
445  }
446 
447  case DOMSelection::RemoveAllRanges:
448  self->m_document->part()->setCaret(DOM::Selection());
449  break;
450 
451  case DOMSelection::ToString:
452  if (sel.isEmpty() || sel.isCollapsed()) {
453  return jsString(UString());
454  } else {
455  DOM::Range r = sel.toRange();
456  DOM::RangeImpl *ri = r.handle();
457  if (ri) {
458  return jsString(ri->toString(exception));
459  }
460  }
461  break;
462  }
463  return jsUndefined();
464 }
465 
466 DOM::Selection DOMSelection::currentSelection() const
467 {
468  if (m_document && m_document->part()) {
469  return m_document->part()->caret();
470  } else {
471  return DOM::Selection();
472  }
473 }
474 
475 bool DOMSelection::attached() const
476 {
477  return m_document && m_document->part();
478 }
479 
480 }
DocumentFragment is a "lightweight" or "minimal" Document object.
Definition: dom_doc.h:1042
DOM operations only raise exceptions in "exceptional" circumstances, i.e., when an operation is impos...
Definition: dom_exception.h:58
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
This library provides a full-featured HTML parser and widget.
const QList< QKeySequence > & end()
char * toString(const T &value)
NodeImpl * handle() const
Definition: dom_node.h:936
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 22:48:18 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.