Warning, file /frameworks/khtml/src/ecma/kjs_range.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
0004  *  Copyright (C) 2003 Apple Computer, Inc.
0005  *  Copyright (C) 2009 Maksim Orlovich (maksim@kde.org)
0006  *
0007  *  This library is free software; you can redistribute it and/or
0008  *  modify it under the terms of the GNU Library General Public
0009  *  License as published by the Free Software Foundation; either
0010  *  version 2 of the License, or (at your option) any later version.
0011  *
0012  *  This library is distributed in the hope that it will be useful,
0013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  *  Library General Public License for more details.
0016  *
0017  *  You should have received a copy of the GNU Library General Public
0018  *  License along with this library; if not, write to the Free Software
0019  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0020  */
0021 
0022 #include "kjs_range.h"
0023 #include "kjs_range.lut.h"
0024 #include "khtml_part.h"
0025 #include "dom/dom_exception.h"
0026 #include "dom/dom2_range.h"
0027 #include "khtml_debug.h"
0028 
0029 using DOM::DOMException;
0030 
0031 namespace KJS
0032 {
0033 // -------------------------------------------------------------------------
0034 
0035 const ClassInfo DOMRange::info = { "Range", nullptr, &DOMRangeTable, nullptr };
0036 /*
0037 @begin DOMRangeTable 7
0038   startContainer    DOMRange::StartContainer    DontDelete|ReadOnly
0039   startOffset       DOMRange::StartOffset       DontDelete|ReadOnly
0040   endContainer      DOMRange::EndContainer      DontDelete|ReadOnly
0041   endOffset     DOMRange::EndOffset     DontDelete|ReadOnly
0042   collapsed     DOMRange::Collapsed     DontDelete|ReadOnly
0043   commonAncestorContainer DOMRange::CommonAncestorContainer DontDelete|ReadOnly
0044 @end
0045 @begin DOMRangeProtoTable 17
0046 setStart            DOMRange::SetStart          DontDelete|Function 2
0047   setEnd            DOMRange::SetEnd            DontDelete|Function 2
0048   setStartBefore        DOMRange::SetStartBefore        DontDelete|Function 1
0049   setStartAfter         DOMRange::SetStartAfter     DontDelete|Function 1
0050   setEndBefore          DOMRange::SetEndBefore      DontDelete|Function 1
0051   setEndAfter           DOMRange::SetEndAfter       DontDelete|Function 1
0052   collapse          DOMRange::Collapse          DontDelete|Function 1
0053   selectNode            DOMRange::SelectNode        DontDelete|Function 1
0054   selectNodeContents        DOMRange::SelectNodeContents    DontDelete|Function 1
0055   compareBoundaryPoints     DOMRange::CompareBoundaryPoints DontDelete|Function 2
0056   deleteContents        DOMRange::DeleteContents        DontDelete|Function 0
0057   extractContents       DOMRange::ExtractContents       DontDelete|Function 0
0058   cloneContents         DOMRange::CloneContents     DontDelete|Function 0
0059   insertNode            DOMRange::InsertNode        DontDelete|Function 1
0060   surroundContents      DOMRange::SurroundContents      DontDelete|Function 1
0061   cloneRange            DOMRange::CloneRange        DontDelete|Function 0
0062   toString          DOMRange::ToString          DontDelete|Function 0
0063   detach            DOMRange::Detach            DontDelete|Function 0
0064   createContextualFragment  DOMRange::CreateContextualFragment  DontDelete|Function 1
0065 @end
0066 */
0067 KJS_DEFINE_PROTOTYPE(DOMRangeProto)
0068 KJS_IMPLEMENT_PROTOFUNC(DOMRangeProtoFunc)
0069 KJS_IMPLEMENT_PROTOTYPE("DOMRange", DOMRangeProto, DOMRangeProtoFunc, ObjectPrototype)
0070 
0071 DOMRange::DOMRange(ExecState *exec, DOM::RangeImpl *r)
0072     : m_impl(r)
0073 {
0074     assert(r);
0075     setPrototype(DOMRangeProto::self(exec));
0076 }
0077 
0078 DOMRange::~DOMRange()
0079 {
0080     ScriptInterpreter::forgetDOMObject(m_impl.get());
0081 }
0082 
0083 bool DOMRange::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0084 {
0085     return getStaticValueSlot<DOMRange, DOMObject>(exec, &DOMRangeTable, this, propertyName, slot);
0086 }
0087 
0088 JSValue *DOMRange::getValueProperty(ExecState *exec, int token) const
0089 {
0090     DOMExceptionTranslator exception(exec);
0091     DOM::RangeImpl &range = *m_impl;
0092 
0093     switch (token) {
0094     case StartContainer:
0095         return getDOMNode(exec, range.startContainer(exception));
0096     case StartOffset:
0097         return jsNumber(range.startOffset(exception));
0098     case EndContainer:
0099         return getDOMNode(exec, range.endContainer(exception));
0100     case EndOffset:
0101         return jsNumber(range.endOffset(exception));
0102     case Collapsed:
0103         return jsBoolean(range.collapsed(exception));
0104     case CommonAncestorContainer: {
0105         return getDOMNode(exec, range.commonAncestorContainer(exception));
0106     }
0107     default:
0108         // qCDebug(KHTML_LOG) << "WARNING: Unhandled token in DOMRange::getValueProperty : " << token;
0109         return jsNull();
0110     }
0111 }
0112 
0113 JSValue *DOMRangeProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
0114 {
0115     KJS_CHECK_THIS(KJS::DOMRange, thisObj);
0116     DOMExceptionTranslator exception(exec);
0117     DOM::RangeImpl &range = *static_cast<DOMRange *>(thisObj)->impl();
0118 
0119     JSValue *result = jsUndefined();
0120 
0121     switch (id) {
0122     case DOMRange::SetStart:
0123         range.setStart(toNode(args[0]), args[1]->toInteger(exec), exception);
0124         break;
0125     case DOMRange::SetEnd:
0126         range.setEnd(toNode(args[0]), args[1]->toInteger(exec), exception);
0127         break;
0128     case DOMRange::SetStartBefore:
0129         range.setStartBefore(toNode(args[0]), exception);
0130         break;
0131     case DOMRange::SetStartAfter:
0132         range.setStartAfter(toNode(args[0]), exception);
0133         break;
0134     case DOMRange::SetEndBefore:
0135         range.setEndBefore(toNode(args[0]), exception);
0136         break;
0137     case DOMRange::SetEndAfter:
0138         range.setEndAfter(toNode(args[0]), exception);
0139         break;
0140     case DOMRange::Collapse:
0141         range.collapse(args[0]->toBoolean(exec), exception);
0142         break;
0143     case DOMRange::SelectNode:
0144         range.selectNode(toNode(args[0]), exception);
0145         break;
0146     case DOMRange::SelectNodeContents:
0147         range.selectNodeContents(toNode(args[0]), exception);
0148         break;
0149     case DOMRange::CompareBoundaryPoints:
0150         result = jsNumber(range.compareBoundaryPoints(static_cast<DOM::Range::CompareHow>(args[0]->toInt32(exec)), toRange(args[1]), exception));
0151         break;
0152     case DOMRange::DeleteContents:
0153         range.deleteContents(exception);
0154         break;
0155     case DOMRange::ExtractContents:
0156         result = getDOMNode(exec, range.extractContents(exception));
0157         break;
0158     case DOMRange::CloneContents:
0159         result = getDOMNode(exec, range.cloneContents(exception));
0160         break;
0161     case DOMRange::InsertNode:
0162         range.insertNode(toNode(args[0]), exception);
0163         break;
0164     case DOMRange::SurroundContents:
0165         range.surroundContents(toNode(args[0]), exception);
0166         break;
0167     case DOMRange::CloneRange:
0168         result = getDOMRange(exec, range.cloneRange(exception));
0169         break;
0170     case DOMRange::ToString:
0171         result = jsString(UString(range.toString(exception)));
0172         break;
0173     case DOMRange::Detach:
0174         range.detach(exception);
0175         break;
0176     case DOMRange::CreateContextualFragment:
0177         JSValue *value = args[0];
0178         DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString();
0179         DOM::DocumentFragment frag = range.createContextualFragment(str, exception);
0180         result = getDOMNode(exec, frag.handle());
0181         break;
0182     };
0183 
0184     return result;
0185 }
0186 
0187 JSValue *getDOMRange(ExecState *exec, DOM::RangeImpl *r)
0188 {
0189     return cacheDOMObject<DOM::RangeImpl, KJS::DOMRange>(exec, r);
0190 }
0191 
0192 // -------------------------------------------------------------------------
0193 
0194 const ClassInfo RangeConstructor::info = { "RangeConstructor", nullptr, &RangeConstructorTable, nullptr };
0195 /*
0196 @begin RangeConstructorTable 5
0197   START_TO_START    DOM::Range::START_TO_START  DontDelete|ReadOnly
0198   START_TO_END      DOM::Range::START_TO_END    DontDelete|ReadOnly
0199   END_TO_END        DOM::Range::END_TO_END      DontDelete|ReadOnly
0200   END_TO_START      DOM::Range::END_TO_START    DontDelete|ReadOnly
0201 @end
0202 */
0203 
0204 RangeConstructor::RangeConstructor(ExecState *exec)
0205     : DOMObject(exec->lexicalInterpreter()->builtinObjectPrototype())
0206 {
0207     putDirect(exec->propertyNames().prototype, DOMRangeProto::self(exec), DontEnum | DontDelete | ReadOnly);
0208 }
0209 
0210 bool RangeConstructor::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0211 {
0212     return getStaticValueSlot<RangeConstructor, DOMObject>(exec, &RangeConstructorTable, this, propertyName, slot);
0213 }
0214 
0215 JSValue *RangeConstructor::getValueProperty(ExecState *, int token) const
0216 {
0217     return jsNumber(token);
0218 }
0219 
0220 JSValue *getRangeConstructor(ExecState *exec)
0221 {
0222     return cacheGlobalObject<RangeConstructor>(exec, "[[range.constructor]]");
0223 }
0224 
0225 DOM::RangeImpl *toRange(JSValue *val)
0226 {
0227     JSObject *obj = val->getObject();
0228     if (!obj || !obj->inherits(&DOMRange::info)) {
0229         return nullptr;
0230     }
0231 
0232     const DOMRange *dobj = static_cast<const DOMRange *>(obj);
0233     return dobj->impl();
0234 }
0235 
0236 /* Source for RangeExceptionProtoTable.
0237 @begin RangeExceptionProtoTable 2
0238 BAD_BOUNDARYPOINTS_ERR  DOM::RangeException::BAD_BOUNDARYPOINTS_ERR    DontDelete|ReadOnly
0239 INVALID_NODE_TYPE_ERR   DOM::RangeException::INVALID_NODE_TYPE_ERR     DontDelete|ReadOnly
0240 @end
0241 */
0242 
0243 DEFINE_CONSTANT_TABLE(RangeExceptionProto)
0244 IMPLEMENT_CONSTANT_TABLE(RangeExceptionProto, "RangeException")
0245 
0246 IMPLEMENT_PSEUDO_CONSTRUCTOR_WITH_PARENT(RangeExceptionPseudoCtor, "RangeException",
0247         RangeExceptionProto, RangeExceptionProto)
0248 
0249 RangeException::RangeException(ExecState *exec)
0250     : DOMObject(RangeExceptionProto::self(exec))
0251 {
0252 }
0253 
0254 const ClassInfo RangeException::info = { "RangeException", nullptr, nullptr, nullptr };
0255 
0256 // -------------------------------------------------------------------------
0257 
0258 const ClassInfo DOMSelection::info = { "Selection", nullptr, &DOMSelectionTable, nullptr };
0259 /*
0260 @begin DOMSelectionTable 7
0261   anchorNode            DOMSelection::AnchorNode        DontDelete|ReadOnly
0262   anchorOffset          DOMSelection::AnchorOffset      DontDelete|ReadOnly
0263   focusNode             DOMSelection::FocusNode         DontDelete|ReadOnly
0264   focusOffset           DOMSelection::FocusOffset       DontDelete|ReadOnly
0265   isCollapsed           DOMSelection::IsCollapsed       DontDelete|ReadOnly
0266   rangeCount            DOMSelection::RangeCount        DontDelete|ReadOnly
0267 @end
0268 @begin DOMSelectionProtoTable 13
0269  collapsed          DOMSelection::Collapsed               DontDelete|Function 2
0270  collapseToStart    DOMSelection::CollapseToStart         DontDelete|Function 0
0271  collapseToEnd      DOMSelection::CollapseToEnd           DontDelete|Function 0
0272  selectAllChildren  DOMSelection::SelectAllChildren       DontDelete|Function 1
0273  deleteFromDocument DOMSelection::DeleteFromDocument      DontDelete|Function 0
0274  getRangeAt         DOMSelection::GetRangeAt              DontDelete|Function 1
0275  addRange           DOMSelection::AddRange                DontDelete|Function 1
0276  removeRange        DOMSelection::RemoveRange             DontDelete|Function 1
0277  removeAllRanges    DOMSelection::RemoveAllRanges         DontDelete|Function 0
0278  toString           DOMSelection::ToString                DontDelete|Function 0
0279 @end
0280 */
0281 KJS_DEFINE_PROTOTYPE(DOMSelectionProto)
0282 KJS_IMPLEMENT_PROTOFUNC(DOMSelectionProtoFunc)
0283 KJS_IMPLEMENT_PROTOTYPE("Selection", DOMSelectionProto, DOMSelectionProtoFunc, ObjectPrototype)
0284 
0285 DOMSelection::DOMSelection(ExecState *exec, DOM::DocumentImpl *parentDocument):
0286     JSObject(DOMSelectionProto::self(exec)), m_document(parentDocument)
0287 {}
0288 
0289 bool DOMSelection::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0290 {
0291     // qCDebug(KHTML_LOG) << propertyName.ustring().qstring();
0292     return getStaticValueSlot<DOMSelection, JSObject>(exec, &DOMSelectionTable, this, propertyName, slot);
0293 }
0294 
0295 JSValue *DOMSelection::getValueProperty(ExecState *exec, int token) const
0296 {
0297     // qCDebug(KHTML_LOG) << token;
0298     DOMExceptionTranslator exception(exec);
0299     DOM::Selection sel = currentSelection();
0300     // ### TODO: below doesn't really distinguish anchor and focus properly.
0301     switch (token) {
0302     case DOMSelection::AnchorNode:
0303         return sel.notEmpty() ? getDOMNode(exec, sel.base().node()) : jsNull();
0304     case DOMSelection::AnchorOffset:
0305         return jsNumber(sel.notEmpty() ? sel.base().offset() : 0L);
0306     case DOMSelection::FocusNode:
0307         return sel.notEmpty() ? getDOMNode(exec, sel.extent().node()) : jsNull();
0308     case DOMSelection::FocusOffset:
0309         return jsNumber(sel.notEmpty() ? sel.extent().offset() : 0L);
0310     case DOMSelection::IsCollapsed:
0311         return jsBoolean(sel.isCollapsed() || sel.isEmpty());
0312     case DOMSelection::RangeCount:
0313         return sel.notEmpty() ? jsNumber(1) : jsNumber(0);
0314     }
0315 
0316     assert(false);
0317     return jsUndefined();
0318 }
0319 
0320 JSValue *DOMSelectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
0321 {
0322     KJS_CHECK_THIS(KJS::DOMSelection, thisObj);
0323 
0324     DOMSelection *self = static_cast<DOMSelection *>(thisObj);
0325     if (!self->attached()) {
0326         return jsUndefined();
0327     }
0328 
0329     DOM::Selection sel = self->currentSelection();
0330 
0331     DOMExceptionTranslator exception(exec);
0332     switch (id) {
0333     case DOMSelection::Collapsed: {
0334         DOM::NodeImpl *node   = toNode(args[0]);
0335         int            offset = args[1]->toInt32(exec);
0336         if (node && node->document() == self->m_document) {
0337             self->m_document->part()->setCaret(DOM::Selection(DOM::Position(node, offset)));
0338         } else {
0339             setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
0340         }
0341         break;
0342     }
0343 
0344     case DOMSelection::CollapseToStart:
0345         if (sel.notEmpty()) {
0346             sel.moveTo(sel.start());
0347             self->m_document->part()->setCaret(sel);
0348         } else {
0349             setDOMException(exec, DOMException::INVALID_STATE_ERR);
0350         }
0351         break;
0352 
0353     case DOMSelection::CollapseToEnd:
0354         if (sel.notEmpty()) {
0355             sel.moveTo(sel.end());
0356             self->m_document->part()->setCaret(sel);
0357         } else {
0358             setDOMException(exec, DOMException::INVALID_STATE_ERR);
0359         }
0360         break;
0361 
0362     case DOMSelection::SelectAllChildren: {
0363         DOM::NodeImpl *node = toNode(args[0]);
0364         if (node && node->document() == self->m_document) {
0365             DOM::RangeImpl *range = new DOM::RangeImpl(self->m_document);
0366             range->selectNodeContents(node, exception);
0367             self->m_document->part()->setCaret(DOM::Selection(DOM::Range(range)));
0368         } else {
0369             setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
0370         }
0371         break;
0372     }
0373 
0374     case DOMSelection::DeleteFromDocument: {
0375         self->m_document->part()->setCaret(DOM::Selection());
0376         DOM::Range r = sel.toRange();
0377         DOM::RangeImpl *ri = r.handle();
0378         if (ri) {
0379             ri->deleteContents(exception);
0380         }
0381         break;
0382     }
0383 
0384     case DOMSelection::GetRangeAt: {
0385         int i = args[0]->toInt32(exec);
0386         if (sel.isEmpty() || i != 0) {
0387             setDOMException(exec, DOMException::INDEX_SIZE_ERR);
0388         } else {
0389             DOM::Range r = sel.toRange();
0390             return getDOMRange(exec, r.handle());
0391         }
0392         break;
0393     }
0394 
0395     case DOMSelection::AddRange: {
0396         // We only support a single range, so we merge the two.
0397         // This does violate HTML5, though, as it's actually supposed to report the
0398         // overlap twice. Perhaps this shouldn't be live?
0399         DOM::RangeImpl *range = toRange(args[0]);
0400 
0401         if (!range) {
0402             return jsUndefined();
0403         }
0404         if (range->ownerDocument() != self->m_document) {
0405             setDOMException(exec, DOMException::WRONG_DOCUMENT_ERR);
0406             return jsUndefined();
0407         }
0408 
0409         if (sel.isEmpty()) {
0410             self->m_document->part()->setCaret(DOM::Selection(range));
0411             return jsUndefined();
0412         }
0413 
0414         DOM::Range      ourRange = sel.toRange();
0415         DOM::RangeImpl *ourRangeImpl = ourRange.handle();
0416 
0417         bool startExisting = ourRangeImpl->compareBoundaryPoints(DOM::Range::START_TO_START, range, exception) == -1;
0418         bool endExisting   = ourRangeImpl->compareBoundaryPoints(DOM::Range::END_TO_END, range, exception) == -1;
0419 
0420         DOM::RangeImpl *rangeForStart = startExisting ? ourRangeImpl : range;
0421         DOM::RangeImpl *rangeForEnd   = endExisting ? ourRangeImpl : range;
0422         DOM::Position start = DOM::Position(rangeForStart->startContainer(exception), rangeForStart->startOffset(exception));
0423         DOM::Position end   = DOM::Position(rangeForEnd->endContainer(exception), rangeForEnd->endOffset(exception));
0424 
0425         self->m_document->part()->setCaret(DOM::Selection(start, end));
0426         break;
0427     }
0428 
0429     case DOMSelection::RemoveRange: {
0430         // This actually take a /Range/. How brittle.
0431         if (sel.isEmpty()) {
0432             return jsUndefined();
0433         }
0434 
0435         DOM::RangeImpl *range    = toRange(args[0]);
0436         DOM::Range      ourRange = sel.toRange();
0437         DOM::RangeImpl *ourRangeImpl = ourRange.handle();
0438         if (range && range->startContainer(exception) == ourRangeImpl->startContainer(exception)
0439                 && range->startOffset(exception)    == ourRangeImpl->startOffset(exception)
0440                 && range->endContainer(exception)   == ourRangeImpl->endContainer(exception)
0441                 && range->endOffset(exception)      == ourRangeImpl->endOffset(exception)) {
0442             self->m_document->part()->setCaret(DOM::Selection());
0443         }
0444         break;
0445     }
0446 
0447     case DOMSelection::RemoveAllRanges:
0448         self->m_document->part()->setCaret(DOM::Selection());
0449         break;
0450 
0451     case DOMSelection::ToString:
0452         if (sel.isEmpty() || sel.isCollapsed()) {
0453             return jsString(UString());
0454         } else {
0455             DOM::Range r = sel.toRange();
0456             DOM::RangeImpl *ri = r.handle();
0457             if (ri) {
0458                 return jsString(ri->toString(exception));
0459             }
0460         }
0461         break;
0462     }
0463     return jsUndefined();
0464 }
0465 
0466 DOM::Selection DOMSelection::currentSelection() const
0467 {
0468     if (m_document && m_document->part()) {
0469         return m_document->part()->caret();
0470     } else {
0471         return DOM::Selection();
0472     }
0473 }
0474 
0475 bool DOMSelection::attached() const
0476 {
0477     return m_document && m_document->part();
0478 }
0479 
0480 }