/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sky/engine/core/dom/Node.h" #include "base/trace_event/trace_event_impl.h" #include "gen/sky/core/HTMLNames.h" #include "sky/engine/bindings/exception_state.h" #include "sky/engine/core/css/resolver/StyleResolver.h" #include "sky/engine/core/dom/Attr.h" #include "sky/engine/core/dom/Attribute.h" #include "sky/engine/core/dom/ChildListMutationScope.h" #include "sky/engine/core/dom/Document.h" #include "sky/engine/core/dom/DocumentFragment.h" #include "sky/engine/core/dom/DocumentMarkerController.h" #include "sky/engine/core/dom/Element.h" #include "sky/engine/core/dom/ElementRareData.h" #include "sky/engine/core/dom/ElementTraversal.h" #include "sky/engine/core/dom/ExceptionCode.h" #include "sky/engine/core/dom/NodeRareData.h" #include "sky/engine/core/dom/NodeTraversal.h" #include "sky/engine/core/dom/Range.h" #include "sky/engine/core/dom/StaticNodeList.h" #include "sky/engine/core/dom/Text.h" #include "sky/engine/core/dom/TreeScopeAdopter.h" #include "sky/engine/core/dom/UserActionElementSet.h" #include "sky/engine/core/dom/WeakNodeMap.h" #include "sky/engine/core/editing/htmlediting.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/frame/Settings.h" #include "sky/engine/core/page/Page.h" #include "sky/engine/core/rendering/RenderBox.h" #include "sky/engine/platform/JSONValues.h" #include "sky/engine/platform/Partitions.h" #include "sky/engine/platform/TraceEvent.h" #include "sky/engine/tonic/dart_gc_visitor.h" #include "sky/engine/wtf/HashSet.h" #include "sky/engine/wtf/PassOwnPtr.h" #include "sky/engine/wtf/RefCountedLeakCounter.h" #include "sky/engine/wtf/Vector.h" #include "sky/engine/wtf/text/CString.h" #include "sky/engine/wtf/text/StringBuilder.h" namespace blink { void* Node::operator new(size_t size) { ASSERT(isMainThread()); return partitionAlloc(Partitions::getObjectModelPartition(), size); } void Node::operator delete(void* ptr) { ASSERT(isMainThread()); partitionFree(ptr); } #if DUMP_NODE_STATISTICS typedef HashSet > WeakNodeSet; static WeakNodeSet& liveNodeSet() { DEFINE_STATIC_LOCAL(OwnPtr, set, (adoptPtr(new WeakNodeSet()))); return *set; } #endif void Node::dumpStatistics() { #if DUMP_NODE_STATISTICS size_t nodesWithRareData = 0; size_t elementNodes = 0; size_t textNodes = 0; size_t piNodes = 0; size_t documentNodes = 0; size_t docTypeNodes = 0; size_t fragmentNodes = 0; HashMap perTagCount; size_t attributes = 0; size_t elementsWithAttributeStorage = 0; size_t elementsWithRareData = 0; for (WeakNodeSet::iterator it = liveNodeSet().begin(); it != liveNodeSet().end(); ++it) { Node* node = *it; if (node->hasRareData()) { ++nodesWithRareData; if (node->isElementNode()) { ++elementsWithRareData; } } switch (node->nodeType()) { case ELEMENT_NODE: { ++elementNodes; // Tag stats Element* element = toElement(node); HashMap::AddResult result = perTagCount.add(element->tagName(), 1); if (!result.isNewEntry) result.storedValue->value++; if (const ElementData* elementData = element->elementData()) { attributes += elementData->attributes().size(); ++elementsWithAttributeStorage; } break; } case TEXT_NODE: { ++textNodes; break; } case DOCUMENT_NODE: { ++documentNodes; break; } case DOCUMENT_FRAGMENT_NODE: { ++fragmentNodes; break; } } } printf("Number of Nodes: %d\n\n", liveNodeSet().size()); printf("Number of Nodes with RareData: %zu\n\n", nodesWithRareData); printf("NodeType distribution:\n"); printf(" Number of Element nodes: %zu\n", elementNodes); printf(" Number of Text nodes: %zu\n", textNodes); printf(" Number of Document nodes: %zu\n", documentNodes); printf(" Number of DocumentType nodes: %zu\n", docTypeNodes); printf(" Number of DocumentFragment nodes: %zu\n", fragmentNodes); printf("Element tag name distibution:\n"); for (HashMap::iterator it = perTagCount.begin(); it != perTagCount.end(); ++it) printf(" Number of <%s> tags: %zu\n", it->key.utf8().data(), it->value); printf("Attributes:\n"); printf(" Number of Attributes (non-Node and Node): %zu [%zu]\n", attributes, sizeof(Attribute)); printf(" Number of Elements with attribute storage: %zu [%zu]\n", elementsWithAttributeStorage, sizeof(ElementData)); printf(" Number of Elements with RareData: %zu\n", elementsWithRareData); #endif } DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, nodeCounter, ("WebCoreNode")); void Node::trackForDebugging() { #ifndef NDEBUG nodeCounter.increment(); #endif #if DUMP_NODE_STATISTICS liveNodeSet().add(this); #endif } Node::Node(TreeScope* treeScope, ConstructionType type) : m_nodeFlags(type) , m_parentNode(nullptr) , m_treeScope(treeScope) , m_previous(nullptr) , m_next(nullptr) { ASSERT(m_treeScope || type == CreateDocument); #if !ENABLE(OILPAN) if (m_treeScope) m_treeScope->guardRef(); #endif #if !defined(NDEBUG) || (defined(DUMP_NODE_STATISTICS) && DUMP_NODE_STATISTICS) trackForDebugging(); #endif InspectorCounters::incrementCounter(InspectorCounters::NodeCounter); } Node::~Node() { #ifndef NDEBUG nodeCounter.decrement(); #endif #if !ENABLE(OILPAN) #if DUMP_NODE_STATISTICS liveNodeSet().remove(this); #endif if (hasRareData()) clearRareData(); RELEASE_ASSERT(!renderer()); if (!isContainerNode()) willBeDeletedFromDocument(); if (m_previous) m_previous->setNextSibling(0); if (m_next) m_next->setPreviousSibling(0); if (m_treeScope) m_treeScope->guardDeref(); if (getFlag(HasWeakReferencesFlag)) WeakNodeMap::notifyNodeDestroyed(this); #else // With Oilpan, the rare data finalizer also asserts for // this condition (we cannot directly access it here.) RELEASE_ASSERT(hasRareData() || !renderer()); #endif InspectorCounters::decrementCounter(InspectorCounters::NodeCounter); } #if !ENABLE(OILPAN) // With Oilpan all of this is handled with weak processing of the document. void Node::willBeDeletedFromDocument() { if (!isTreeScopeInitialized()) return; Document& document = this->document(); document.markers().removeMarkers(this); } #endif NodeRareData* Node::rareData() const { ASSERT_WITH_SECURITY_IMPLICATION(hasRareData()); return static_cast(m_data.m_rareData); } NodeRareData& Node::ensureRareData() { if (hasRareData()) return *rareData(); if (isElementNode()) m_data.m_rareData = ElementRareData::create(m_data.m_renderer); else m_data.m_rareData = NodeRareData::create(m_data.m_renderer); ASSERT(m_data.m_rareData); setFlag(HasRareDataFlag); return *rareData(); } void Node::clearRareData() { ASSERT(hasRareData()); ASSERT(!transientMutationObserverRegistry() || transientMutationObserverRegistry()->isEmpty()); RenderObject* renderer = m_data.m_rareData->renderer(); if (isElementNode()) delete static_cast(m_data.m_rareData); else delete static_cast(m_data.m_rareData); m_data.m_renderer = renderer; clearFlag(HasRareDataFlag); } static const Node* rootForGC(const Node* node) { if (node->inDocument()) return &node->document(); while (Node* parent = node->parentNode()) node = parent; return node; } void Node::AcceptDartGCVisitor(DartGCVisitor& visitor) const { visitor.AddToSetForRoot(rootForGC(this), dart_wrapper()); } PassRefPtr Node::insertBefore(PassRefPtr newChild, Node* refChild, ExceptionState& exceptionState) { if (isContainerNode()) return toContainerNode(this)->insertBefore(newChild, refChild, exceptionState); exceptionState.ThrowDOMException(HierarchyRequestError, "This node type does not support this method."); return nullptr; } PassRefPtr Node::replaceChild(PassRefPtr newChild, PassRefPtr oldChild, ExceptionState& exceptionState) { if (isContainerNode()) return toContainerNode(this)->replaceChild(newChild, oldChild, exceptionState); exceptionState.ThrowDOMException(HierarchyRequestError, "This node type does not support this method."); return nullptr; } PassRefPtr Node::removeChild(PassRefPtr oldChild, ExceptionState& exceptionState) { if (isContainerNode()) return toContainerNode(this)->removeChild(oldChild, exceptionState); exceptionState.ThrowDOMException(NotFoundError, "This node type does not support this method."); return nullptr; } PassRefPtr Node::appendChild(PassRefPtr newChild, ExceptionState& exceptionState) { if (isContainerNode()) return toContainerNode(this)->appendChild(newChild, exceptionState); exceptionState.ThrowDOMException(HierarchyRequestError, "This node type does not support this method."); return nullptr; } Element* Node::previousElementSibling() { return ElementTraversal::previousSibling(*this); } Element* Node::nextElementSibling() { return ElementTraversal::nextSibling(*this); } void Node::newInsertBefore(Vector>& nodes, ExceptionState& es) { RefPtr parent = parentNode(); if (!parent) return; RefPtr protect(this); for (auto& node : nodes) { parent->insertBefore(node.release(), this, es); if (es.had_exception()) return; } } void Node::newInsertAfter(Vector>& nodes, ExceptionState& es) { RefPtr parent = this->parentNode(); if (!parent) return; RefPtr reference = m_next; for (auto& node : nodes) { parent->insertBefore(node.release(), reference.get(), es); if (es.had_exception()) return; } } void Node::replaceWith(Vector>& nodes, ExceptionState& es) { RefPtr parent = this->parentNode(); if (!parent) return; RefPtr reference = m_next; remove(es); if (es.had_exception()) return; for (auto& node : nodes) { parent->insertBefore(node, reference.get(), es); if (es.had_exception()) return; } } void Node::remove(ExceptionState& exceptionState) { if (ContainerNode* parent = parentNode()) parent->removeChild(this, exceptionState); } const AtomicString& Node::localName() const { return nullAtom; } bool Node::isContentEditable(UserSelectAllTreatment treatment) { document().updateRenderTreeIfNeeded(); return hasEditableStyle(Editable, treatment); } bool Node::isContentRichlyEditable() { document().updateRenderTreeIfNeeded(); return hasEditableStyle(RichlyEditable, UserSelectAllIsAlwaysNonEditable); } bool Node::hasEditableStyle(EditableLevel editableLevel, UserSelectAllTreatment treatment) const { ASSERT(!needsStyleRecalc()); for (const Node* node = this; node; node = node->parentNode()) { if (node->isElementNode() && node->renderer()) { // Elements with user-select: all style are considered atomic // therefore non editable. if (Position::nodeIsUserSelectAll(node) && treatment == UserSelectAllIsAlwaysNonEditable) return false; if (static_cast(node)->hasAttribute(HTMLNames::contenteditableAttr)) return editableLevel != RichlyEditable; return false; } } return false; } bool Node::isEditableToAccessibility(EditableLevel editableLevel) const { return hasEditableStyle(editableLevel); } RenderBox* Node::renderBox() const { RenderObject* renderer = this->renderer(); return renderer && renderer->isBox() ? toRenderBox(renderer) : 0; } RenderBoxModelObject* Node::renderBoxModelObject() const { RenderObject* renderer = this->renderer(); return renderer && renderer->isBoxModelObject() ? toRenderBoxModelObject(renderer) : 0; } LayoutRect Node::boundingBox() const { if (renderer()) return renderer()->absoluteBoundingBoxRect(); return LayoutRect(); } void Node::setIsLink(bool isLink) { setFlag(isLink, IsLinkFlag); } namespace { class JSONTraceValue : public base::trace_event::ConvertableToTraceFormat { public: explicit JSONTraceValue(RefPtr value) : m_value(value.release()) { } void AppendAsTraceFormat(std::string* out) const override { out->append(m_value->toJSONString().utf8().data()); } private: RefPtr m_value; }; } // namespace unsigned Node::styledSubtreeSize() const { unsigned nodeCount = 0; for (const Node* node = this; node; node = NodeTraversal::next(*node, this)) { if (node->isTextNode() || node->isElementNode()) nodeCount++; } return nodeCount; } void Node::traceStyleChange(StyleChangeType changeType) { static const unsigned kMinLoggedSize = 100; unsigned nodeCount = styledSubtreeSize(); if (nodeCount < kMinLoggedSize) return; TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("style.debug"), "Node::setNeedsStyleRecalc", TRACE_EVENT_SCOPE_PROCESS ); } void Node::traceStyleChangeIfNeeded(StyleChangeType changeType) { // TRACE_EVENT_CATEGORY_GROUP_ENABLED macro loads a global static bool into our local bool. bool styleTracingEnabled; TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("style.debug"), &styleTracingEnabled); if (UNLIKELY(styleTracingEnabled)) traceStyleChange(changeType); } inline void Node::setStyleChange(StyleChangeType changeType) { m_nodeFlags = (m_nodeFlags & ~StyleChangeMask) | changeType; } void Node::markAncestorsWithChildNeedsStyleRecalc() { for (ContainerNode* p = parentNode(); p && !p->childNeedsStyleRecalc(); p = p->parentNode()) p->setChildNeedsStyleRecalc(); document().scheduleRenderTreeUpdateIfNeeded(); } void Node::setNeedsStyleRecalc(StyleChangeType changeType) { ASSERT(changeType != NoStyleChange); if (!inActiveDocument()) return; StyleChangeType existingChangeType = styleChangeType(); if (changeType > existingChangeType) { setStyleChange(changeType); if (changeType >= SubtreeStyleChange) traceStyleChangeIfNeeded(changeType); } if (existingChangeType == NoStyleChange) markAncestorsWithChildNeedsStyleRecalc(); } void Node::clearNeedsStyleRecalc() { m_nodeFlags &= ~StyleChangeMask; } bool Node::inActiveDocument() const { return inDocument() && document().isActive(); } unsigned Node::nodeIndex() const { Node *_tempNode = previousSibling(); unsigned count=0; for ( count=0; _tempNode; count++ ) _tempNode = _tempNode->previousSibling(); return count; } bool Node::isDescendantOf(const Node *other) const { // Return true if other is an ancestor of this, otherwise false if (!other || !other->hasChildren() || inDocument() != other->inDocument()) return false; if (other->treeScope() != treeScope()) return false; if (other->isTreeScope()) return !isTreeScope(); for (const ContainerNode* n = parentNode(); n; n = n->parentNode()) { if (n == other) return true; } return false; } bool Node::contains(const Node* node) const { if (!node) return false; return this == node || node->isDescendantOf(this); } void Node::reattach(const AttachContext& context) { AttachContext reattachContext(context); reattachContext.performingReattach = true; // We only need to detach if the node has already been through attach(). if (styleChangeType() < NeedsReattachStyleChange) detach(reattachContext); attach(reattachContext); } void Node::attach(const AttachContext&) { ASSERT(document().inStyleRecalc() || isDocumentNode()); ASSERT(needsAttach()); ASSERT(!renderer() || (renderer()->style() && (renderer()->parent() || renderer()->isRenderView()))); clearNeedsStyleRecalc(); } void Node::detach(const AttachContext& context) { if (renderer()) renderer()->destroy(); setRenderer(0); // Do not remove the element's hovered and active status // if performing a reattach. if (!context.performingReattach) { Document& doc = document(); if (isUserActionElement()) { if (hovered()) doc.hoveredNodeDetached(this); if (inActiveChain()) doc.activeChainNodeDetached(this); doc.userActionElements().didDetach(this); } } setStyleChange(NeedsReattachStyleChange); setChildNeedsStyleRecalc(); } // FIXME: This code is used by editing. Seems like it could move over there and not pollute Node. Node *Node::previousNodeConsideringAtomicNodes() const { if (previousSibling()) { Node *n = previousSibling(); while (!isAtomicNode(n) && n->lastChild()) n = n->lastChild(); return n; } else if (parentNode()) { return parentNode(); } else { return 0; } } Node *Node::nextNodeConsideringAtomicNodes() const { if (!isAtomicNode(this) && hasChildren()) return firstChild(); if (nextSibling()) return nextSibling(); const Node *n = this; while (n && !n->nextSibling()) n = n->parentNode(); if (n) return n->nextSibling(); return 0; } Node *Node::previousLeafNode() const { Node *node = previousNodeConsideringAtomicNodes(); while (node) { if (isAtomicNode(node)) return node; node = node->previousNodeConsideringAtomicNodes(); } return 0; } Node *Node::nextLeafNode() const { Node *node = nextNodeConsideringAtomicNodes(); while (node) { if (isAtomicNode(node)) return node; node = node->nextNodeConsideringAtomicNodes(); } return 0; } RenderStyle* Node::virtualComputedStyle() { return parentNode() ? parentNode()->computedStyle() : 0; } int Node::maxCharacterOffset() const { ASSERT_NOT_REACHED(); return 0; } // FIXME: Shouldn't these functions be in the editing code? Code that asks questions about HTML in the core DOM class // is obviously misplaced. bool Node::canStartSelection() const { if (hasEditableStyle()) return true; return parentNode() ? parentNode()->canStartSelection() : true; } bool Node::isRootEditableElement() const { return hasEditableStyle() && isElementNode() && (!parentNode() || !parentNode()->hasEditableStyle() || !parentNode()->isElementNode()); } Element* Node::rootEditableElement(EditableType editableType) const { return rootEditableElement(); } Element* Node::rootEditableElement() const { Element* result = 0; for (Node* n = const_cast(this); n && n->hasEditableStyle(); n = n->parentNode()) { if (n->isElementNode()) result = toElement(n); } return result; } // FIXME: End of obviously misplaced HTML editing functions. Try to move these out of Node. Document* Node::ownerDocument() const { Document* doc = &document(); return doc == this ? 0 : doc; } ContainerNode* Node::owner() const { if (inDocument()) return &treeScope().rootNode(); return 0; } String Node::textContent() const { if (isTextNode()) return toText(this)->data(); StringBuilder content; for (const Node* node = this; node; node = NodeTraversal::next(*node, this)) { if (node->isTextNode()) content.append(toText(node)->data()); } return content.toString(); } void Node::setTextContent(const String& text) { switch (nodeType()) { case TEXT_NODE: toText(this)->setData(text); return; case ELEMENT_NODE: case DOCUMENT_FRAGMENT_NODE: { // FIXME: Merge this logic into replaceChildrenWithText. RefPtr container = toContainerNode(this); // Note: This is an intentional optimization. // See crbug.com/352836 also. // No need to do anything if the text is identical. if (container->hasOneTextChild() && toText(container->firstChild())->data() == text) return; ChildListMutationScope mutation(*this); container->removeChildren(); // Note: This API will not insert empty text nodes: // http://dom.spec.whatwg.org/#dom-node-textcontent if (!text.isEmpty()) container->appendChild(Text::create(document(), text), ASSERT_NO_EXCEPTION); return; } case DOCUMENT_NODE: // FIXME(sky): When we get rid of the Document being special, go down the ELEMENT_NODE codepath. // Do nothing. return; } } bool Node::offsetInCharacters() const { return false; } unsigned short Node::compareDocumentPosition(const Node* otherNode) const { // It is not clear what should be done if |otherNode| is 0. if (!otherNode) return DOCUMENT_POSITION_DISCONNECTED; if (otherNode == this) return DOCUMENT_POSITION_EQUIVALENT; const Node* start1 = this; const Node* start2 = otherNode; // If either of start1 or start2 is null, then we are disconnected, since one of the nodes is // an orphaned attribute node. if (!start1 || !start2) { unsigned short direction = (this > otherNode) ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING; return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | direction; } Vector chain1; Vector chain2; // If one node is in the document and the other is not, we must be disconnected. // If the nodes have different owning documents, they must be disconnected. Note that we avoid // comparing Attr nodes here, since they return false from inDocument() all the time (which seems like a bug). if (start1->inDocument() != start2->inDocument() || (start1->treeScope() != start2->treeScope())) { unsigned short direction = (this > otherNode) ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING; return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | direction; } // We need to find a common ancestor container, and then compare the indices of the two immediate children. const Node* current; for (current = start1; current; current = current->parentNode()) chain1.append(current); for (current = start2; current; current = current->parentNode()) chain2.append(current); unsigned index1 = chain1.size(); unsigned index2 = chain2.size(); // If the two elements don't have a common root, they're not in the same tree. if (chain1[index1 - 1] != chain2[index2 - 1]) { unsigned short direction = (this > otherNode) ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING; return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | direction; } unsigned connection = start1->treeScope() != start2->treeScope() ? DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC : 0; // Walk the two chains backwards and look for the first difference. for (unsigned i = std::min(index1, index2); i; --i) { const Node* child1 = chain1[--index1]; const Node* child2 = chain2[--index2]; if (child1 != child2) { if (!child2->nextSibling()) return DOCUMENT_POSITION_FOLLOWING | connection; if (!child1->nextSibling()) return DOCUMENT_POSITION_PRECEDING | connection; // Otherwise we need to see which node occurs first. Crawl backwards from child2 looking for child1. for (Node* child = child2->previousSibling(); child; child = child->previousSibling()) { if (child == child1) return DOCUMENT_POSITION_FOLLOWING | connection; } return DOCUMENT_POSITION_PRECEDING | connection; } } // There was no difference between the two parent chains, i.e., one was a subset of the other. The shorter // chain is the ancestor. return index1 < index2 ? DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_CONTAINED_BY | connection : DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_CONTAINS | connection; } String Node::debugName() const { StringBuilder name; name.append(nodeName()); if (isElementNode()) { const Element& thisElement = toElement(*this); if (thisElement.hasID()) { name.appendLiteral(" id=\'"); name.append(thisElement.getIdAttribute()); name.append('\''); } if (thisElement.hasClass()) { name.appendLiteral(" class=\'"); for (size_t i = 0; i < thisElement.classNames().size(); ++i) { if (i > 0) name.append(' '); name.append(thisElement.classNames()[i]); } name.append('\''); } } return name.toString(); } #ifndef NDEBUG static void appendAttributeDesc(const Node* node, StringBuilder& stringBuilder, const QualifiedName& name, const char* attrDesc) { if (!node->isElementNode()) return; String attr = toElement(node)->getAttribute(name); if (attr.isEmpty()) return; stringBuilder.append(attrDesc); stringBuilder.appendLiteral("=\""); stringBuilder.append(attr); stringBuilder.appendLiteral("\""); } void Node::showNode(const char* prefix) const { if (!prefix) prefix = ""; if (isTextNode()) { String value = toText(this)->data(); value.replaceWithLiteral('\\', "\\\\"); value.replaceWithLiteral('\n', "\\n"); fprintf(stderr, "%s%s\t%p \"%s\"\n", prefix, nodeName().utf8().data(), this, value.utf8().data()); } else { StringBuilder attrs; appendAttributeDesc(this, attrs, HTMLNames::idAttr, " ID"); appendAttributeDesc(this, attrs, HTMLNames::classAttr, " CLASS"); appendAttributeDesc(this, attrs, HTMLNames::styleAttr, " STYLE"); fprintf(stderr, "%s%s\t%p%s\n", prefix, nodeName().utf8().data(), this, attrs.toString().utf8().data()); } } void Node::showTreeForThis() const { showTreeAndMark(this, "*"); } void Node::showNodePathForThis() const { Vector chain; const Node* node = this; while (node->parentNode()) { chain.append(node); node = node->parentNode(); } for (unsigned index = chain.size(); index > 0; --index) { const Node* node = chain[index - 1]; switch (node->nodeType()) { case ELEMENT_NODE: { fprintf(stderr, "/%s", node->nodeName().utf8().data()); const Element* element = toElement(node); const AtomicString& idattr = element->getIdAttribute(); bool hasIdAttr = !idattr.isNull() && !idattr.isEmpty(); if (node->previousSibling() || node->nextSibling()) { int count = 0; for (Node* previous = node->previousSibling(); previous; previous = previous->previousSibling()) if (previous->nodeName() == node->nodeName()) ++count; if (hasIdAttr) fprintf(stderr, "[@id=\"%s\" and position()=%d]", idattr.utf8().data(), count); else fprintf(stderr, "[%d]", count); } else if (hasIdAttr) { fprintf(stderr, "[@id=\"%s\"]", idattr.utf8().data()); } break; } case TEXT_NODE: fprintf(stderr, "/text()"); break; default: break; } } fprintf(stderr, "\n"); } static void traverseTreeAndMark(const String& baseIndent, const Node* rootNode, const Node* markedNode1, const char* markedLabel1, const Node* markedNode2, const char* markedLabel2) { for (const Node* node = rootNode; node; node = NodeTraversal::next(*node)) { if (node == markedNode1) fprintf(stderr, "%s", markedLabel1); if (node == markedNode2) fprintf(stderr, "%s", markedLabel2); StringBuilder indent; indent.append(baseIndent); for (const Node* tmpNode = node; tmpNode && tmpNode != rootNode; tmpNode = tmpNode->parentNode()) indent.append('\t'); fprintf(stderr, "%s", indent.toString().utf8().data()); node->showNode(); indent.append('\t'); } } void Node::showTreeAndMark(const Node* markedNode1, const char* markedLabel1, const Node* markedNode2, const char* markedLabel2) const { const Node* rootNode; const Node* node = this; while (node->parentNode()) node = node->parentNode(); rootNode = node; String startingIndent; traverseTreeAndMark(startingIndent, rootNode, markedNode1, markedLabel1, markedNode2, markedLabel2); } void Node::formatForDebugger(char* buffer, unsigned length) const { String result; String s; s = nodeName(); if (s.isEmpty()) result = ""; else result = s; strncpy(buffer, result.utf8().data(), length - 1); } static void showSubTreeAcrossFrame(const Node* node, const Node* markedNode, const String& indent) { if (node == markedNode) fputs("*", stderr); fputs(indent.utf8().data(), stderr); node->showNode(); for (Node* child = node->firstChild(); child; child = child->nextSibling()) showSubTreeAcrossFrame(child, markedNode, indent + "\t"); } #endif // -------- void Node::didMoveToNewDocument(Document& oldDocument) { TreeScopeAdopter::ensureDidMoveToNewDocumentWasCalled(oldDocument); oldDocument.markers().removeMarkers(this); oldDocument.updateRangesAfterNodeMovedToAnotherDocument(*this); if (Vector >* registry = mutationObserverRegistry()) { for (size_t i = 0; i < registry->size(); ++i) { document().addMutationObserverTypes(registry->at(i)->mutationTypes()); } } if (HashSet >* transientRegistry = transientMutationObserverRegistry()) { for (HashSet >::iterator iter = transientRegistry->begin(); iter != transientRegistry->end(); ++iter) { document().addMutationObserverTypes((*iter)->mutationTypes()); } } } Vector >* Node::mutationObserverRegistry() { if (!hasRareData()) return 0; NodeMutationObserverData* data = rareData()->mutationObserverData(); if (!data) return 0; return &data->registry; } HashSet >* Node::transientMutationObserverRegistry() { if (!hasRareData()) return 0; NodeMutationObserverData* data = rareData()->mutationObserverData(); if (!data) return 0; return &data->transientRegistry; } template static inline void collectMatchingObserversForMutation(HashMap, MutationRecordDeliveryOptions>& observers, Registry* registry, Node& target, MutationObserver::MutationType type, const QualifiedName* attributeName) { if (!registry) return; for (typename Registry::iterator iter = registry->begin(); iter != registry->end(); ++iter) { const MutationObserverRegistration& registration = **iter; if (registration.shouldReceiveMutationFrom(target, type, attributeName)) { MutationRecordDeliveryOptions deliveryOptions = registration.deliveryOptions(); HashMap, MutationRecordDeliveryOptions>::AddResult result = observers.add(®istration.observer(), deliveryOptions); if (!result.isNewEntry) result.storedValue->value |= deliveryOptions; } } } void Node::getRegisteredMutationObserversOfType(HashMap, MutationRecordDeliveryOptions>& observers, MutationObserver::MutationType type, const QualifiedName* attributeName) { ASSERT((type == MutationObserver::Attributes && attributeName) || !attributeName); collectMatchingObserversForMutation(observers, mutationObserverRegistry(), *this, type, attributeName); collectMatchingObserversForMutation(observers, transientMutationObserverRegistry(), *this, type, attributeName); for (Node* node = parentNode(); node; node = node->parentNode()) { collectMatchingObserversForMutation(observers, node->mutationObserverRegistry(), *this, type, attributeName); collectMatchingObserversForMutation(observers, node->transientMutationObserverRegistry(), *this, type, attributeName); } } void Node::registerMutationObserver(MutationObserver& observer, MutationObserverOptions options, const HashSet& attributeFilter) { MutationObserverRegistration* registration = 0; Vector >& registry = ensureRareData().ensureMutationObserverData().registry; for (size_t i = 0; i < registry.size(); ++i) { if (®istry[i]->observer() == &observer) { registration = registry[i].get(); registration->resetObservation(options, attributeFilter); } } if (!registration) { registry.append(MutationObserverRegistration::create(observer, this, options, attributeFilter)); registration = registry.last().get(); } document().addMutationObserverTypes(registration->mutationTypes()); } void Node::unregisterMutationObserver(MutationObserverRegistration* registration) { Vector >* registry = mutationObserverRegistry(); ASSERT(registry); if (!registry) return; size_t index = registry->find(registration); ASSERT(index != kNotFound); if (index == kNotFound) return; // Deleting the registration may cause this node to be derefed, so we must make sure the Vector operation completes // before that, in case |this| is destroyed (see MutationObserverRegistration::m_registrationNodeKeepAlive). // FIXME: Simplify the registration/transient registration logic to make this understandable by humans. RefPtr protect(this); registry->remove(index); } void Node::registerTransientMutationObserver(MutationObserverRegistration* registration) { ensureRareData().ensureMutationObserverData().transientRegistry.add(registration); } void Node::unregisterTransientMutationObserver(MutationObserverRegistration* registration) { HashSet >* transientRegistry = transientMutationObserverRegistry(); ASSERT(transientRegistry); if (!transientRegistry) return; ASSERT(transientRegistry->contains(registration)); transientRegistry->remove(registration); } void Node::notifyMutationObserversNodeWillDetach() { if (!document().hasMutationObservers()) return; for (Node* node = parentNode(); node; node = node->parentNode()) { if (Vector >* registry = node->mutationObserverRegistry()) { const size_t size = registry->size(); for (size_t i = 0; i < size; ++i) registry->at(i)->observedSubtreeNodeWillDetach(*this); } if (HashSet >* transientRegistry = node->transientMutationObserverRegistry()) { for (HashSet >::iterator iter = transientRegistry->begin(); iter != transientRegistry->end(); ++iter) (*iter)->observedSubtreeNodeWillDetach(*this); } } } #if !ENABLE(OILPAN) // This is here for inlining inline void TreeScope::removedLastRefToScope() { ASSERT_WITH_SECURITY_IMPLICATION(!deletionHasBegun()); if (m_guardRefCount) { // If removing a child removes the last self-only ref, we don't // want the scope to be destructed until after // removeDetachedChildren returns, so we guard ourselves with an // extra self-only ref. guardRef(); dispose(); #if ENABLE(ASSERT) // We need to do this right now since guardDeref() can delete this. rootNode().m_inRemovedLastRefFunction = false; #endif guardDeref(); } else { #if ENABLE(ASSERT) rootNode().m_inRemovedLastRefFunction = false; #endif #if ENABLE(SECURITY_ASSERT) beginDeletion(); #endif delete this; } } // It's important not to inline removedLastRef, because we don't want to inline the code to // delete a Node at each deref call site. void Node::removedLastRef() { // An explicit check for Document here is better than a virtual function since it is // faster for non-Document nodes, and because the call to removedLastRef that is inlined // at all deref call sites is smaller if it's a non-virtual function. if (isTreeScope()) { treeScope().removedLastRefToScope(); return; } #if ENABLE(SECURITY_ASSERT) m_deletionHasBegun = true; #endif delete this; } #endif void Node::setActive(bool flag) { document().userActionElements().setActive(this, flag); } void Node::setHovered(bool flag) { document().userActionElements().setHovered(this, flag); } bool Node::isUserActionElementActive() const { ASSERT(isUserActionElement()); return document().userActionElements().isActive(this); } bool Node::isUserActionElementInActiveChain() const { ASSERT(isUserActionElement()); return document().userActionElements().isInActiveChain(this); } bool Node::isUserActionElementHovered() const { ASSERT(isUserActionElement()); return document().userActionElements().isHovered(this); } unsigned Node::lengthOfContents() const { // This switch statement must be consistent with that of Range::processContentsBetweenOffsets. switch (nodeType()) { case Node::TEXT_NODE: return toCharacterData(this)->length(); case Node::ELEMENT_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: return toContainerNode(this)->countChildren(); } ASSERT_NOT_REACHED(); return 0; } } // namespace blink #ifndef NDEBUG void showNode(const blink::Node* node) { if (node) node->showNode(""); } void showTree(const blink::Node* node) { if (node) node->showTreeForThis(); } void showNodePath(const blink::Node* node) { if (node) node->showNodePathForThis(); } #endif