OLD | NEW |
---|---|
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 Loading... | |
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(*this); | |
dcheng
2016/11/04 02:15:54
I think this should be scoped to |oldChild|.
esprehn
2016/11/04 23:04:08
Err yeah
| |
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. | |
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(*this); | |
dcheng
2016/11/04 02:15:54
I think this should be scoped to |oldChild|
esprehn
2016/11/04 23:04:08
Yup
| |
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(); | |
dcheng
2016/11/04 02:15:54
I think we won't need this if the disabler is scop
esprehn
2016/11/04 23:04:08
childrenChanged is going to run script, so we need
| |
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); | |
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 Loading... | |
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 |
OLD | NEW |