blob: 82f47a15958110f2d9e67ae4238b6ec28fd7d720 [file] [log] [blame]
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/node.h"
#include <vector>
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "cobalt/base/user_log.h"
#include "cobalt/cssom/css_rule_visitor.h"
#include "cobalt/cssom/css_style_rule.h"
#include "cobalt/dom/cdata_section.h"
#include "cobalt/dom/comment.h"
#include "cobalt/dom/document.h"
#include "cobalt/dom/document_type.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/global_stats.h"
#include "cobalt/dom/html_collection.h"
#include "cobalt/dom/html_element_context.h"
#include "cobalt/dom/mutation_reporter.h"
#include "cobalt/dom/node_descendants_iterator.h"
#include "cobalt/dom/node_list.h"
#include "cobalt/dom/node_list_live.h"
#include "cobalt/dom/rule_matching.h"
#include "cobalt/dom/text.h"
#include "cobalt/dom/window.h"
#if defined(OS_STARBOARD)
#include "starboard/configuration.h"
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
#define HANDLE_CORE_DUMP
#include "starboard/ps4/core_dump_handler.h"
#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
#endif // defined(OS_STARBOARD)
namespace cobalt {
namespace dom {
namespace {
// This struct manages the user log information for Node count.
struct NodeCountLog {
public:
NodeCountLog() : count(0) {
base::UserLog::Register(base::UserLog::kNodeCountIndex, "NodeCnt", &count,
sizeof(count));
#if defined(HANDLE_CORE_DUMP)
SbCoreDumpRegisterHandler(CoreDumpHandler, this);
#endif
}
~NodeCountLog() {
#if defined(HANDLE_CORE_DUMP)
SbCoreDumpUnregisterHandler(CoreDumpHandler, this);
#endif
base::UserLog::Deregister(base::UserLog::kNodeCountIndex);
}
#if defined(HANDLE_CORE_DUMP)
static void CoreDumpHandler(void* context) {
SbCoreDumpLogInteger(
"Total number of nodes",
static_cast<NodeCountLog*>(context)->count);
}
#endif
int count;
private:
DISALLOW_COPY_AND_ASSIGN(NodeCountLog);
};
base::LazyInstance<NodeCountLog> node_count_log = LAZY_INSTANCE_INITIALIZER;
} // namespace
// Diagram for DispatchEvent:
// https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
bool Node::DispatchEvent(const scoped_refptr<Event>& event) {
DCHECK(event);
DCHECK(!event->IsBeingDispatched());
DCHECK(event->initialized_flag());
TRACE_EVENT1("cobalt::dom", "Node::DispatchEvent", "event",
event->type().c_str());
if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
return false;
}
// The event is now being dispatched. Track it in the global stats.
GlobalStats::GetInstance()->StartJavaScriptEvent();
scoped_refptr<Window> window;
if (IsInDocument()) {
DCHECK(node_document());
window = node_document()->default_view();
}
typedef std::vector<scoped_refptr<Node> > Ancestors;
Ancestors ancestors;
for (Node* current = this->parent_node(); current != NULL;
current = current->parent_node()) {
ancestors.push_back(current);
}
event->set_target(this);
// The capture phase: The event object propagates through the target's
// ancestors from the Window to the target's parent. This phase is also known
// as the capturing phase.
event->set_event_phase(Event::kCapturingPhase);
if (window) {
window->FireEventOnListeners(event);
}
if (!event->propagation_stopped() && !ancestors.empty()) {
for (Ancestors::reverse_iterator iter = ancestors.rbegin();
iter != ancestors.rend() && !event->propagation_stopped(); ++iter) {
(*iter)->FireEventOnListeners(event);
}
}
if (!event->propagation_stopped()) {
// The target phase: The event object arrives at the event object's event
// target. This phase is also known as the at-target phase.
event->set_event_phase(Event::kAtTarget);
FireEventOnListeners(event);
}
// If the event type indicates that the event doesn't bubble, then the event
// object will halt after completion of this phase.
if (!event->propagation_stopped() && event->bubbles()) {
if (!ancestors.empty()) {
// The bubble phase: The event object propagates through the target's
// ancestors in reverse order, starting with the target's parent and
// ending with the Window. This phase is also known as the bubbling phase.
event->set_event_phase(Event::kBubblingPhase);
for (Ancestors::iterator iter = ancestors.begin();
iter != ancestors.end() && !event->propagation_stopped(); ++iter) {
(*iter)->FireEventOnListeners(event);
}
if (window) {
window->FireEventOnListeners(event);
}
}
}
event->set_event_phase(Event::kNone);
// The event has completed being dispatched. Stop tracking it in the global
// stats.
GlobalStats::GetInstance()->StopJavaScriptEvent();
return !event->default_prevented();
}
// Algorithm for owner_document:
// https://www.w3.org/TR/2015/WD-dom-20150618/#dom-node-ownerdocument
Document* Node::owner_document() const {
// 1. If the context object is a document, return null.
if (IsDocument()) {
return NULL;
}
// 2. Return the node document.
return node_document();
}
Element* Node::parent_element() const {
return parent_ ? parent_->AsElement() : NULL;
}
bool Node::HasChildNodes() const { return first_child_ != NULL; }
scoped_refptr<NodeList> Node::child_nodes() const {
return NodeListLive::CreateWithChildren(this);
}
// Algorithm for CloneNode:
// https://www.w3.org/TR/2015/WD-dom-20150618/#dom-node-clonenode
scoped_refptr<Node> Node::CloneNode(bool deep) const {
TRACK_MEMORY_SCOPE("DOM");
scoped_refptr<Node> new_node = Duplicate();
DCHECK(new_node);
if (deep) {
scoped_refptr<Node> child = first_child_;
while (child) {
scoped_refptr<Node> new_child = child->CloneNode(true);
DCHECK(new_child);
new_node->AppendChild(new_child);
child = child->next_sibling_;
}
}
return new_node;
}
bool Node::Contains(const scoped_refptr<Node>& other_node) const {
const Node* child = first_child_;
while (child) {
if (child == other_node || child->Contains(other_node)) {
return true;
}
child = child->next_sibling_;
}
return false;
}
// Algorithm for InsertBefore:
// https://www.w3.org/TR/dom/#dom-node-insertbefore
scoped_refptr<Node> Node::InsertBefore(
const scoped_refptr<Node>& new_child,
const scoped_refptr<Node>& reference_child) {
// The insertBefore(node, child) method must return the result of
// pre-inserting node into the context object before child.
return PreInsert(new_child, reference_child);
}
// Algorithm for AppendChild:
// https://www.w3.org/TR/dom/#dom-node-appendchild
scoped_refptr<Node> Node::AppendChild(const scoped_refptr<Node>& new_child) {
// The appendChild(node) method must return the result of appending node to
// the context object.
// To append a node to a parent, pre-insert node into parent before null.
return PreInsert(new_child, NULL);
}
// Algorithm for ReplaceChild:
// https://www.w3.org/TR/dom/#dom-node-replacechild
scoped_refptr<Node> Node::ReplaceChild(const scoped_refptr<Node>& node,
const scoped_refptr<Node>& child) {
// The replaceChild(node, child) method must return the result of replacing
// child with node within the context object.
// To replace a child with node within a parent, run these steps:
// https://www.w3.org/TR/dom/#concept-node-replace
// Custom, not in any spec.
if (!node || !child) {
// TODO: Throw JS ReferenceError.
return NULL;
}
if (child == node) {
return node;
}
// 1. If parent is not a Document, DocumentFragment, or Element node, throw a
// "HierarchyRequestError".
if (!IsDocument() && !IsElement()) {
// TODO: Throw JS HierarchyRequestError.
return NULL;
}
// 2. If node is a host-including inclusive ancestor of parent, throw a
// "HierarchyRequestError".
Node* ancestor = this;
while (ancestor) {
if (node == ancestor) {
// TODO: Throw JS HierarchyRequestError.
return NULL;
}
ancestor = ancestor->parent_;
}
// 3. If child's parent is not parent, throw a "NotFoundError" exception.
if (child->parent_ != this) {
// TODO: Throw JS NotFoundError.
return NULL;
}
// 4. If node is not a DocumentFragment, DocumentType, Element, Text,
// ProcessingInstruction, or Comment node, throw a "HierarchyRequestError".
// Note: Since we support CDATASection, it is also included here, so the only
// type that is excluded is document.
if (node->IsDocument()) {
// TODO: Throw JS HierarchyRequestError.
return NULL;
}
// 5. If either node is a Text node and parent is a document, or node is a
// doctype and parent is not a document, throw a "HierarchyRequestError".
if ((node->IsText() && IsDocument()) ||
(node->IsDocumentType() && !IsDocument())) {
// TODO: Throw JS HierarchyRequestError.
return NULL;
}
// 6. Not needed by Cobalt.
// 7. Let reference child be child's next sibling.
scoped_refptr<Node> reference_child = child->next_sibling_;
// 8. If reference child is node, set it to node's next sibling.
if (reference_child == node) {
reference_child = node->next_sibling_;
}
// 9. Adopt node into parent's node document.
node->AdoptIntoDocument(node_document_);
// 10. Remove child from its parent with the suppress observers flag set.
Remove(child, true);
// 11. Insert node into parent before reference child with the suppress
// observers flag set.
Insert(node, reference_child, true);
// 12. Let nodes be node's children if node is a DocumentFragment node, and a
// list containing solely node otherwise.
// 13. Queue a mutation record of "childList" for target parent with
// addedNodes nodes, removedNodes a list solely containing child, nextSibling
// reference child, and previousSibling child's previous sibling.
MutationReporter mutation_reporter(this, GatherInclusiveAncestorsObservers());
scoped_refptr<dom::NodeList> added_nodes = new dom::NodeList();
added_nodes->AppendNode(node);
scoped_refptr<dom::NodeList> removed_nodes = new dom::NodeList();
removed_nodes->AppendNode(child);
mutation_reporter.ReportChildListMutation(
added_nodes, removed_nodes,
child->previous_sibling_ /* previous_sibling */,
reference_child /* next_sibling */);
return child;
}
// Algorithm for RemoveChild:
// https://www.w3.org/TR/dom/#dom-node-removechild
scoped_refptr<Node> Node::RemoveChild(const scoped_refptr<Node>& node) {
// The removeChild(child) method must return the result of pre-removing child
// from the context object.
return PreRemove(node);
}
scoped_refptr<HTMLCollection> Node::children() {
if (!children_collection_) {
children_collection_ = HTMLCollection::CreateWithChildElements(this);
}
return children_collection_;
}
Element* Node::first_element_child() const {
Node* child = first_child();
while (child) {
if (child->IsElement()) {
return child->AsElement();
}
child = child->next_sibling();
}
return NULL;
}
Element* Node::last_element_child() const {
Node* child = last_child();
while (child) {
if (child->IsElement()) {
return child->AsElement();
}
child = child->previous_sibling();
}
return NULL;
}
unsigned int Node::child_element_count() const {
unsigned int num_elements = 0;
const Node* child = first_child();
while (child) {
if (child->IsElement()) {
++num_elements;
}
child = child->next_sibling();
}
return num_elements;
}
scoped_refptr<Element> Node::QuerySelector(const std::string& selectors) {
return dom::QuerySelector(
this, selectors, node_document_->html_element_context()->css_parser());
}
scoped_refptr<NodeList> Node::QuerySelectorAll(const std::string& selectors) {
return dom::QuerySelectorAll(
this, selectors, node_document_->html_element_context()->css_parser());
}
Element* Node::previous_element_sibling() const {
Node* sibling = previous_sibling();
while (sibling) {
if (sibling->IsElement()) {
return sibling->AsElement();
}
sibling = sibling->previous_sibling();
}
return NULL;
}
Element* Node::next_element_sibling() const {
Node* sibling = next_sibling();
while (sibling) {
if (sibling->IsElement()) {
return sibling->AsElement();
}
sibling = sibling->next_sibling();
}
return NULL;
}
// Algorithm for AdoptIntoDocument:
// https://www.w3.org/TR/dom/#concept-node-adopt
void Node::AdoptIntoDocument(Document* document) {
DCHECK(!IsDocument());
if (!document) {
return;
}
// 1, Not needed by Cobalt.
// 2. If node's parent is not null, remove node from its parent.
if (parent_) {
parent_->RemoveChild(this);
}
// 3. Set node's inclusive descendants's node document to document.
node_document_ = base::AsWeakPtr(document);
NodeDescendantsIterator it(this);
Node* descendant = it.First();
while (descendant) {
descendant->node_document_ = base::AsWeakPtr(document);
descendant = it.Next();
}
// 4. Not needed by Cobalt.
}
Node* Node::GetRootNode() {
Node* root = this;
while (root->parent_node()) {
root = root->parent_node();
}
return root;
}
CDATASection* Node::AsCDATASection() { return NULL; }
Comment* Node::AsComment() { return NULL; }
Document* Node::AsDocument() { return NULL; }
DocumentType* Node::AsDocumentType() { return NULL; }
Element* Node::AsElement() { return NULL; }
Text* Node::AsText() { return NULL; }
void Node::TraceMembers(script::Tracer* tracer) {
EventTarget::TraceMembers(tracer);
tracer->Trace(node_document_);
tracer->Trace(parent_);
tracer->Trace(previous_sibling_);
tracer->Trace(last_child_);
tracer->Trace(first_child_);
tracer->Trace(next_sibling_);
for (RegisteredObserverList::RegisteredObserverVector::const_iterator it =
registered_observers_.registered_observers().begin();
it != registered_observers_.registered_observers().end(); ++it) {
tracer->Trace(it->observer());
}
}
Node::Node(Document* document)
: node_document_(base::AsWeakPtr(document)),
parent_(NULL),
previous_sibling_(NULL),
last_child_(NULL),
inserted_into_document_(false),
node_generation_(kInitialNodeGeneration),
ALLOW_THIS_IN_INITIALIZER_LIST(registered_observers_(this)) {
DCHECK(node_document_);
++(node_count_log.Get().count);
GlobalStats::GetInstance()->Add(this);
}
Node::~Node() {
Node* node = last_child_;
while (node) {
node->parent_ = NULL;
node->next_sibling_ = NULL;
Node* previous_sibling = node->previous_sibling_;
node->previous_sibling_ = NULL;
node = previous_sibling;
}
--(node_count_log.Get().count);
GlobalStats::GetInstance()->Remove(this);
}
void Node::OnInsertedIntoDocument() {
DCHECK(node_document_);
DCHECK(!inserted_into_document_);
inserted_into_document_ = true;
Node* child = first_child_;
while (child) {
child->OnInsertedIntoDocument();
child = child->next_sibling_;
}
}
void Node::OnRemovedFromDocument() {
DCHECK(inserted_into_document_);
inserted_into_document_ = false;
Node* child = first_child_;
while (child) {
child->OnRemovedFromDocument();
child = child->next_sibling_;
}
}
void Node::MarkDisplayNoneOnNodeAndDescendants() {
MarkDisplayNoneOnDescendants();
}
void Node::PurgeCachedBackgroundImagesOfNodeAndDescendants() {
PurgeCachedBackgroundImagesOfDescendants();
}
void Node::InvalidateComputedStylesOfNodeAndDescendants() {
InvalidateComputedStylesOfDescendants();
}
void Node::InvalidateLayoutBoxesOfNodeAndAncestors() {
InvalidateLayoutBoxesOfAncestors();
}
void Node::InvalidateLayoutBoxesOfNodeAndDescendants() {
InvalidateLayoutBoxesOfDescendants();
}
void Node::MarkDisplayNoneOnDescendants() {
Node* child = first_child_;
while (child) {
child->MarkDisplayNoneOnNodeAndDescendants();
child = child->next_sibling_;
}
}
void Node::PurgeCachedBackgroundImagesOfDescendants() {
Node* child = first_child_;
while (child) {
child->PurgeCachedBackgroundImagesOfNodeAndDescendants();
child = child->next_sibling_;
}
}
void Node::InvalidateComputedStylesOfDescendants() {
Node* child = first_child_;
while (child) {
child->InvalidateComputedStylesOfNodeAndDescendants();
child = child->next_sibling_;
}
}
void Node::InvalidateLayoutBoxesOfAncestors() {
if (parent_) {
parent_->InvalidateLayoutBoxesOfNodeAndAncestors();
}
}
void Node::InvalidateLayoutBoxesOfDescendants() {
Node* child = first_child_;
while (child) {
child->InvalidateLayoutBoxesOfNodeAndDescendants();
child = child->next_sibling_;
}
}
void Node::UpdateGenerationForNodeAndAncestors() {
if (++node_generation_ == kInvalidNodeGeneration) {
node_generation_ = kInitialNodeGeneration;
}
if (parent_) {
parent_->UpdateGenerationForNodeAndAncestors();
}
}
scoped_ptr<Node::RegisteredObserverVector>
Node::GatherInclusiveAncestorsObservers() {
scoped_ptr<RegisteredObserverVector> inclusive_observers(
new RegisteredObserverVector());
Node* current = this;
while (current) {
const RegisteredObserverList::RegisteredObserverVector& node_observers =
current->registered_observers_.registered_observers();
inclusive_observers->insert(inclusive_observers->end(),
node_observers.begin(), node_observers.end());
current = current->parent_;
}
return inclusive_observers.Pass();
}
// Algorithm for EnsurePreInsertionValidity:
// https://www.w3.org/TR/dom/#concept-node-ensure-pre-insertion-validity
bool Node::EnsurePreInsertionValidity(const scoped_refptr<Node>& node,
const scoped_refptr<Node>& child) {
if (!node) {
return false;
}
// 1. If parent is not a Document, DocumentFragment, or Element node, throw a
// "HierarchyRequestError".
if (!IsDocument() && !IsElement()) {
// TODO: Throw JS HierarchyRequestError.
return false;
}
// 2. If node is a host-including inclusive ancestor of parent, throw a
// "HierarchyRequestError".
Node* ancestor = this;
while (ancestor) {
if (node == ancestor) {
// TODO: Throw JS HierarchyRequestError.
return false;
}
ancestor = ancestor->parent_;
}
// 3. If child is not null and its parent is not parent, throw a
// "NotFoundError" exception.
if (child && child->parent_ != this) {
// TODO: Throw JS NotFoundError.
return false;
}
// 4. If node is not a DocumentFragment, DocumentType, Element, Text,
// ProcessingInstruction, or Comment node, throw a "HierarchyRequestError".
// Note: Since we support CDATASection, it is also included here, so the only
// type that is excluded is document.
if (node->IsDocument()) {
// TODO: Throw JS HierarchyRequestError.
return false;
}
// 5. If either node is a Text node and parent is a document, or node is a
// doctype and parent is not a document, throw a "HierarchyRequestError".
if ((node->IsText() && IsDocument()) ||
(node->IsDocumentType() && !IsDocument())) {
// TODO: Throw JS HierarchyRequestError.
return false;
}
// 6. Not needed by Cobalt.
return true;
}
// Algorithm for PreInsert:
// https://www.w3.org/TR/dom/#concept-node-pre-insert
scoped_refptr<Node> Node::PreInsert(const scoped_refptr<Node>& node,
const scoped_refptr<Node>& child) {
// 1. Ensure pre-insertion validity of node into parent before child.
if (!EnsurePreInsertionValidity(node, child)) {
return NULL;
}
// 2. Let reference child be child.
// 3. If reference child is node, set it to node's next sibling.
// 4. Adopt node into parent's node document.
// 5. Insert node into parent before reference child.
node->AdoptIntoDocument(node_document_);
Insert(node, child == node ? child->next_sibling_ : child, false);
// 6. Return node.
return node;
}
// Algorithm for Insert:
// https://www.w3.org/TR/dom/#concept-node-insert
void Node::Insert(const scoped_refptr<Node>& node,
const scoped_refptr<Node>& child, bool suppress_observers) {
// 1. 2. Not needed by Cobalt.
// 3. Let nodes be node's children if node is a DocumentFragment node, and a
// list containing solely node otherwise.
// 4. 5. Not needed by Cobalt.
// 6. If suppress observers flag is unset, queue a mutation record of
// "childList" for parent with addedNodes nodes, nextSibling child, and
// previousSibling child's previous sibling or parent's last child if
// child is null.
// 7. For each newNode in nodes, in tree order, run these substeps:
// 1. Insert newNode into parent before child or at the end of parent if
// child is null.
// 2. Run the insertion steps with newNode.
if (!suppress_observers) {
scoped_ptr<RegisteredObserverVector> observers =
GatherInclusiveAncestorsObservers();
if (!observers->empty()) {
MutationReporter mutation_reporter(this, observers.Pass());
scoped_refptr<dom::NodeList> added_nodes = new dom::NodeList();
added_nodes->AppendNode(node);
mutation_reporter.ReportChildListMutation(
added_nodes, NULL, child ? child->previous_sibling_
: this->last_child_ /* previous_sibling */,
child /* next_sibling */);
}
}
node->parent_ = this;
scoped_refptr<Node> next_sibling = child;
Node* previous_sibling =
next_sibling ? next_sibling->previous_sibling_ : last_child_;
if (previous_sibling) {
previous_sibling->next_sibling_ = node;
} else {
first_child_ = node;
}
node->previous_sibling_ = previous_sibling;
if (next_sibling) {
next_sibling->previous_sibling_ = node;
} else {
last_child_ = node;
}
node->next_sibling_ = next_sibling;
// Custom, not in any spec.
OnMutation();
node->UpdateGenerationForNodeAndAncestors();
// Invalidate the layout boxes of the new parent as a result of its children
// being changed.
// NOTE: The added node does not have any invalidations done, because they
// occur on the remove and are guaranteed to not be needed at this point.
InvalidateLayoutBoxesOfNodeAndAncestors();
if (inserted_into_document_) {
node->OnInsertedIntoDocument();
Document* document = node_document();
if (document) {
document->OnDOMMutation();
}
}
}
// Algorithm for PreRemove:
// https://www.w3.org/TR/dom/#concept-node-pre-remove
scoped_refptr<Node> Node::PreRemove(const scoped_refptr<Node>& child) {
// 1. If child's parent is not parent, throw a "NotFoundError" exception.
if (!child || child->parent_ != this) {
// TODO: Throw JS NotFoundError.
return NULL;
}
// 2. Remove child from parent.
Remove(child, false);
// 3. Return child.
return child;
}
// Algorithm for Remove:
// https://www.w3.org/TR/dom/#concept-node-remove
void Node::Remove(const scoped_refptr<Node>& node, bool suppress_observers) {
DCHECK(node);
OnMutation();
node->UpdateGenerationForNodeAndAncestors();
// Invalidate the layout boxes of the previous parent as a result of its
// children being changed.
InvalidateLayoutBoxesOfNodeAndAncestors();
// Purge any cached background images now that this node and its descendants
// are no longer in the tree, so that the images can be released from the
// resource cache.
node->PurgeCachedBackgroundImagesOfNodeAndDescendants();
// Invalidate the styles and layout boxes of the node being removed from
// the tree. These are no longer valid as a result of the child and its
// descendants losing their inherited styles.
node->InvalidateComputedStylesOfNodeAndDescendants();
node->InvalidateLayoutBoxesOfNodeAndDescendants();
bool was_inserted_to_document = node->inserted_into_document_;
if (was_inserted_to_document) {
node->OnRemovedFromDocument();
}
// 1. 5. Not needed by Cobalt.
// 6. Let oldPreviousSibling be node's previous sibling
// 7. If suppress observers flag is unset, queue a mutation record of
// "childList" for parent with removedNodes a list solely containing node,
// nextSibling node's next sibling, and previousSibling oldPreviousSibling.
// 8. For each ancestor ancestor of node, if ancestor has any registered
// observers whose options's subtree is true, then for each such registered
// observer registered, append a transient registered observer whose observer
// and options are identical to those of registered and source which is
// registered to node's list of registered observers.
// 9. Remove node from its parent.
// 10. Run the removing steps with node, parent, and oldPreviousSibling.
scoped_ptr<RegisteredObserverVector> observers =
GatherInclusiveAncestorsObservers();
if (!observers->empty()) {
// Step 7 - Queue a mutation record.
if (!suppress_observers) {
MutationReporter mutation_reporter(this, observers.Pass());
scoped_refptr<dom::NodeList> removed_nodes = new dom::NodeList();
removed_nodes->AppendNode(node);
mutation_reporter.ReportChildListMutation(
NULL, removed_nodes, node->previous_sibling_ /* previous_sibling */,
node->next_sibling_ /* next_sibling */);
}
// TODO: transient registered observers.
}
if (node->previous_sibling_) {
node->previous_sibling_->next_sibling_ = node->next_sibling_;
} else {
first_child_ = node->next_sibling_;
}
if (node->next_sibling_) {
node->next_sibling_->previous_sibling_ = node->previous_sibling_;
} else {
last_child_ = node->previous_sibling_;
}
node->parent_ = NULL;
node->previous_sibling_ = NULL;
node->next_sibling_ = NULL;
// Custom, not in any spec.
if (was_inserted_to_document) {
scoped_refptr<Document> document = node->owner_document();
if (document) {
document->OnDOMMutation();
}
}
}
// Algorithm for ReplaceAll:
// https://www.w3.org/TR/dom/#concept-node-replace-all
void Node::ReplaceAll(const scoped_refptr<Node>& node) {
// 1. If node is not null, adopt node into parent's node document.
if (node) {
node->AdoptIntoDocument(this->node_document());
}
// 2. Let removedNodes be parent's children.
scoped_refptr<NodeList> removed_nodes = new NodeList();
scoped_refptr<Node> next_child = first_child_;
while (next_child) {
removed_nodes->AppendNode(next_child);
next_child = next_child->next_sibling();
}
// 3. Let addedNodes be the empty list if node is null, node's children if
// node is a DocumentFragment node, and a list containing node otherwise.
scoped_refptr<NodeList> added_nodes = new NodeList();
if (node) {
added_nodes->AppendNode(node);
}
// 4. Remove all parent's children, in tree order, with the suppress observers
// flag set.
while (HasChildNodes()) {
Remove(first_child(), true);
}
// 5. If node is not null, insert node into parent before null with the
// suppress observers flag set.
if (node) {
Insert(node, NULL, true);
}
// 6. Queue a mutation record of "childList" for parent with addedNodes
// addedNodes and removedNodes removedNodes.
scoped_ptr<RegisteredObserverVector> observers =
GatherInclusiveAncestorsObservers();
if (!observers->empty()) {
MutationReporter mutation_reporter(this, observers.Pass());
scoped_refptr<dom::NodeList> new_added_nodes = new dom::NodeList();
if (node) {
new_added_nodes->AppendNode(node);
}
if (new_added_nodes->length() > 0 || removed_nodes->length() > 0) {
mutation_reporter.ReportChildListMutation(new_added_nodes, removed_nodes,
NULL /* previous_sibling */,
NULL /* next_sibling */);
}
}
}
} // namespace dom
} // namespace cobalt