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 }