Title: [272703] trunk/Source
Revision
272703
Author
rn...@webkit.org
Date
2021-02-10 19:18:46 -0800 (Wed, 10 Feb 2021)

Log Message

Reduce the overhead of DocumentFragment in innerHTML & outerHTML
https://bugs.webkit.org/show_bug.cgi?id=221535
<rdar://problem/73861015>

Reviewed by Geoffrey Garen.

Source/WebCore:

This patch reduces the overhead of using DocumentFragment in innerHTMl and outerHTML setters by
1. Cache DocumentFragment used for innerHTML and outerHTML.
2. Adding a fast path to removeAllChildrenWithScriptAssertion when removing child nodes from (1)
immediately before appending it to the new parent. This is safe for now since no DOM nodes or API
store information about its root node or parent node when it's DocumentFragment, and and there
are no node flags to be updated or invalidated since we're removing already-disconnected nodes
to which no script ever had access up until this point. We release-assert these conditions before
going into the fast path.

No new tests since there should be no observable behavior change.

* dom/ContainerNode.cpp:
(WebCore::ContainerNode::removeAllChildrenWithScriptAssertion): Added a fast path. See above.
* dom/Document.cpp:
(WebCore::Document::commonTeardown): Clear m_documentFragmentForInnerOuterHTML as it would keep
the ownewr document (this Document) alive otherwise.
(WebCore::Document::documentFragmentForInnerOuterHTML): Added. Lazily create a DocumentFragment
used to parse the fragment for innerHTML and outerHTML setters. Remove any child nodes left in
the document fragment in the case the last call to replaceChildrenWithFragment took a fast path
for a single text node, which case we don't remove any child nodes from DocumentFragment.
* dom/Document.h:
* dom/DocumentFragment.h:
(WebCore::DocumentFragment::setIsDocumentFragmentForInnerOuterHTML): Added.
* dom/Node.h:
(WebCore::Node::isDocumentFragmentForInnerOuterHTML const): Added.
* editing/markup.cpp:
(WebCore::createFragmentForMarkup): Extracted from createFragmentForInnerOuterHTML to share code
between createFragmentForInnerOuterHTML and createContextualFragment.
(WebCore::createFragmentForInnerOuterHTML): Reuse the fragment in createFragmentForMarkup.
(WebCore::createContextualFragment): Don't reuse the fragment in createFragmentForMarkup as this
function is used by Range::createContextualFragment which exposes the document fragment to
arbitrary author scripts.
(WebCore::hasOneChild): Deleted since we now have Node::hasOneChild.
(WebCore::hasOneTextChild): Use Node::hasOneChild.
(WebCore::replaceChildrenWithFragment): Added assertions to make sure we don't have any child nodes
left after replacing the children.

Source/WTF:

Added a helper function for writing assertions.

* wtf/WeakPtr.h:
(WTF::WeakPtrFactory::isInitialized const): Added.

Modified Paths

Diff

Modified: trunk/Source/WTF/ChangeLog (272702 => 272703)


--- trunk/Source/WTF/ChangeLog	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WTF/ChangeLog	2021-02-11 03:18:46 UTC (rev 272703)
@@ -1,3 +1,16 @@
+2021-02-10  Ryosuke Niwa  <rn...@webkit.org>
+
+        Reduce the overhead of DocumentFragment in innerHTML & outerHTML
+        https://bugs.webkit.org/show_bug.cgi?id=221535
+        <rdar://problem/73861015>
+
+        Reviewed by Geoffrey Garen.
+
+        Added a helper function for writing assertions.
+
+        * wtf/WeakPtr.h:
+        (WTF::WeakPtrFactory::isInitialized const): Added.
+
 2021-02-10  Wenson Hsieh  <wenson_hs...@apple.com>
 
         Use HAVE(PEPPER_UI_CORE) instead of PLATFORM(WATCHOS) to guard code that uses PepperUICore

Modified: trunk/Source/WTF/wtf/WeakPtr.h (272702 => 272703)


--- trunk/Source/WTF/wtf/WeakPtr.h	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WTF/wtf/WeakPtr.h	2021-02-11 03:18:46 UTC (rev 272703)
@@ -176,6 +176,10 @@
         m_impl = nullptr;
     }
 
+#if ASSERT_ENABLED
+    bool isInitialized() const { return m_impl; }
+#endif
+
 private:
     template<typename, typename> friend class WeakHashSet;
 

