Bug 995321 - nsIDocumentEncoder.encodeToString should offer a way to limit the size of the output, r=smaug.
authorFlorian Quèze <florian@queze.net>
Thu, 17 Apr 2014 00:01:55 +0200
changeset 198523 8c982eaeef86a3c50a3289a8fd3a599b85e3f765
parent 198486 b3ea0d746ee49ac5aa7d6839db33951a101c128e
child 198524 7b4cfd722bcd350a9045863a254d8dcd141b7f6f
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs995321
milestone31.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 995321 - nsIDocumentEncoder.encodeToString should offer a way to limit the size of the output, r=smaug.
content/base/public/nsIDocumentEncoder.idl
content/base/src/nsDocumentEncoder.cpp
content/base/src/nsPlainTextSerializer.cpp
content/base/src/nsXMLContentSerializer.cpp
content/base/test/mochitest.ini
content/base/test/test_encodeToStringWithMaxLength.html
--- a/content/base/public/nsIDocumentEncoder.idl
+++ b/content/base/public/nsIDocumentEncoder.idl
@@ -30,17 +30,17 @@ interface nsIDocumentEncoderNodeFixup : 
    * @param [OUT] aSerializeCloneKids True if the document encoder should
    * apply recursive serialization to the children of the fixed up node
    * instead of the children of the original node.
    * @return The resulting fixed up node.
    */
   nsIDOMNode fixupNode(in nsIDOMNode aNode, out boolean aSerializeCloneKids);
 };
 
-[scriptable, uuid(7222bdf1-c2b9-41f1-a40a-a3d65283a95b)]
+[scriptable, uuid(1158bd7e-a08b-4ff6-9417-6f99144cfccc)]
 interface nsIDocumentEncoder : nsISupports
 {
   // Output methods flag bits. There are a frightening number of these,
   // because everyone wants something a little bit different
    
 
   /** 
    * Output only the selection (as opposed to the whole document).
@@ -325,13 +325,28 @@ interface nsIDocumentEncoder : nsISuppor
    *              be stored.
    * @return The document encoded as a string.
    * 
    */
   AString encodeToStringWithContext( out AString aContextString,
                                      out AString aInfoString);
 
   /**
+   * Encode the document into a string of limited size.
+   * @param aMaxLength After aMaxLength characters, the encoder will stop
+   *                   encoding new data.
+   *                   Only values > 0 will be considered.
+   *                   The returned string may be slightly larger than
+   *                   aMaxLength because some serializers (eg. HTML)
+   *                   may need to close some tags after they stop
+   *                   encoding new data, or finish a line (72 columns
+   *                   by default for the plain text serializer).
+   *
+   * @return The document encoded into a string.
+   */
+  AString encodeToStringWithMaxLength(in unsigned long aMaxLength);
+
+  /**
    * Set the fixup object associated with node persistence.
    * @param aFixup The fixup object.
    */
   void setNodeFixup(in nsIDocumentEncoderNodeFixup aFixup);
 };
--- a/content/base/src/nsDocumentEncoder.cpp
+++ b/content/base/src/nsDocumentEncoder.cpp
@@ -76,17 +76,18 @@ public:
 
 protected:
   void Initialize(bool aClearCachedSerializer = true);
   nsresult SerializeNodeStart(nsINode* aNode, int32_t aStartOffset,
                               int32_t aEndOffset, nsAString& aStr,
                               nsINode* aOriginalNode = nullptr);
   nsresult SerializeToStringRecursive(nsINode* aNode,
                                       nsAString& aStr,
-                                      bool aDontSerializeRoot);
+                                      bool aDontSerializeRoot,
+                                      uint32_t aMaxLength = 0);
   nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr);
   // This serializes the content of aNode.
   nsresult SerializeToStringIterative(nsINode* aNode,
                                       nsAString& aStr);
   nsresult SerializeRangeToString(nsRange *aRange,
                                   nsAString& aOutputString);
   nsresult SerializeRangeNodes(nsRange* aRange, 
                                nsINode* aNode, 
@@ -445,18 +446,23 @@ nsDocumentEncoder::SerializeNodeEnd(nsIN
     mSerializer->AppendElementEnd(aNode->AsElement(), aStr);
   }
   return NS_OK;
 }
 
 nsresult
 nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
                                               nsAString& aStr,
