1 | /* |
2 | * (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 2000 Gunnstein Lye (gunnstein@netcom.no) |
4 | * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) |
5 | * (C) 2001 Peter Kelly (pmk@post.com) |
6 | * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. |
7 | * Copyright (C) 2011 Motorola Mobility. All rights reserved. |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Library General Public |
11 | * License as published by the Free Software Foundation; either |
12 | * version 2 of the License, or (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * Library General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Library General Public License |
20 | * along with this library; see the file COPYING.LIB. If not, write to |
21 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
22 | * Boston, MA 02110-1301, USA. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "Range.h" |
27 | |
28 | #include "Comment.h" |
29 | #include "DOMRect.h" |
30 | #include "DOMRectList.h" |
31 | #include "DocumentFragment.h" |
32 | #include "Editing.h" |
33 | #include "Event.h" |
34 | #include "Frame.h" |
35 | #include "FrameView.h" |
36 | #include "HTMLBodyElement.h" |
37 | #include "HTMLElement.h" |
38 | #include "HTMLHtmlElement.h" |
39 | #include "HTMLNames.h" |
40 | #include "NodeTraversal.h" |
41 | #include "NodeWithIndex.h" |
42 | #include "ProcessingInstruction.h" |
43 | #include "RenderBoxModelObject.h" |
44 | #include "RenderText.h" |
45 | #include "ScopedEventQueue.h" |
46 | #include "TextIterator.h" |
47 | #include "VisiblePosition.h" |
48 | #include "VisibleUnits.h" |
49 | #include "markup.h" |
50 | #include <stdio.h> |
51 | #include <wtf/RefCountedLeakCounter.h> |
52 | #include <wtf/text/CString.h> |
53 | #include <wtf/text/StringBuilder.h> |
54 | |
55 | #if PLATFORM(IOS_FAMILY) |
56 | #include "SelectionRect.h" |
57 | #endif |
58 | |
59 | namespace WebCore { |
60 | |
61 | using namespace HTMLNames; |
62 | |
63 | DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range" )); |
64 | |
65 | enum ContentsProcessDirection { ProcessContentsForward, ProcessContentsBackward }; |
66 | enum class CoordinateSpace { Absolute, Client }; |
67 | |
68 | static ExceptionOr<void> processNodes(Range::ActionType, Vector<Ref<Node>>&, Node* oldContainer, RefPtr<Node> newContainer); |
69 | static ExceptionOr<RefPtr<Node>> processContentsBetweenOffsets(Range::ActionType, RefPtr<DocumentFragment>, RefPtr<Node> container, unsigned startOffset, unsigned endOffset); |
70 | static ExceptionOr<RefPtr<Node>> processAncestorsAndTheirSiblings(Range::ActionType, Node* container, ContentsProcessDirection, ExceptionOr<RefPtr<Node>>&& passedClonedContainer, Node* commonRoot); |
71 | |
72 | inline Range::Range(Document& ownerDocument) |
73 | : m_ownerDocument(ownerDocument) |
74 | , m_start(&ownerDocument) |
75 | , m_end(&ownerDocument) |
76 | { |
77 | #ifndef NDEBUG |
78 | rangeCounter.increment(); |
79 | #endif |
80 | |
81 | m_ownerDocument->attachRange(*this); |
82 | } |
83 | |
84 | Ref<Range> Range::create(Document& ownerDocument) |
85 | { |
86 | return adoptRef(*new Range(ownerDocument)); |
87 | } |
88 | |
89 | inline Range::Range(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset) |
90 | : m_ownerDocument(ownerDocument) |
91 | , m_start(&ownerDocument) |
92 | , m_end(&ownerDocument) |
93 | { |
94 | #ifndef NDEBUG |
95 | rangeCounter.increment(); |
96 | #endif |
97 | |
98 | m_ownerDocument->attachRange(*this); |
99 | |
100 | // Simply setting the containers and offsets directly would not do any of the checking |
101 | // that setStart and setEnd do, so we call those functions. |
102 | if (startContainer) |
103 | setStart(*startContainer, startOffset); |
104 | if (endContainer) |
105 | setEnd(*endContainer, endOffset); |
106 | } |
107 | |
108 | Ref<Range> Range::create(Document& ownerDocument, RefPtr<Node>&& startContainer, int startOffset, RefPtr<Node>&& endContainer, int endOffset) |
109 | { |
110 | return adoptRef(*new Range(ownerDocument, startContainer.get(), startOffset, endContainer.get(), endOffset)); |
111 | } |
112 | |
113 | Ref<Range> Range::create(Document& ownerDocument, const Position& start, const Position& end) |
114 | { |
115 | return adoptRef(*new Range(ownerDocument, start.containerNode(), start.computeOffsetInContainerNode(), end.containerNode(), end.computeOffsetInContainerNode())); |
116 | } |
117 | |
118 | Ref<Range> Range::create(Document& ownerDocument, const VisiblePosition& visibleStart, const VisiblePosition& visibleEnd) |
119 | { |
120 | Position start = visibleStart.deepEquivalent().parentAnchoredEquivalent(); |
121 | Position end = visibleEnd.deepEquivalent().parentAnchoredEquivalent(); |
122 | return adoptRef(*new Range(ownerDocument, start.anchorNode(), start.deprecatedEditingOffset(), end.anchorNode(), end.deprecatedEditingOffset())); |
123 | } |
124 | |
125 | Range::~Range() |
126 | { |
127 | m_ownerDocument->detachRange(*this); |
128 | |
129 | #ifndef NDEBUG |
130 | rangeCounter.decrement(); |
131 | #endif |
132 | } |
133 | |
134 | void Range::setDocument(Document& document) |
135 | { |
136 | ASSERT(m_ownerDocument.ptr() != &document); |
137 | m_ownerDocument->detachRange(*this); |
138 | m_ownerDocument = document; |
139 | m_start.setToStartOfNode(document); |
140 | m_end.setToStartOfNode(document); |
141 | m_ownerDocument->attachRange(*this); |
142 | } |
143 | |
144 | Node* Range::commonAncestorContainer(Node* containerA, Node* containerB) |
145 | { |
146 | for (Node* parentA = containerA; parentA; parentA = parentA->parentNode()) { |
147 | for (Node* parentB = containerB; parentB; parentB = parentB->parentNode()) { |
148 | if (parentA == parentB) |
149 | return parentA; |
150 | } |
151 | } |
152 | return nullptr; |
153 | } |
154 | |
155 | static inline bool checkForDifferentRootContainer(const RangeBoundaryPoint& start, const RangeBoundaryPoint& end) |
156 | { |
157 | Node* endRootContainer = end.container(); |
158 | while (endRootContainer->parentNode()) |
159 | endRootContainer = endRootContainer->parentNode(); |
160 | Node* startRootContainer = start.container(); |
161 | while (startRootContainer->parentNode()) |
162 | startRootContainer = startRootContainer->parentNode(); |
163 | |
164 | return startRootContainer != endRootContainer || Range::compareBoundaryPoints(start, end).releaseReturnValue() > 0; |
165 | } |
166 | |
167 | ExceptionOr<void> Range::setStart(Ref<Node>&& refNode, unsigned offset) |
168 | { |
169 | bool didMoveDocument = false; |
170 | if (&refNode->document() != &ownerDocument()) { |
171 | setDocument(refNode->document()); |
172 | didMoveDocument = true; |
173 | } |
174 | |
175 | auto childNode = checkNodeWOffset(refNode, offset); |
176 | if (childNode.hasException()) |
177 | return childNode.releaseException(); |
178 | |
179 | m_start.set(WTFMove(refNode), offset, childNode.releaseReturnValue()); |
180 | |
181 | if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) |
182 | collapse(true); |
183 | |
184 | return { }; |
185 | } |
186 | |
187 | ExceptionOr<void> Range::setEnd(Ref<Node>&& refNode, unsigned offset) |
188 | { |
189 | bool didMoveDocument = false; |
190 | if (&refNode->document() != &ownerDocument()) { |
191 | setDocument(refNode->document()); |
192 | didMoveDocument = true; |
193 | } |
194 | |
195 | auto childNode = checkNodeWOffset(refNode, offset); |
196 | if (childNode.hasException()) |
197 | return childNode.releaseException(); |
198 | |
199 | m_end.set(WTFMove(refNode), offset, childNode.releaseReturnValue()); |
200 | |
201 | if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) |
202 | collapse(false); |
203 | |
204 | return { }; |
205 | } |
206 | |
207 | ExceptionOr<void> Range::setStart(const Position& start) |
208 | { |
209 | Position parentAnchored = start.parentAnchoredEquivalent(); |
210 | if (!parentAnchored.containerNode()) |
211 | return Exception { TypeError }; |
212 | return setStart(*parentAnchored.containerNode(), parentAnchored.offsetInContainerNode()); |
213 | } |
214 | |
215 | ExceptionOr<void> Range::setEnd(const Position& end) |
216 | { |
217 | Position parentAnchored = end.parentAnchoredEquivalent(); |
218 | if (!parentAnchored.containerNode()) |
219 | return Exception { TypeError }; |
220 | return setEnd(*parentAnchored.containerNode(), parentAnchored.offsetInContainerNode()); |
221 | } |
222 | |
223 | void Range::collapse(bool toStart) |
224 | { |
225 | if (toStart) |
226 | m_end = m_start; |
227 | else |
228 | m_start = m_end; |
229 | } |
230 | |
231 | ExceptionOr<bool> Range::isPointInRange(Node& refNode, unsigned offset) |
232 | { |
233 | if (&refNode.document() != &ownerDocument()) |
234 | return false; |
235 | |
236 | auto checkNodeResult = checkNodeWOffset(refNode, offset); |
237 | if (checkNodeResult.hasException()) { |
238 | // DOM4 spec requires us to check whether refNode and start container have the same root first |
239 | // but we do it in the reverse order to avoid O(n) operation here in common case. |
240 | if (!commonAncestorContainer(&refNode, &startContainer())) |
241 | return false; |
242 | return checkNodeResult.releaseException(); |
243 | } |
244 | |
245 | auto startCompareResult = compareBoundaryPoints(&refNode, offset, &startContainer(), m_start.offset()); |
246 | if (!(!startCompareResult.hasException() && startCompareResult.releaseReturnValue() >= 0)) |
247 | return false; |
248 | auto endCompareResult = compareBoundaryPoints(&refNode, offset, &endContainer(), m_end.offset()); |
249 | return !endCompareResult.hasException() && endCompareResult.releaseReturnValue() <= 0; |
250 | } |
251 | |
252 | ExceptionOr<short> Range::comparePoint(Node& refNode, unsigned offset) const |
253 | { |
254 | // http://developer.mozilla.org/en/docs/DOM:range.comparePoint |
255 | // This method returns -1, 0 or 1 depending on if the point described by the |
256 | // refNode node and an offset within the node is before, same as, or after the range respectively. |
257 | if (&refNode.document() != &ownerDocument()) |
258 | return Exception { WrongDocumentError }; |
259 | |
260 | auto checkNodeResult = checkNodeWOffset(refNode, offset); |
261 | if (checkNodeResult.hasException()) { |
262 | // DOM4 spec requires us to check whether refNode and start container have the same root first |
263 | // but we do it in the reverse order to avoid O(n) operation here in common case. |
264 | if (!refNode.isConnected() && !commonAncestorContainer(&refNode, &startContainer())) |
265 | return Exception { WrongDocumentError }; |
266 | return checkNodeResult.releaseException(); |
267 | } |
268 | |
269 | // compare to start, and point comes before |
270 | auto startCompareResult = compareBoundaryPoints(&refNode, offset, &startContainer(), m_start.offset()); |
271 | if (startCompareResult.hasException()) |
272 | return startCompareResult.releaseException(); |
273 | if (startCompareResult.releaseReturnValue() < 0) |
274 | return -1; |
275 | |
276 | // compare to end, and point comes after |
277 | auto endCompareResult = compareBoundaryPoints(&refNode, offset, &endContainer(), m_end.offset()); |
278 | if (endCompareResult.hasException()) |
279 | return endCompareResult.releaseException(); |
280 | if (endCompareResult.releaseReturnValue() > 0) |
281 | return 1; |
282 | |
283 | // point is in the middle of this range, or on the boundary points |
284 | return 0; |
285 | } |
286 | |
287 | ExceptionOr<Range::CompareResults> Range::compareNode(Node& refNode) const |
288 | { |
289 | // http://developer.mozilla.org/en/docs/DOM:range.compareNode |
290 | // This method returns 0, 1, 2, or 3 based on if the node is before, after, |
291 | // before and after(surrounds), or inside the range, respectively |
292 | |
293 | if (!refNode.isConnected()) { |
294 | // Firefox doesn't throw an exception for this case; it returns 0. |
295 | return NODE_BEFORE; |
296 | } |
297 | |
298 | if (&refNode.document() != &ownerDocument()) { |
299 | // Firefox doesn't throw an exception for this case; it returns 0. |
300 | return NODE_BEFORE; |
301 | } |
302 | |
303 | auto* parentNode = refNode.parentNode(); |
304 | if (!parentNode) { |
305 | // If the node is the top of the tree we should return NODE_BEFORE_AND_AFTER, |
306 | // but we throw to match firefox behavior. |
307 | return Exception { NotFoundError }; |
308 | } |
309 | auto nodeIndex = refNode.computeNodeIndex(); |
310 | |
311 | auto nodeStartCompareResult = comparePoint(*parentNode, nodeIndex); |
312 | if (nodeStartCompareResult.hasException()) |
313 | return nodeStartCompareResult.releaseException(); |
314 | auto nodeEndCompareResult = comparePoint(*parentNode, nodeIndex + 1); |
315 | if (nodeEndCompareResult.hasException()) |
316 | return nodeEndCompareResult.releaseException(); |
317 | |
318 | bool nodeStartsBeforeRange = nodeStartCompareResult.releaseReturnValue() < 0; |
319 | bool nodeEndsAfterRange = nodeEndCompareResult.releaseReturnValue() > 0; |
320 | |
321 | return nodeStartsBeforeRange |
322 | ? (nodeEndsAfterRange ? NODE_BEFORE_AND_AFTER : NODE_BEFORE) |
323 | : (nodeEndsAfterRange ? NODE_AFTER : NODE_INSIDE); |
324 | } |
325 | |
326 | static inline Node* top(Node& node) |
327 | { |
328 | auto* top = &node; |
329 | while (auto* parent = top->parentNode()) |
330 | top = parent; |
331 | return top; |
332 | } |
333 | |
334 | ExceptionOr<short> Range::compareBoundaryPoints(CompareHow how, const Range& sourceRange) const |
335 | { |
336 | auto* thisContainer = commonAncestorContainer(); |
337 | auto* sourceContainer = sourceRange.commonAncestorContainer(); |
338 | if (!thisContainer || !sourceContainer || &thisContainer->document() != &sourceContainer->document() || top(*thisContainer) != top(*sourceContainer)) |
339 | return Exception { WrongDocumentError }; |
340 | |
341 | switch (how) { |
342 | case START_TO_START: |
343 | return compareBoundaryPoints(m_start, sourceRange.m_start); |
344 | case START_TO_END: |
345 | return compareBoundaryPoints(m_end, sourceRange.m_start); |
346 | case END_TO_END: |
347 | return compareBoundaryPoints(m_end, sourceRange.m_end); |
348 | case END_TO_START: |
349 | return compareBoundaryPoints(m_start, sourceRange.m_end); |
350 | } |
351 | |
352 | return Exception { SyntaxError }; |
353 | } |
354 | |
355 | ExceptionOr<short> Range::compareBoundaryPointsForBindings(unsigned short how, const Range& sourceRange) const |
356 | { |
357 | switch (how) { |
358 | case START_TO_START: |
359 | case START_TO_END: |
360 | case END_TO_END: |
361 | case END_TO_START: |
362 | return compareBoundaryPoints(static_cast<CompareHow>(how), sourceRange); |
363 | } |
364 | return Exception { NotSupportedError }; |
365 | } |
366 | |
367 | ExceptionOr<short> Range::compareBoundaryPoints(Node* containerA, unsigned offsetA, Node* containerB, unsigned offsetB) |
368 | { |
369 | ASSERT(containerA); |
370 | ASSERT(containerB); |
371 | |
372 | if (!containerA) |
373 | return -1; |
374 | if (!containerB) |
375 | return 1; |
376 | |
377 | // see DOM2 traversal & range section 2.5 |
378 | |
379 | // case 1: both points have the same container |
380 | if (containerA == containerB) { |
381 | if (offsetA == offsetB) |
382 | return 0; // A is equal to B |
383 | if (offsetA < offsetB) |
384 | return -1; // A is before B |
385 | return 1; // A is after B |
386 | } |
387 | |
388 | // case 2: node C (container B or an ancestor) is a child node of A |
389 | Node* c = containerB; |
390 | while (c && c->parentNode() != containerA) |
391 | c = c->parentNode(); |
392 | if (c) { |
393 | unsigned offsetC = 0; |
394 | Node* n = containerA->firstChild(); |
395 | while (n != c && offsetC < offsetA) { |
396 | offsetC++; |
397 | n = n->nextSibling(); |
398 | } |
399 | if (offsetA <= offsetC) |
400 | return -1; // A is before B |
401 | return 1; // A is after B |
402 | } |
403 | |
404 | // case 3: node C (container A or an ancestor) is a child node of B |
405 | c = containerA; |
406 | while (c && c->parentNode() != containerB) |
407 | c = c->parentNode(); |
408 | if (c) { |
409 | unsigned offsetC = 0; |
410 | Node* n = containerB->firstChild(); |
411 | while (n != c && offsetC < offsetB) { |
412 | offsetC++; |
413 | n = n->nextSibling(); |
414 | } |
415 | if (offsetC < offsetB) |
416 | return -1; // A is before B |
417 | return 1; // A is after B |
418 | } |
419 | |
420 | // case 4: containers A & B are siblings, or children of siblings |
421 | // ### we need to do a traversal here instead |
422 | auto* commonAncestor = commonAncestorContainer(containerA, containerB); |
423 | if (!commonAncestor) |
424 | return Exception { WrongDocumentError }; |
425 | Node* childA = containerA; |
426 | while (childA && childA->parentNode() != commonAncestor) |
427 | childA = childA->parentNode(); |
428 | if (!childA) |
429 | childA = commonAncestor; |
430 | Node* childB = containerB; |
431 | while (childB && childB->parentNode() != commonAncestor) |
432 | childB = childB->parentNode(); |
433 | if (!childB) |
434 | childB = commonAncestor; |
435 | |
436 | if (childA == childB) |
437 | return 0; // A is equal to B |
438 | |
439 | Node* n = commonAncestor->firstChild(); |
440 | while (n) { |
441 | if (n == childA) |
442 | return -1; // A is before B |
443 | if (n == childB) |
444 | return 1; // A is after B |
445 | n = n->nextSibling(); |
446 | } |
447 | |
448 | // Should never reach this point. |
449 | ASSERT_NOT_REACHED(); |
450 | return 0; |
451 | } |
452 | |
453 | ExceptionOr<short> Range::compareBoundaryPoints(const RangeBoundaryPoint& boundaryA, const RangeBoundaryPoint& boundaryB) |
454 | { |
455 | return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset()); |
456 | } |
457 | |
458 | bool Range::boundaryPointsValid() const |
459 | { |
460 | auto result = compareBoundaryPoints(m_start, m_end); |
461 | return !result.hasException() && result.releaseReturnValue() <= 0; |
462 | } |
463 | |
464 | ExceptionOr<void> Range::deleteContents() |
465 | { |
466 | auto result = processContents(Delete); |
467 | if (result.hasException()) |
468 | return result.releaseException(); |
469 | return { }; |
470 | } |
471 | |
472 | ExceptionOr<bool> Range::intersectsNode(Node& refNode) const |
473 | { |
474 | if (!refNode.isConnected() || &refNode.document() != &ownerDocument()) |
475 | return false; |
476 | |
477 | ContainerNode* parentNode = refNode.parentNode(); |
478 | if (!parentNode) |
479 | return true; |
480 | |
481 | unsigned nodeIndex = refNode.computeNodeIndex(); |
482 | |
483 | // If (parent, offset) is before end and (parent, offset + 1) is after start, return true. |
484 | // Otherwise, return false. |
485 | auto result = comparePoint(*parentNode, nodeIndex); |
486 | if (result.hasException()) |
487 | return result.releaseException(); |
488 | auto compareFirst = result.releaseReturnValue(); |
489 | result = comparePoint(*parentNode, nodeIndex + 1); |
490 | if (result.hasException()) |
491 | return result.releaseException(); |
492 | auto compareSecond = result.releaseReturnValue(); |
493 | |
494 | bool isFirstBeforeEnd = m_start == m_end ? compareFirst < 0 : compareFirst <= 0; |
495 | bool isSecondAfterStart = m_start == m_end ? compareSecond > 0 : compareSecond >= 0; |
496 | |
497 | return isFirstBeforeEnd && isSecondAfterStart; |
498 | } |
499 | |
500 | static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot) |
501 | { |
502 | if (node == commonRoot) |
503 | return 0; |
504 | |
505 | ASSERT(commonRoot->contains(node)); |
506 | |
507 | while (node->parentNode() != commonRoot) |
508 | node = node->parentNode(); |
509 | |
510 | return node; |
511 | } |
512 | |
513 | static inline Node* childOfCommonRootBeforeOffset(Node* container, unsigned offset, Node* commonRoot) |
514 | { |
515 | ASSERT(container); |
516 | ASSERT(commonRoot); |
517 | |
518 | if (!commonRoot->contains(container)) |
519 | return 0; |
520 | |
521 | if (container == commonRoot) { |
522 | container = container->firstChild(); |
523 | for (unsigned i = 0; container && i < offset; i++) |
524 | container = container->nextSibling(); |
525 | } else { |
526 | while (container->parentNode() != commonRoot) |
527 | container = container->parentNode(); |
528 | } |
529 | |
530 | return container; |
531 | } |
532 | |
533 | static inline unsigned lengthOfContentsInNode(Node& node) |
534 | { |
535 | // This switch statement must be consistent with that of Range::processContentsBetweenOffsets. |
536 | switch (node.nodeType()) { |
537 | case Node::DOCUMENT_TYPE_NODE: |
538 | case Node::ATTRIBUTE_NODE: |
539 | return 0; |
540 | case Node::TEXT_NODE: |
541 | case Node::CDATA_SECTION_NODE: |
542 | case Node::COMMENT_NODE: |
543 | case Node::PROCESSING_INSTRUCTION_NODE: |
544 | return downcast<CharacterData>(node).length(); |
545 | case Node::ELEMENT_NODE: |
546 | case Node::DOCUMENT_NODE: |
547 | case Node::DOCUMENT_FRAGMENT_NODE: |
548 | return downcast<ContainerNode>(node).countChildNodes(); |
549 | } |
550 | ASSERT_NOT_REACHED(); |
551 | return 0; |
552 | } |
553 | |
554 | ExceptionOr<RefPtr<DocumentFragment>> Range::processContents(ActionType action) |
555 | { |
556 | RefPtr<DocumentFragment> fragment; |
557 | if (action == Extract || action == Clone) |
558 | fragment = DocumentFragment::create(ownerDocument()); |
559 | |
560 | if (collapsed()) |
561 | return fragment; |
562 | |
563 | RefPtr<Node> commonRoot = commonAncestorContainer(); |
564 | ASSERT(commonRoot); |
565 | |
566 | if (&startContainer() == &endContainer()) { |
567 | auto result = processContentsBetweenOffsets(action, fragment, &startContainer(), m_start.offset(), m_end.offset()); |
568 | if (result.hasException()) |
569 | return result.releaseException(); |
570 | return fragment; |
571 | } |
572 | |
573 | // Since mutation events can modify the range during the process, the boundary points need to be saved. |
574 | RangeBoundaryPoint originalStart(m_start); |
575 | RangeBoundaryPoint originalEnd(m_end); |
576 | |
577 | // what is the highest node that partially selects the start / end of the range? |
578 | RefPtr<Node> partialStart = highestAncestorUnderCommonRoot(originalStart.container(), commonRoot.get()); |
579 | RefPtr<Node> partialEnd = highestAncestorUnderCommonRoot(originalEnd.container(), commonRoot.get()); |
580 | |
581 | // Start and end containers are different. |
582 | // There are three possibilities here: |
583 | // 1. Start container == commonRoot (End container must be a descendant) |
584 | // 2. End container == commonRoot (Start container must be a descendant) |
585 | // 3. Neither is commonRoot, they are both descendants |
586 | // |
587 | // In case 3, we grab everything after the start (up until a direct child |
588 | // of commonRoot) into leftContents, and everything before the end (up until |
589 | // a direct child of commonRoot) into rightContents. Then we process all |
590 | // commonRoot children between leftContents and rightContents |
591 | // |
592 | // In case 1 or 2, we skip either processing of leftContents or rightContents, |
593 | // in which case the last lot of nodes either goes from the first or last |
594 | // child of commonRoot. |
595 | // |
596 | // These are deleted, cloned, or extracted (i.e. both) depending on action. |
597 | |
598 | // Note that we are verifying that our common root hierarchy is still intact |
599 | // after any DOM mutation event, at various stages below. See webkit bug 60350. |
600 | |
601 | RefPtr<Node> leftContents; |
602 | if (originalStart.container() != commonRoot && commonRoot->contains(originalStart.container())) { |
603 | auto firstResult = processContentsBetweenOffsets(action, nullptr, originalStart.container(), originalStart.offset(), lengthOfContentsInNode(*originalStart.container())); |
604 | auto secondResult = processAncestorsAndTheirSiblings(action, originalStart.container(), ProcessContentsForward, WTFMove(firstResult), commonRoot.get()); |
605 | // FIXME: A bit peculiar that we silently ignore the exception here, but we do have at least some regression tests that rely on this behavior. |
606 | if (!secondResult.hasException()) |
607 | leftContents = secondResult.releaseReturnValue(); |
608 | } |
609 | |
610 | RefPtr<Node> rightContents; |
611 | if (&endContainer() != commonRoot && commonRoot->contains(originalEnd.container())) { |
612 | auto firstResult = processContentsBetweenOffsets(action, nullptr, originalEnd.container(), 0, originalEnd.offset()); |
613 | auto secondResult = processAncestorsAndTheirSiblings(action, originalEnd.container(), ProcessContentsBackward, WTFMove(firstResult), commonRoot.get()); |
614 | // FIXME: A bit peculiar that we silently ignore the exception here, but we do have at least some regression tests that rely on this behavior. |
615 | if (!secondResult.hasException()) |
616 | rightContents = secondResult.releaseReturnValue(); |
617 | } |
618 | |
619 | // delete all children of commonRoot between the start and end container |
620 | RefPtr<Node> processStart = childOfCommonRootBeforeOffset(originalStart.container(), originalStart.offset(), commonRoot.get()); |
621 | if (processStart && originalStart.container() != commonRoot) // processStart contains nodes before m_start. |
622 | processStart = processStart->nextSibling(); |
623 | RefPtr<Node> processEnd = childOfCommonRootBeforeOffset(originalEnd.container(), originalEnd.offset(), commonRoot.get()); |
624 | |
625 | // Collapse the range, making sure that the result is not within a node that was partially selected. |
626 | if (action == Extract || action == Delete) { |
627 | if (partialStart && commonRoot->contains(partialStart.get())) { |
628 | auto result = setStart(*partialStart->parentNode(), partialStart->computeNodeIndex() + 1); |
629 | if (result.hasException()) |
630 | return result.releaseException(); |
631 | } else if (partialEnd && commonRoot->contains(partialEnd.get())) { |
632 | auto result = setStart(*partialEnd->parentNode(), partialEnd->computeNodeIndex()); |
633 | if (result.hasException()) |
634 | return result.releaseException(); |
635 | } |
636 | m_end = m_start; |
637 | } |
638 | |
639 | // Now add leftContents, stuff in between, and rightContents to the fragment |
640 | // (or just delete the stuff in between) |
641 | |
642 | if ((action == Extract || action == Clone) && leftContents) { |
643 | auto result = fragment->appendChild(*leftContents); |
644 | if (result.hasException()) |
645 | return result.releaseException(); |
646 | } |
647 | |
648 | if (processStart) { |
649 | Vector<Ref<Node>> nodes; |
650 | for (Node* node = processStart.get(); node && node != processEnd; node = node->nextSibling()) |
651 | nodes.append(*node); |
652 | auto result = processNodes(action, nodes, commonRoot.get(), fragment.get()); |
653 | if (result.hasException()) |
654 | return result.releaseException(); |
655 | } |
656 | |
657 | if ((action == Extract || action == Clone) && rightContents) { |
658 | auto result = fragment->appendChild(*rightContents); |
659 | if (result.hasException()) |
660 | return result.releaseException(); |
661 | } |
662 | |
663 | return fragment; |
664 | } |
665 | |
666 | static inline ExceptionOr<void> deleteCharacterData(CharacterData& data, unsigned startOffset, unsigned endOffset) |
667 | { |
668 | if (data.length() - endOffset) { |
669 | auto result = data.deleteData(endOffset, data.length() - endOffset); |
670 | if (result.hasException()) |
671 | return result.releaseException(); |
672 | } |
673 | if (startOffset) { |
674 | auto result = data.deleteData(0, startOffset); |
675 | if (result.hasException()) |
676 | return result.releaseException(); |
677 | } |
678 | return { }; |
679 | } |
680 | |
681 | static ExceptionOr<RefPtr<Node>> processContentsBetweenOffsets(Range::ActionType action, RefPtr<DocumentFragment> fragment, RefPtr<Node> container, unsigned startOffset, unsigned endOffset) |
682 | { |
683 | ASSERT(container); |
684 | ASSERT(startOffset <= endOffset); |
685 | |
686 | RefPtr<Node> result; |
687 | |
688 | // This switch statement must be consistent with that of lengthOfContentsInNode. |
689 | switch (container->nodeType()) { |
690 | case Node::TEXT_NODE: |
691 | case Node::CDATA_SECTION_NODE: |
692 | case Node::COMMENT_NODE: |
693 | endOffset = std::min(endOffset, downcast<CharacterData>(*container).length()); |
694 | startOffset = std::min(startOffset, endOffset); |
695 | if (action == Range::Extract || action == Range::Clone) { |
696 | Ref<CharacterData> characters = downcast<CharacterData>(container->cloneNode(true).get()); |
697 | auto deleteResult = deleteCharacterData(characters, startOffset, endOffset); |
698 | if (deleteResult.hasException()) |
699 | return deleteResult.releaseException(); |
700 | if (fragment) { |
701 | result = fragment; |
702 | auto appendResult = result->appendChild(characters); |
703 | if (appendResult.hasException()) |
704 | return appendResult.releaseException(); |
705 | } else |
706 | result = WTFMove(characters); |
707 | } |
708 | if (action == Range::Extract || action == Range::Delete) { |
709 | auto deleteResult = downcast<CharacterData>(*container).deleteData(startOffset, endOffset - startOffset); |
710 | if (deleteResult.hasException()) |
711 | return deleteResult.releaseException(); |
712 | } |
713 | break; |
714 | case Node::PROCESSING_INSTRUCTION_NODE: { |
715 | auto& instruction = downcast<ProcessingInstruction>(*container); |
716 | endOffset = std::min(endOffset, downcast<ProcessingInstruction>(*container).data().length()); |
717 | startOffset = std::min(startOffset, endOffset); |
718 | if (action == Range::Extract || action == Range::Clone) { |
719 | Ref<ProcessingInstruction> processingInstruction = downcast<ProcessingInstruction>(container->cloneNode(true).get()); |
720 | processingInstruction->setData(processingInstruction->data().substring(startOffset, endOffset - startOffset)); |
721 | if (fragment) { |
722 | result = fragment; |
723 | auto appendResult = result->appendChild(processingInstruction); |
724 | if (appendResult.hasException()) |
725 | return appendResult.releaseException(); |
726 | } else |
727 | result = WTFMove(processingInstruction); |
728 | } |
729 | if (action == Range::Extract || action == Range::Delete) { |
730 | String data { instruction.data() }; |
731 | data.remove(startOffset, endOffset - startOffset); |
732 | instruction.setData(data); |
733 | } |
734 | break; |
735 | } |
736 | case Node::ELEMENT_NODE: |
737 | case Node::ATTRIBUTE_NODE: |
738 | case Node::DOCUMENT_NODE: |
739 | case Node::DOCUMENT_TYPE_NODE: |
740 | case Node::DOCUMENT_FRAGMENT_NODE: |
741 | // FIXME: Should we assert that some nodes never appear here? |
742 | if (action == Range::Extract || action == Range::Clone) { |
743 | if (fragment) |
744 | result = fragment; |
745 | else |
746 | result = container->cloneNode(false); |
747 | } |
748 | Vector<Ref<Node>> nodes; |
749 | Node* n = container->firstChild(); |
750 | for (unsigned i = startOffset; n && i; i--) |
751 | n = n->nextSibling(); |
752 | for (unsigned i = startOffset; n && i < endOffset; i++, n = n->nextSibling()) { |
753 | if (action != Range::Delete && n->isDocumentTypeNode()) { |
754 | return Exception { HierarchyRequestError }; |
755 | } |
756 | nodes.append(*n); |
757 | } |
758 | auto processResult = processNodes(action, nodes, container.get(), result); |
759 | if (processResult.hasException()) |
760 | return processResult.releaseException(); |
761 | break; |
762 | } |
763 | |
764 | return result; |
765 | } |
766 | |
767 | static ExceptionOr<void> processNodes(Range::ActionType action, Vector<Ref<Node>>& nodes, Node* oldContainer, RefPtr<Node> newContainer) |
768 | { |
769 | for (auto& node : nodes) { |
770 | switch (action) { |
771 | case Range::Delete: { |
772 | auto result = oldContainer->removeChild(node); |
773 | if (result.hasException()) |
774 | return result.releaseException(); |
775 | break; |
776 | } |
777 | case Range::Extract: { |
778 | auto result = newContainer->appendChild(node); // will remove node from its parent |
779 | if (result.hasException()) |
780 | return result.releaseException(); |
781 | break; |
782 | } |
783 | case Range::Clone: { |
784 | auto result = newContainer->appendChild(node->cloneNode(true)); |
785 | if (result.hasException()) |
786 | return result.releaseException(); |
787 | break; |
788 | } |
789 | } |
790 | } |
791 | return { }; |
792 | } |
793 | |
794 | ExceptionOr<RefPtr<Node>> processAncestorsAndTheirSiblings(Range::ActionType action, Node* container, ContentsProcessDirection direction, ExceptionOr<RefPtr<Node>>&& passedClonedContainer, Node* commonRoot) |
795 | { |
796 | if (passedClonedContainer.hasException()) |
797 | return WTFMove(passedClonedContainer); |
798 | |
799 | RefPtr<Node> clonedContainer = passedClonedContainer.releaseReturnValue(); |
800 | |
801 | Vector<Ref<ContainerNode>> ancestors; |
802 | for (ContainerNode* ancestor = container->parentNode(); ancestor && ancestor != commonRoot; ancestor = ancestor->parentNode()) |
803 | ancestors.append(*ancestor); |
804 | |
805 | RefPtr<Node> firstChildInAncestorToProcess = direction == ProcessContentsForward ? container->nextSibling() : container->previousSibling(); |
806 | for (auto& ancestor : ancestors) { |
807 | if (action == Range::Extract || action == Range::Clone) { |
808 | auto clonedAncestor = ancestor->cloneNode(false); // Might have been removed already during mutation event. |
809 | if (clonedContainer) { |
810 | auto result = clonedAncestor->appendChild(*clonedContainer); |
811 | if (result.hasException()) |
812 | return result.releaseException(); |
813 | } |
814 | clonedContainer = WTFMove(clonedAncestor); |
815 | } |
816 | |
817 | // Copy siblings of an ancestor of start/end containers |
818 | // FIXME: This assertion may fail if DOM is modified during mutation event |
819 | // FIXME: Share code with Range::processNodes |
820 | ASSERT(!firstChildInAncestorToProcess || firstChildInAncestorToProcess->parentNode() == ancestor.ptr()); |
821 | |
822 | Vector<Ref<Node>> nodes; |
823 | for (Node* child = firstChildInAncestorToProcess.get(); child; |
824 | child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling()) |
825 | nodes.append(*child); |
826 | |
827 | for (auto& child : nodes) { |
828 | switch (action) { |
829 | case Range::Delete: { |
830 | auto result = ancestor->removeChild(child); |
831 | if (result.hasException()) |
832 | return result.releaseException(); |
833 | break; |
834 | } |
835 | case Range::Extract: // will remove child from ancestor |
836 | if (direction == ProcessContentsForward) { |
837 | auto result = clonedContainer->appendChild(child); |
838 | if (result.hasException()) |
839 | return result.releaseException(); |
840 | } else { |
841 | auto result = clonedContainer->insertBefore(child, clonedContainer->firstChild()); |
842 | if (result.hasException()) |
843 | return result.releaseException(); |
844 | } |
845 | break; |
846 | case Range::Clone: |
847 | if (direction == ProcessContentsForward) { |
848 | auto result = clonedContainer->appendChild(child->cloneNode(true)); |
849 | if (result.hasException()) |
850 | return result.releaseException(); |
851 | } else { |
852 | auto result = clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild()); |
853 | if (result.hasException()) |
854 | return result.releaseException(); |
855 | } |
856 | break; |
857 | } |
858 | } |
859 | firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling(); |
860 | } |
861 | |
862 | return clonedContainer; |
863 | } |
864 | |
865 | ExceptionOr<Ref<DocumentFragment>> Range::() |
866 | { |
867 | auto result = processContents(Extract); |
868 | if (result.hasException()) |
869 | return result.releaseException(); |
870 | return result.releaseReturnValue().releaseNonNull(); |
871 | } |
872 | |
873 | ExceptionOr<Ref<DocumentFragment>> Range::cloneContents() |
874 | { |
875 | auto result = processContents(Clone); |
876 | if (result.hasException()) |
877 | return result.releaseException(); |
878 | return result.releaseReturnValue().releaseNonNull(); |
879 | } |
880 | |
881 | ExceptionOr<void> Range::insertNode(Ref<Node>&& node) |
882 | { |
883 | auto startContainerNodeType = startContainer().nodeType(); |
884 | |
885 | if (startContainerNodeType == Node::COMMENT_NODE || startContainerNodeType == Node::PROCESSING_INSTRUCTION_NODE) |
886 | return Exception { HierarchyRequestError }; |
887 | bool startIsText = startContainerNodeType == Node::TEXT_NODE; |
888 | if (startIsText && !startContainer().parentNode()) |
889 | return Exception { HierarchyRequestError }; |
890 | if (node.ptr() == &startContainer()) |
891 | return Exception { HierarchyRequestError }; |
892 | |
893 | RefPtr<Node> referenceNode = startIsText ? &startContainer() : startContainer().traverseToChildAt(startOffset()); |
894 | Node* parentNode = referenceNode ? referenceNode->parentNode() : &startContainer(); |
895 | if (!is<ContainerNode>(parentNode)) |
896 | return Exception { HierarchyRequestError }; |
897 | |
898 | Ref<ContainerNode> parent = downcast<ContainerNode>(*parentNode); |
899 | |
900 | auto result = parent->ensurePreInsertionValidity(node, referenceNode.get()); |
901 | if (result.hasException()) |
902 | return result.releaseException(); |
903 | |
904 | EventQueueScope scope; |
905 | if (startIsText) { |
906 | auto result = downcast<Text>(startContainer()).splitText(startOffset()); |
907 | if (result.hasException()) |
908 | return result.releaseException(); |
909 | referenceNode = result.releaseReturnValue(); |
910 | } |
911 | |
912 | if (referenceNode == node.ptr()) |
913 | referenceNode = referenceNode->nextSibling(); |
914 | |
915 | auto removeResult = node->remove(); |
916 | if (removeResult.hasException()) |
917 | return removeResult.releaseException(); |
918 | |
919 | unsigned newOffset = referenceNode ? referenceNode->computeNodeIndex() : parent->countChildNodes(); |
920 | if (is<DocumentFragment>(node)) |
921 | newOffset += downcast<DocumentFragment>(node.get()).countChildNodes(); |
922 | else |
923 | ++newOffset; |
924 | |
925 | auto insertResult = parent->insertBefore(node, referenceNode.get()); |
926 | if (insertResult.hasException()) |
927 | return insertResult.releaseException(); |
928 | |
929 | if (collapsed()) |
930 | return setEnd(WTFMove(parent), newOffset); |
931 | |
932 | return { }; |
933 | } |
934 | |
935 | String Range::toString() const |
936 | { |
937 | StringBuilder builder; |
938 | |
939 | Node* pastLast = pastLastNode(); |
940 | for (Node* node = firstNode(); node != pastLast; node = NodeTraversal::next(*node)) { |
941 | auto type = node->nodeType(); |
942 | if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) { |
943 | auto& data = downcast<CharacterData>(*node).data(); |
944 | unsigned length = data.length(); |
945 | unsigned start = node == &startContainer() ? std::min(m_start.offset(), length) : 0U; |
946 | unsigned end = node == &endContainer() ? std::min(std::max(start, m_end.offset()), length) : length; |
947 | builder.append(data, start, end - start); |
948 | } |
949 | } |
950 | |
951 | return builder.toString(); |
952 | } |
953 | |
954 | String Range::text() const |
955 | { |
956 | // We need to update layout, since plainText uses line boxes in the render tree. |
957 | // FIXME: As with innerText, we'd like this to work even if there are no render objects. |
958 | startContainer().document().updateLayout(); |
959 | |
960 | return plainText(this); |
961 | } |
962 | |
963 | // https://w3c.github.io/DOM-Parsing/#widl-Range-createContextualFragment-DocumentFragment-DOMString-fragment |
964 | ExceptionOr<Ref<DocumentFragment>> Range::createContextualFragment(const String& markup) |
965 | { |
966 | Node& node = startContainer(); |
967 | RefPtr<Element> element; |
968 | if (is<Document>(node) || is<DocumentFragment>(node)) |
969 | element = nullptr; |
970 | else if (is<Element>(node)) |
971 | element = &downcast<Element>(node); |
972 | else |
973 | element = node.parentElement(); |
974 | if (!element || (element->document().isHTMLDocument() && is<HTMLHtmlElement>(*element))) |
975 | element = HTMLBodyElement::create(node.document()); |
976 | return WebCore::createContextualFragment(*element, markup, AllowScriptingContentAndDoNotMarkAlreadyStarted); |
977 | } |
978 | |
979 | void Range::detach() |
980 | { |
981 | // This is now a no-op as per the DOM specification. |
982 | } |
983 | |
984 | ExceptionOr<Node*> Range::checkNodeWOffset(Node& node, unsigned offset) const |
985 | { |
986 | switch (node.nodeType()) { |
987 | case Node::DOCUMENT_TYPE_NODE: |
988 | return Exception { InvalidNodeTypeError }; |
989 | case Node::CDATA_SECTION_NODE: |
990 | case Node::COMMENT_NODE: |
991 | case Node::TEXT_NODE: |
992 | case Node::PROCESSING_INSTRUCTION_NODE: |
993 | if (offset > downcast<CharacterData>(node).length()) |
994 | return Exception { IndexSizeError }; |
995 | return nullptr; |
996 | case Node::ATTRIBUTE_NODE: |
997 | case Node::DOCUMENT_FRAGMENT_NODE: |
998 | case Node::DOCUMENT_NODE: |
999 | case Node::ELEMENT_NODE: { |
1000 | if (!offset) |
1001 | return nullptr; |
1002 | Node* childBefore = node.traverseToChildAt(offset - 1); |
1003 | if (!childBefore) |
1004 | return Exception { IndexSizeError }; |
1005 | return childBefore; |
1006 | } |
1007 | } |
1008 | ASSERT_NOT_REACHED(); |
1009 | return Exception { InvalidNodeTypeError }; |
1010 | } |
1011 | |
1012 | Ref<Range> Range::cloneRange() const |
1013 | { |
1014 | return Range::create(ownerDocument(), &startContainer(), m_start.offset(), &endContainer(), m_end.offset()); |
1015 | } |
1016 | |
1017 | ExceptionOr<void> Range::setStartAfter(Node& refNode) |
1018 | { |
1019 | if (!refNode.parentNode()) |
1020 | return Exception { InvalidNodeTypeError }; |
1021 | return setStart(*refNode.parentNode(), refNode.computeNodeIndex() + 1); |
1022 | } |
1023 | |
1024 | ExceptionOr<void> Range::setEndBefore(Node& refNode) |
1025 | { |
1026 | if (!refNode.parentNode()) |
1027 | return Exception { InvalidNodeTypeError }; |
1028 | return setEnd(*refNode.parentNode(), refNode.computeNodeIndex()); |
1029 | } |
1030 | |
1031 | ExceptionOr<void> Range::setEndAfter(Node& refNode) |
1032 | { |
1033 | if (!refNode.parentNode()) |
1034 | return Exception { InvalidNodeTypeError }; |
1035 | return setEnd(*refNode.parentNode(), refNode.computeNodeIndex() + 1); |
1036 | } |
1037 | |
1038 | ExceptionOr<void> Range::selectNode(Node& refNode) |
1039 | { |
1040 | if (!refNode.parentNode()) |
1041 | return Exception { InvalidNodeTypeError }; |
1042 | |
1043 | if (&ownerDocument() != &refNode.document()) |
1044 | setDocument(refNode.document()); |
1045 | |
1046 | unsigned index = refNode.computeNodeIndex(); |
1047 | auto result = setStart(*refNode.parentNode(), index); |
1048 | if (result.hasException()) |
1049 | return result.releaseException(); |
1050 | return setEnd(*refNode.parentNode(), index + 1); |
1051 | } |
1052 | |
1053 | ExceptionOr<void> Range::selectNodeContents(Node& refNode) |
1054 | { |
1055 | if (refNode.isDocumentTypeNode()) |
1056 | return Exception { InvalidNodeTypeError }; |
1057 | |
1058 | if (&ownerDocument() != &refNode.document()) |
1059 | setDocument(refNode.document()); |
1060 | |
1061 | m_start.setToStartOfNode(refNode); |
1062 | m_end.setToEndOfNode(refNode); |
1063 | |
1064 | return { }; |
1065 | } |
1066 | |
1067 | // https://dom.spec.whatwg.org/#dom-range-surroundcontents |
1068 | ExceptionOr<void> Range::surroundContents(Node& newParent) |
1069 | { |
1070 | Ref<Node> protectedNewParent(newParent); |
1071 | |
1072 | // Step 1: If a non-Text node is partially contained in the context object, then throw an InvalidStateError. |
1073 | Node* startNonTextContainer = &startContainer(); |
1074 | if (startNonTextContainer->nodeType() == Node::TEXT_NODE) |
1075 | startNonTextContainer = startNonTextContainer->parentNode(); |
1076 | Node* endNonTextContainer = &endContainer(); |
1077 | if (endNonTextContainer->nodeType() == Node::TEXT_NODE) |
1078 | endNonTextContainer = endNonTextContainer->parentNode(); |
1079 | if (startNonTextContainer != endNonTextContainer) |
1080 | return Exception { InvalidStateError }; |
1081 | |
1082 | // Step 2: If newParent is a Document, DocumentType, or DocumentFragment node, then throw an InvalidNodeTypeError. |
1083 | switch (newParent.nodeType()) { |
1084 | case Node::ATTRIBUTE_NODE: |
1085 | case Node::DOCUMENT_FRAGMENT_NODE: |
1086 | case Node::DOCUMENT_NODE: |
1087 | case Node::DOCUMENT_TYPE_NODE: |
1088 | return Exception { InvalidNodeTypeError }; |
1089 | case Node::CDATA_SECTION_NODE: |
1090 | case Node::COMMENT_NODE: |
1091 | case Node::ELEMENT_NODE: |
1092 | case Node::PROCESSING_INSTRUCTION_NODE: |
1093 | case Node::TEXT_NODE: |
1094 | break; |
1095 | } |
1096 | |
1097 | // Step 3: Let fragment be the result of extracting context object. |
1098 | auto fragment = extractContents(); |
1099 | if (fragment.hasException()) |
1100 | return fragment.releaseException(); |
1101 | |
1102 | // Step 4: If newParent has children, replace all with null within newParent. |
1103 | if (newParent.hasChildNodes()) |
1104 | downcast<ContainerNode>(newParent).replaceAllChildren(nullptr); |
1105 | |
1106 | // Step 5: Insert newParent into context object. |
1107 | auto insertResult = insertNode(newParent); |
1108 | if (insertResult.hasException()) |
1109 | return insertResult.releaseException(); |
1110 | |
1111 | // Step 6: Append fragment to newParent. |
1112 | auto appendResult = newParent.appendChild(fragment.releaseReturnValue()); |
1113 | if (appendResult.hasException()) |
1114 | return appendResult.releaseException(); |
1115 | |
1116 | // Step 7: Select newParent within context object. |
1117 | return selectNode(newParent); |
1118 | } |
1119 | |
1120 | ExceptionOr<void> Range::setStartBefore(Node& refNode) |
1121 | { |
1122 | if (!refNode.parentNode()) |
1123 | return Exception { InvalidNodeTypeError }; |
1124 | return setStart(*refNode.parentNode(), refNode.computeNodeIndex()); |
1125 | } |
1126 | |
1127 | Node* Range::firstNode() const |
1128 | { |
1129 | if (startContainer().isCharacterDataNode()) |
1130 | return &startContainer(); |
1131 | if (Node* child = startContainer().traverseToChildAt(m_start.offset())) |
1132 | return child; |
1133 | if (!m_start.offset()) |
1134 | return &startContainer(); |
1135 | return NodeTraversal::nextSkippingChildren(startContainer()); |
1136 | } |
1137 | |
1138 | ShadowRoot* Range::shadowRoot() const |
1139 | { |
1140 | return startContainer().containingShadowRoot(); |
1141 | } |
1142 | |
1143 | Node* Range::pastLastNode() const |
1144 | { |
1145 | if (endContainer().isCharacterDataNode()) |
1146 | return NodeTraversal::nextSkippingChildren(endContainer()); |
1147 | if (Node* child = endContainer().traverseToChildAt(m_end.offset())) |
1148 | return child; |
1149 | return NodeTraversal::nextSkippingChildren(endContainer()); |
1150 | } |
1151 | |
1152 | IntRect Range::absoluteBoundingBox() const |
1153 | { |
1154 | IntRect result; |
1155 | Vector<IntRect> rects; |
1156 | absoluteTextRects(rects); |
1157 | for (auto& rect : rects) |
1158 | result.unite(rect); |
1159 | return result; |
1160 | } |
1161 | |
1162 | Vector<FloatRect> Range::absoluteRectsForRangeInText(Node* node, RenderText& renderText, bool useSelectionHeight, bool& isFixed, RespectClippingForTextRects respectClippingForTextRects) const |
1163 | { |
1164 | unsigned startOffset = node == &startContainer() ? m_start.offset() : 0; |
1165 | unsigned endOffset = node == &endContainer() ? m_end.offset() : std::numeric_limits<unsigned>::max(); |
1166 | |
1167 | auto textQuads = renderText.absoluteQuadsForRange(startOffset, endOffset, useSelectionHeight, &isFixed); |
1168 | |
1169 | if (respectClippingForTextRects == RespectClippingForTextRects::Yes) { |
1170 | Vector<FloatRect> clippedRects; |
1171 | clippedRects.reserveInitialCapacity(textQuads.size()); |
1172 | |
1173 | auto absoluteClippedOverflowRect = renderText.absoluteClippedOverflowRect(); |
1174 | |
1175 | for (auto& quad : textQuads) { |
1176 | auto clippedRect = intersection(quad.boundingBox(), absoluteClippedOverflowRect); |
1177 | if (!clippedRect.isEmpty()) |
1178 | clippedRects.uncheckedAppend(clippedRect); |
1179 | } |
1180 | |
1181 | return clippedRects; |
1182 | } |
1183 | |
1184 | Vector<FloatRect> floatRects; |
1185 | floatRects.reserveInitialCapacity(textQuads.size()); |
1186 | for (auto& quad : textQuads) |
1187 | floatRects.uncheckedAppend(quad.boundingBox()); |
1188 | return floatRects; |
1189 | } |
1190 | |
1191 | void Range::absoluteTextRects(Vector<IntRect>& rects, bool useSelectionHeight, RangeInFixedPosition* inFixed, RespectClippingForTextRects respectClippingForTextRects) const |
1192 | { |
1193 | // FIXME: This function should probably return FloatRects. |
1194 | |
1195 | bool allFixed = true; |
1196 | bool someFixed = false; |
1197 | |
1198 | Node* stopNode = pastLastNode(); |
1199 | for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { |
1200 | RenderObject* renderer = node->renderer(); |
1201 | if (!renderer) |
1202 | continue; |
1203 | bool isFixed = false; |
1204 | if (renderer->isBR()) |
1205 | renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute())); |
1206 | else if (is<RenderText>(*renderer)) { |
1207 | auto rectsForRenderer = absoluteRectsForRangeInText(node, downcast<RenderText>(*renderer), useSelectionHeight, isFixed, respectClippingForTextRects); |
1208 | for (auto& rect : rectsForRenderer) |
1209 | rects.append(enclosingIntRect(rect)); |
1210 | } else |
1211 | continue; |
1212 | allFixed &= isFixed; |
1213 | someFixed |= isFixed; |
1214 | } |
1215 | |
1216 | if (inFixed) |
1217 | *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); |
1218 | } |
1219 | |
1220 | void Range::absoluteTextQuads(Vector<FloatQuad>& quads, bool useSelectionHeight, RangeInFixedPosition* inFixed) const |
1221 | { |
1222 | bool allFixed = true; |
1223 | bool someFixed = false; |
1224 | |
1225 | Node* stopNode = pastLastNode(); |
1226 | for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { |
1227 | RenderObject* renderer = node->renderer(); |
1228 | if (!renderer) |
1229 | continue; |
1230 | bool isFixed = false; |
1231 | if (renderer->isBR()) |
1232 | renderer->absoluteQuads(quads, &isFixed); |
1233 | else if (is<RenderText>(*renderer)) { |
1234 | unsigned startOffset = node == &startContainer() ? m_start.offset() : 0; |
1235 | unsigned endOffset = node == &endContainer() ? m_end.offset() : std::numeric_limits<unsigned>::max(); |
1236 | quads.appendVector(downcast<RenderText>(*renderer).absoluteQuadsForRange(startOffset, endOffset, useSelectionHeight, &isFixed)); |
1237 | } else |
1238 | continue; |
1239 | allFixed &= isFixed; |
1240 | someFixed |= isFixed; |
1241 | } |
1242 | |
1243 | if (inFixed) |
1244 | *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); |
1245 | } |
1246 | |
1247 | #if PLATFORM(IOS_FAMILY) |
1248 | static bool intervalsSufficientlyOverlap(int startA, int endA, int startB, int endB) |
1249 | { |
1250 | if (endA <= startA || endB <= startB) |
1251 | return false; |
1252 | |
1253 | const float sufficientOverlap = .75; |
1254 | |
1255 | int lengthA = endA - startA; |
1256 | int lengthB = endB - startB; |
1257 | |
1258 | int maxStart = std::max(startA, startB); |
1259 | int minEnd = std::min(endA, endB); |
1260 | |
1261 | if (maxStart > minEnd) |
1262 | return false; |
1263 | |
1264 | return minEnd - maxStart >= sufficientOverlap * std::min(lengthA, lengthB); |
1265 | } |
1266 | |
1267 | static inline void adjustLineHeightOfSelectionRects(Vector<SelectionRect>& rects, size_t numberOfRects, int lineNumber, int lineTop, int lineHeight) |
1268 | { |
1269 | ASSERT(rects.size() >= numberOfRects); |
1270 | for (size_t i = numberOfRects; i; ) { |
1271 | --i; |
1272 | if (rects[i].lineNumber()) |
1273 | break; |
1274 | rects[i].setLineNumber(lineNumber); |
1275 | rects[i].setLogicalTop(lineTop); |
1276 | rects[i].setLogicalHeight(lineHeight); |
1277 | } |
1278 | } |
1279 | |
1280 | static SelectionRect coalesceSelectionRects(const SelectionRect& original, const SelectionRect& previous) |
1281 | { |
1282 | SelectionRect result(unionRect(previous.rect(), original.rect()), original.isHorizontal(), original.pageNumber()); |
1283 | result.setDirection(original.containsStart() || original.containsEnd() ? original.direction() : previous.direction()); |
1284 | result.setContainsStart(previous.containsStart() || original.containsStart()); |
1285 | result.setContainsEnd(previous.containsEnd() || original.containsEnd()); |
1286 | result.setIsFirstOnLine(previous.isFirstOnLine() || original.isFirstOnLine()); |
1287 | result.setIsLastOnLine(previous.isLastOnLine() || original.isLastOnLine()); |
1288 | return result; |
1289 | } |
1290 | |
1291 | // This function is similar in spirit to addLineBoxRects, but annotates the returned rectangles |
1292 | // with additional state which helps iOS draw selections in its unique way. |
1293 | int Range::collectSelectionRectsWithoutUnionInteriorLines(Vector<SelectionRect>& rects) const |
1294 | { |
1295 | auto& startContainer = this->startContainer(); |
1296 | auto& endContainer = this->endContainer(); |
1297 | int startOffset = m_start.offset(); |
1298 | int endOffset = m_end.offset(); |
1299 | |
1300 | Vector<SelectionRect> newRects; |
1301 | Node* stopNode = pastLastNode(); |
1302 | bool hasFlippedWritingMode = startContainer.renderer() && startContainer.renderer()->style().isFlippedBlocksWritingMode(); |
1303 | bool containsDifferentWritingModes = false; |
1304 | for (Node* node = firstNode(); node && node != stopNode; node = NodeTraversal::next(*node)) { |
1305 | RenderObject* renderer = node->renderer(); |
1306 | // Only ask leaf render objects for their line box rects. |
1307 | if (renderer && !renderer->firstChildSlow() && renderer->style().userSelect() != UserSelect::None) { |
1308 | bool isStartNode = renderer->node() == &startContainer; |
1309 | bool isEndNode = renderer->node() == &endContainer; |
1310 | if (hasFlippedWritingMode != renderer->style().isFlippedBlocksWritingMode()) |
1311 | containsDifferentWritingModes = true; |
1312 | // FIXME: Sending 0 for the startOffset is a weird way of telling the renderer that the selection |
1313 | // doesn't start inside it, since we'll also send 0 if the selection *does* start in it, at offset 0. |
1314 | // |
1315 | // FIXME: Selection endpoints aren't always inside leaves, and we only build SelectionRects for leaves, |
1316 | // so we can't accurately determine which SelectionRects contain the selection start and end using |
1317 | // only the offsets of the start and end. We need to pass the whole Range. |
1318 | int beginSelectionOffset = isStartNode ? startOffset : 0; |
1319 | int endSelectionOffset = isEndNode ? endOffset : std::numeric_limits<int>::max(); |
1320 | renderer->collectSelectionRects(newRects, beginSelectionOffset, endSelectionOffset); |
1321 | for (auto& selectionRect : newRects) { |
1322 | if (selectionRect.containsStart() && !isStartNode) |
1323 | selectionRect.setContainsStart(false); |
1324 | if (selectionRect.containsEnd() && !isEndNode) |
1325 | selectionRect.setContainsEnd(false); |
1326 | if (selectionRect.logicalWidth() || selectionRect.logicalHeight()) |
1327 | rects.append(selectionRect); |
1328 | } |
1329 | newRects.shrink(0); |
1330 | } |
1331 | } |
1332 | |
1333 | // The range could span over nodes with different writing modes. |
1334 | // If this is the case, we use the writing mode of the common ancestor. |
1335 | if (containsDifferentWritingModes) { |
1336 | if (Node* ancestor = commonAncestorContainer(&startContainer, &endContainer)) |
1337 | hasFlippedWritingMode = ancestor->renderer()->style().isFlippedBlocksWritingMode(); |
1338 | } |
1339 | |
1340 | const size_t numberOfRects = rects.size(); |
1341 | |
1342 | // If the selection ends in a BR, then add the line break bit to the last |
1343 | // rect we have. This will cause its selection rect to extend to the |
1344 | // end of the line. |
1345 | if (stopNode && stopNode->hasTagName(HTMLNames::brTag) && numberOfRects) { |
1346 | // Only set the line break bit if the end of the range actually |
1347 | // extends all the way to include the <br>. VisiblePosition helps to |
1348 | // figure this out. |
1349 | VisiblePosition endPosition(createLegacyEditingPosition(&endContainer, endOffset), VP_DEFAULT_AFFINITY); |
1350 | VisiblePosition brPosition(createLegacyEditingPosition(stopNode, 0), VP_DEFAULT_AFFINITY); |
1351 | if (endPosition == brPosition) |
1352 | rects.last().setIsLineBreak(true); |
1353 | } |
1354 | |
1355 | int lineTop = std::numeric_limits<int>::max(); |
1356 | int lineBottom = std::numeric_limits<int>::min(); |
1357 | int lastLineTop = lineTop; |
1358 | int lastLineBottom = lineBottom; |
1359 | int lineNumber = 0; |
1360 | |
1361 | for (size_t i = 0; i < numberOfRects; ++i) { |
1362 | int currentRectTop = rects[i].logicalTop(); |
1363 | int currentRectBottom = currentRectTop + rects[i].logicalHeight(); |
1364 | |
1365 | // We don't want to count the ruby text as a separate line. |
1366 | if (intervalsSufficientlyOverlap(currentRectTop, currentRectBottom, lineTop, lineBottom) || (i && rects[i].isRubyText())) { |
1367 | // Grow the current line bounds. |
1368 | lineTop = std::min(lineTop, currentRectTop); |
1369 | lineBottom = std::max(lineBottom, currentRectBottom); |
1370 | // Avoid overlap with the previous line. |
1371 | if (!hasFlippedWritingMode) |
1372 | lineTop = std::max(lastLineBottom, lineTop); |
1373 | else |
1374 | lineBottom = std::min(lastLineTop, lineBottom); |
1375 | } else { |
1376 | adjustLineHeightOfSelectionRects(rects, i, lineNumber, lineTop, lineBottom - lineTop); |
1377 | if (!hasFlippedWritingMode) { |
1378 | lastLineTop = lineTop; |
1379 | if (currentRectBottom >= lastLineTop) { |
1380 | lastLineBottom = lineBottom; |
1381 | lineTop = lastLineBottom; |
1382 | } else { |
1383 | lineTop = currentRectTop; |
1384 | lastLineBottom = std::numeric_limits<int>::min(); |
1385 | } |
1386 | lineBottom = currentRectBottom; |
1387 | } else { |
1388 | lastLineBottom = lineBottom; |
1389 | if (currentRectTop <= lastLineBottom && i && rects[i].pageNumber() == rects[i - 1].pageNumber()) { |
1390 | lastLineTop = lineTop; |
1391 | lineBottom = lastLineTop; |
1392 | } else { |
1393 | lastLineTop = std::numeric_limits<int>::max(); |
1394 | lineBottom = currentRectBottom; |
1395 | } |
1396 | lineTop = currentRectTop; |
1397 | } |
1398 | ++lineNumber; |
1399 | } |
1400 | } |
1401 | |
1402 | // Adjust line height. |
1403 | adjustLineHeightOfSelectionRects(rects, numberOfRects, lineNumber, lineTop, lineBottom - lineTop); |
1404 | |
1405 | // Sort the rectangles and make sure there are no gaps. The rectangles could be unsorted when |
1406 | // there is ruby text and we could have gaps on the line when adjacent elements on the line |
1407 | // have a different orientation. |
1408 | size_t firstRectWithCurrentLineNumber = 0; |
1409 | for (size_t currentRect = 1; currentRect < numberOfRects; ++currentRect) { |
1410 | if (rects[currentRect].lineNumber() != rects[currentRect - 1].lineNumber()) { |
1411 | firstRectWithCurrentLineNumber = currentRect; |
1412 | continue; |
1413 | } |
1414 | if (rects[currentRect].logicalLeft() >= rects[currentRect - 1].logicalLeft()) |
1415 | continue; |
1416 | |
1417 | SelectionRect selectionRect = rects[currentRect]; |
1418 | size_t i; |
1419 | for (i = currentRect; i > firstRectWithCurrentLineNumber && selectionRect.logicalLeft() < rects[i - 1].logicalLeft(); --i) |
1420 | rects[i] = rects[i - 1]; |
1421 | rects[i] = selectionRect; |
1422 | } |
1423 | |
1424 | for (size_t j = 1; j < numberOfRects; ++j) { |
1425 | if (rects[j].lineNumber() != rects[j - 1].lineNumber()) |
1426 | continue; |
1427 | SelectionRect& previousRect = rects[j - 1]; |
1428 | bool previousRectMayNotReachRightEdge = (previousRect.direction() == TextDirection::LTR && previousRect.containsEnd()) || (previousRect.direction() == TextDirection::RTL && previousRect.containsStart()); |
1429 | if (previousRectMayNotReachRightEdge) |
1430 | continue; |
1431 | int adjustedWidth = rects[j].logicalLeft() - previousRect.logicalLeft(); |
1432 | if (adjustedWidth > previousRect.logicalWidth()) |
1433 | previousRect.setLogicalWidth(adjustedWidth); |
1434 | } |
1435 | |
1436 | int maxLineNumber = lineNumber; |
1437 | |
1438 | // Extend rects out to edges as needed. |
1439 | for (size_t i = 0; i < numberOfRects; ++i) { |
1440 | SelectionRect& selectionRect = rects[i]; |
1441 | if (!selectionRect.isLineBreak() && selectionRect.lineNumber() >= maxLineNumber) |
1442 | continue; |
1443 | if (selectionRect.direction() == TextDirection::RTL && selectionRect.isFirstOnLine()) { |
1444 | selectionRect.setLogicalWidth(selectionRect.logicalWidth() + selectionRect.logicalLeft() - selectionRect.minX()); |
1445 | selectionRect.setLogicalLeft(selectionRect.minX()); |
1446 | } else if (selectionRect.direction() == TextDirection::LTR && selectionRect.isLastOnLine()) |
1447 | selectionRect.setLogicalWidth(selectionRect.maxX() - selectionRect.logicalLeft()); |
1448 | } |
1449 | |
1450 | return maxLineNumber; |
1451 | } |
1452 | |
1453 | void Range::collectSelectionRects(Vector<SelectionRect>& rects) const |
1454 | { |
1455 | int maxLineNumber = collectSelectionRectsWithoutUnionInteriorLines(rects); |
1456 | const size_t numberOfRects = rects.size(); |
1457 | |
1458 | // Union all the rectangles on interior lines (i.e. not first or last). |
1459 | // On first and last lines, just avoid having overlaps by merging intersecting rectangles. |
1460 | Vector<SelectionRect> unionedRects; |
1461 | IntRect interiorUnionRect; |
1462 | for (size_t i = 0; i < numberOfRects; ++i) { |
1463 | SelectionRect& currentRect = rects[i]; |
1464 | if (currentRect.lineNumber() == 1) { |
1465 | ASSERT(interiorUnionRect.isEmpty()); |
1466 | if (!unionedRects.isEmpty()) { |
1467 | SelectionRect& previousRect = unionedRects.last(); |
1468 | if (previousRect.rect().intersects(currentRect.rect())) { |
1469 | previousRect = coalesceSelectionRects(currentRect, previousRect); |
1470 | continue; |
1471 | } |
1472 | } |
1473 | // Couldn't merge with previous rect, so just appending. |
1474 | unionedRects.append(currentRect); |
1475 | } else if (currentRect.lineNumber() < maxLineNumber) { |
1476 | if (interiorUnionRect.isEmpty()) { |
1477 | // Start collecting interior rects. |
1478 | interiorUnionRect = currentRect.rect(); |
1479 | } else if (interiorUnionRect.intersects(currentRect.rect()) |
1480 | || interiorUnionRect.maxX() == currentRect.rect().x() |
1481 | || interiorUnionRect.maxY() == currentRect.rect().y() |
1482 | || interiorUnionRect.x() == currentRect.rect().maxX() |
1483 | || interiorUnionRect.y() == currentRect.rect().maxY()) { |
1484 | // Only union the lines that are attached. |
1485 | // For iBooks, the interior lines may cross multiple horizontal pages. |
1486 | interiorUnionRect.unite(currentRect.rect()); |
1487 | } else { |
1488 | unionedRects.append(SelectionRect(interiorUnionRect, currentRect.isHorizontal(), currentRect.pageNumber())); |
1489 | interiorUnionRect = currentRect.rect(); |
1490 | } |
1491 | } else { |
1492 | // Processing last line. |
1493 | if (!interiorUnionRect.isEmpty()) { |
1494 | unionedRects.append(SelectionRect(interiorUnionRect, currentRect.isHorizontal(), currentRect.pageNumber())); |
1495 | interiorUnionRect = IntRect(); |
1496 | } |
1497 | |
1498 | ASSERT(!unionedRects.isEmpty()); |
1499 | SelectionRect& previousRect = unionedRects.last(); |
1500 | if (previousRect.logicalTop() == currentRect.logicalTop() && previousRect.rect().intersects(currentRect.rect())) { |
1501 | // previousRect is also on the last line, and intersects the current one. |
1502 | previousRect = coalesceSelectionRects(currentRect, previousRect); |
1503 | continue; |
1504 | } |
1505 | // Couldn't merge with previous rect, so just appending. |
1506 | unionedRects.append(currentRect); |
1507 | } |
1508 | } |
1509 | |
1510 | rects.swap(unionedRects); |
1511 | } |
1512 | #endif |
1513 | |
1514 | #if ENABLE(TREE_DEBUGGING) |
1515 | void Range::formatForDebugger(char* buffer, unsigned length) const |
1516 | { |
1517 | StringBuilder result; |
1518 | |
1519 | const int FormatBufferSize = 1024; |
1520 | char s[FormatBufferSize]; |
1521 | result.appendLiteral("from offset " ); |
1522 | result.appendNumber(m_start.offset()); |
1523 | result.appendLiteral(" of " ); |
1524 | startContainer().formatForDebugger(s, FormatBufferSize); |
1525 | result.append(s); |
1526 | result.appendLiteral(" to offset " ); |
1527 | result.appendNumber(m_end.offset()); |
1528 | result.appendLiteral(" of " ); |
1529 | endContainer().formatForDebugger(s, FormatBufferSize); |
1530 | result.append(s); |
1531 | |
1532 | strncpy(buffer, result.toString().utf8().data(), length - 1); |
1533 | } |
1534 | #endif |
1535 | |
1536 | bool Range::contains(const Range& other) const |
1537 | { |
1538 | if (commonAncestorContainer()->ownerDocument() != other.commonAncestorContainer()->ownerDocument()) |
1539 | return false; |
1540 | |
1541 | auto startToStart = compareBoundaryPoints(Range::START_TO_START, other); |
1542 | if (startToStart.hasException() || startToStart.releaseReturnValue() > 0) |
1543 | return false; |
1544 | |
1545 | auto endToEnd = compareBoundaryPoints(Range::END_TO_END, other); |
1546 | return !endToEnd.hasException() && endToEnd.releaseReturnValue() >= 0; |
1547 | } |
1548 | |
1549 | bool Range::contains(const VisiblePosition& position) const |
1550 | { |
1551 | RefPtr<Range> positionRange = makeRange(position, position); |
1552 | if (!positionRange) |
1553 | return false; |
1554 | return contains(*positionRange); |
1555 | } |
1556 | |
1557 | bool areRangesEqual(const Range* a, const Range* b) |
1558 | { |
1559 | if (a == b) |
1560 | return true; |
1561 | if (!a || !b) |
1562 | return false; |
1563 | return a->startPosition() == b->startPosition() && a->endPosition() == b->endPosition(); |
1564 | } |
1565 | |
1566 | bool rangesOverlap(const Range* a, const Range* b) |
1567 | { |
1568 | if (!a || !b) |
1569 | return false; |
1570 | |
1571 | if (a == b) |
1572 | return true; |
1573 | |
1574 | if (a->commonAncestorContainer()->ownerDocument() != b->commonAncestorContainer()->ownerDocument()) |
1575 | return false; |
1576 | |
1577 | short startToStart = a->compareBoundaryPoints(Range::START_TO_START, *b).releaseReturnValue(); |
1578 | short endToEnd = a->compareBoundaryPoints(Range::END_TO_END, *b).releaseReturnValue(); |
1579 | |
1580 | // First range contains the second range. |
1581 | if (startToStart <= 0 && endToEnd >= 0) |
1582 | return true; |
1583 | |
1584 | // End of first range is inside second range. |
1585 | if (a->compareBoundaryPoints(Range::START_TO_END, *b).releaseReturnValue() >= 0 && endToEnd <= 0) |
1586 | return true; |
1587 | |
1588 | // Start of first range is inside second range. |
1589 | if (startToStart >= 0 && a->compareBoundaryPoints(Range::END_TO_START, *b).releaseReturnValue() <= 0) |
1590 | return true; |
1591 | |
1592 | return false; |
1593 | } |
1594 | |
1595 | Ref<Range> rangeOfContents(Node& node) |
1596 | { |
1597 | auto range = Range::create(node.document()); |
1598 | range->selectNodeContents(node); |
1599 | return range; |
1600 | } |
1601 | |
1602 | static inline void boundaryNodeChildrenChanged(RangeBoundaryPoint& boundary, ContainerNode& container) |
1603 | { |
1604 | if (!boundary.childBefore()) |
1605 | return; |
1606 | if (boundary.container() != &container) |
1607 | return; |
1608 | boundary.invalidateOffset(); |
1609 | } |
1610 | |
1611 | void Range::nodeChildrenChanged(ContainerNode& container) |
1612 | { |
1613 | ASSERT(&container.document() == &ownerDocument()); |
1614 | boundaryNodeChildrenChanged(m_start, container); |
1615 | boundaryNodeChildrenChanged(m_end, container); |
1616 | } |
1617 | |
1618 | static inline void boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode& container) |
1619 | { |
1620 | for (Node* nodeToBeRemoved = container.firstChild(); nodeToBeRemoved; nodeToBeRemoved = nodeToBeRemoved->nextSibling()) { |
1621 | if (boundary.childBefore() == nodeToBeRemoved) { |
1622 | boundary.setToStartOfNode(container); |
1623 | return; |
1624 | } |
1625 | |
1626 | for (Node* n = boundary.container(); n; n = n->parentNode()) { |
1627 | if (n == nodeToBeRemoved) { |
1628 | boundary.setToStartOfNode(container); |
1629 | return; |
1630 | } |
1631 | } |
1632 | } |
1633 | } |
1634 | |
1635 | void Range::nodeChildrenWillBeRemoved(ContainerNode& container) |
1636 | { |
1637 | ASSERT(&container.document() == &ownerDocument()); |
1638 | boundaryNodeChildrenWillBeRemoved(m_start, container); |
1639 | boundaryNodeChildrenWillBeRemoved(m_end, container); |
1640 | } |
1641 | |
1642 | static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node& nodeToBeRemoved) |
1643 | { |
1644 | if (boundary.childBefore() == &nodeToBeRemoved) { |
1645 | boundary.childBeforeWillBeRemoved(); |
1646 | return; |
1647 | } |
1648 | |
1649 | for (Node* n = boundary.container(); n; n = n->parentNode()) { |
1650 | if (n == &nodeToBeRemoved) { |
1651 | boundary.setToBeforeChild(nodeToBeRemoved); |
1652 | return; |
1653 | } |
1654 | } |
1655 | } |
1656 | |
1657 | void Range::nodeWillBeRemoved(Node& node) |
1658 | { |
1659 | ASSERT(&node.document() == &ownerDocument()); |
1660 | ASSERT(&node != &ownerDocument()); |
1661 | ASSERT(node.parentNode()); |
1662 | boundaryNodeWillBeRemoved(m_start, node); |
1663 | boundaryNodeWillBeRemoved(m_end, node); |
1664 | } |
1665 | |
1666 | static inline void boundaryTextInserted(RangeBoundaryPoint& boundary, Node& text, unsigned offset, unsigned length) |
1667 | { |
1668 | if (boundary.container() != &text) |
1669 | return; |
1670 | unsigned boundaryOffset = boundary.offset(); |
1671 | if (offset >= boundaryOffset) |
1672 | return; |
1673 | boundary.setOffset(boundaryOffset + length); |
1674 | } |
1675 | |
1676 | void Range::textInserted(Node& text, unsigned offset, unsigned length) |
1677 | { |
1678 | ASSERT(&text.document() == &ownerDocument()); |
1679 | boundaryTextInserted(m_start, text, offset, length); |
1680 | boundaryTextInserted(m_end, text, offset, length); |
1681 | } |
1682 | |
1683 | static inline void boundaryTextRemoved(RangeBoundaryPoint& boundary, Node& text, unsigned offset, unsigned length) |
1684 | { |
1685 | if (boundary.container() != &text) |
1686 | return; |
1687 | unsigned boundaryOffset = boundary.offset(); |
1688 | if (offset >= boundaryOffset) |
1689 | return; |
1690 | if (offset + length >= boundaryOffset) |
1691 | boundary.setOffset(offset); |
1692 | else |
1693 | boundary.setOffset(boundaryOffset - length); |
1694 | } |
1695 | |
1696 | void Range::textRemoved(Node& text, unsigned offset, unsigned length) |
1697 | { |
1698 | ASSERT(&text.document() == &ownerDocument()); |
1699 | boundaryTextRemoved(m_start, text, offset, length); |
1700 | boundaryTextRemoved(m_end, text, offset, length); |
1701 | } |
1702 | |
1703 | static inline void boundaryTextNodesMerged(RangeBoundaryPoint& boundary, NodeWithIndex& oldNode, unsigned offset) |
1704 | { |
1705 | if (boundary.container() == oldNode.node()) |
1706 | boundary.set(*oldNode.node()->previousSibling(), boundary.offset() + offset, 0); |
1707 | else if (boundary.container() == oldNode.node()->parentNode() && static_cast<int>(boundary.offset()) == oldNode.index()) |
1708 | boundary.set(*oldNode.node()->previousSibling(), offset, 0); |
1709 | } |
1710 | |
1711 | void Range::textNodesMerged(NodeWithIndex& oldNode, unsigned offset) |
1712 | { |
1713 | ASSERT(oldNode.node()); |
1714 | ASSERT(&oldNode.node()->document() == &ownerDocument()); |
1715 | ASSERT(oldNode.node()->parentNode()); |
1716 | ASSERT(oldNode.node()->isTextNode()); |
1717 | ASSERT(oldNode.node()->previousSibling()); |
1718 | ASSERT(oldNode.node()->previousSibling()->isTextNode()); |
1719 | boundaryTextNodesMerged(m_start, oldNode, offset); |
1720 | boundaryTextNodesMerged(m_end, oldNode, offset); |
1721 | } |
1722 | |
1723 | static inline void boundaryTextNodesSplit(RangeBoundaryPoint& boundary, Text& oldNode) |
1724 | { |
1725 | auto* parent = oldNode.parentNode(); |
1726 | if (boundary.container() == &oldNode) { |
1727 | unsigned splitOffset = oldNode.length(); |
1728 | unsigned boundaryOffset = boundary.offset(); |
1729 | if (boundaryOffset > splitOffset) { |
1730 | if (parent) |
1731 | boundary.set(*oldNode.nextSibling(), boundaryOffset - splitOffset, 0); |
1732 | else |
1733 | boundary.setOffset(splitOffset); |
1734 | } |
1735 | return; |
1736 | } |
1737 | if (!parent) |
1738 | return; |
1739 | if (boundary.container() == parent && boundary.childBefore() == &oldNode) { |
1740 | auto* newChild = oldNode.nextSibling(); |
1741 | ASSERT(newChild); |
1742 | boundary.setToAfterChild(*newChild); |
1743 | } |
1744 | } |
1745 | |
1746 | void Range::textNodeSplit(Text& oldNode) |
1747 | { |
1748 | ASSERT(&oldNode.document() == &ownerDocument()); |
1749 | ASSERT(!oldNode.parentNode() || oldNode.nextSibling()); |
1750 | ASSERT(!oldNode.parentNode() || oldNode.nextSibling()->isTextNode()); |
1751 | boundaryTextNodesSplit(m_start, oldNode); |
1752 | boundaryTextNodesSplit(m_end, oldNode); |
1753 | } |
1754 | |
1755 | ExceptionOr<void> Range::expand(const String& unit) |
1756 | { |
1757 | VisiblePosition start { startPosition() }; |
1758 | VisiblePosition end { endPosition() }; |
1759 | if (unit == "word" ) { |
1760 | start = startOfWord(start); |
1761 | end = endOfWord(end); |
1762 | } else if (unit == "sentence" ) { |
1763 | start = startOfSentence(start); |
1764 | end = endOfSentence(end); |
1765 | } else if (unit == "block" ) { |
1766 | start = startOfParagraph(start); |
1767 | end = endOfParagraph(end); |
1768 | } else if (unit == "document" ) { |
1769 | start = startOfDocument(start); |
1770 | end = endOfDocument(end); |
1771 | } else |
1772 | return { }; |
1773 | |
1774 | auto* startContainer = start.deepEquivalent().containerNode(); |
1775 | if (!startContainer) |
1776 | return Exception { TypeError }; |
1777 | auto result = setStart(*startContainer, start.deepEquivalent().computeOffsetInContainerNode()); |
1778 | if (result.hasException()) |
1779 | return result.releaseException(); |
1780 | auto* endContainer = end.deepEquivalent().containerNode(); |
1781 | if (!endContainer) |
1782 | return Exception { TypeError }; |
1783 | return setEnd(*endContainer, end.deepEquivalent().computeOffsetInContainerNode()); |
1784 | } |
1785 | |
1786 | Ref<DOMRectList> Range::getClientRects() const |
1787 | { |
1788 | return DOMRectList::create(borderAndTextRects(CoordinateSpace::Client)); |
1789 | } |
1790 | |
1791 | Ref<DOMRect> Range::getBoundingClientRect() const |
1792 | { |
1793 | return DOMRect::create(boundingRect(CoordinateSpace::Client)); |
1794 | } |
1795 | |
1796 | Vector<FloatRect> Range::borderAndTextRects(CoordinateSpace space, RespectClippingForTextRects respectClippingForTextRects) const |
1797 | { |
1798 | Vector<FloatRect> rects; |
1799 | |
1800 | ownerDocument().updateLayoutIgnorePendingStylesheets(); |
1801 | |
1802 | Node* stopNode = pastLastNode(); |
1803 | |
1804 | HashSet<Node*> selectedElementsSet; |
1805 | for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { |
1806 | if (is<Element>(*node)) |
1807 | selectedElementsSet.add(node); |
1808 | } |
1809 | |
1810 | // Don't include elements that are only partially selected. |
1811 | Node* lastNode = m_end.childBefore() ? m_end.childBefore() : &endContainer(); |
1812 | for (Node* parent = lastNode->parentNode(); parent; parent = parent->parentNode()) |
1813 | selectedElementsSet.remove(parent); |
1814 | |
1815 | for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { |
1816 | if (is<Element>(*node) && selectedElementsSet.contains(node) && (!node->parentNode() || !selectedElementsSet.contains(node->parentNode()))) { |
1817 | if (auto* renderer = downcast<Element>(*node).renderBoxModelObject()) { |
1818 | Vector<FloatQuad> elementQuads; |
1819 | renderer->absoluteQuads(elementQuads); |
1820 | if (space == CoordinateSpace::Client) |
1821 | node->document().convertAbsoluteToClientQuads(elementQuads, renderer->style()); |
1822 | |
1823 | for (auto& quad : elementQuads) |
1824 | rects.append(quad.boundingBox()); |
1825 | } |
1826 | } else if (is<Text>(*node)) { |
1827 | if (auto* renderer = downcast<Text>(*node).renderer()) { |
1828 | bool isFixed; |
1829 | auto clippedRects = absoluteRectsForRangeInText(node, *renderer, false, isFixed, respectClippingForTextRects); |
1830 | if (space == CoordinateSpace::Client) |
1831 | node->document().convertAbsoluteToClientRects(clippedRects, renderer->style()); |
1832 | |
1833 | rects.appendVector(clippedRects); |
1834 | } |
1835 | } |
1836 | } |
1837 | |
1838 | return rects; |
1839 | } |
1840 | |
1841 | FloatRect Range::boundingRect(CoordinateSpace space, RespectClippingForTextRects respectClippingForTextRects) const |
1842 | { |
1843 | FloatRect result; |
1844 | for (auto& rect : borderAndTextRects(space, respectClippingForTextRects)) |
1845 | result.uniteIfNonZero(rect); |
1846 | return result; |
1847 | } |
1848 | |
1849 | FloatRect Range::absoluteBoundingRect(RespectClippingForTextRects respectClippingForTextRects) const |
1850 | { |
1851 | return boundingRect(CoordinateSpace::Absolute, respectClippingForTextRects); |
1852 | } |
1853 | |
1854 | WTF::TextStream& operator<<(WTF::TextStream& ts, const RangeBoundaryPoint& r) |
1855 | { |
1856 | return ts << r.toPosition(); |
1857 | } |
1858 | |
1859 | WTF::TextStream& operator<<(WTF::TextStream& ts, const Range& r) |
1860 | { |
1861 | return ts << "Range: " << "start: " << r.startPosition() << " end: " << r.endPosition(); |
1862 | } |
1863 | |
1864 | } // namespace WebCore |
1865 | |
1866 | #if ENABLE(TREE_DEBUGGING) |
1867 | |
1868 | void showTree(const WebCore::Range* range) |
1869 | { |
1870 | if (range && range->boundaryPointsValid()) { |
1871 | range->startContainer().showTreeAndMark(&range->startContainer(), "S" , &range->endContainer(), "E" ); |
1872 | fprintf(stderr, "start offset: %d, end offset: %d\n" , range->startOffset(), range->endOffset()); |
1873 | } |
1874 | } |
1875 | |
1876 | #endif |
1877 | |