Modified: trunk/Source/WebCore/ChangeLog (272702 => 272703)


--- trunk/Source/WebCore/ChangeLog	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/ChangeLog	2021-02-11 03:18:46 UTC (rev 272703)
@@ -1,3 +1,48 @@
+2021-02-10  Ryosuke Niwa  <rn...@webkit.org>
+
+        Reduce the overhead of DocumentFragment in innerHTML & outerHTML
+        https://bugs.webkit.org/show_bug.cgi?id=221535
+        <rdar://problem/73861015>
+
+        Reviewed by Geoffrey Garen.
+
+        This patch reduces the overhead of using DocumentFragment in innerHTMl and outerHTML setters by
+        1. Cache DocumentFragment used for innerHTML and outerHTML.
+        2. Adding a fast path to removeAllChildrenWithScriptAssertion when removing child nodes from (1)
+        immediately before appending it to the new parent. This is safe for now since no DOM nodes or API
+        store information about its root node or parent node when it's DocumentFragment, and and there
+        are no node flags to be updated or invalidated since we're removing already-disconnected nodes
+        to which no script ever had access up until this point. We release-assert these conditions before
+        going into the fast path.
+
+        No new tests since there should be no observable behavior change.
+
+        * dom/ContainerNode.cpp:
+        (WebCore::ContainerNode::removeAllChildrenWithScriptAssertion): Added a fast path. See above.
+        * dom/Document.cpp:
+        (WebCore::Document::commonTeardown): Clear m_documentFragmentForInnerOuterHTML as it would keep
+        the ownewr document (this Document) alive otherwise.
+        (WebCore::Document::documentFragmentForInnerOuterHTML): Added. Lazily create a DocumentFragment
+        used to parse the fragment for innerHTML and outerHTML setters. Remove any child nodes left in
+        the document fragment in the case the last call to replaceChildrenWithFragment took a fast path
+        for a single text node, which case we don't remove any child nodes from DocumentFragment.
+        * dom/Document.h:
+        * dom/DocumentFragment.h:
+        (WebCore::DocumentFragment::setIsDocumentFragmentForInnerOuterHTML): Added.
+        * dom/Node.h:
+        (WebCore::Node::isDocumentFragmentForInnerOuterHTML const): Added.
+        * editing/markup.cpp:
+        (WebCore::createFragmentForMarkup): Extracted from createFragmentForInnerOuterHTML to share code
+        between createFragmentForInnerOuterHTML and createContextualFragment.
+        (WebCore::createFragmentForInnerOuterHTML): Reuse the fragment in createFragmentForMarkup.
+        (WebCore::createContextualFragment): Don't reuse the fragment in createFragmentForMarkup as this
+        function is used by Range::createContextualFragment which exposes the document fragment to
+        arbitrary author scripts.
+        (WebCore::hasOneChild): Deleted since we now have Node::hasOneChild.
+        (WebCore::hasOneTextChild): Use Node::hasOneChild.
+        (WebCore::replaceChildrenWithFragment): Added assertions to make sure we don't have any child nodes
+        left after replacing the children.
+
 2021-02-10  Zalan Bujtas  <za...@apple.com>
 
         [LFC][IFC] Logical width of a line box is equal to the inner logical width of its containing block

Modified: trunk/Source/WebCore/dom/ContainerNode.cpp (272702 => 272703)


