Bug 543062 - When document.write() blocks, pre-parse the tail of the buffer for speculative loads. r=jonas, a=blocking2.0-beta8.
authorHenri Sivonen <hsivonen@iki.fi>
Thu, 18 Nov 2010 10:23:48 +0200
changeset 57849 fe9637495f974593b4c6e706f1ddb22b57c021f6
parent 57848 a74526663273be7605ba513fa1ca6a81e75ac7dc
child 57850 d6d9cb57b170c100ea9ed8b54629d936b6a1052c
push id17040
push userhsivonen@iki.fi
push dateThu, 18 Nov 2010 08:43:19 +0000
treeherdermozilla-central@d6d9cb57b170 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonas, blocking2.0-beta8
bugs543062
milestone2.0b8pre
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 543062 - When document.write() blocks, pre-parse the tail of the buffer for speculative loads. r=jonas, a=blocking2.0-beta8.
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5Parser.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeOpStage.h
parser/htmlparser/tests/mochitest/Makefile.in
parser/htmlparser/tests/mochitest/file_bug543062.sjs
parser/htmlparser/tests/mochitest/test_bug543062.html
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -95,16 +95,19 @@ nsHtml5Parser::nsHtml5Parser()
   mAtomTable.Init(); // we aren't checking for OOM anyway...
   mTokenizer->setInterner(&mAtomTable);
   // There's a zeroing operator new for everything else
 }
 
 nsHtml5Parser::~nsHtml5Parser()
 {
   mTokenizer->end();
+  if (mDocWriteSpeculativeTokenizer) {
+    mDocWriteSpeculativeTokenizer->end();
+  }
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::SetContentSink(nsIContentSink* aSink)
 {
   NS_ASSERTION(aSink == static_cast<nsIContentSink*> (mExecutor), 
                "Attempt to set a foreign sink.");
 }
@@ -373,20 +376,59 @@ nsHtml5Parser::Parse(const nsAString& aS
         mTreeBuilder->Flush(); // Move ops to the executor
         mExecutor->FlushDocumentWrite(); // run the ops
       }
       // Ignore suspension requests
     }
   }
 
   if (!mBlocked) { // buffer was tokenized to completion
-    NS_ASSERTION(!buffer->hasMore(), "Buffer wasn't tokenized to completion?");  
+    NS_ASSERTION(!buffer->hasMore(), "Buffer wasn't tokenized to completion?");
     // Scripting semantics require a forced tree builder flush here
     mTreeBuilder->Flush(); // Move ops to the executor
-    mExecutor->FlushDocumentWrite(); // run the ops    
+    mExecutor->FlushDocumentWrite(); // run the ops
+  } else if (buffer->hasMore()) {
+    // The buffer wasn't tokenized to completion. Tokenize the untokenized
+    // content in order to preload stuff. This content will be retokenized
+    // later for normal parsing.
+    if (!mDocWriteSpeculatorActive) {
+      mDocWriteSpeculatorActive = PR_TRUE;
+      if (!mDocWriteSpeculativeTreeBuilder) {
+        // Lazily initialize if uninitialized
+        mDocWriteSpeculativeTreeBuilder =
+            new nsHtml5TreeBuilder(nsnull, mExecutor->GetStage());
+        mDocWriteSpeculativeTokenizer =
+            new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder);
+        mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable);
+        mDocWriteSpeculativeTokenizer->start();
+      }
+      mDocWriteSpeculativeTokenizer->resetToDataState();
+      mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable);
+      mDocWriteSpeculativeLastWasCR = PR_FALSE;
+    }
+
+    // Note that with multilevel document.write if we didn't just activate the
+    // speculator, it's possible that the speculator is now in the wrong state.
+    // That's OK for the sake of simplicity. The worst that can happen is
+    // that the speculative loads aren't exactly right. The content will be
+    // reparsed anyway for non-preload purposes.
+
+    PRInt32 originalStart = buffer->getStart();
+    while (buffer->hasMore()) {
+      buffer->adjust(mDocWriteSpeculativeLastWasCR);
+      if (buffer->hasMore()) {
+        mDocWriteSpeculativeLastWasCR =
+            mDocWriteSpeculativeTokenizer->tokenizeBuffer(buffer);
+      }
+    }
+    buffer->setStart(originalStart);
+
+    mDocWriteSpeculativeTreeBuilder->Flush();
+    mDocWriteSpeculativeTreeBuilder->DropHandles();
+    mExecutor->FlushSpeculativeLoads();
   }
 
   return NS_OK;
 }
 
 /**
  * This magic value is passed to the previous method on document.close()
  */
