Bug 1330236 - Compute SVG getNumberOfChars() quicker in simple cases. r=longsonr draft
authorCameron McCormack <cam@mcc.id.au>
Tue, 17 Jan 2017 13:45:43 +0800
changeset 462363 c0cc3f35090524d006ed5170111988ec23978bf0
parent 460619 bb6cabf1f0417c8076056a52991217522c4cbf01
child 542361 faa77ba985d321ada5beb304b662e757637ab00f
push id41717
push userbmo:cam@mcc.id.au
push dateTue, 17 Jan 2017 05:45:57 +0000
reviewerslongsonr
bugs1330236
milestone53.0a1
Bug 1330236 - Compute SVG getNumberOfChars() quicker in simple cases. r=longsonr MozReview-Commit-ID: FgwR9dxTefx
dom/svg/SVGTextContentElement.cpp
dom/svg/SVGTextContentElement.h
layout/generic/nsTextFrameUtils.cpp
layout/generic/nsTextFrameUtils.h
--- a/dom/svg/SVGTextContentElement.cpp
+++ b/dom/svg/SVGTextContentElement.cpp
@@ -1,18 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/SVGTextContentElement.h"
+
+#include "mozilla/dom/SVGIRect.h"
+#include "nsBidiUtils.h"
 #include "nsISVGPoint.h"
+#include "nsTextFragment.h"
+#include "nsTextFrameUtils.h"
+#include "nsTextNode.h"
 #include "SVGTextFrame.h"
