Bug 1330375 - P2: Reduce layout flushes in nsGenericHTMLElement::GetInnerText. r=emilio,heycam
authorDan Glastonbury <dan.glastonbury@gmail.com>
Thu, 19 Jul 2018 14:26:46 +1000
changeset 428078 28c07aae7d24a8b47126cdb7fae46fd8d6c4f993
parent 428077 b4ae7a07d29ac560f13d7ecb938baa97090524c3
child 428079 28200567c44f70b54380ed5e9a5953186809f3ab
push id66809
push userdglastonbury@mozilla.com
push dateTue, 24 Jul 2018 22:52:05 +0000
treeherderautoland@36a6d366b9c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio, heycam
bugs1330375
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1330375 - P2: Reduce layout flushes in nsGenericHTMLElement::GetInnerText. r=emilio,heycam Instead of unconditionally flushing layout, flush style and skip flush layout unless any frame state bits on the element, or ancestors, indicate that a flush is required. MozReview-Commit-ID: 4zaB1eaE0fm
dom/html/nsGenericHTMLElement.cpp
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2896,37 +2896,84 @@ nsGenericHTMLElement::NewURIFromString(c
     NS_RELEASE(*aURI);
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   return NS_OK;
 }
 
 static bool
-IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell)
+IsOrHasAncestorWithDisplayNone(Element* aElement)
 {
   return !aElement->HasServoData() || Servo_Element_IsDisplayNone(aElement);
 }
 
 void
 nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
                                    mozilla::ErrorResult& aError)
 {
-  if (!GetPrimaryFrame(FlushType::Layout)) {
-    nsIPresShell* presShell = nsContentUtils::GetPresShellForContent(this);
-    // NOTE(emilio): We need to check the presshell is initialized in order to
-    // ensure the document is styled.
-    if (!presShell || !presShell->DidInitialize() ||
-        IsOrHasAncestorWithDisplayNone(this, presShell)) {
-      GetTextContentInternal(aValue, aError);
-      return;
+  // innerText depends on layout. For example, white space processing is
+  // something that happens during reflow and which must be reflected by
+  // innerText.  So for:
+  //
+  //   <div style="white-space:normal"> A     B C </div>
+  //
+  // innerText should give "A B C".
+  //
+  // The approach taken here to avoid the expense of reflow is to flush style
+  // and then see whether it's necessary to flush layout afterwards. Flushing
+  // layout can be skipped if we can detect that the element or its descendants
+  // are not dirty.
+
+  // Obtain the composed doc to handle elements in Shadow DOM.
+  nsIDocument* doc = GetComposedDoc();
+  if (doc) {
+    doc->FlushPendingNotifications(FlushType::Style);
+  }
+
+  // Elements with `display: content` will not have a frame. To handle Shadow
+  // DOM, walk the flattened tree looking for parent frame.
+  nsIFrame* frame = GetPrimaryFrame();
+  if (IsDisplayContents()) {
+    for (Element* parent = GetFlattenedTreeParentElement();
+         parent;
+         parent = parent->GetFlattenedTreeParentElement())
+    {
+      frame = parent->GetPrimaryFrame();
+      if (frame) {
+        break;
+      }
     }
   }
 
-  nsRange::GetInnerTextNoFlush(aValue, aError, this);
+  // Check for dirty reflow roots in the subtree from targetFrame; this requires
+  // a reflow flush.
+  bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame);
+
+  // The way we do that is by checking whether the element has either of the two
+  // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
+  // ancestor has NS_FRAME_IS_DIRTY.  We need to check for NS_FRAME_IS_DIRTY on
+  // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
+  // descendants.
+  dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+  while (!dirty && frame) {
+    dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
+    frame = frame->GetInFlowParent();
+  }
+
+  // Flush layout if we determined a reflow is required.
+  if (dirty && doc) {
+    doc->FlushPendingNotifications(FlushType::Layout);
+  }
+
+  if (IsOrHasAncestorWithDisplayNone(this)) {
+    GetTextContentInternal(aValue, aError);
+  } else {
+    nsRange::GetInnerTextNoFlush(aValue, aError, this);
+  }
 }
 
 void
 nsGenericHTMLElement::SetInnerText(const nsAString& aValue)
 {
   // Batch possible DOMSubtreeModified events.
   mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
   FireNodeRemovedForChildren();