@@ -515,16 +557,18 @@ nsHtml5Parser::CancelParsingEvents()
 {
   NS_NOTREACHED("Don't call this!");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void
 nsHtml5Parser::Reset()
 {
+  NS_PRECONDITION(mExecutor->IsFragmentMode(),
+                  "Reset called on a non-fragment parser.");
   mExecutor->Reset();
   mLastWasCR = PR_FALSE;
   UnblockParser();
   mDocumentClosed = PR_FALSE;
   mStreamParser = nsnull;
   mRootContextLineNumber = 1;
   mParserInsertedScriptsBeingEvaluated = 0;
   mRootContextKey = nsnull;
@@ -589,16 +633,18 @@ nsHtml5Parser::ParseUntilBlocked()
     return;
   }
 
   if (mExecutor->IsComplete()) {
     return;
   }
   NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
 
+  mDocWriteSpeculatorActive = PR_FALSE;
+
   for (;;) {
     if (!mFirstBuffer->hasMore()) {
       if (mFirstBuffer == mLastBuffer) {
         if (mExecutor->IsComplete()) {
           // something like cache manisfests stopped the parse in mid-flight
           return;
         }
         if (mDocumentClosed) {
--- a/parser/html/nsHtml5Parser.h
+++ b/parser/html/nsHtml5Parser.h
@@ -339,24 +339,35 @@ class nsHtml5Parser : public nsAHtml5Fra
     // State variables
 
     /**
      * Whether the last character tokenized was a carriage return (for CRLF)
      */
     PRBool                        mLastWasCR;
 
     /**
+     * Whether the last character tokenized was a carriage return (for CRLF)
+     * when preparsing document.write.
+     */
+    PRBool                        mDocWriteSpeculativeLastWasCR;
+
+    /**
      * The parser is in the fragment mode
      */
     PRBool                        mFragmentMode;
 
     /**
      * The parser is blocking on a script
      */
     PRBool                        mBlocked;
+
+    /**
+     * Whether the document.write() speculator is already active.
+     */
+    PRBool                        mDocWriteSpeculatorActive;
     
     /**
      * The number of parser-inserted script currently being evaluated.
      */
     PRInt32                       mParserInsertedScriptsBeingEvaluated;
 
     /**
      * True if document.close() has been called.
@@ -389,16 +400,26 @@ class nsHtml5Parser : public nsAHtml5Fra
     const nsAutoPtr<nsHtml5TreeBuilder> mTreeBuilder;
 
     /**
      * The HTML5 tokenizer
      */
     const nsAutoPtr<nsHtml5Tokenizer>   mTokenizer;
 
     /**
+     * Another HTML5 tree builder for preloading document.written content.
+     */
+    nsAutoPtr<nsHtml5TreeBuilder> mDocWriteSpeculativeTreeBuilder;
+
+    /**
+     * Another HTML5 tokenizer for preloading document.written content.
+     */
+    nsAutoPtr<nsHtml5Tokenizer>   mDocWriteSpeculativeTokenizer;
+
+    /**
      * The stream parser.
      */
     nsRefPtr<nsHtml5StreamParser>       mStreamParser;
 
     /**
      *
      */
     PRInt32                             mRootContextLineNumber;
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -599,21 +599,26 @@ nsHtml5TreeBuilder::HasScript()
   return mOpQueue.ElementAt(len - 1).IsRunScript();
 }
 
 PRBool
 nsHtml5TreeBuilder::Flush()
 {
   flushCharacters();
   FlushLoads();
-  PRBool hasOps = !mOpQueue.IsEmpty();
-  if (hasOps) {
-    mOpSink->MoveOpsFrom(mOpQueue);
+  if (mOpSink) {
+    PRBool hasOps = !mOpQueue.IsEmpty();
+    if (hasOps) {
+      mOpSink->MoveOpsFrom(mOpQueue);
+    }
+    return hasOps;
   }
-  return hasOps;
+  // no op sink: throw away ops
+  mOpQueue.Clear();
+  return PR_FALSE;
 }
 
 void
 nsHtml5TreeBuilder::FlushLoads()
 {
   if (!mSpeculativeLoadQueue.IsEmpty()) {
     mSpeculativeLoadStage->MoveSpeculativeLoadsFrom(mSpeculativeLoadQueue);
   }
--- a/parser/html/nsHtml5TreeOpStage.h
+++ b/parser/html/nsHtml5TreeOpStage.h
@@ -44,17 +44,17 @@
 #include "nsAHtml5TreeOpSink.h"
 #include "nsHtml5SpeculativeLoad.h"
 
 class nsHtml5TreeOpStage : public nsAHtml5TreeOpSink {
   public:
   
     nsHtml5TreeOpStage();
     
-    ~nsHtml5TreeOpStage();
+    virtual ~nsHtml5TreeOpStage();
   
     /**
      * Flush the operations from the tree operations from the argument
      * queue unconditionally.
      */
     virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
     
     /**
--- a/parser/htmlparser/tests/mochitest/Makefile.in
+++ b/parser/htmlparser/tests/mochitest/Makefile.in
@@ -58,16 +58,18 @@ include $(topsrcdir)/config/rules.mk
 		test_bug174351.html \
 	 	test_bug339350.xhtml \
 		test_bug358797.html \
 		test_bug396568.html \
 		test_bug418464.html \
 		test_bug460437.xhtml \
 		test_bug502091.html \
 		bug_502091_iframe.html \
+		test_bug543062.html \
+		file_bug543062.sjs \
 		test_bug552938.html \
 		test_bug552938-2.html \
 		test_bug566879.html \
 		test_compatmode.html \
 		invalidchar.xml \
 		file_bug534293.sjs \
 		file_bug534293-slow.sjs \
 		test_bug599584.html \
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug543062.sjs
@@ -0,0 +1,30 @@
+function armTimer(response) {
+  var timer = Components.classes["@mozilla.org/timer;1"]
+    .createInstance(Components.interfaces.nsITimer);
+  timer.initWithCallback(function() {
+      if (getState("docwritepreloadssecond") == "second" && getState("docwritepreloadsthird") == "third") {
+        response.write("ok(true, 'Second and third scripts should have started loading while the first one is loading');");
+        response.finish();
+      } else {
+        armTimer(response);
+      }
+    }, 20, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
+
+function handleRequest(request, response)
+{
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/javascript", false);
+  if (request.queryString.indexOf("first") != -1) {
+    response.write("// first\n");
+    response.processAsync();
+    armTimer(response);
+  } else if (request.queryString.indexOf("second") != -1) {
+    response.write("// second\n");
+    setState("docwritepreloadssecond", "second");
+  } else {
+    response.write("// third\n");
+    setState("docwritepreloadsthird", "third");
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/test_bug543062.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=543062
+-->
+<head>
+  <title>Test for Bug 543062</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=543062">Mozilla Bug 543062</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+document.write("\u003Cscript src='file_bug543062.sjs?first'>\u003C/script>\u003Cscript src='file_bug543062.sjs?second'>\u003C/script>");
+document.write("\u003Cscript src='file_bug543062.sjs?third'>\u003C/script>");
+</script>
+</pre>
+</body>
+</html>
+