--- trunk/Source/WebCore/dom/ContainerNode.cpp	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/dom/ContainerNode.cpp	2021-02-11 03:18:46 UTC (rev 272703)
@@ -82,6 +82,16 @@
 {
     auto children = collectChildNodes(*this);
 
+    if (UNLIKELY(isDocumentFragmentForInnerOuterHTML())) {
+        ScriptDisallowedScope::InMainThread scriptDisallowedScope;
+        RELEASE_ASSERT(!connectedSubframeCount() && !hasRareData() && !wrapper());
+        ASSERT(!weakPtrFactory().isInitialized());
+        while (RefPtr<Node> child = m_firstChild)
+            removeBetween(nullptr, child->nextSibling(), *child);
+        document().incDOMTreeVersion();
+        return children;
+    }
+
     if (source == ChildChange::Source::API) {
         ChildListMutationScope mutation(*this);
         for (auto& child : children) {

Modified: trunk/Source/WebCore/dom/Document.cpp (272702 => 272703)


--- trunk/Source/WebCore/dom/Document.cpp	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/dom/Document.cpp	2021-02-11 03:18:46 UTC (rev 272703)
@@ -852,7 +852,9 @@
         accessSVGExtensions().pauseAnimations();
 
     clearScriptedAnimationController();
-    
+
+    m_documentFragmentForInnerOuterHTML = nullptr;
+
     if (m_highlightRegister)
         m_highlightRegister->clear();
 #if ENABLE(APP_HIGHLIGHTS)
@@ -7251,6 +7253,16 @@
     return *m_templateDocument;
 }
 
+Ref<DocumentFragment> Document::documentFragmentForInnerOuterHTML()
+{
+    if (UNLIKELY(!m_documentFragmentForInnerOuterHTML)) {
+        m_documentFragmentForInnerOuterHTML = DocumentFragment::create(*this);
+        m_documentFragmentForInnerOuterHTML->setIsDocumentFragmentForInnerOuterHTML();
+    } else if (UNLIKELY(m_documentFragmentForInnerOuterHTML->hasChildNodes()))
+        m_documentFragmentForInnerOuterHTML->removeChildren();
+    return *m_documentFragmentForInnerOuterHTML;
+}
+
 Ref<FontFaceSet> Document::fonts()
 {
     updateStyleIfNeeded();

Modified: trunk/Source/WebCore/dom/Document.h (272702 => 272703)


--- trunk/Source/WebCore/dom/Document.h	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/dom/Document.h	2021-02-11 03:18:46 UTC (rev 272703)
@@ -1326,6 +1326,8 @@
     void setTemplateDocumentHost(Document* templateDocumentHost) { m_templateDocumentHost = makeWeakPtr(templateDocumentHost); }
     Document* templateDocumentHost() { return m_templateDocumentHost.get(); }
 
+    Ref<DocumentFragment> documentFragmentForInnerOuterHTML();
+
     void didAssociateFormControl(Element&);
     bool hasDisabledFieldsetElement() const { return m_disabledFieldsetElementsCount; }
     void addDisabledFieldsetElement() { m_disabledFieldsetElementsCount++; }
@@ -1969,6 +1971,8 @@
     RefPtr<Document> m_templateDocument;
     WeakPtr<Document> m_templateDocumentHost; // Manually managed weakref (backpointer from m_templateDocument).
 
+    RefPtr<DocumentFragment> m_documentFragmentForInnerOuterHTML;
+
     Ref<CSSFontSelector> m_fontSelector;
 
     WeakHashSet<MediaProducer> m_audioProducers;

Modified: trunk/Source/WebCore/dom/DocumentFragment.h (272702 => 272703)


--- trunk/Source/WebCore/dom/DocumentFragment.h	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/dom/DocumentFragment.h	2021-02-11 03:18:46 UTC (rev 272703)
@@ -35,10 +35,12 @@
 
     void parseHTML(const String&, Element* contextElement, ParserContentPolicy = AllowScriptingContent);
     bool parseXML(const String&, Element* contextElement, ParserContentPolicy = AllowScriptingContent);
-    
+
     bool canContainRangeEndPoint() const final { return true; }
     virtual bool isTemplateContent() const { return false; }
 
+    void setIsDocumentFragmentForInnerOuterHTML() { setNodeFlag(NodeFlag::IsDocumentFragmentForInnerOuterHTML); }
+
     // From the NonElementParentNode interface - https://dom.spec.whatwg.org/#interface-nonelementparentnode
     WEBCORE_EXPORT Element* getElementById(const AtomString&) const;
 

Modified: trunk/Source/WebCore/dom/Node.h (272702 => 272703)


--- trunk/Source/WebCore/dom/Node.h	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/dom/Node.h	2021-02-11 03:18:46 UTC (rev 272703)
@@ -295,6 +295,8 @@
     bool childNeedsStyleRecalc() const { return hasStyleFlag(NodeStyleFlag::DescendantNeedsStyleResolution); }
     bool isEditingText() const { return hasNodeFlag(NodeFlag::IsEditingText); }
 
+    bool isDocumentFragmentForInnerOuterHTML() const { return hasNodeFlag(NodeFlag::IsDocumentFragmentForInnerOuterHTML); }
+
     void setChildNeedsStyleRecalc() { setStyleFlag(NodeStyleFlag::DescendantNeedsStyleResolution); }
     void clearChildNeedsStyleRecalc();
 
@@ -528,10 +530,10 @@
         IsInShadowTree = 1 << 11,
         HasEventTargetData = 1 << 12,
         // UnusedFlag = 1 << 13,
-        // UnusedFlag = 1 << 14,
 
         // These bits are used by derived classes, pulled up here so they can
         // be stored in the same memory word as the Node bits above.
+        IsDocumentFragmentForInnerOuterHTML = 1 << 14, // DocumentFragment
         IsEditingText = 1 << 15, // Text
         HasFocusWithin = 1 << 16, // Element
         IsLink = 1 << 17,

Modified: trunk/Source/WebCore/editing/markup.cpp (272702 => 272703)


--- trunk/Source/WebCore/editing/markup.cpp	2021-02-11 03:06:39 UTC (rev 272702)
+++ trunk/Source/WebCore/editing/markup.cpp	2021-02-11 03:18:46 UTC (rev 272703)
@@ -1244,13 +1244,12 @@
     return markup.toString();
 }
 
-ExceptionOr<Ref<DocumentFragment>> createFragmentForInnerOuterHTML(Element& contextElement, const String& markup, ParserContentPolicy parserContentPolicy)
+enum class DocumentFragmentMode { New, ReuseForInnerOuterHTML };
+static ALWAYS_INLINE ExceptionOr<Ref<DocumentFragment>> createFragmentForMarkup(Element& contextElement, const String& markup, DocumentFragmentMode mode, ParserContentPolicy parserContentPolicy)
 {
-    auto* document = &contextElement.document();
-    if (contextElement.hasTagName(templateTag))
-        document = &document->ensureTemplateDocument();
-    auto fragment = DocumentFragment::create(*document);
-
+    auto document = makeRef(contextElement.hasTagName(templateTag) ? contextElement.document().ensureTemplateDocument() : contextElement.document());
+    auto fragment = mode == DocumentFragmentMode::New ? DocumentFragment::create(document.get()) : document->documentFragmentForInnerOuterHTML();
+    ASSERT(!fragment->hasChildNodes());
     if (document->isHTMLDocument()) {
         fragment->parseHTML(markup, &contextElement, parserContentPolicy);
         return fragment;
@@ -1262,6 +1261,11 @@
     return fragment;
 }
 
+ExceptionOr<Ref<DocumentFragment>> createFragmentForInnerOuterHTML(Element& contextElement, const String& markup, ParserContentPolicy parserContentPolicy)
+{
+    return createFragmentForMarkup(contextElement, markup, DocumentFragmentMode::ReuseForInnerOuterHTML, parserContentPolicy);
+}
+
 RefPtr<DocumentFragment> createFragmentForTransformToFragment(Document& outputDoc, const String& sourceString, const String& sourceMIMEType)
 {
     RefPtr<DocumentFragment> fragment = outputDoc.createDocumentFragment();
@@ -1328,7 +1332,7 @@
 
 ExceptionOr<Ref<DocumentFragment>> createContextualFragment(Element& element, const String& markup, ParserContentPolicy parserContentPolicy)
 {
-    auto result = createFragmentForInnerOuterHTML(element, markup, parserContentPolicy);
+    auto result = createFragmentForMarkup(element, markup, DocumentFragmentMode::New, parserContentPolicy);
     if (result.hasException())
         return result.releaseException();
 
@@ -1344,15 +1348,9 @@
     return fragment;
 }
 
-static inline bool hasOneChild(ContainerNode& node)
-{
-    Node* firstChild = node.firstChild();
-    return firstChild && !firstChild->nextSibling();
-}
-
 static inline bool hasOneTextChild(ContainerNode& node)
 {
-    return hasOneChild(node) && node.firstChild()->isTextNode();
+    return node.hasOneChild() && node.firstChild()->isTextNode();
 }
 
 static inline bool hasMutationEventListeners(const Document& document)
@@ -1393,7 +1391,10 @@
     }
 
     containerNode->removeChildren();
-    return containerNode->appendChild(fragment);
+    auto result = containerNode->appendChild(fragment);
+    ASSERT(!fragment->hasChildNodes());
+    ASSERT(!fragment->wrapper());
+    return result;
 }
 
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to