-                                              bool aDontSerializeRoot)
+                                              bool aDontSerializeRoot,
+                                              uint32_t aMaxLength)
 {
+  if (aMaxLength > 0 && aStr.Length() >= aMaxLength) {
+    return NS_OK;
+  }
+
   if (!IsVisibleNode(aNode))
     return NS_OK;
 
   nsresult rv = NS_OK;
   bool serializeClonedChildren = false;
   nsINode* maybeFixedNode = nullptr;
 
   // Keep the node from FixupNode alive.
@@ -482,26 +488,31 @@ nsDocumentEncoder::SerializeToStringRecu
         if (!isSelectable){
           aDontSerializeRoot = true;
         }
       }
     }
   }
 
   if (!aDontSerializeRoot) {
-    rv = SerializeNodeStart(maybeFixedNode, 0, -1, aStr, aNode);
+    int32_t endOffset = -1;
+    if (aMaxLength > 0) {
+      MOZ_ASSERT(aMaxLength >= aStr.Length());
+      endOffset = aMaxLength - aStr.Length();
+    }
+    rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsINode* node = serializeClonedChildren ? maybeFixedNode : aNode;
 
   for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node);
        child;
        child = child->GetNextSibling()) {
-    rv = SerializeToStringRecursive(child, aStr, false);
+    rv = SerializeToStringRecursive(child, aStr, false, aMaxLength);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (!aDontSerializeRoot) {
     rv = SerializeNodeEnd(node, aStr);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
@@ -1012,16 +1023,23 @@ nsDocumentEncoder::SerializeRangeToStrin
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
 {
+  return EncodeToStringWithMaxLength(0, aOutputString);
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
+                                               nsAString& aOutputString)
+{
   if (!mDocument)
     return NS_ERROR_NOT_INITIALIZED;
 
   aOutputString.Truncate();
 
   nsString output;
   static const size_t bufferSize = 2048;
   if (!mCachedBuffer) {
@@ -1141,17 +1159,17 @@ nsDocumentEncoder::EncodeToString(nsAStr
     } else {
       rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer);
     }
     mNode = nullptr;
   } else {
     rv = mSerializer->AppendDocumentStart(mDocument, output);
 
     if (NS_SUCCEEDED(rv)) {
-      rv = SerializeToStringRecursive(mDocument, output, false);
+      rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength);
     }
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mSerializer->Flush(output);
  
   mCachedBuffer = nsStringBuffer::FromString(output);
   // We have to be careful how we set aOutputString, because we don't
--- a/content/base/src/nsPlainTextSerializer.cpp
+++ b/content/base/src/nsPlainTextSerializer.cpp
@@ -273,17 +273,18 @@ nsPlainTextSerializer::AppendText(nsICon
   nsresult rv = NS_OK;
 
   nsIContent* content = aText;
   const nsTextFragment* frag;
   if (!content || !(frag = content->GetText())) {
     return NS_ERROR_FAILURE;
   }
   
-  int32_t endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
+  int32_t fragLength = frag->GetLength();
+  int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
   NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
 
   int32_t length = endoffset - aStartOffset;
   if (length <= 0) {
     return NS_OK;
   }
 
   nsAutoString textstr;
--- a/content/base/src/nsXMLContentSerializer.cpp
+++ b/content/base/src/nsXMLContentSerializer.cpp
@@ -133,17 +133,18 @@ nsXMLContentSerializer::AppendTextData(n
                                        bool aTranslateEntities)
 {
   nsIContent* content = aNode;
   const nsTextFragment* frag;
   if (!content || !(frag = content->GetText())) {
     return NS_ERROR_FAILURE;
   }
 
-  int32_t endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
+  int32_t fragLength = frag->GetLength();
+  int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
   int32_t length = endoffset - aStartOffset;
 
   NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
   NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
 
   if (length <= 0) {
     // XXX Zero is a legal value, maybe non-zero values should be an
     // error.
@@ -295,22 +296,26 @@ nsXMLContentSerializer::AppendComment(ns
   nsCOMPtr<nsIDOMComment> comment = do_QueryInterface(aComment);
   NS_ENSURE_ARG(comment);
   nsresult rv;
   nsAutoString data;
 
   rv = comment->GetData(data);
   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
 
-  if (aStartOffset || (aEndOffset != -1)) {
-    int32_t length = (aEndOffset == -1) ? data.Length() : aEndOffset;
+  int32_t dataLength = data.Length();
+  if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
+    int32_t length =
+      (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
     length -= aStartOffset;
 
     nsAutoString frag;
-    data.Mid(frag, aStartOffset, length);
+    if (length > 0) {
+      data.Mid(frag, aStartOffset, length);
+    }
     data.Assign(frag);
   }
 
   MaybeAddNewlineForRootNode(aStr);
 
   NS_NAMED_LITERAL_STRING(startComment, "<!--");
 
   if (mPreLevel > 0 || mDoRaw) {
--- a/content/base/test/mochitest.ini
+++ b/content/base/test/mochitest.ini
@@ -554,16 +554,17 @@ skip-if = (buildapp == 'b2g' && toolkit 
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 904183 # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_copypaste.xhtml]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 904183 # b2g(bug 904183) b2g-debug(bug 904183) b2g-desktop(bug 904183)
 [test_createHTMLDocument.html]
 [test_declare_stylesheet_obsolete.html]
 [test_domparser_null_char.html]
 [test_domparsing.html]
 [test_elementTraversal.html]
+[test_encodeToStringWithMaxLength.html]
 [test_fileapi.html]
 skip-if = e10s
 [test_fileapi_slice.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 775227
 [test_getElementById.html]
 [test_html_colors_quirks.html]
 [test_html_colors_standards.html]
 [test_html_in_xhr.html]
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_encodeToStringWithMaxLength.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=995321
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 995321 - encodeToStringWithMaxLength</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+  function getEncoder() {
+    const de = SpecialPowers.Ci.nsIDocumentEncoder;
+    const Cc = SpecialPowers.Cc;
+
+    // Create a plaintext encoder without flags.
+    var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+                  .createInstance(de);
+    encoder.init(document, "text/plain", 0);
+    return encoder;
+  }
+
+  function testPlaintextSerializerWithMaxLength() {
+    var string = getEncoder().encodeToString();
+
+    var shorterString = getEncoder().encodeToStringWithMaxLength(1);
+    ok(shorterString.length < 1 + 72,
+       "test length is in the expected range after limiting the length to 1");
+    ok(string.startsWith(shorterString.trimRight()),
+       "the shorter string has the expected content");
+
+    shorterString = getEncoder().encodeToStringWithMaxLength(300);
+    ok(shorterString.length < 300 + 72,
+       "test length is in the expected range after limiting the length to 300");
+    ok(string.startsWith(shorterString.trimRight()),
+       "the shorter string has the expected content");
+
+    is(getEncoder().encodeToStringWithMaxLength(0), string,
+       "limiting the length to 0 should be ignored");
+
+    is(getEncoder().encodeToStringWithMaxLength(10000), string,
+       "limiting the length to a huge value should return the whole page");
+
+    SimpleTest.finish();
+  }
+
+  addLoadEvent(testPlaintextSerializerWithMaxLength);
+  SimpleTest.waitForExplicitFinish();
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=995321">Mozilla Bug 995321</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+The <em>Mozilla</em> project is a global community of <strong>people</strong> who believe that openness, innovation, and opportunity are key to the continued health of the Internet. We have worked together since 1998 to ensure that the Internet is developed in a way that benefits everyone. We are best known for creating the Mozilla Firefox web browser.
+
+The Mozilla project uses a community-based approach to create world-class open source software and to develop new types of collaborative activities. We create communities of people involved in making the Internet experience better for all of us.
+
+As a result of these efforts, we have distilled a set of principles that we believe are critical for the Internet to continue to benefit the public good as well as commercial aspects of life. We set out these principles below.
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>