- 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;
}
}