1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
4  *  Copyright (C) 2003 Apple Computer, Inc.
5  *  Copyright (C) 2009 Maksim Orlovich (maksim@kde.org)
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)
KJS_IMPLEMENT_PROTOFUNC(DOMRangeProtoFunc)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 
~DOMRange()78 DOMRange::~DOMRange()
79 {
80     ScriptInterpreter::forgetDOMObject(m_impl.get());
81 }
82 
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)83 bool DOMRange::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
84 {
85     return getStaticValueSlot<DOMRange, DOMObject>(exec, &DOMRangeTable, this, propertyName, slot);
86 }
87 
getValueProperty(ExecState * exec,int token) const88 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 
callAsFunction(ExecState * exec,JSObject * thisObj,const List & args)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 
getDOMRange(ExecState * exec,DOM::RangeImpl * r)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 
RangeConstructor(ExecState * exec)204 RangeConstructor::RangeConstructor(ExecState *exec)
205     : DOMObject(exec->lexicalInterpreter()->builtinObjectPrototype())
206 {
207     putDirect(exec->propertyNames().prototype, DOMRangeProto::self(exec), DontEnum | DontDelete | ReadOnly);
208 }
209 
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)210 bool RangeConstructor::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
211 {
212     return getStaticValueSlot<RangeConstructor, DOMObject>(exec, &RangeConstructorTable, this, propertyName, slot);
213 }
214 
getValueProperty(ExecState *,int token) const215 JSValue *RangeConstructor::getValueProperty(ExecState *, int token) const
216 {
217     return jsNumber(token);
218 }
219 
getRangeConstructor(ExecState * exec)220 JSValue *getRangeConstructor(ExecState *exec)
221 {
222     return cacheGlobalObject<RangeConstructor>(exec, "[[range.constructor]]");
223 }
224 
toRange(JSValue * val)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 
RangeException(ExecState * exec)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)
KJS_IMPLEMENT_PROTOFUNC(DOMSelectionProtoFunc)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 
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)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 
getValueProperty(ExecState * exec,int token) const295 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 
callAsFunction(ExecState * exec,JSObject * thisObj,const List & args)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 
currentSelection() const466 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 
attached() const475 bool DOMSelection::attached() const
476 {
477     return m_document && m_document->part();
478 }
479 
480 }
481