Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(403)

Side by Side Diff: third_party/WebKit/Source/core/dom/ContainerNode.cpp

Issue 2478573002: Make removeChild, parserRemoveChild and removeChildren more consistent. (Closed)
Patch Set: SubframeLoadingDisabler (oldChild) Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights 5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights
6 * reserved. 6 * reserved.
7 * 7 *
8 * This library is free software; you can redistribute it and/or 8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public 9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either 10 * License as published by the Free Software Foundation; either
(...skipping 420 matching lines...) Expand 10 before | Expand all | Expand 10 after
431 targets, exceptionState)) 431 targets, exceptionState))
432 return oldChild; 432 return oldChild;
433 433
434 if (next) 434 if (next)
435 insertNodeVector(targets, next, AdoptAndInsertBefore()); 435 insertNodeVector(targets, next, AdoptAndInsertBefore());
436 else 436 else
437 insertNodeVector(targets, nullptr, AdoptAndAppendChild()); 437 insertNodeVector(targets, nullptr, AdoptAndAppendChild());
438 return oldChild; 438 return oldChild;
439 } 439 }
440 440
441 void ContainerNode::willRemoveChild(Node& child) {
442 DCHECK_EQ(child.parentNode(), this);
443 ChildListMutationScope(*this).willRemoveChild(child);
444 child.notifyMutationObserversNodeWillDetach();
445 dispatchChildRemovalEvents(child);
446 ChildFrameDisconnector(child).disconnect();
447 if (document() != child.document()) {
448 // |child| was moved another document by DOM mutation event handler.
449 return;
450 }
451
452 // |nodeWillBeRemoved()| must be run after |ChildFrameDisconnector|, because
453 // |ChildFrameDisconnector| can run script which may cause state that is to
454 // be invalidated by removing the node.
455 ScriptForbiddenScope scriptForbiddenScope;
456 EventDispatchForbiddenScope assertNoEventDispatch;
457 // e.g. mutation event listener can create a new range.
458 document().nodeWillBeRemoved(child);
459 }
460
461 void ContainerNode::willRemoveChildren() {
462 NodeVector children;
463 getChildNodes(*this, children);
464
465 ChildListMutationScope mutation(*this);
466 for (const auto& node : children) {
467 DCHECK(node);
468 Node& child = *node;
469 mutation.willRemoveChild(child);
470 child.notifyMutationObserversNodeWillDetach();
471 dispatchChildRemovalEvents(child);
472 }
473
474 ChildFrameDisconnector(*this).disconnect(
475 ChildFrameDisconnector::DescendantsOnly);
476 }
477
478 DEFINE_TRACE(ContainerNode) { 441 DEFINE_TRACE(ContainerNode) {
479 visitor->trace(m_firstChild); 442 visitor->trace(m_firstChild);
480 visitor->trace(m_lastChild); 443 visitor->trace(m_lastChild);
481 Node::trace(visitor); 444 Node::trace(visitor);
482 } 445 }
483 446
484 DEFINE_TRACE_WRAPPERS(ContainerNode) { 447 DEFINE_TRACE_WRAPPERS(ContainerNode) {
485 visitor->traceWrappersWithManualWriteBarrier(m_firstChild); 448 visitor->traceWrappersWithManualWriteBarrier(m_firstChild);
486 visitor->traceWrappersWithManualWriteBarrier(m_lastChild); 449 visitor->traceWrappersWithManualWriteBarrier(m_lastChild);
487 Node::traceWrappers(visitor); 450 Node::traceWrappers(visitor);
488 } 451 }
489 452
490 Node* ContainerNode::removeChild(Node* oldChild, 453 Node* ContainerNode::removeChild(Node* oldChild,
491 ExceptionState& exceptionState) { 454 ExceptionState& exceptionState) {
492 // NotFoundError: Raised if oldChild is not a child of this node. 455 if (!oldChild || oldChild->parentNode() != this) {
493 // FIXME: We should never really get PseudoElements in here, but editing will
494 // sometimes attempt to remove them still. We should fix that and enable this
495 // DCHECK. DCHECK(!oldChild->isPseudoElement())
496 if (!oldChild || oldChild->parentNode() != this ||
497 oldChild->isPseudoElement()) {
498 exceptionState.throwDOMException( 456 exceptionState.throwDOMException(
499 NotFoundError, "The node to be removed is not a child of this node."); 457 NotFoundError, "The node to be removed is not a child of this node.");
500 return nullptr; 458 return nullptr;
501 } 459 }
502 460
503 Node* child = oldChild; 461 // TODO(esprehn): We should never really get PseudoElements in here, but
462 // editing will sometimes attempt to remove them still. We should fix that and
463 // turn this into an assert.
464 if (oldChild->isPseudoElement()) {
465 exceptionState.throwDOMException(NotFoundError,
466 "Pseudo elements cannot be removed.");
467 return nullptr;
468 }
504 469
505 document().removeFocusedElementOfSubtree(child); 470 // MutationObserver, no script here.
471 ChildListMutationScope(*this).willRemoveChild(*oldChild);
472 oldChild->notifyMutationObserversNodeWillDetach();
506 473
507 // Events fired when blurring currently focused node might have moved this 474 // Run script in 'DOMNodeRemoved' and 'DOMNodeRemovedFromDocument' event
508 // child into a different parent. 475 // handlers.
509 if (child->parentNode() != this) { 476 dispatchChildRemovalEvents(*oldChild);
477
478 if (oldChild->parentNode() != this) {
479 exceptionState.throwDOMException(NotFoundError,
480 "The node to be removed is no longer a "
481 "child of this node. Perhaps it was moved "
482 "in response to a mutation?");
483 return nullptr;
484 }
485
486 // Run script in 'unload' events for <iframe>, <object>, <embed>, etc.
487 ChildFrameDisconnector(*oldChild).disconnect();
488
489 // Forbid loading frames inside the steps below that run script since we'd
490 // never disconnect them.
491 SubframeLoadingDisabler subframeLoadingDisabler(*oldChild);
492
493 if (oldChild->parentNode() != this) {
494 exceptionState.throwDOMException(NotFoundError,
495 "The node to be removed is no longer a "
496 "child of this node. Perhaps it was moved "
497 "in an 'unload' event handler?");
498 return nullptr;
499 }
500
501 // Run script in blur events.
502 document().removeFocusedElementOfSubtree(oldChild);
503
504 if (oldChild->parentNode() != this) {
510 exceptionState.throwDOMException(NotFoundError, 505 exceptionState.throwDOMException(NotFoundError,
511 "The node to be removed is no longer a " 506 "The node to be removed is no longer a "
512 "child of this node. Perhaps it was moved " 507 "child of this node. Perhaps it was moved "
513 "in a 'blur' event handler?"); 508 "in a 'blur' event handler?");
514 return nullptr; 509 return nullptr;
515 } 510 }
516 511
517 willRemoveChild(*child);
518
519 // Mutation events might have moved this child into a different parent.
520 if (child->parentNode() != this) {
521 exceptionState.throwDOMException(NotFoundError,
522 "The node to be removed is no longer a "
523 "child of this node. Perhaps it was moved "
524 "in response to a mutation?");
525 return nullptr;
526 }
527
528 { 512 {
513 // Script may run inside the UpdateSuspendScope as a result of plugins.
529 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates; 514 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
530 DocumentOrderedMap::RemoveScope treeRemoveScope; 515 DocumentOrderedMap::RemoveScope treeRemoveScope;
531 516
532 Node* prev = child->previousSibling(); 517 Node* prev = oldChild->previousSibling();
533 Node* next = child->nextSibling(); 518 Node* next = oldChild->nextSibling();
534 removeBetween(prev, next, *child); 519
535 notifyNodeRemoved(*child); 520 {
536 childrenChanged(ChildrenChange::forRemoval(*child, prev, next, 521 EventDispatchForbiddenScope forbidEvents;
522 ScriptForbiddenScope forbidScript;
523
524 if (document() == oldChild->document())
525 document().nodeWillBeRemoved(*oldChild);
526
527 // Actually remove the node from the tree and then call removedFrom on it
528 // and all descendants.
529 removeBetween(prev, next, *oldChild);
530 notifyNodeRemoved(*oldChild);
531 }
532
533 // Script could run inside childrenChanged, enable iframe loading again.
dcheng 2016/11/07 20:50:57 Nit: it's not clear to me what "enable iframe load
esprehn 2016/11/07 22:42:33 There is one on the stack, see above subframeLoadi
534 // TODO(esprehn): Can script run inside childrenChanged for Removal? It
535 // looks like it can only happen for Insertion.
536 childrenChanged(ChildrenChange::forRemoval(*oldChild, prev, next,
537 ChildrenChangeSourceAPI)); 537 ChildrenChangeSourceAPI));
538 } 538 }
539
540 // Run script in DOMSubtreeModified event handlers.
539 dispatchSubtreeModifiedEvent(); 541 dispatchSubtreeModifiedEvent();
540 return child; 542 return oldChild;
541 } 543 }
542 544
543 void ContainerNode::removeBetween(Node* previousChild, 545 void ContainerNode::removeBetween(Node* previousChild,
544 Node* nextChild, 546 Node* nextChild,
545 Node& oldChild) { 547 Node& oldChild) {
548 ScriptForbiddenScope scriptForbiddenScope;
546 EventDispatchForbiddenScope assertNoEventDispatch; 549 EventDispatchForbiddenScope assertNoEventDispatch;
547 550
548 DCHECK_EQ(oldChild.parentNode(), this); 551 DCHECK_EQ(oldChild.parentNode(), this);
549 552
550 AttachContext context; 553 AttachContext context;
551 context.clearInvalidation = true; 554 context.clearInvalidation = true;
552 if (!oldChild.needsAttach()) 555 if (!oldChild.needsAttach())
553 oldChild.detachLayoutTree(context); 556 oldChild.detachLayoutTree(context);
554 557
555 if (nextChild) 558 if (nextChild)
556 nextChild->setPreviousSibling(previousChild); 559 nextChild->setPreviousSibling(previousChild);
557 if (previousChild) 560 if (previousChild)
558 previousChild->setNextSibling(nextChild); 561 previousChild->setNextSibling(nextChild);
559 if (m_firstChild == &oldChild) 562 if (m_firstChild == &oldChild)
560 setFirstChild(nextChild); 563 setFirstChild(nextChild);
561 if (m_lastChild == &oldChild) 564 if (m_lastChild == &oldChild)
562 setLastChild(previousChild); 565 setLastChild(previousChild);
563 566
564 oldChild.setPreviousSibling(nullptr); 567 oldChild.setPreviousSibling(nullptr);
565 oldChild.setNextSibling(nullptr); 568 oldChild.setNextSibling(nullptr);
566 oldChild.setParentOrShadowHostNode(nullptr); 569 oldChild.setParentOrShadowHostNode(nullptr);
567 570
568 document().adoptIfNeeded(oldChild); 571 document().adoptIfNeeded(oldChild);
569 } 572 }
570 573
574 // TODO(esprehn): This function skips MutationEvents but runs script in lots of
575 // other ways, perhaps we should change to dispatching those too and just call
576 // removeChild() inside the parser?
571 void ContainerNode::parserRemoveChild(Node& oldChild) { 577 void ContainerNode::parserRemoveChild(Node& oldChild) {
572 DCHECK_EQ(oldChild.parentNode(), this); 578 DCHECK_EQ(oldChild.parentNode(), this);
573 DCHECK(!oldChild.isDocumentFragment()); 579 DCHECK(!oldChild.isDocumentFragment());
574 580
575 // This may cause arbitrary Javascript execution via onunload handlers. 581 // MutationObserver, no script here.
576 if (oldChild.connectedSubframeCount()) 582 ChildListMutationScope(*this).willRemoveChild(oldChild);
577 ChildFrameDisconnector(oldChild).disconnect(); 583 oldChild.notifyMutationObserversNodeWillDetach();
584
585 // Run script in 'unload' events for <iframe>, <object>, <embed>, etc.
586 ChildFrameDisconnector(oldChild).disconnect();
587
588 // Forbid loading frames inside the steps below that run script since we'd
589 // never disconnect them.
590 SubframeLoadingDisabler subframeLoadingDisabler(oldChild);
dcheng 2016/11/07 20:50:57 I'd like to understand why this needs to be scoped
esprehn 2016/11/07 22:42:32 Anything that finds a ScriptForbiddenScope bypass
578 591
579 if (oldChild.parentNode() != this) 592 if (oldChild.parentNode() != this)
580 return; 593 return;
581 594
582 ChildListMutationScope(*this).willRemoveChild(oldChild); 595 // TODO(esprehn): Why don't we call removeFocusedElementOfSubtree? This means
583 oldChild.notifyMutationObserversNodeWillDetach(); 596 // focus doesn't get updated when the parser moves children around.
584 597
585 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates; 598 {
586 DocumentOrderedMap::RemoveScope treeRemoveScope; 599 // Script may run inside the UpdateSuspendScope as a result of plugins.
600 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
601 DocumentOrderedMap::RemoveScope treeRemoveScope;
587 602
588 Node* prev = oldChild.previousSibling(); 603 Node* prev = oldChild.previousSibling();
589 Node* next = oldChild.nextSibling(); 604 Node* next = oldChild.nextSibling();
590 removeBetween(prev, next, oldChild);
591 605
592 notifyNodeRemoved(oldChild); 606 {
593 childrenChanged(ChildrenChange::forRemoval(oldChild, prev, next, 607 EventDispatchForbiddenScope forbidEvents;
594 ChildrenChangeSourceParser)); 608 ScriptForbiddenScope forbidScript;
609
610 // TODO(esprehn): Why don't we call document().nodeWillBeRemoved(oldChild)
611 // here? This means the parser can corrupt ranges, selection, node
612 // iterators and more.
613
614 // Actually remove the node from the tree and then call removedFrom on it.
615 // No script can run here.
616 removeBetween(prev, next, oldChild);
617 notifyNodeRemoved(oldChild);
618 }
619
620 // Script could run inside childrenChanged, enable iframe loading again.
621 // TODO(esprehn): Can script run inside childrenChanged for Removal? It
622 // looks like it can only happen for Insertion.
623 subframeLoadingDisabler.release();
624 childrenChanged(ChildrenChange::forRemoval(oldChild, prev, next,
625 ChildrenChangeSourceParser));
626 }
595 } 627 }
596 628
597 // This differs from other remove functions because it forcibly removes all the 629 // This differs from other remove functions because it forcibly removes all the
598 // children, regardless of read-only status or event exceptions, e.g. 630 // children, regardless of read-only status or event exceptions, e.g.
599 void ContainerNode::removeChildren(SubtreeModificationAction action) { 631 void ContainerNode::removeChildren(SubtreeModificationAction action) {
600 if (!m_firstChild) 632 if (!m_firstChild)
601 return; 633 return;
602 634
603 // Do any prep work needed before actually starting to detach 635 // TODO(esprehn): We shouldn't allow removing PseudoElements here.
604 // and remove... e.g. stop loading frames, fire unload events.
605 willRemoveChildren();
606 636
607 { 637 {
608 // Removing focus can cause frames to load, either via events (focusout, 638 NodeVector children;
609 // blur) or widget updates (e.g., for <embed>). 639 getChildNodes(*this, children);
610 SubframeLoadingDisabler disabler(*this);
611 640
612 // Exclude this node when looking for removed focusedElement since only 641 // This must be inside a block scope before the below steps so
613 // children will be removed. 642 // MutationRecords are visible in takeRecords() inside those event
614 // This must be later than willRemoveChildren, which might change focus 643 // handlers.
615 // state of a child. 644 ChildListMutationScope mutation(*this);
616 document().removeFocusedElementOfSubtree(this, true); 645 for (const auto& oldChild : children) {
646 // MutationObserver, no script here.
647 mutation.willRemoveChild(*oldChild);
648 oldChild->notifyMutationObserversNodeWillDetach();
617 649
618 // Removing a node from a selection can cause widget updates. 650 // TODO(esprehn): removeChild() exposes the MutationRecords to the
619 document().nodeChildrenWillBeRemoved(*this); 651 // child removal event handlers, but this function doesn't because we
652 // batch the records in the ChildListMutationScope above. Should we
653 // make these consistent?
654
655 // Run script in 'DOMNodeRemoved' and 'DOMNodeRemovedFromDocument' event
656 // handlers.
657 dispatchChildRemovalEvents(*oldChild);
658 }
620 } 659 }
621 660
661 // Run script in 'unload' events for <iframe>, <object>, <embed>, etc.
662 ChildFrameDisconnector(*this).disconnect(
663 ChildFrameDisconnector::DescendantsOnly);
664
665 // Forbid loading frames inside the steps below that run script since we'd
666 // never disconnect them.
667 SubframeLoadingDisabler subframeLoadingDisabler(*this);
668
669 // Exclude this node when looking for removed focusedElement since only
670 // children will be removed.
671 document().removeFocusedElementOfSubtree(this, true);
dcheng 2016/11/07 20:50:57 From reading this, perhaps we should just scope Su
esprehn 2016/11/07 22:42:32 That wouldn't have prevented the recent UXSS, sinc
dcheng 2016/11/08 01:51:12 Sorry, one more question, because I'd really like
672
622 { 673 {
674 // Script may run inside the UpdateSuspendScope as a result of plugins.
623 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates; 675 HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
624 DocumentOrderedMap::RemoveScope treeRemoveScope; 676 DocumentOrderedMap::RemoveScope treeRemoveScope;
677
625 { 678 {
626 EventDispatchForbiddenScope assertNoEventDispatch; 679 EventDispatchForbiddenScope assertNoEventDispatch;
627 ScriptForbiddenScope forbidScript; 680 ScriptForbiddenScope forbidScript;
628 681
629 while (Node* child = m_firstChild) { 682 document().nodeChildrenWillBeRemoved(*this);
630 removeBetween(0, child->nextSibling(), *child); 683
631 notifyNodeRemoved(*child); 684 // Remove each node from the tree and then call removedFrom on it and all
685 // descendants.
686 while (Node* oldChild = m_firstChild) {
687 removeBetween(0, oldChild->nextSibling(), *oldChild);
688 notifyNodeRemoved(*oldChild);
632 } 689 }
633 } 690 }
634 691
692 // Script could run inside childrenChanged, enable iframe loading again.
693 // TODO(esprehn): Can script run inside childrenChanged for Removal? It
694 // looks like it can only happen for Insertion.
695 subframeLoadingDisabler.release();
635 ChildrenChange change = {AllChildrenRemoved, nullptr, nullptr, nullptr, 696 ChildrenChange change = {AllChildrenRemoved, nullptr, nullptr, nullptr,
636 ChildrenChangeSourceAPI}; 697 ChildrenChangeSourceAPI};
637 childrenChanged(change); 698 childrenChanged(change);
638 } 699 }
639 700
701 // Run script in DOMSubtreeModified event handlers.
640 if (action == DispatchSubtreeModifiedEvent) 702 if (action == DispatchSubtreeModifiedEvent)
641 dispatchSubtreeModifiedEvent(); 703 dispatchSubtreeModifiedEvent();
642 } 704 }
643 705
644 Node* ContainerNode::appendChild(Node* newChild, 706 Node* ContainerNode::appendChild(Node* newChild,
645 ExceptionState& exceptionState) { 707 ExceptionState& exceptionState) {
646 // Make sure adding the new child is ok 708 // Make sure adding the new child is ok
647 if (!checkAcceptChild(newChild, 0, exceptionState)) 709 if (!checkAcceptChild(newChild, 0, exceptionState))
648 return newChild; 710 return newChild;
649 DCHECK(newChild); 711 DCHECK(newChild);
(...skipping 818 matching lines...) Expand 10 before | Expand all | Expand 10 after
1468 return true; 1530 return true;
1469 1531
1470 if (node->isElementNode() && toElement(node)->shadow()) 1532 if (node->isElementNode() && toElement(node)->shadow())
1471 return true; 1533 return true;
1472 1534
1473 return false; 1535 return false;
1474 } 1536 }
1475 #endif 1537 #endif
1476 1538
1477 } // namespace blink 1539 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/dom/ContainerNode.h ('k') | third_party/WebKit/Source/core/dom/Document.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698