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
7 * rights reserved.
8 * Copyright (C) 2011 Motorola Mobility. All rights reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26 #include "third_party/blink/renderer/core/dom/range.h"
27
28 #include "third_party/blink/renderer/core/dom/character_data.h"
29 #include "third_party/blink/renderer/core/dom/container_node.h"
30 #include "third_party/blink/renderer/core/dom/document_fragment.h"
31 #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
32 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
33 #include "third_party/blink/renderer/core/dom/node.h"
34 #include "third_party/blink/renderer/core/dom/node_traversal.h"
35 #include "third_party/blink/renderer/core/dom/node_with_index.h"
36 #include "third_party/blink/renderer/core/dom/processing_instruction.h"
37 #include "third_party/blink/renderer/core/dom/text.h"
38 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
39 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
40 #include "third_party/blink/renderer/core/editing/frame_selection.h"
41 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
42 #include "third_party/blink/renderer/core/editing/selection_template.h"
43 #include "third_party/blink/renderer/core/editing/serializers/serialization.h"
44 #include "third_party/blink/renderer/core/editing/set_selection_options.h"
45 #include "third_party/blink/renderer/core/editing/visible_position.h"
46 #include "third_party/blink/renderer/core/editing/visible_units.h"
47 #include "third_party/blink/renderer/core/frame/local_frame.h"
48 #include "third_party/blink/renderer/core/frame/settings.h"
49 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
50 #include "third_party/blink/renderer/core/geometry/dom_rect_list.h"
51 #include "third_party/blink/renderer/core/html/html_body_element.h"
52 #include "third_party/blink/renderer/core/html/html_element.h"
53 #include "third_party/blink/renderer/core/layout/layout_object.h"
54 #include "third_party/blink/renderer/core/layout/layout_text.h"
55 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
56 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
57 #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
58 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
59 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
60 #include "third_party/blink/renderer/platform/heap/heap.h"
61 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
62 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
63
64 namespace blink {
65
66 class RangeUpdateScope {
67 STACK_ALLOCATED();
68
69 public:
RangeUpdateScope(Range * range)70 explicit RangeUpdateScope(Range* range) {
71 DCHECK(range);
72 if (++scope_count_ == 1) {
73 range_ = range;
74 old_document_ = &range->OwnerDocument();
75 #if DCHECK_IS_ON()
76 current_range_ = range;
77 } else {
78 DCHECK_EQ(current_range_, range);
79 #endif
80 }
81 }
82 RangeUpdateScope(const RangeUpdateScope&) = delete;
83 RangeUpdateScope& operator=(const RangeUpdateScope&) = delete;
84
~RangeUpdateScope()85 ~RangeUpdateScope() {
86 DCHECK_GE(scope_count_, 1);
87 if (--scope_count_ > 0)
88 return;
89 Settings* settings = old_document_->GetFrame()
90 ? old_document_->GetFrame()->GetSettings()
91 : nullptr;
92 if (!settings ||
93 !settings->GetDoNotUpdateSelectionOnMutatingSelectionRange()) {
94 range_->RemoveFromSelectionIfInDifferentRoot(*old_document_);
95 range_->UpdateSelectionIfAddedToSelection();
96 }
97 #if DCHECK_IS_ON()
98 current_range_ = nullptr;
99 #endif
100 }
101
102 private:
103 static int scope_count_;
104 #if DCHECK_IS_ON()
105 // This raw pointer is safe because
106 // - s_currentRange has a valid pointer only if RangeUpdateScope instance is
107 // live.
108 // - RangeUpdateScope is used only in Range member functions.
109 static Range* current_range_;
110 #endif
111 Range* range_ = nullptr;
112 Document* old_document_ = nullptr;
113
114 };
115
116 int RangeUpdateScope::scope_count_ = 0;
117 #if DCHECK_IS_ON()
118 Range* RangeUpdateScope::current_range_;
119 #endif
120
Range(Document & owner_document)121 Range::Range(Document& owner_document)
122 : owner_document_(&owner_document),
123 start_(*owner_document_),
124 end_(*owner_document_) {
125 owner_document_->AttachRange(this);
126 }
127
Create(Document & owner_document)128 Range* Range::Create(Document& owner_document) {
129 return MakeGarbageCollected<Range>(owner_document);
130 }
131
Range(Document & owner_document,Node * start_container,unsigned start_offset,Node * end_container,unsigned end_offset)132 Range::Range(Document& owner_document,
133 Node* start_container,
134 unsigned start_offset,
135 Node* end_container,
136 unsigned end_offset)
137 : owner_document_(&owner_document),
138 start_(*owner_document_),
139 end_(*owner_document_) {
140 owner_document_->AttachRange(this);
141
142 // Simply setting the containers and offsets directly would not do any of the
143 // checking that setStart and setEnd do, so we call those functions.
144 setStart(start_container, start_offset);
145 setEnd(end_container, end_offset);
146 }
147
Range(Document & owner_document,const Position & start,const Position & end)148 Range::Range(Document& owner_document,
149 const Position& start,
150 const Position& end)
151 : Range(owner_document,
152 start.ComputeContainerNode(),
153 start.ComputeOffsetInContainerNode(),
154 end.ComputeContainerNode(),
155 end.ComputeOffsetInContainerNode()) {}
156
Dispose()157 void Range::Dispose() {
158 // A prompt detach from the owning Document helps avoid GC overhead.
159 owner_document_->DetachRange(this);
160 }
161
IsConnected() const162 bool Range::IsConnected() const {
163 DCHECK_EQ(start_.IsConnected(), end_.IsConnected());
164 return start_.IsConnected();
165 }
166
SetDocument(Document & document)167 void Range::SetDocument(Document& document) {
168 DCHECK_NE(owner_document_, document);
169 DCHECK(owner_document_);
170 owner_document_->DetachRange(this);
171 owner_document_ = &document;
172 start_.SetToStartOfNode(document);
173 end_.SetToStartOfNode(document);
174 owner_document_->AttachRange(this);
175 }
176
commonAncestorContainer() const177 Node* Range::commonAncestorContainer() const {
178 return commonAncestorContainer(&start_.Container(), &end_.Container());
179 }
180
commonAncestorContainer(const Node * container_a,const Node * container_b)181 Node* Range::commonAncestorContainer(const Node* container_a,
182 const Node* container_b) {
183 if (!container_a || !container_b)
184 return nullptr;
185 return container_a->CommonAncestor(*container_b, NodeTraversal::Parent);
186 }
187
CheckForDifferentRootContainer(const RangeBoundaryPoint & start,const RangeBoundaryPoint & end)188 static inline bool CheckForDifferentRootContainer(
189 const RangeBoundaryPoint& start,
190 const RangeBoundaryPoint& end) {
191 Node* end_root_container = &end.Container();
192 while (end_root_container->parentNode())
193 end_root_container = end_root_container->parentNode();
194 Node* start_root_container = &start.Container();
195 while (start_root_container->parentNode())
196 start_root_container = start_root_container->parentNode();
197
198 return start_root_container != end_root_container ||
199 (Range::compareBoundaryPoints(start, end, ASSERT_NO_EXCEPTION) > 0);
200 }
201
setStart(Node * ref_node,unsigned offset,ExceptionState & exception_state)202 void Range::setStart(Node* ref_node,
203 unsigned offset,
204 ExceptionState& exception_state) {
205 if (!ref_node) {
206 // FIXME: Generated bindings code never calls with null, and neither should
207 // other callers!
208 exception_state.ThrowTypeError("The node provided is null.");
209 return;
210 }
211
212 RangeUpdateScope scope(this);
213 bool did_move_document = false;
214 if (ref_node->GetDocument() != owner_document_) {
215 SetDocument(ref_node->GetDocument());
216 did_move_document = true;
217 }
218
219 Node* child_node = CheckNodeWOffset(ref_node, offset, exception_state);
220 if (exception_state.HadException())
221 return;
222
223 start_.Set(*ref_node, offset, child_node);
224
225 if (did_move_document || CheckForDifferentRootContainer(start_, end_))
226 collapse(true);
227 }
228
setEnd(Node * ref_node,unsigned offset,ExceptionState & exception_state)229 void Range::setEnd(Node* ref_node,
230 unsigned offset,
231 ExceptionState& exception_state) {
232 if (!ref_node) {
233 // FIXME: Generated bindings code never calls with null, and neither should
234 // other callers!
235 exception_state.ThrowTypeError("The node provided is null.");
236 return;
237 }
238
239 RangeUpdateScope scope(this);
240 bool did_move_document = false;
241 if (ref_node->GetDocument() != owner_document_) {
242 SetDocument(ref_node->GetDocument());
243 did_move_document = true;
244 }
245
246 Node* child_node = CheckNodeWOffset(ref_node, offset, exception_state);
247 if (exception_state.HadException())
248 return;
249
250 end_.Set(*ref_node, offset, child_node);
251
252 if (did_move_document || CheckForDifferentRootContainer(start_, end_))
253 collapse(false);
254 }
255
setStart(const Position & start,ExceptionState & exception_state)256 void Range::setStart(const Position& start, ExceptionState& exception_state) {
257 Position parent_anchored = start.ParentAnchoredEquivalent();
258 setStart(parent_anchored.ComputeContainerNode(),
259 parent_anchored.OffsetInContainerNode(), exception_state);
260 }
261
setEnd(const Position & end,ExceptionState & exception_state)262 void Range::setEnd(const Position& end, ExceptionState& exception_state) {
263 Position parent_anchored = end.ParentAnchoredEquivalent();
264 setEnd(parent_anchored.ComputeContainerNode(),
265 parent_anchored.OffsetInContainerNode(), exception_state);
266 }
267
collapse(bool to_start)268 void Range::collapse(bool to_start) {
269 RangeUpdateScope scope(this);
270 if (to_start)
271 end_ = start_;
272 else
273 start_ = end_;
274 }
275
HasSameRoot(const Node & node) const276 bool Range::HasSameRoot(const Node& node) const {
277 if (node.GetDocument() != owner_document_)
278 return false;
279 // commonAncestorContainer() is O(depth). We should avoid to call it in common
280 // cases.
281 if (node.IsInTreeScope() && start_.Container().IsInTreeScope() &&
282 &node.GetTreeScope() == &start_.Container().GetTreeScope())
283 return true;
284 return node.CommonAncestor(start_.Container(), NodeTraversal::Parent);
285 }
286
isPointInRange(Node * ref_node,unsigned offset,ExceptionState & exception_state) const287 bool Range::isPointInRange(Node* ref_node,
288 unsigned offset,
289 ExceptionState& exception_state) const {
290 if (!ref_node) {
291 // FIXME: Generated bindings code never calls with null, and neither should
292 // other callers!
293 exception_state.ThrowTypeError("The node provided is null.");
294 return false;
295 }
296 if (!HasSameRoot(*ref_node))
297 return false;
298
299 CheckNodeWOffset(ref_node, offset, exception_state);
300 if (exception_state.HadException())
301 return false;
302
303 return compareBoundaryPoints(ref_node, offset, &start_.Container(),
304 start_.Offset(), exception_state) >= 0 &&
305 !exception_state.HadException() &&
306 compareBoundaryPoints(ref_node, offset, &end_.Container(),
307 end_.Offset(), exception_state) <= 0 &&
308 !exception_state.HadException();
309 }
310
comparePoint(Node * ref_node,unsigned offset,ExceptionState & exception_state) const311 int16_t Range::comparePoint(Node* ref_node,
312 unsigned offset,
313 ExceptionState& exception_state) const {
314 // http://developer.mozilla.org/en/docs/DOM:range.comparePoint
315 // This method returns -1, 0 or 1 depending on if the point described by the
316 // refNode node and an offset within the node is before, same as, or after the
317 // range respectively.
318
319 if (!HasSameRoot(*ref_node)) {
320 exception_state.ThrowDOMException(
321 DOMExceptionCode::kWrongDocumentError,
322 "The node provided and the Range are not in the same tree.");
323 return 0;
324 }
325
326 CheckNodeWOffset(ref_node, offset, exception_state);
327 if (exception_state.HadException())
328 return 0;
329
330 // compare to start, and point comes before
331 if (compareBoundaryPoints(ref_node, offset, &start_.Container(),
332 start_.Offset(), exception_state) < 0)
333 return -1;
334
335 if (exception_state.HadException())
336 return 0;
337
338 // compare to end, and point comes after
339 if (compareBoundaryPoints(ref_node, offset, &end_.Container(), end_.Offset(),
340 exception_state) > 0 &&
341 !exception_state.HadException())
342 return 1;
343
344 // point is in the middle of this range, or on the boundary points
345 return 0;
346 }
347
compareBoundaryPoints(unsigned how,const Range * source_range,ExceptionState & exception_state) const348 int16_t Range::compareBoundaryPoints(unsigned how,
349 const Range* source_range,
350 ExceptionState& exception_state) const {
351 if (!(how == kStartToStart || how == kStartToEnd || how == kEndToEnd ||
352 how == kEndToStart)) {
353 exception_state.ThrowDOMException(
354 DOMExceptionCode::kNotSupportedError,
355 "The comparison method provided must be "
356 "one of 'START_TO_START', 'START_TO_END', "
357 "'END_TO_END', or 'END_TO_START'.");
358 return 0;
359 }
360
361 Node* this_cont = commonAncestorContainer();
362 Node* source_cont = source_range->commonAncestorContainer();
363 if (this_cont->GetDocument() != source_cont->GetDocument()) {
364 exception_state.ThrowDOMException(
365 DOMExceptionCode::kWrongDocumentError,
366 "The source range is in a different document than this range.");
367 return 0;
368 }
369
370 Node* this_top = this_cont;
371 Node* source_top = source_cont;
372 while (this_top->parentNode())
373 this_top = this_top->parentNode();
374 while (source_top->parentNode())
375 source_top = source_top->parentNode();
376 if (this_top != source_top) { // in different DocumentFragments
377 exception_state.ThrowDOMException(
378 DOMExceptionCode::kWrongDocumentError,
379 "The source range is in a different document than this range.");
380 return 0;
381 }
382
383 switch (how) {
384 case kStartToStart:
385 return compareBoundaryPoints(start_, source_range->start_,
386 exception_state);
387 case kStartToEnd:
388 return compareBoundaryPoints(end_, source_range->start_, exception_state);
389 case kEndToEnd:
390 return compareBoundaryPoints(end_, source_range->end_, exception_state);
391 case kEndToStart:
392 return compareBoundaryPoints(start_, source_range->end_, exception_state);
393 }
394
395 NOTREACHED();
396 return 0;
397 }
398
compareBoundaryPoints(Node * container_a,unsigned offset_a,Node * container_b,unsigned offset_b,ExceptionState & exception_state)399 int16_t Range::compareBoundaryPoints(Node* container_a,
400 unsigned offset_a,
401 Node* container_b,
402 unsigned offset_b,
403 ExceptionState& exception_state) {
404 bool disconnected = false;
405 int16_t result = ComparePositionsInDOMTree(container_a, offset_a, container_b,
406 offset_b, &disconnected);
407 if (disconnected) {
408 exception_state.ThrowDOMException(
409 DOMExceptionCode::kWrongDocumentError,
410 "The two ranges are in separate documents.");
411 return 0;
412 }
413 return result;
414 }
415
compareBoundaryPoints(const RangeBoundaryPoint & boundary_a,const RangeBoundaryPoint & boundary_b,ExceptionState & exception_state)416 int16_t Range::compareBoundaryPoints(const RangeBoundaryPoint& boundary_a,
417 const RangeBoundaryPoint& boundary_b,
418 ExceptionState& exception_state) {
419 return compareBoundaryPoints(&boundary_a.Container(), boundary_a.Offset(),
420 &boundary_b.Container(), boundary_b.Offset(),
421 exception_state);
422 }
423
BoundaryPointsValid() const424 bool Range::BoundaryPointsValid() const {
425 DummyExceptionStateForTesting exception_state;
426 return compareBoundaryPoints(start_, end_, exception_state) <= 0 &&
427 !exception_state.HadException();
428 }
429
deleteContents(ExceptionState & exception_state)430 void Range::deleteContents(ExceptionState& exception_state) {
431 DCHECK(BoundaryPointsValid());
432
433 {
434 EventQueueScope event_queue_scope;
435 ProcessContents(DELETE_CONTENTS, exception_state);
436 }
437 }
438
intersectsNode(Node * ref_node,ExceptionState & exception_state)439 bool Range::intersectsNode(Node* ref_node, ExceptionState& exception_state) {
440 // http://developer.mozilla.org/en/docs/DOM:range.intersectsNode
441 // Returns a bool if the node intersects the range.
442 if (!ref_node) {
443 // FIXME: Generated bindings code never calls with null, and neither should
444 // other callers!
445 exception_state.ThrowTypeError("The node provided is null.");
446 return false;
447 }
448 if (!HasSameRoot(*ref_node))
449 return false;
450
451 ContainerNode* parent_node = ref_node->parentNode();
452 if (!parent_node)
453 return true;
454
455 int node_index = ref_node->NodeIndex();
456 return Position(parent_node, node_index) < end_.ToPosition() &&
457 Position(parent_node, node_index + 1) > start_.ToPosition();
458 }
459
HighestAncestorUnderCommonRoot(Node * node,Node * common_root)460 static inline Node* HighestAncestorUnderCommonRoot(Node* node,
461 Node* common_root) {
462 if (node == common_root)
463 return nullptr;
464
465 DCHECK(common_root->contains(node));
466
467 while (node->parentNode() != common_root)
468 node = node->parentNode();
469
470 return node;
471 }
472
ChildOfCommonRootBeforeOffset(Node * container,unsigned offset,Node * common_root)473 static inline Node* ChildOfCommonRootBeforeOffset(Node* container,
474 unsigned offset,
475 Node* common_root) {
476 DCHECK(container);
477 DCHECK(common_root);
478
479 if (!common_root->contains(container))
480 return nullptr;
481
482 if (container == common_root) {
483 container = container->firstChild();
484 for (unsigned i = 0; container && i < offset; i++)
485 container = container->nextSibling();
486 } else {
487 while (container->parentNode() != common_root)
488 container = container->parentNode();
489 }
490
491 return container;
492 }
493
LengthOfContents(const Node * node)494 static unsigned LengthOfContents(const Node* node) {
495 // This switch statement must be consistent with that of
496 // Range::processContentsBetweenOffsets.
497 switch (node->getNodeType()) {
498 case Node::kTextNode:
499 case Node::kCdataSectionNode:
500 case Node::kCommentNode:
501 case Node::kProcessingInstructionNode:
502 return To<CharacterData>(node)->length();
503 case Node::kElementNode:
504 case Node::kDocumentNode:
505 case Node::kDocumentFragmentNode:
506 return To<ContainerNode>(node)->CountChildren();
507 case Node::kAttributeNode:
508 case Node::kDocumentTypeNode:
509 return 0;
510 }
511 NOTREACHED();
512 return 0;
513 }
514
ProcessContents(ActionType action,ExceptionState & exception_state)515 DocumentFragment* Range::ProcessContents(ActionType action,
516 ExceptionState& exception_state) {
517 DocumentFragment* fragment = nullptr;
518 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS)
519 fragment = DocumentFragment::Create(*owner_document_.Get());
520
521 if (collapsed())
522 return fragment;
523
524 Node* common_root = commonAncestorContainer();
525 DCHECK(common_root);
526
527 if (start_.Container() == end_.Container()) {
528 ProcessContentsBetweenOffsets(action, fragment, &start_.Container(),
529 start_.Offset(), end_.Offset(),
530 exception_state);
531 return fragment;
532 }
533
534 // Since mutation observers can modify the range during the process, the
535 // boundary points need to be saved.
536 const RangeBoundaryPoint original_start(start_);
537 const RangeBoundaryPoint original_end(end_);
538
539 // what is the highest node that partially selects the start / end of the
540 // range?
541 Node* partial_start =
542 HighestAncestorUnderCommonRoot(&original_start.Container(), common_root);
543 Node* partial_end =
544 HighestAncestorUnderCommonRoot(&original_end.Container(), common_root);
545
546 // Start and end containers are different.
547 // There are three possibilities here:
548 // 1. Start container == commonRoot (End container must be a descendant)
549 // 2. End container == commonRoot (Start container must be a descendant)
550 // 3. Neither is commonRoot, they are both descendants
551 //
552 // In case 3, we grab everything after the start (up until a direct child
553 // of commonRoot) into leftContents, and everything before the end (up until
554 // a direct child of commonRoot) into rightContents. Then we process all
555 // commonRoot children between leftContents and rightContents
556 //
557 // In case 1 or 2, we skip either processing of leftContents or rightContents,
558 // in which case the last lot of nodes either goes from the first or last
559 // child of commonRoot.
560 //
561 // These are deleted, cloned, or extracted (i.e. both) depending on action.
562
563 // Note that we are verifying that our common root hierarchy is still intact
564 // after any DOM mutation event, at various stages below. See webkit bug
565 // 60350.
566
567 Node* left_contents = nullptr;
568 if (original_start.Container() != common_root &&
569 common_root->contains(&original_start.Container())) {
570 left_contents = ProcessContentsBetweenOffsets(
571 action, nullptr, &original_start.Container(), original_start.Offset(),
572 LengthOfContents(&original_start.Container()), exception_state);
573 left_contents = ProcessAncestorsAndTheirSiblings(
574 action, &original_start.Container(), kProcessContentsForward,
575 left_contents, common_root, exception_state);
576 }
577
578 Node* right_contents = nullptr;
579 if (end_.Container() != common_root &&
580 common_root->contains(&original_end.Container())) {
581 right_contents = ProcessContentsBetweenOffsets(
582 action, nullptr, &original_end.Container(), 0, original_end.Offset(),
583 exception_state);
584 right_contents = ProcessAncestorsAndTheirSiblings(
585 action, &original_end.Container(), kProcessContentsBackward,
586 right_contents, common_root, exception_state);
587 }
588
589 // delete all children of commonRoot between the start and end container
590 Node* process_start = ChildOfCommonRootBeforeOffset(
591 &original_start.Container(), original_start.Offset(), common_root);
592 // process_start contains nodes before start_.
593 if (process_start && original_start.Container() != common_root)
594 process_start = process_start->nextSibling();
595 Node* process_end = ChildOfCommonRootBeforeOffset(
596 &original_end.Container(), original_end.Offset(), common_root);
597
598 // Collapse the range, making sure that the result is not within a node that
599 // was partially selected.
600 if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
601 if (partial_start && common_root->contains(partial_start)) {
602 // FIXME: We should not continue if we have an earlier error.
603 exception_state.ClearException();
604 setStart(partial_start->parentNode(), partial_start->NodeIndex() + 1,
605 exception_state);
606 } else if (partial_end && common_root->contains(partial_end)) {
607 // FIXME: We should not continue if we have an earlier error.
608 exception_state.ClearException();
609 setStart(partial_end->parentNode(), partial_end->NodeIndex(),
610 exception_state);
611 }
612 if (exception_state.HadException())
613 return nullptr;
614 end_ = start_;
615 }
616
617 // Now add leftContents, stuff in between, and rightContents to the fragment
618 // (or just delete the stuff in between)
619
620 if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && left_contents)
621 fragment->AppendChild(left_contents, exception_state);
622
623 if (process_start) {
624 NodeVector nodes;
625 for (Node* n = process_start; n && n != process_end; n = n->nextSibling())
626 nodes.push_back(n);
627 ProcessNodes(action, nodes, common_root, fragment, exception_state);
628 }
629
630 if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) &&
631 right_contents)
632 fragment->AppendChild(right_contents, exception_state);
633
634 return fragment;
635 }
636
DeleteCharacterData(CharacterData * data,unsigned start_offset,unsigned end_offset,ExceptionState & exception_state)637 static inline void DeleteCharacterData(CharacterData* data,
638 unsigned start_offset,
639 unsigned end_offset,
640 ExceptionState& exception_state) {
641 if (data->length() - end_offset)
642 data->deleteData(end_offset, data->length() - end_offset, exception_state);
643 if (start_offset)
644 data->deleteData(0, start_offset, exception_state);
645 }
646
ProcessContentsBetweenOffsets(ActionType action,DocumentFragment * fragment,Node * container,unsigned start_offset,unsigned end_offset,ExceptionState & exception_state)647 Node* Range::ProcessContentsBetweenOffsets(ActionType action,
648 DocumentFragment* fragment,
649 Node* container,
650 unsigned start_offset,
651 unsigned end_offset,
652 ExceptionState& exception_state) {
653 DCHECK(container);
654 DCHECK_LE(start_offset, end_offset);
655
656 // This switch statement must be consistent with that of
657 // lengthOfContents.
658 Node* result = nullptr;
659 switch (container->getNodeType()) {
660 case Node::kTextNode:
661 case Node::kCdataSectionNode:
662 case Node::kCommentNode:
663 case Node::kProcessingInstructionNode:
664 end_offset = std::min(end_offset, To<CharacterData>(container)->length());
665 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
666 CharacterData* c =
667 static_cast<CharacterData*>(container->cloneNode(true));
668 DeleteCharacterData(c, start_offset, end_offset, exception_state);
669 if (fragment) {
670 result = fragment;
671 result->appendChild(c, exception_state);
672 } else {
673 result = c;
674 }
675 }
676 if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
677 To<CharacterData>(container)->deleteData(
678 start_offset, end_offset - start_offset, exception_state);
679 break;
680 case Node::kElementNode:
681 case Node::kAttributeNode:
682 case Node::kDocumentNode:
683 case Node::kDocumentTypeNode:
684 case Node::kDocumentFragmentNode:
685 // FIXME: Should we assert that some nodes never appear here?
686 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
687 if (fragment)
688 result = fragment;
689 else
690 result = container->cloneNode(false);
691 }
692
693 Node* n = container->firstChild();
694 NodeVector nodes;
695 for (unsigned i = start_offset; n && i; i--)
696 n = n->nextSibling();
697 for (unsigned i = start_offset; n && i < end_offset;
698 i++, n = n->nextSibling())
699 nodes.push_back(n);
700
701 ProcessNodes(action, nodes, container, result, exception_state);
702 break;
703 }
704
705 return result;
706 }
707
ProcessNodes(ActionType action,NodeVector & nodes,Node * old_container,Node * new_container,ExceptionState & exception_state)708 void Range::ProcessNodes(ActionType action,
709 NodeVector& nodes,
710 Node* old_container,
711 Node* new_container,
712 ExceptionState& exception_state) {
713 for (auto& node : nodes) {
714 switch (action) {
715 case DELETE_CONTENTS:
716 old_container->removeChild(node.Get(), exception_state);
717 break;
718 case EXTRACT_CONTENTS:
719 new_container->appendChild(
720 node.Release(), exception_state); // Will remove n from its parent.
721 break;
722 case CLONE_CONTENTS:
723 new_container->appendChild(node->cloneNode(true), exception_state);
724 break;
725 }
726 }
727 }
728
ProcessAncestorsAndTheirSiblings(ActionType action,Node * container,ContentsProcessDirection direction,Node * cloned_container,Node * common_root,ExceptionState & exception_state)729 Node* Range::ProcessAncestorsAndTheirSiblings(
730 ActionType action,
731 Node* container,
732 ContentsProcessDirection direction,
733 Node* cloned_container,
734 Node* common_root,
735 ExceptionState& exception_state) {
736 NodeVector ancestors;
737 for (Node& runner : NodeTraversal::AncestorsOf(*container)) {
738 if (runner == common_root)
739 break;
740 ancestors.push_back(runner);
741 }
742
743 Node* first_child_in_ancestor_to_process =
744 direction == kProcessContentsForward ? container->nextSibling()
745 : container->previousSibling();
746 for (const auto& ancestor : ancestors) {
747 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
748 // Might have been removed already during mutation event.
749 if (Node* cloned_ancestor = ancestor->cloneNode(false)) {
750 cloned_ancestor->appendChild(cloned_container, exception_state);
751 cloned_container = cloned_ancestor;
752 }
753 }
754
755 // Copy siblings of an ancestor of start/end containers
756 // FIXME: This assertion may fail if DOM is modified during mutation event
757 // FIXME: Share code with Range::processNodes
758 DCHECK(!first_child_in_ancestor_to_process ||
759 first_child_in_ancestor_to_process->parentNode() == ancestor);
760
761 NodeVector nodes;
762 for (Node* child = first_child_in_ancestor_to_process; child;
763 child = (direction == kProcessContentsForward)
764 ? child->nextSibling()
765 : child->previousSibling())
766 nodes.push_back(child);
767
768 for (const auto& node : nodes) {
769 Node* child = node.Get();
770 switch (action) {
771 case DELETE_CONTENTS:
772 // Prior call of ancestor->removeChild() may cause a tree change due
773 // to DOMSubtreeModified event. Therefore, we need to make sure
774 // |ancestor| is still |child|'s parent.
775 if (ancestor == child->parentNode())
776 ancestor->removeChild(child, exception_state);
777 break;
778 case EXTRACT_CONTENTS: // will remove child from ancestor
779 if (direction == kProcessContentsForward)
780 cloned_container->appendChild(child, exception_state);
781 else
782 cloned_container->insertBefore(
783 child, cloned_container->firstChild(), exception_state);
784 break;
785 case CLONE_CONTENTS:
786 if (direction == kProcessContentsForward)
787 cloned_container->appendChild(child->cloneNode(true),
788 exception_state);
789 else
790 cloned_container->insertBefore(child->cloneNode(true),
791 cloned_container->firstChild(),
792 exception_state);
793 break;
794 }
795 }
796 first_child_in_ancestor_to_process = direction == kProcessContentsForward
797 ? ancestor->nextSibling()
798 : ancestor->previousSibling();
799 }
800
801 return cloned_container;
802 }
803
extractContents(ExceptionState & exception_state)804 DocumentFragment* Range::extractContents(ExceptionState& exception_state) {
805 CheckExtractPrecondition(exception_state);
806 if (exception_state.HadException())
807 return nullptr;
808
809 EventQueueScope scope;
810 DocumentFragment* fragment = ProcessContents(EXTRACT_CONTENTS,
811 exception_state);
812 // |extractContents| has extended attributes [NewObject, DoNotTestNewObject],
813 // so it's better to have a test that exercises the following condition:
814 //
815 // !fragment || DOMDataStore::GetWrapper(fragment, isolate).IsEmpty()
816 //
817 // however, there is no access to |isolate| so far. So, we simply omit the
818 // test so far.
819 return fragment;
820 }
821
cloneContents(ExceptionState & exception_state)822 DocumentFragment* Range::cloneContents(ExceptionState& exception_state) {
823 return ProcessContents(CLONE_CONTENTS, exception_state);
824 }
825
826 // https://dom.spec.whatwg.org/#concept-range-insert
insertNode(Node * new_node,ExceptionState & exception_state)827 void Range::insertNode(Node* new_node, ExceptionState& exception_state) {
828 if (!new_node) {
829 // FIXME: Generated bindings code never calls with null, and neither should
830 // other callers!
831 exception_state.ThrowTypeError("The node provided is null.");
832 return;
833 }
834
835 // 1. If range’s start node is a ProcessingInstruction or Comment node, is a
836 // Text node whose parent is null, or is node, then throw a
837 // HierarchyRequestError.
838 Node& start_node = start_.Container();
839 if (start_node.getNodeType() == Node::kProcessingInstructionNode ||
840 start_node.getNodeType() == Node::kCommentNode) {
841 exception_state.ThrowDOMException(
842 DOMExceptionCode::kHierarchyRequestError,
843 "Nodes of type '" + new_node->nodeName() +
844 "' may not be inserted inside nodes of type '" +
845 start_node.nodeName() + "'.");
846 return;
847 }
848 const bool start_is_text = start_node.IsTextNode();
849 if (start_is_text && !start_node.parentNode()) {
850 exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError,
851 "This operation would split a text node, "
852 "but there's no parent into which to "
853 "insert.");
854 return;
855 }
856 if (start_node == new_node) {
857 exception_state.ThrowDOMException(
858 DOMExceptionCode::kHierarchyRequestError,
859 "Unable to insert a node into a Range starting from the node itself.");
860 return;
861 }
862
863 // According to the specification, the following condition is checked in the
864 // step 6. However our EnsurePreInsertionValidity() supports only
865 // ContainerNode parent.
866 if (start_node.IsAttributeNode()) {
867 exception_state.ThrowDOMException(
868 DOMExceptionCode::kHierarchyRequestError,
869 "Nodes of type '" + new_node->nodeName() +
870 "' may not be inserted inside nodes of type 'Attr'.");
871 return;
872 }
873
874 // 2. Let referenceNode be null.
875 Node* reference_node = nullptr;
876 // 3. If range’s start node is a Text node, set referenceNode to that Text
877 // node.
878 // 4. Otherwise, set referenceNode to the child of start node whose index is
879 // start offset, and null if there is no such child.
880 if (start_is_text)
881 reference_node = &start_node;
882 else
883 reference_node = NodeTraversal::ChildAt(start_node, start_.Offset());
884
885 // 5. Let parent be range’s start node if referenceNode is null, and
886 // referenceNode’s parent otherwise.
887 ContainerNode& parent = reference_node ? *reference_node->parentNode()
888 : To<ContainerNode>(start_node);
889
890 // 6. Ensure pre-insertion validity of node into parent before referenceNode.
891 if (!parent.EnsurePreInsertionValidity(*new_node, reference_node, nullptr,
892 exception_state))
893 return;
894
895 EventQueueScope scope;
896 // 7. If range's start node is a Text node, set referenceNode to the result of
897 // splitting it with offset range’s start offset.
898 if (start_is_text) {
899 reference_node =
900 To<Text>(start_node).splitText(start_.Offset(), exception_state);
901 if (exception_state.HadException())
902 return;
903 }
904
905 // 8. If node is referenceNode, set referenceNode to its next sibling.
906 if (new_node == reference_node)
907 reference_node = reference_node->nextSibling();
908
909 // 9. If node's parent is not null, remove node from its parent.
910 if (new_node->parentNode()) {
911 new_node->remove(exception_state);
912 if (exception_state.HadException())
913 return;
914 }
915
916 // 10. Let newOffset be parent's length if referenceNode is null, and
917 // referenceNode's index otherwise.
918 unsigned new_offset =
919 reference_node ? reference_node->NodeIndex() : LengthOfContents(&parent);
920
921 // 11. Increase newOffset by node's length if node is a DocumentFragment node,
922 // and one otherwise.
923 new_offset += new_node->IsDocumentFragment() ? LengthOfContents(new_node) : 1;
924
925 // 12. Pre-insert node into parent before referenceNode.
926 parent.insertBefore(new_node, reference_node, exception_state);
927 if (exception_state.HadException())
928 return;
929
930 // 13. If range's start and end are the same, set range's end to (parent,
931 // newOffset).
932 if (start_ == end_)
933 setEnd(&parent, new_offset, exception_state);
934 }
935
toString() const936 String Range::toString() const {
937 StringBuilder builder;
938
939 Node* past_last = PastLastNode();
940 for (Node* n = FirstNode(); n != past_last; n = NodeTraversal::Next(*n)) {
941 Node::NodeType type = n->getNodeType();
942 if (type == Node::kTextNode || type == Node::kCdataSectionNode) {
943 String data = To<CharacterData>(n)->data();
944 unsigned length = data.length();
945 unsigned start =
946 (n == start_.Container()) ? std::min(start_.Offset(), length) : 0;
947 unsigned end = (n == end_.Container())
948 ? std::min(std::max(start, end_.Offset()), length)
949 : length;
950 builder.Append(data, start, end - start);
951 }
952 }
953
954 return builder.ToString();
955 }
956
GetText() const957 String Range::GetText() const {
958 DCHECK(!owner_document_->NeedsLayoutTreeUpdate());
959 return PlainText(EphemeralRange(this),
960 TextIteratorBehavior::Builder()
961 .SetEmitsObjectReplacementCharacter(true)
962 .Build());
963 }
964
createContextualFragment(const String & markup,ExceptionState & exception_state)965 DocumentFragment* Range::createContextualFragment(
966 const String& markup,
967 ExceptionState& exception_state) {
968 // Algorithm:
969 // http://domparsing.spec.whatwg.org/#extensions-to-the-range-interface
970
971 DCHECK(!markup.IsNull());
972
973 Node* node = &start_.Container();
974
975 // Step 1.
976 Element* element;
977 if (!start_.Offset() &&
978 (node->IsDocumentNode() || node->IsDocumentFragment()))
979 element = nullptr;
980 else if (auto* node_element = DynamicTo<Element>(node))
981 element = node_element;
982 else
983 element = node->parentElement();
984
985 // Step 2.
986 if (!element || IsA<HTMLHtmlElement>(element)) {
987 Document& document = node->GetDocument();
988
989 if (document.IsSVGDocument()) {
990 element = document.documentElement();
991 if (!element)
992 element = MakeGarbageCollected<SVGSVGElement>(document);
993 } else {
994 // Optimization over spec: try to reuse the existing <body> element, if it
995 // is available.
996 element = document.body();
997 if (!element)
998 element = MakeGarbageCollected<HTMLBodyElement>(document);
999 }
1000 }
1001
1002 // Steps 3, 4, 5.
1003 return blink::CreateContextualFragment(
1004 markup, element, kAllowScriptingContentAndDoNotMarkAlreadyStarted,
1005 exception_state);
1006 }
1007
detach()1008 void Range::detach() {
1009 // This is now a no-op as per the DOM specification.
1010 }
1011
CheckNodeWOffset(Node * n,unsigned offset,ExceptionState & exception_state)1012 Node* Range::CheckNodeWOffset(Node* n,
1013 unsigned offset,
1014 ExceptionState& exception_state) {
1015 switch (n->getNodeType()) {
1016 case Node::kDocumentTypeNode:
1017 exception_state.ThrowDOMException(
1018 DOMExceptionCode::kInvalidNodeTypeError,
1019 "The node provided is of type '" + n->nodeName() + "'.");
1020 return nullptr;
1021 case Node::kCdataSectionNode:
1022 case Node::kCommentNode:
1023 case Node::kTextNode:
1024 if (offset > To<CharacterData>(n)->length()) {
1025 exception_state.ThrowDOMException(
1026 DOMExceptionCode::kIndexSizeError,
1027 "The offset " + String::Number(offset) +
1028 " is larger than the node's length (" +
1029 String::Number(To<CharacterData>(n)->length()) + ").");
1030 } else if (offset >
1031 static_cast<unsigned>(std::numeric_limits<int>::max())) {
1032 exception_state.ThrowDOMException(
1033 DOMExceptionCode::kIndexSizeError,
1034 "The offset " + String::Number(offset) + " is invalid.");
1035 }
1036 return nullptr;
1037 case Node::kProcessingInstructionNode:
1038 if (offset > To<ProcessingInstruction>(n)->data().length()) {
1039 exception_state.ThrowDOMException(
1040 DOMExceptionCode::kIndexSizeError,
1041 "The offset " + String::Number(offset) +
1042 " is larger than the node's length (" +
1043 String::Number(To<ProcessingInstruction>(n)->data().length()) +
1044 ").");
1045 } else if (offset >
1046 static_cast<unsigned>(std::numeric_limits<int>::max())) {
1047 exception_state.ThrowDOMException(
1048 DOMExceptionCode::kIndexSizeError,
1049 "The offset " + String::Number(offset) + " is invalid.");
1050 }
1051 return nullptr;
1052 case Node::kAttributeNode:
1053 case Node::kDocumentFragmentNode:
1054 case Node::kDocumentNode:
1055 case Node::kElementNode: {
1056 if (!offset)
1057 return nullptr;
1058 if (offset > static_cast<unsigned>(std::numeric_limits<int>::max())) {
1059 exception_state.ThrowDOMException(
1060 DOMExceptionCode::kIndexSizeError,
1061 "The offset " + String::Number(offset) + " is invalid.");
1062 return nullptr;
1063 }
1064 Node* child_before = NodeTraversal::ChildAt(*n, offset - 1);
1065 if (!child_before) {
1066 exception_state.ThrowDOMException(
1067 DOMExceptionCode::kIndexSizeError,
1068 "There is no child at offset " + String::Number(offset) + ".");
1069 }
1070 return child_before;
1071 }
1072 }
1073 NOTREACHED();
1074 return nullptr;
1075 }
1076
CheckNodeBA(Node * n,ExceptionState & exception_state) const1077 void Range::CheckNodeBA(Node* n, ExceptionState& exception_state) const {
1078 if (!n) {
1079 // FIXME: Generated bindings code never calls with null, and neither should
1080 // other callers!
1081 exception_state.ThrowTypeError("The node provided is null.");
1082 return;
1083 }
1084
1085 // InvalidNodeTypeError: Raised if the root container of refNode is not an
1086 // Attr, Document, DocumentFragment or ShadowRoot node, or part of a SVG
1087 // shadow DOM tree, or if refNode is a Document, DocumentFragment, ShadowRoot,
1088 // Attr, Entity, or Notation node.
1089
1090 if (!n->parentNode()) {
1091 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidNodeTypeError,
1092 "the given Node has no parent.");
1093 return;
1094 }
1095
1096 switch (n->getNodeType()) {
1097 case Node::kAttributeNode:
1098 case Node::kDocumentFragmentNode:
1099 case Node::kDocumentNode:
1100 exception_state.ThrowDOMException(
1101 DOMExceptionCode::kInvalidNodeTypeError,
1102 "The node provided is of type '" + n->nodeName() + "'.");
1103 return;
1104 case Node::kCdataSectionNode:
1105 case Node::kCommentNode:
1106 case Node::kDocumentTypeNode:
1107 case Node::kElementNode:
1108 case Node::kProcessingInstructionNode:
1109 case Node::kTextNode:
1110 break;
1111 }
1112
1113 Node* root = n;
1114 while (ContainerNode* parent = root->parentNode())
1115 root = parent;
1116
1117 switch (root->getNodeType()) {
1118 case Node::kAttributeNode:
1119 case Node::kDocumentNode:
1120 case Node::kDocumentFragmentNode:
1121 case Node::kElementNode:
1122 break;
1123 case Node::kCdataSectionNode:
1124 case Node::kCommentNode:
1125 case Node::kDocumentTypeNode:
1126 case Node::kProcessingInstructionNode:
1127 case Node::kTextNode:
1128 exception_state.ThrowDOMException(
1129 DOMExceptionCode::kInvalidNodeTypeError,
1130 "The node provided is of type '" + n->nodeName() + "'.");
1131 return;
1132 }
1133 }
1134
cloneRange() const1135 Range* Range::cloneRange() const {
1136 return MakeGarbageCollected<Range>(*owner_document_.Get(),
1137 &start_.Container(), start_.Offset(),
1138 &end_.Container(), end_.Offset());
1139 }
1140
setStartAfter(Node * ref_node,ExceptionState & exception_state)1141 void Range::setStartAfter(Node* ref_node, ExceptionState& exception_state) {
1142 CheckNodeBA(ref_node, exception_state);
1143 if (exception_state.HadException())
1144 return;
1145
1146 setStart(ref_node->parentNode(), ref_node->NodeIndex() + 1, exception_state);
1147 }
1148
setEndBefore(Node * ref_node,ExceptionState & exception_state)1149 void Range::setEndBefore(Node* ref_node, ExceptionState& exception_state) {
1150 CheckNodeBA(ref_node, exception_state);
1151 if (exception_state.HadException())
1152 return;
1153
1154 setEnd(ref_node->parentNode(), ref_node->NodeIndex(), exception_state);
1155 }
1156
setEndAfter(Node * ref_node,ExceptionState & exception_state)1157 void Range::setEndAfter(Node* ref_node, ExceptionState& exception_state) {
1158 CheckNodeBA(ref_node, exception_state);
1159 if (exception_state.HadException())
1160 return;
1161
1162 setEnd(ref_node->parentNode(), ref_node->NodeIndex() + 1, exception_state);
1163 }
1164
selectNode(Node * ref_node,ExceptionState & exception_state)1165 void Range::selectNode(Node* ref_node, ExceptionState& exception_state) {
1166 if (!ref_node) {
1167 // FIXME: Generated bindings code never calls with null, and neither should
1168 // other callers!
1169 exception_state.ThrowTypeError("The node provided is null.");
1170 return;
1171 }
1172
1173 if (!ref_node->parentNode()) {
1174 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidNodeTypeError,
1175 "the given Node has no parent.");
1176 return;
1177 }
1178
1179 switch (ref_node->getNodeType()) {
1180 case Node::kCdataSectionNode:
1181 case Node::kCommentNode:
1182 case Node::kDocumentTypeNode:
1183 case Node::kElementNode:
1184 case Node::kProcessingInstructionNode:
1185 case Node::kTextNode:
1186 break;
1187 case Node::kAttributeNode:
1188 case Node::kDocumentFragmentNode:
1189 case Node::kDocumentNode:
1190 exception_state.ThrowDOMException(
1191 DOMExceptionCode::kInvalidNodeTypeError,
1192 "The node provided is of type '" + ref_node->nodeName() + "'.");
1193 return;
1194 }
1195
1196 RangeUpdateScope scope(this);
1197 setStartBefore(ref_node);
1198 setEndAfter(ref_node);
1199 }
1200
selectNodeContents(Node * ref_node,ExceptionState & exception_state)1201 void Range::selectNodeContents(Node* ref_node,
1202 ExceptionState& exception_state) {
1203 if (!ref_node) {
1204 // FIXME: Generated bindings code never calls with null, and neither should
1205 // other callers!
1206 exception_state.ThrowTypeError("The node provided is null.");
1207 return;
1208 }
1209
1210 // InvalidNodeTypeError: Raised if refNode or an ancestor of refNode is an
1211 // Entity, Notation
1212 // or DocumentType node.
1213 for (Node* n = ref_node; n; n = n->parentNode()) {
1214 switch (n->getNodeType()) {
1215 case Node::kAttributeNode:
1216 case Node::kCdataSectionNode:
1217 case Node::kCommentNode:
1218 case Node::kDocumentFragmentNode:
1219 case Node::kDocumentNode:
1220 case Node::kElementNode:
1221 case Node::kProcessingInstructionNode:
1222 case Node::kTextNode:
1223 break;
1224 case Node::kDocumentTypeNode:
1225 exception_state.ThrowDOMException(
1226 DOMExceptionCode::kInvalidNodeTypeError,
1227 "The node provided is of type '" + ref_node->nodeName() + "'.");
1228 return;
1229 }
1230 }
1231
1232 RangeUpdateScope scope(this);
1233 if (owner_document_ != ref_node->GetDocument())
1234 SetDocument(ref_node->GetDocument());
1235
1236 start_.SetToStartOfNode(*ref_node);
1237 end_.SetToEndOfNode(*ref_node);
1238 }
1239
selectNodeContents(Node * ref_node,Position & start,Position & end)1240 bool Range::selectNodeContents(Node* ref_node, Position& start, Position& end) {
1241 if (!ref_node) {
1242 return false;
1243 }
1244
1245 for (Node* n = ref_node; n; n = n->parentNode()) {
1246 switch (n->getNodeType()) {
1247 case Node::kAttributeNode:
1248 case Node::kCdataSectionNode:
1249 case Node::kCommentNode:
1250 case Node::kDocumentFragmentNode:
1251 case Node::kDocumentNode:
1252 case Node::kElementNode:
1253 case Node::kProcessingInstructionNode:
1254 case Node::kTextNode:
1255 break;
1256 case Node::kDocumentTypeNode:
1257 return false;
1258 }
1259 }
1260
1261 RangeBoundaryPoint start_boundary_point(*ref_node);
1262 start_boundary_point.SetToStartOfNode(*ref_node);
1263 start = start_boundary_point.ToPosition();
1264 RangeBoundaryPoint end_boundary_point(*ref_node);
1265 end_boundary_point.SetToEndOfNode(*ref_node);
1266 end = end_boundary_point.ToPosition();
1267 return true;
1268 }
1269
1270 // https://dom.spec.whatwg.org/#dom-range-surroundcontents
surroundContents(Node * new_parent,ExceptionState & exception_state)1271 void Range::surroundContents(Node* new_parent,
1272 ExceptionState& exception_state) {
1273 if (!new_parent) {
1274 // FIXME: Generated bindings code never calls with null, and neither should
1275 // other callers!
1276 exception_state.ThrowTypeError("The node provided is null.");
1277 return;
1278 }
1279
1280 // 1. If a non-Text node is partially contained in the context object, then
1281 // throw an InvalidStateError.
1282 Node* start_non_text_container = &start_.Container();
1283 if (start_non_text_container->getNodeType() == Node::kTextNode)
1284 start_non_text_container = start_non_text_container->parentNode();
1285 Node* end_non_text_container = &end_.Container();
1286 if (end_non_text_container->getNodeType() == Node::kTextNode)
1287 end_non_text_container = end_non_text_container->parentNode();
1288 if (start_non_text_container != end_non_text_container) {
1289 exception_state.ThrowDOMException(
1290 DOMExceptionCode::kInvalidStateError,
1291 "The Range has partially selected a non-Text node.");
1292 return;
1293 }
1294
1295 // 2. If newParent is a Document, DocumentType, or DocumentFragment node, then
1296 // throw an InvalidNodeTypeError.
1297 switch (new_parent->getNodeType()) {
1298 case Node::kAttributeNode:
1299 case Node::kDocumentFragmentNode:
1300 case Node::kDocumentNode:
1301 case Node::kDocumentTypeNode:
1302 exception_state.ThrowDOMException(
1303 DOMExceptionCode::kInvalidNodeTypeError,
1304 "The node provided is of type '" + new_parent->nodeName() + "'.");
1305 return;
1306 case Node::kCdataSectionNode:
1307 case Node::kCommentNode:
1308 case Node::kElementNode:
1309 case Node::kProcessingInstructionNode:
1310 case Node::kTextNode:
1311 break;
1312 }
1313
1314 EventQueueScope scope;
1315
1316 // 3. Let fragment be the result of extracting context object.
1317 DocumentFragment* fragment = extractContents(exception_state);
1318 if (exception_state.HadException())
1319 return;
1320
1321 // 4. If newParent has children, replace all with null within newParent.
1322 while (Node* n = new_parent->firstChild()) {
1323 To<ContainerNode>(new_parent)->RemoveChild(n, exception_state);
1324 if (exception_state.HadException())
1325 return;
1326 }
1327
1328 // 5. If newParent has children, replace all with null within newParent.
1329 insertNode(new_parent, exception_state);
1330 if (exception_state.HadException())
1331 return;
1332
1333 // 6. Append fragment to newParent.
1334 new_parent->appendChild(fragment, exception_state);
1335 if (exception_state.HadException())
1336 return;
1337
1338 // 7. Select newParent within context object.
1339 selectNode(new_parent, exception_state);
1340 }
1341
setStartBefore(Node * ref_node,ExceptionState & exception_state)1342 void Range::setStartBefore(Node* ref_node, ExceptionState& exception_state) {
1343 CheckNodeBA(ref_node, exception_state);
1344 if (exception_state.HadException())
1345 return;
1346
1347 setStart(ref_node->parentNode(), ref_node->NodeIndex(), exception_state);
1348 }
1349
CheckExtractPrecondition(ExceptionState & exception_state)1350 void Range::CheckExtractPrecondition(ExceptionState& exception_state) {
1351 DCHECK(BoundaryPointsValid());
1352
1353 if (!commonAncestorContainer())
1354 return;
1355
1356 Node* past_last = PastLastNode();
1357 for (Node* n = FirstNode(); n != past_last; n = NodeTraversal::Next(*n)) {
1358 if (n->IsDocumentTypeNode()) {
1359 exception_state.ThrowDOMException(
1360 DOMExceptionCode::kHierarchyRequestError,
1361 "The Range contains a doctype node.");
1362 return;
1363 }
1364 }
1365 }
1366
FirstNode() const1367 Node* Range::FirstNode() const {
1368 return StartPosition().NodeAsRangeFirstNode();
1369 }
1370
PastLastNode() const1371 Node* Range::PastLastNode() const {
1372 return EndPosition().NodeAsRangePastLastNode();
1373 }
1374
BoundingBox() const1375 IntRect Range::BoundingBox() const {
1376 return ComputeTextRect(EphemeralRange(this));
1377 }
1378
AreRangesEqual(const Range * a,const Range * b)1379 bool AreRangesEqual(const Range* a, const Range* b) {
1380 if (a == b)
1381 return true;
1382 if (!a || !b)
1383 return false;
1384 return a->StartPosition() == b->StartPosition() &&
1385 a->EndPosition() == b->EndPosition();
1386 }
1387
BoundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint & boundary,ContainerNode & container)1388 static inline void BoundaryNodeChildrenWillBeRemoved(
1389 RangeBoundaryPoint& boundary,
1390 ContainerNode& container) {
1391 for (Node* node_to_be_removed = container.firstChild(); node_to_be_removed;
1392 node_to_be_removed = node_to_be_removed->nextSibling()) {
1393 if (boundary.ChildBefore() == node_to_be_removed) {
1394 boundary.SetToStartOfNode(container);
1395 return;
1396 }
1397
1398 for (Node* n = &boundary.Container(); n; n = n->parentNode()) {
1399 if (n == node_to_be_removed) {
1400 boundary.SetToStartOfNode(container);
1401 return;
1402 }
1403 }
1404 }
1405 }
1406
BoundaryShadowNodeChildrenWillBeRemoved(RangeBoundaryPoint & boundary,ContainerNode & container)1407 static void BoundaryShadowNodeChildrenWillBeRemoved(
1408 RangeBoundaryPoint& boundary,
1409 ContainerNode& container) {
1410 for (Node* node_to_be_removed = container.firstChild(); node_to_be_removed;
1411 node_to_be_removed = node_to_be_removed->nextSibling()) {
1412 for (Node* n = &boundary.Container(); n;
1413 n = n->ParentOrShadowHostElement()) {
1414 if (n == node_to_be_removed) {
1415 boundary.SetToStartOfNode(container);
1416 return;
1417 }
1418 }
1419 }
1420 }
1421
NodeChildrenWillBeRemoved(ContainerNode & container)1422 void Range::NodeChildrenWillBeRemoved(ContainerNode& container) {
1423 DCHECK_EQ(container.GetDocument(), owner_document_);
1424 BoundaryNodeChildrenWillBeRemoved(start_, container);
1425 BoundaryNodeChildrenWillBeRemoved(end_, container);
1426 }
1427
FixupRemovedChildrenAcrossShadowBoundary(ContainerNode & container)1428 void Range::FixupRemovedChildrenAcrossShadowBoundary(ContainerNode& container) {
1429 DCHECK_EQ(container.GetDocument(), owner_document_);
1430 BoundaryShadowNodeChildrenWillBeRemoved(start_, container);
1431 BoundaryShadowNodeChildrenWillBeRemoved(end_, container);
1432 }
1433
BoundaryNodeWillBeRemoved(RangeBoundaryPoint & boundary,Node & node_to_be_removed)1434 static inline void BoundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary,
1435 Node& node_to_be_removed) {
1436 if (boundary.ChildBefore() == node_to_be_removed) {
1437 boundary.ChildBeforeWillBeRemoved();
1438 return;
1439 }
1440
1441 for (Node* n = &boundary.Container(); n; n = n->parentNode()) {
1442 if (n == node_to_be_removed) {
1443 boundary.SetToBeforeChild(node_to_be_removed);
1444 return;
1445 }
1446 }
1447 }
1448
BoundaryShadowNodeWillBeRemoved(RangeBoundaryPoint & boundary,Node & node_to_be_removed)1449 static inline void BoundaryShadowNodeWillBeRemoved(RangeBoundaryPoint& boundary,
1450 Node& node_to_be_removed) {
1451 DCHECK_NE(boundary.ChildBefore(), node_to_be_removed);
1452
1453 for (Node* node = &boundary.Container(); node;
1454 node = node->ParentOrShadowHostElement()) {
1455 if (node == node_to_be_removed) {
1456 boundary.SetToBeforeChild(node_to_be_removed);
1457 return;
1458 }
1459 }
1460 }
1461
NodeWillBeRemoved(Node & node)1462 void Range::NodeWillBeRemoved(Node& node) {
1463 DCHECK_EQ(node.GetDocument(), owner_document_);
1464 DCHECK_NE(node, owner_document_.Get());
1465
1466 // FIXME: Once DOMNodeRemovedFromDocument mutation event removed, we
1467 // should change following if-statement to DCHECK(!node->parentNode).
1468 if (!node.parentNode())
1469 return;
1470 BoundaryNodeWillBeRemoved(start_, node);
1471 BoundaryNodeWillBeRemoved(end_, node);
1472 }
1473
FixupRemovedNodeAcrossShadowBoundary(Node & node)1474 void Range::FixupRemovedNodeAcrossShadowBoundary(Node& node) {
1475 BoundaryShadowNodeWillBeRemoved(start_, node);
1476 BoundaryShadowNodeWillBeRemoved(end_, node);
1477 }
1478
BoundaryTextInserted(RangeBoundaryPoint & boundary,const CharacterData & text,unsigned offset,unsigned length)1479 static inline void BoundaryTextInserted(RangeBoundaryPoint& boundary,
1480 const CharacterData& text,
1481 unsigned offset,
1482 unsigned length) {
1483 if (boundary.Container() != &text)
1484 return;
1485 boundary.MarkValid();
1486 unsigned boundary_offset = boundary.Offset();
1487 if (offset >= boundary_offset)
1488 return;
1489 boundary.SetOffset(boundary_offset + length);
1490 }
1491
DidInsertText(const CharacterData & text,unsigned offset,unsigned length)1492 void Range::DidInsertText(const CharacterData& text,
1493 unsigned offset,
1494 unsigned length) {
1495 DCHECK_EQ(text.GetDocument(), owner_document_);
1496 BoundaryTextInserted(start_, text, offset, length);
1497 BoundaryTextInserted(end_, text, offset, length);
1498 }
1499
BoundaryTextRemoved(RangeBoundaryPoint & boundary,const CharacterData & text,unsigned offset,unsigned length)1500 static inline void BoundaryTextRemoved(RangeBoundaryPoint& boundary,
1501 const CharacterData& text,
1502 unsigned offset,
1503 unsigned length) {
1504 if (boundary.Container() != &text)
1505 return;
1506 boundary.MarkValid();
1507 unsigned boundary_offset = boundary.Offset();
1508 if (offset >= boundary_offset)
1509 return;
1510 if (offset + length >= boundary_offset)
1511 boundary.SetOffset(offset);
1512 else
1513 boundary.SetOffset(boundary_offset - length);
1514 }
1515
DidRemoveText(const CharacterData & text,unsigned offset,unsigned length)1516 void Range::DidRemoveText(const CharacterData& text,
1517 unsigned offset,
1518 unsigned length) {
1519 DCHECK_EQ(text.GetDocument(), owner_document_);
1520 BoundaryTextRemoved(start_, text, offset, length);
1521 BoundaryTextRemoved(end_, text, offset, length);
1522 }
1523
BoundaryTextNodesMerged(RangeBoundaryPoint & boundary,const NodeWithIndex & old_node,unsigned offset)1524 static inline void BoundaryTextNodesMerged(RangeBoundaryPoint& boundary,
1525 const NodeWithIndex& old_node,
1526 unsigned offset) {
1527 if (boundary.Container() == old_node.GetNode()) {
1528 Node* const previous_sibling = old_node.GetNode().previousSibling();
1529 DCHECK(previous_sibling);
1530 boundary.Set(*previous_sibling, boundary.Offset() + offset, nullptr);
1531 } else if (boundary.Container() == old_node.GetNode().parentNode() &&
1532 boundary.Offset() == static_cast<unsigned>(old_node.Index())) {
1533 Node* const previous_sibling = old_node.GetNode().previousSibling();
1534 DCHECK(previous_sibling);
1535 boundary.Set(*previous_sibling, offset, nullptr);
1536 }
1537 }
1538
DidMergeTextNodes(const NodeWithIndex & old_node,unsigned offset)1539 void Range::DidMergeTextNodes(const NodeWithIndex& old_node, unsigned offset) {
1540 DCHECK_EQ(old_node.GetNode().GetDocument(), owner_document_);
1541 DCHECK(old_node.GetNode().parentNode());
1542 DCHECK(old_node.GetNode().IsTextNode());
1543 DCHECK(old_node.GetNode().previousSibling());
1544 DCHECK(old_node.GetNode().previousSibling()->IsTextNode());
1545 BoundaryTextNodesMerged(start_, old_node, offset);
1546 BoundaryTextNodesMerged(end_, old_node, offset);
1547 }
1548
UpdateOwnerDocumentIfNeeded()1549 void Range::UpdateOwnerDocumentIfNeeded() {
1550 Document& new_document = start_.Container().GetDocument();
1551 DCHECK_EQ(new_document, end_.Container().GetDocument());
1552 if (new_document == owner_document_)
1553 return;
1554 owner_document_->DetachRange(this);
1555 owner_document_ = &new_document;
1556 owner_document_->AttachRange(this);
1557 }
1558
BoundaryTextNodeSplit(RangeBoundaryPoint & boundary,const Text & old_node)1559 static inline void BoundaryTextNodeSplit(RangeBoundaryPoint& boundary,
1560 const Text& old_node) {
1561 unsigned boundary_offset = boundary.Offset();
1562 if (boundary.ChildBefore() == &old_node) {
1563 boundary.Set(boundary.Container(), boundary_offset + 1,
1564 old_node.nextSibling());
1565 } else if (boundary.Container() == &old_node &&
1566 boundary_offset > old_node.length()) {
1567 Node* const next_sibling = old_node.nextSibling();
1568 DCHECK(next_sibling);
1569 boundary.Set(*next_sibling, boundary_offset - old_node.length(), nullptr);
1570 }
1571 }
1572
DidSplitTextNode(const Text & old_node)1573 void Range::DidSplitTextNode(const Text& old_node) {
1574 DCHECK_EQ(old_node.GetDocument(), owner_document_);
1575 DCHECK(old_node.parentNode());
1576 DCHECK(old_node.nextSibling());
1577 DCHECK(old_node.nextSibling()->IsTextNode());
1578 BoundaryTextNodeSplit(start_, old_node);
1579 BoundaryTextNodeSplit(end_, old_node);
1580 DCHECK(BoundaryPointsValid());
1581 }
1582
expand(const String & unit,ExceptionState & exception_state)1583 void Range::expand(const String& unit, ExceptionState& exception_state) {
1584 if (!StartPosition().IsConnected() || !EndPosition().IsConnected())
1585 return;
1586 owner_document_->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
1587 VisiblePosition start = CreateVisiblePosition(StartPosition());
1588 VisiblePosition end = CreateVisiblePosition(EndPosition());
1589 if (unit == "word") {
1590 start = CreateVisiblePosition(StartOfWordPosition(start.DeepEquivalent()));
1591 end = CreateVisiblePosition(EndOfWordPosition(end.DeepEquivalent()));
1592 } else if (unit == "sentence") {
1593 start =
1594 CreateVisiblePosition(StartOfSentencePosition(start.DeepEquivalent()));
1595 end = CreateVisiblePosition(EndOfSentence(end.DeepEquivalent()));
1596 } else if (unit == "block") {
1597 start = StartOfParagraph(start);
1598 end = EndOfParagraph(end);
1599 } else if (unit == "document") {
1600 start = StartOfDocument(start);
1601 end = EndOfDocument(end);
1602 } else {
1603 return;
1604 }
1605 setStart(start.DeepEquivalent().ComputeContainerNode(),
1606 start.DeepEquivalent().ComputeOffsetInContainerNode(),
1607 exception_state);
1608 setEnd(end.DeepEquivalent().ComputeContainerNode(),
1609 end.DeepEquivalent().ComputeOffsetInContainerNode(), exception_state);
1610 }
1611
getClientRects() const1612 DOMRectList* Range::getClientRects() const {
1613 owner_document_->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
1614
1615 Vector<FloatQuad> quads;
1616 GetBorderAndTextQuads(quads);
1617
1618 return MakeGarbageCollected<DOMRectList>(quads);
1619 }
1620
getBoundingClientRect() const1621 DOMRect* Range::getBoundingClientRect() const {
1622 return DOMRect::FromFloatRect(BoundingRect());
1623 }
1624
1625 // TODO(editing-dev): We should make
1626 // |Document::AdjustFloatQuadsForScrollAndAbsoluteZoom()| as const function
1627 // and takes |const LayoutObject&|.
ComputeTextQuads(const Document & owner_document,const LayoutText & layout_text,unsigned start_offset,unsigned end_offset)1628 static Vector<FloatQuad> ComputeTextQuads(const Document& owner_document,
1629 const LayoutText& layout_text,
1630 unsigned start_offset,
1631 unsigned end_offset) {
1632 Vector<FloatQuad> text_quads;
1633 layout_text.AbsoluteQuadsForRange(text_quads, start_offset, end_offset);
1634 const_cast<Document&>(owner_document)
1635 .AdjustFloatQuadsForScrollAndAbsoluteZoom(
1636 text_quads, const_cast<LayoutText&>(layout_text));
1637 return text_quads;
1638 }
1639
1640 // https://www.w3.org/TR/cssom-view-1/#dom-range-getclientrects
GetBorderAndTextQuads(Vector<FloatQuad> & quads) const1641 void Range::GetBorderAndTextQuads(Vector<FloatQuad>& quads) const {
1642 Node* start_container = &start_.Container();
1643 Node* end_container = &end_.Container();
1644 Node* stop_node = PastLastNode();
1645
1646 // Stores the elements selected by the range.
1647 HeapHashSet<Member<const Node>> selected_elements;
1648 for (Node* node = FirstNode(); node != stop_node;
1649 node = NodeTraversal::Next(*node)) {
1650 if (!node->IsElementNode())
1651 continue;
1652 if (selected_elements.Contains(node->parentNode()) ||
1653 (!node->contains(start_container) && !node->contains(end_container))) {
1654 DCHECK_LE(StartPosition(), Position::BeforeNode(*node));
1655 DCHECK_GE(EndPosition(), Position::AfterNode(*node));
1656 selected_elements.insert(node);
1657 }
1658 }
1659
1660 for (const Node* node = FirstNode(); node != stop_node;
1661 node = NodeTraversal::Next(*node)) {
1662 auto* element_node = DynamicTo<Element>(node);
1663 if (element_node) {
1664 if (!selected_elements.Contains(node) ||
1665 selected_elements.Contains(node->parentNode()))
1666 continue;
1667 LayoutObject* const layout_object = element_node->GetLayoutObject();
1668 if (!layout_object)
1669 continue;
1670 Vector<FloatQuad> element_quads;
1671 layout_object->AbsoluteQuads(element_quads);
1672 owner_document_->AdjustFloatQuadsForScrollAndAbsoluteZoom(element_quads,
1673 *layout_object);
1674
1675 quads.AppendVector(element_quads);
1676 continue;
1677 }
1678
1679 auto* const text_node = DynamicTo<Text>(node);
1680 if (!text_node)
1681 continue;
1682 LayoutText* const layout_text = text_node->GetLayoutObject();
1683 if (!layout_text)
1684 continue;
1685
1686 // TODO(editing-dev): Offset in |LayoutText| doesn't match to DOM offset
1687 // when |text-transform| applied. We should map DOM offset to offset in
1688 // |LayouText| for |start_offset| and |end_offset|.
1689 const unsigned start_offset =
1690 (node == start_container) ? start_.Offset() : 0;
1691 const unsigned end_offset = (node == end_container)
1692 ? end_.Offset()
1693 : std::numeric_limits<unsigned>::max();
1694 if (!layout_text->IsTextFragment()) {
1695 quads.AppendVector(ComputeTextQuads(*owner_document_, *layout_text,
1696 start_offset, end_offset));
1697 continue;
1698 }
1699
1700 // Handle ::first-letter
1701 const auto& first_letter_part =
1702 *To<LayoutTextFragment>(AssociatedLayoutObjectOf(*node, 0));
1703 const bool overlaps_with_first_letter =
1704 start_offset < first_letter_part.FragmentLength() ||
1705 (start_offset == first_letter_part.FragmentLength() &&
1706 end_offset == start_offset);
1707 if (overlaps_with_first_letter) {
1708 const unsigned start_in_first_letter = start_offset;
1709 const unsigned end_in_first_letter =
1710 std::min(end_offset, first_letter_part.FragmentLength());
1711 quads.AppendVector(ComputeTextQuads(*owner_document_, first_letter_part,
1712 start_in_first_letter,
1713 end_in_first_letter));
1714 }
1715 const auto& remaining_part = *To<LayoutTextFragment>(layout_text);
1716 if (end_offset > remaining_part.Start()) {
1717 const unsigned start_in_remaining_part =
1718 std::max(start_offset, remaining_part.Start()) -
1719 remaining_part.Start();
1720 // TODO(editing-dev): As we previously set |end_offset == UINT_MAX| as a
1721 // hacky support for |text-transform|, we need the same hack here.
1722 const unsigned end_in_remaining_part =
1723 end_offset == UINT_MAX ? end_offset
1724 : end_offset - remaining_part.Start();
1725 quads.AppendVector(ComputeTextQuads(*owner_document_, remaining_part,
1726 start_in_remaining_part,
1727 end_in_remaining_part));
1728 }
1729 }
1730 }
1731
BoundingRect() const1732 FloatRect Range::BoundingRect() const {
1733 owner_document_->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
1734
1735 Vector<FloatQuad> quads;
1736 GetBorderAndTextQuads(quads);
1737
1738 FloatRect result;
1739 for (const FloatQuad& quad : quads)
1740 result.Unite(quad.BoundingBox()); // Skips empty rects.
1741
1742 // If all rects are empty, return the first rect.
1743 if (result.IsEmpty() && !quads.IsEmpty())
1744 return quads.front().BoundingBox();
1745
1746 return result;
1747 }
1748
UpdateSelectionIfAddedToSelection()1749 void Range::UpdateSelectionIfAddedToSelection() {
1750 if (!OwnerDocument().GetFrame())
1751 return;
1752 FrameSelection& selection = OwnerDocument().GetFrame()->Selection();
1753 if (this != selection.DocumentCachedRange())
1754 return;
1755 DCHECK(startContainer()->isConnected());
1756 DCHECK(startContainer()->GetDocument() == OwnerDocument());
1757 DCHECK(endContainer()->isConnected());
1758 DCHECK(endContainer()->GetDocument() == OwnerDocument());
1759 EventDispatchForbiddenScope no_events;
1760 selection.SetSelection(SelectionInDOMTree::Builder()
1761 .Collapse(StartPosition())
1762 .Extend(EndPosition())
1763 .Build(),
1764 SetSelectionOptions::Builder()
1765 .SetShouldCloseTyping(true)
1766 .SetShouldClearTypingStyle(true)
1767 .SetDoNotSetFocus(true)
1768 .Build());
1769 selection.CacheRangeOfDocument(this);
1770 }
1771
RemoveFromSelectionIfInDifferentRoot(Document & old_document)1772 void Range::RemoveFromSelectionIfInDifferentRoot(Document& old_document) {
1773 if (!old_document.GetFrame())
1774 return;
1775 FrameSelection& selection = old_document.GetFrame()->Selection();
1776 if (this != selection.DocumentCachedRange())
1777 return;
1778 if (OwnerDocument() == old_document && startContainer()->isConnected() &&
1779 endContainer()->isConnected())
1780 return;
1781 selection.Clear();
1782 selection.ClearDocumentCachedRange();
1783 }
1784
Trace(Visitor * visitor) const1785 void Range::Trace(Visitor* visitor) const {
1786 visitor->Trace(owner_document_);
1787 visitor->Trace(start_);
1788 visitor->Trace(end_);
1789 ScriptWrappable::Trace(visitor);
1790 }
1791
1792 } // namespace blink
1793
1794 #if DCHECK_IS_ON()
1795
showTree(const blink::Range * range)1796 void showTree(const blink::Range* range) {
1797 if (range && range->BoundaryPointsValid()) {
1798 LOG(INFO) << "\n"
1799 << range->startContainer()
1800 ->ToMarkedTreeString(range->startContainer(), "S",
1801 range->endContainer(), "E")
1802 .Utf8()
1803 << "start offset: " << range->startOffset()
1804 << ", end offset: " << range->endOffset();
1805 } else {
1806 LOG(INFO) << "Cannot show tree if range is null, or if boundary points are "
1807 "invalid.";
1808 }
1809 }
1810
1811 #endif
1812