-#include "mozilla/dom/SVGIRect.h"
 
 namespace mozilla {
 namespace dom {
 
 nsSVGEnumMapping SVGTextContentElement::sLengthAdjustMap[] = {
   { &nsGkAtoms::spacing, SVG_LENGTHADJUST_SPACING },
   { &nsGkAtoms::spacingAndGlyphs, SVG_LENGTHADJUST_SPACINGANDGLYPHS },
   { nullptr, 0 }
@@ -27,43 +32,100 @@ nsSVGElement::LengthInfo SVGTextContentE
 {
   { &nsGkAtoms::textLength, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::XY }
 };
 
 SVGTextFrame*
 SVGTextContentElement::GetSVGTextFrame()
 {
   nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
-  while (frame) {
-    SVGTextFrame* textFrame = do_QueryFrame(frame);
-    if (textFrame) {
-      return textFrame;
-    }
-    frame = frame->GetParent();
-  }
-  return nullptr;
+  nsIFrame* textFrame =
+    nsLayoutUtils::GetClosestFrameOfType(frame, nsGkAtoms::svgTextFrame);
+  return static_cast<SVGTextFrame*>(textFrame);
+}
+
+SVGTextFrame*
+SVGTextContentElement::GetSVGTextFrameForNonLayoutDependentQuery()
+{
+  nsIFrame* frame = GetPrimaryFrame(FlushType::Frames);
+  nsIFrame* textFrame =
+    nsLayoutUtils::GetClosestFrameOfType(frame, nsGkAtoms::svgTextFrame);
+  return static_cast<SVGTextFrame*>(textFrame);
 }
 
 already_AddRefed<SVGAnimatedLength>
 SVGTextContentElement::TextLength()
 {
   return LengthAttributes()[TEXTLENGTH].ToDOMAnimatedLength(this);
 }
 
 already_AddRefed<SVGAnimatedEnumeration>
 SVGTextContentElement::LengthAdjust()
 {
   return EnumAttributes()[LENGTHADJUST].ToDOMAnimatedEnum(this);
 }
 
 //----------------------------------------------------------------------
 
+template<typename T>
+static bool
+FragmentHasSkippableCharacter(const T* aBuffer, uint32_t aLength)
+{
+  for (uint32_t i = 0; i < aLength; i++) {
+    if (nsTextFrameUtils::IsSkippableCharacterForTransformText(aBuffer[i])) {
+      return true;
+    }
+  }
+  return false;
+}
+
+Maybe<int32_t>
+SVGTextContentElement::GetNonLayoutDependentNumberOfChars()
+{
+  SVGTextFrame* frame = GetSVGTextFrameForNonLayoutDependentQuery();
+  if (!frame || frame != GetPrimaryFrame()) {
+    // Only support this fast path on <text>, not child <tspan>s, etc.
+    return Some(0);
+  }
+
+  uint32_t num = 0;
+
+  for (nsINode* n = Element::GetFirstChild(); n; n = n->GetNextSibling()) {
+    if (!n->IsNodeOfType(nsINode::eTEXT)) {
+      return Nothing();
+    }
+
+    const nsTextFragment* text = static_cast<nsTextNode*>(n)->GetText();
+    uint32_t length = text->GetLength();
+
+    if (text->Is2b()) {
+      if (FragmentHasSkippableCharacter(text->Get2b(), length)) {
+        return Nothing();
+      }
+    } else {
+      auto buffer = reinterpret_cast<const uint8_t*>(text->Get1b());
+      if (FragmentHasSkippableCharacter(buffer, length)) {
+        return Nothing();
+      }
+    }
+
+    num += length;
+  }
+
+  return Some(num);
+}
+
 int32_t
 SVGTextContentElement::GetNumberOfChars()
 {
+  Maybe<int32_t> num = GetNonLayoutDependentNumberOfChars();
+  if (num) {
+    return *num;
+  }
+
   SVGTextFrame* textFrame = GetSVGTextFrame();
   return textFrame ? textFrame->GetNumberOfChars(this) : 0;
 }
 
 float
 SVGTextContentElement::GetComputedTextLength()
 {
   SVGTextFrame* textFrame = GetSVGTextFrame();
--- a/dom/svg/SVGTextContentElement.h
+++ b/dom/svg/SVGTextContentElement.h
@@ -47,16 +47,18 @@ public:
 
 protected:
 
   explicit SVGTextContentElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
     : SVGTextContentElementBase(aNodeInfo)
   {}
 
   SVGTextFrame* GetSVGTextFrame();
+  SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery();
+  mozilla::Maybe<int32_t> GetNonLayoutDependentNumberOfChars();
 
   enum { LENGTHADJUST };
   virtual nsSVGEnum* EnumAttributes() = 0;
   static nsSVGEnumMapping sLengthAdjustMap[];
   static EnumInfo sEnumInfo[1];
 
   enum { TEXTLENGTH };
   virtual nsSVGLength2* LengthAttributes() = 0;
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -51,16 +51,46 @@ IsSpaceOrTab(char16_t aCh)
 }
 
 static bool
 IsSpaceOrTabOrSegmentBreak(char16_t aCh)
 {
   return IsSpaceOrTab(aCh) || IsSegmentBreak(aCh);
 }
 
+template<typename CharT>
+/* static */ bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(CharT aChar)
+{
+  return aChar == ' ' ||
+         aChar == '\t' ||
+         aChar == '\n' ||
+         aChar == CH_SHY ||
+         (aChar > 0xFF && IsBidiControl(aChar));
+}
+
+#ifdef DEBUG
+template<typename CharT>
+static void AssertSkippedExpectedChars(const CharT* aText,
+                                       const gfxSkipChars& aSkipChars,
+                                       int32_t aSkipCharsOffset)
+{
+  gfxSkipCharsIterator it(aSkipChars);
+  it.AdvanceOriginal(aSkipCharsOffset);
+  while (it.GetOriginalOffset() < it.GetOriginalEnd()) {
+    CharT ch = aText[it.GetOriginalOffset() - aSkipCharsOffset];
+    MOZ_ASSERT(!it.IsOriginalCharSkipped() ||
+               nsTextFrameUtils::IsSkippableCharacterForTransformText(ch),
+               "skipped unexpected character; need to update "
+               "IsSkippableCharacterForTransformText?");
+    it.AdvanceOriginal(1);
+  }
+}
+#endif
+
 template<class CharT>
 static CharT*
 TransformWhiteSpaces(const CharT* aText, uint32_t aLength,
                      uint32_t aBegin, uint32_t aEnd,
                      bool aHasSegmentBreak,
                      bool& aInWhitespace,
                      CharT* aOutput,
                      uint32_t& aFlags,
@@ -178,16 +208,19 @@ nsTextFrameUtils::TransformText(const Ch
                                 CharT* aOutput,
                                 CompressionMode aCompression,
                                 uint8_t* aIncomingFlags,
                                 gfxSkipChars* aSkipChars,
                                 uint32_t* aAnalysisFlags)
 {
   uint32_t flags = 0;
   CharT* outputStart = aOutput;
+#ifdef DEBUG
+  int32_t skipCharsOffset = aSkipChars->GetOriginalCharCount();
+#endif
 
   bool lastCharArabic = false;
   if (aCompression == COMPRESS_NONE ||
       aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) {
     // Skip discardables.
     uint32_t i;
     for (i = 0; i < aLength; ++i) {
       CharT ch = aText[i];
@@ -298,22 +331,28 @@ nsTextFrameUtils::TransformText(const Ch
       *aIncomingFlags &= ~INCOMING_WHITESPACE;
     }
   }
 
   if (outputStart + aLength != aOutput) {
     flags |= TEXT_WAS_TRANSFORMED;
   }
   *aAnalysisFlags = flags;
+
+#ifdef DEBUG
+  AssertSkippedExpectedChars(aText, *aSkipChars, skipCharsOffset);
+#endif
   return aOutput;
 }
+
 /*
- * NOTE: This template is part of public API of nsTextFrameUtils, while
- * its function body is not available in the header. It may stop working
- * (fail to resolve symbol in link time) once its callsites are moved to a
+ * NOTE: The TransformText and IsSkippableCharacterForTransformText template
+ * functions are part of the public API of nsTextFrameUtils, while
+ * their function bodies are not available in the header. They may stop working
+ * (fail to resolve symbol in link time) once their callsites are moved to a
  * different translation unit (e.g. a different unified source file).
  * Explicit instantiating this function template with `uint8_t` and `char16_t`
  * could prevent us from the potential risk.
  */
 template uint8_t*
 nsTextFrameUtils::TransformText(const uint8_t* aText, uint32_t aLength,
                                 uint8_t* aOutput,
                                 CompressionMode aCompression,
@@ -322,16 +361,20 @@ nsTextFrameUtils::TransformText(const ui
                                 uint32_t* aAnalysisFlags);
 template char16_t*
 nsTextFrameUtils::TransformText(const char16_t* aText, uint32_t aLength,
                                 char16_t* aOutput,
                                 CompressionMode aCompression,
                                 uint8_t* aIncomingFlags,
                                 gfxSkipChars* aSkipChars,
                                 uint32_t* aAnalysisFlags);
+template bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(uint8_t aChar);
+template bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(char16_t aChar);
 
 uint32_t
 nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
                     nsIContent *aContent, const nsStyleText *aStyleText)
 {
   const nsTextFragment *frag = aContent->GetText();
   // This is an approximation so we don't really need anything
   // too fancy here.
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -116,16 +116,26 @@ public:
   template<class CharT>
   static CharT* TransformText(const CharT* aText, uint32_t aLength,
                               CharT* aOutput,
                               CompressionMode aCompression,
                               uint8_t* aIncomingFlags,
                               gfxSkipChars* aSkipChars,
                               uint32_t* aAnalysisFlags);
 
+  /**
+   * Returns whether aChar is a character that nsTextFrameUtils::TransformText
+   * might mark as skipped.  This is used by
+   * SVGTextContentElement::GetNumberOfChars to know whether reflowing frames,
+   * so that we have the results of TransformText, is required, or whether we
+   * can use a fast path instead.
+   */
+  template<class CharT>
+  static bool IsSkippableCharacterForTransformText(CharT aChar);
+
   static void
   AppendLineBreakOffset(nsTArray<uint32_t>* aArray, uint32_t aOffset)
   {
     if (aArray->Length() > 0 && (*aArray)[aArray->Length() - 1] == aOffset)
       return;
     aArray->AppendElement(aOffset);
   }