| /* |
| * 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<RawPtr<Node> > WeakNodeSet; |
| static WeakNodeSet& liveNodeSet() |
| { |
| DEFINE_STATIC_LOCAL(OwnPtr<WeakNodeSet>, 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<String, size_t> 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<String, size_t>::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<String, size_t>::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<NodeRareData*>(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<ElementRareData*>(m_data.m_rareData); |
| else |
| delete static_cast<NodeRareData*>(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> Node::insertBefore(PassRefPtr<Node> 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> Node::replaceChild(PassRefPtr<Node> newChild, PassRefPtr<Node> 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> Node::removeChild(PassRefPtr<Node> 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> Node::appendChild(PassRefPtr<Node> 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<RefPtr<Node>>& nodes, ExceptionState& es) |
| { |
| RefPtr<ContainerNode> parent = parentNode(); |
| if (!parent) |
| return; |
| RefPtr<Node> protect(this); |
| for (auto& node : nodes) { |
| parent->insertBefore(node.release(), this, es); |
| if (es.had_exception()) |
| return; |
| } |
| } |
| |
| void Node::newInsertAfter(Vector<RefPtr<Node>>& nodes, ExceptionState& es) |
| { |
| RefPtr<ContainerNode> parent = this->parentNode(); |
| if (!parent) |
| return; |
| RefPtr<Node> reference = m_next; |
| for (auto& node : nodes) { |
| parent->insertBefore(node.release(), reference.get(), es); |
| if (es.had_exception()) |
| return; |
| } |
| } |
| |
| void Node::replaceWith(Vector<RefPtr<Node>>& nodes, ExceptionState& es) |
| { |
| RefPtr<ContainerNode> parent = this->parentNode(); |
| if (!parent) |
| return; |
| RefPtr<Node> 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<const Element*>(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<JSONValue> value) |
| : m_value(value.release()) { } |
| |
| void AppendAsTraceFormat(std::string* out) const override |
| { |
| out->append(m_value->toJSONString().utf8().data()); |
| } |
| |
| private: |
| RefPtr<JSONValue> 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<Node*>(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<ContainerNode> 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<const Node*, 16> chain1; |
| Vector<const Node*, 16> 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<const Node*, 16> 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 = "<none>"; |
| 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<OwnPtr<MutationObserverRegistration> >* registry = mutationObserverRegistry()) { |
| for (size_t i = 0; i < registry->size(); ++i) { |
| document().addMutationObserverTypes(registry->at(i)->mutationTypes()); |
| } |
| } |
| |
| if (HashSet<RawPtr<MutationObserverRegistration> >* transientRegistry = transientMutationObserverRegistry()) { |
| for (HashSet<RawPtr<MutationObserverRegistration> >::iterator iter = transientRegistry->begin(); iter != transientRegistry->end(); ++iter) { |
| document().addMutationObserverTypes((*iter)->mutationTypes()); |
| } |
| } |
| } |
| |
| Vector<OwnPtr<MutationObserverRegistration> >* Node::mutationObserverRegistry() |
| { |
| if (!hasRareData()) |
| return 0; |
| NodeMutationObserverData* data = rareData()->mutationObserverData(); |
| if (!data) |
| return 0; |
| return &data->registry; |
| } |
| |
| HashSet<RawPtr<MutationObserverRegistration> >* Node::transientMutationObserverRegistry() |
| { |
| if (!hasRareData()) |
| return 0; |
| NodeMutationObserverData* data = rareData()->mutationObserverData(); |
| if (!data) |
| return 0; |
| return &data->transientRegistry; |
| } |
| |
| template<typename Registry> |
| static inline void collectMatchingObserversForMutation(HashMap<RawPtr<MutationObserver>, 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<RawPtr<MutationObserver>, MutationRecordDeliveryOptions>::AddResult result = observers.add(®istration.observer(), deliveryOptions); |
| if (!result.isNewEntry) |
| result.storedValue->value |= deliveryOptions; |
| } |
| } |
| } |
| |
| void Node::getRegisteredMutationObserversOfType(HashMap<RawPtr<MutationObserver>, 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<AtomicString>& attributeFilter) |
| { |
| MutationObserverRegistration* registration = 0; |
| Vector<OwnPtr<MutationObserverRegistration> >& 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<OwnPtr<MutationObserverRegistration> >* 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<Node> protect(this); |
| registry->remove(index); |
| } |
| |
| void Node::registerTransientMutationObserver(MutationObserverRegistration* registration) |
| { |
| ensureRareData().ensureMutationObserverData().transientRegistry.add(registration); |
| } |
| |
| void Node::unregisterTransientMutationObserver(MutationObserverRegistration* registration) |
| { |
| HashSet<RawPtr<MutationObserverRegistration> >* 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<OwnPtr<MutationObserverRegistration> >* registry = node->mutationObserverRegistry()) { |
| const size_t size = registry->size(); |
| for (size_t i = 0; i < size; ++i) |
| registry->at(i)->observedSubtreeNodeWillDetach(*this); |
| } |
| |
| if (HashSet<RawPtr<MutationObserverRegistration> >* transientRegistry = node->transientMutationObserverRegistry()) { |
| for (HashSet<RawPtr<MutationObserverRegistration> >::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 |