Do not re-initialize PendingScript in HTMLParserScriptRunner

Previously, |HTMLParserScriptRunner::m_parserBlockingScript| was
re-initialized every time it processes a new script tag
(i.e. a new ScriptLoader), and therefore one PendingScript can be
reused for multiple ScriptLoaders.

This CL makes |m_parserBlockingScript| to be newly created rather than
re-initialized with the same object.
This clarifies that a PendingScript corresponds to a ScriptLoader,
and also enables creating ModulePendingScript in a cleaner way in
https://codereview.chromium.org/2653923008/.

BUG=686281

Review-Url: https://codereview.chromium.org/2693423002
Cr-Commit-Position: refs/heads/master@{#451911}
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptStreamerTest.cpp b/third_party/WebKit/Source/bindings/core/v8/ScriptStreamerTest.cpp
index 52ec735..ee31bc6 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ScriptStreamerTest.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/ScriptStreamerTest.cpp
@@ -34,9 +34,9 @@
         m_settings(Settings::create()),
         m_resourceRequest("http://www.streaming-test.com/"),
         m_resource(ScriptResource::create(m_resourceRequest, "UTF-8")),
-        m_pendingScript(PendingScript::create(0, m_resource.get())) {
+        m_pendingScript(PendingScript::createForTesting(m_resource.get())) {
     m_resource->setStatus(ResourceStatus::Pending);
-    m_pendingScript = PendingScript::create(0, m_resource.get());
+    m_pendingScript = PendingScript::createForTesting(m_resource.get());
     ScriptStreamer::setSmallScriptThresholdForTesting(0);
   }
 
diff --git a/third_party/WebKit/Source/core/dom/PendingScript.cpp b/third_party/WebKit/Source/core/dom/PendingScript.cpp
index 4507e23..aef2998 100644
--- a/third_party/WebKit/Source/core/dom/PendingScript.cpp
+++ b/third_party/WebKit/Source/core/dom/PendingScript.cpp
@@ -35,16 +35,31 @@
 
 PendingScript* PendingScript::create(Element* element,
                                      ScriptResource* resource) {
-  return new PendingScript(element, resource);
+  return new PendingScript(element, resource, TextPosition());
 }
 
-PendingScript::PendingScript(Element* element, ScriptResource* resource)
+PendingScript* PendingScript::create(Element* element,
+                                     const TextPosition& startingPosition) {
+  return new PendingScript(element, nullptr, startingPosition);
+}
+
+PendingScript* PendingScript::createForTesting(ScriptResource* resource) {
+  return new PendingScript(nullptr, resource, TextPosition(), true);
+}
+
+PendingScript::PendingScript(Element* element,
+                             ScriptResource* resource,
+                             const TextPosition& startingPosition,
+                             bool isForTesting)
     : m_watchingForLoad(false),
       m_element(element),
+      m_startingPosition(startingPosition),
       m_integrityFailure(false),
       m_parserBlockingLoadStartTime(0),
-      m_client(nullptr) {
-  setScriptResource(resource);
+      m_client(nullptr),
+      m_isForTesting(isForTesting) {
+  CHECK(m_isForTesting || m_element);
+  setResource(resource);
   MemoryCoordinator::instance().registerClient(this);
 }
 
@@ -55,7 +70,7 @@
   DCHECK(!m_client);
   DCHECK(!m_watchingForLoad);
 
-  setScriptResource(nullptr);
+  setResource(nullptr);
   m_startingPosition = TextPosition::belowRangePosition();
   m_integrityFailure = false;
   m_parserBlockingLoadStartTime = 0;
@@ -86,20 +101,19 @@
   m_watchingForLoad = false;
 }
 
+Element* PendingScript::element() const {
+  // As mentioned in the comment at |m_element| declaration, |m_element|
+  // must points to the corresponding ScriptLoader's element.
+  CHECK(m_element);
+  return m_element.get();
+}
+
 void PendingScript::streamingFinished() {
   DCHECK(resource());
   if (m_client)
     m_client->pendingScriptFinished(this);
 }
 
-void PendingScript::setElement(Element* element) {
-  m_element = element;
-}
-
-void PendingScript::setScriptResource(ScriptResource* resource) {
-  setResource(resource);
-}
-
 void PendingScript::markParserBlockingLoadStartTime() {
   DCHECK_EQ(m_parserBlockingLoadStartTime, 0.0);
   m_parserBlockingLoadStartTime = monotonicallyIncreasingTime();
@@ -128,6 +142,7 @@
   // objects (perhaps attached to identical Resource objects) per request.
   //
   // See https://crbug.com/500701 for more information.
+  CHECK(m_isForTesting || m_element);
   if (m_element) {
     DCHECK_EQ(resource->getType(), Resource::Script);
     ScriptResource* scriptResource = toScriptResource(resource);
diff --git a/third_party/WebKit/Source/core/dom/PendingScript.h b/third_party/WebKit/Source/core/dom/PendingScript.h
index 6c3dfaf..6b8c0cb 100644
--- a/third_party/WebKit/Source/core/dom/PendingScript.h
+++ b/third_party/WebKit/Source/core/dom/PendingScript.h
@@ -70,13 +70,16 @@
   WTF_MAKE_NONCOPYABLE(PendingScript);
 
  public:
+  // For script from an external file.
   static PendingScript* create(Element*, ScriptResource*);
+  // For inline script.
+  static PendingScript* create(Element*, const TextPosition&);
+
+  static PendingScript* createForTesting(ScriptResource*);
+
   ~PendingScript() override;
 
   TextPosition startingPosition() const { return m_startingPosition; }
-  void setStartingPosition(const TextPosition& position) {
-    m_startingPosition = position;
-  }
   void markParserBlockingLoadStartTime();
   // Returns the time the load of this script started blocking the parser, or
   // zero if this script hasn't yet blocked the parser, in
@@ -88,10 +91,7 @@
   void watchForLoad(PendingScriptClient*);
   void stopWatchingForLoad();
 
-  Element* element() const { return m_element.get(); }
-  void setElement(Element*);
-
-  void setScriptResource(ScriptResource*);
+  Element* element() const;
 
   DECLARE_TRACE();
 
@@ -107,7 +107,10 @@
   void dispose();
 
  private:
-  PendingScript(Element*, ScriptResource*);
+  PendingScript(Element*,
+                ScriptResource*,
+                const TextPosition&,
+                bool isForTesting = false);
   PendingScript() = delete;
 
   // ScriptResourceClient
@@ -119,13 +122,21 @@
   void onPurgeMemory() override;
 
   bool m_watchingForLoad;
+
+  // |m_element| must points to the corresponding ScriptLoader's element and
+  // thus must be non-null before dispose() is called (except for unit tests).
   Member<Element> m_element;
+
   TextPosition m_startingPosition;  // Only used for inline script tags.
   bool m_integrityFailure;
   double m_parserBlockingLoadStartTime;
 
   Member<ScriptStreamer> m_streamer;
   Member<PendingScriptClient> m_client;
+
+  // This flag is used to skip non-null checks of |m_element| in unit tests,
+  // because |m_element| can be null in unit tests.
+  const bool m_isForTesting;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
index 59c1816..e32b451 100644
--- a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
+++ b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
@@ -142,7 +142,7 @@
   m_nonBlocking = false;
 }
 
-void ScriptLoader::detach() {
+void ScriptLoader::detachPendingScript() {
   if (!m_pendingScript)
     return;
   m_pendingScript->dispose();
@@ -817,7 +817,7 @@
   DCHECK(m_pendingScript->resource());
   bool errorOccurred = false;
   ScriptSourceCode source = m_pendingScript->getSource(KURL(), errorOccurred);
-  m_pendingScript->dispose();
+  detachPendingScript();
   if (errorOccurred) {
     dispatchErrorEvent();
   } else if (!m_resource->wasCanceled()) {
@@ -848,7 +848,7 @@
 
   Document* contextDocument = m_element->document().contextDocument();
   if (!contextDocument) {
-    detach();
+    detachPendingScript();
     return;
   }
 
@@ -857,7 +857,7 @@
   if (m_resource->errorOccurred()) {
     contextDocument->scriptRunner()->notifyScriptLoadError(this,
                                                            m_asyncExecType);
-    detach();
+    detachPendingScript();
     dispatchErrorEvent();
     return;
   }
diff --git a/third_party/WebKit/Source/core/dom/ScriptLoader.h b/third_party/WebKit/Source/core/dom/ScriptLoader.h
index d483642b..295c12c 100644
--- a/third_party/WebKit/Source/core/dom/ScriptLoader.h
+++ b/third_party/WebKit/Source/core/dom/ScriptLoader.h
@@ -107,9 +107,6 @@
     return m_pendingScript && m_pendingScript->errorOccurred();
   }
 
-  // Clears the connection to the PendingScript (and Element and Resource).
-  void detach();
-
   bool wasCreatedDuringDocumentWrite() { return m_createdDuringDocumentWrite; }
 
   bool disallowedFetchForDocWrittenScript() {
@@ -136,6 +133,9 @@
 
   ScriptLoaderClient* client() const;
 
+  // Clears the connection to the PendingScript.
+  void detachPendingScript();
+
   // PendingScriptClient
   void pendingScriptFinished(PendingScript*) override;
 
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
index dd2b05ff..ec9bf4e 100644
--- a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
+++ b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
@@ -157,7 +157,7 @@
     : m_reentryPermit(reentryPermit),
       m_document(document),
       m_host(host),
-      m_parserBlockingScript(PendingScript::create(nullptr, nullptr)) {
+      m_parserBlockingScript(nullptr) {
   DCHECK(m_host);
 }
 
@@ -170,7 +170,9 @@
   if (!m_document)
     return;
 
-  m_parserBlockingScript->dispose();
+  if (m_parserBlockingScript)
+    m_parserBlockingScript->dispose();
+  m_parserBlockingScript = nullptr;
 
   while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
     PendingScript* pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
@@ -183,9 +185,10 @@
 }
 
 bool HTMLParserScriptRunner::isParserBlockingScriptReady() {
+  DCHECK(parserBlockingScript());
   if (!m_document->isScriptExecutionReady())
     return false;
-  return m_parserBlockingScript->isReady();
+  return parserBlockingScript()->isReady();
 }
 
 // This has two callers and corresponds to different concepts in the spec:
@@ -228,6 +231,11 @@
   //     There is no longer a pending parsing-blocking script."
   // Clear the pending script before possible re-entrancy from executeScript()
   pendingScript->dispose();
+  pendingScript = nullptr;
+
+  if (pendingScriptType == ScriptStreamer::ParsingBlocking) {
+    m_parserBlockingScript = nullptr;
+  }
 
   if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element)) {
     // 7. "Increment the parser's script nesting level by one (it should be
@@ -312,16 +320,15 @@
     PendingScript* pendingScript) {
   // If the script was blocked as part of document.write intervention,
   // then send an asynchronous GET request with an interventions header.
-  TextPosition startingPosition;
-  bool isParserInserted = false;
 
-  if (m_parserBlockingScript != pendingScript)
+  if (!parserBlockingScript())
     return;
 
-  Element* element = m_parserBlockingScript->element();
-  if (!element)
+  if (parserBlockingScript() != pendingScript)
     return;
 
+  Element* element = parserBlockingScript()->element();
+
   ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element);
   if (!scriptLoader || !scriptLoader->disallowedFetchForDocWrittenScript())
     return;
@@ -338,8 +345,8 @@
 
   emitErrorForDocWriteScripts(pendingScript->resource()->url().getString(),
                               *m_document);
-  startingPosition = m_parserBlockingScript->startingPosition();
-  isParserInserted = scriptLoader->isParserInserted();
+  TextPosition startingPosition = parserBlockingScript()->startingPosition();
+  bool isParserInserted = scriptLoader->isParserInserted();
   // Remove this resource entry from memory cache as the new request
   // should not join onto this existing entry.
   memoryCache()->remove(pendingScript->resource());
@@ -402,7 +409,7 @@
 
     // - "Otherwise":
 
-    traceParserBlockingScript(m_parserBlockingScript.get(),
+    traceParserBlockingScript(parserBlockingScript(),
                               !m_document->isScriptExecutionReady());
     m_parserBlockingScript->markParserBlockingLoadStartTime();
 
@@ -416,7 +423,7 @@
 }
 
 bool HTMLParserScriptRunner::hasParserBlockingScript() const {
-  return !!m_parserBlockingScript->element();
+  return parserBlockingScript();
 }
 
 // The "Otherwise" Clause of 'An end tag whose tag name is "script"'
@@ -442,7 +449,7 @@
     InsertionPointRecord insertionPointRecord(m_host->inputStream());
 
     // 1., 7.--9.
-    executePendingScriptAndDispatchEvent(m_parserBlockingScript.get(),
+    executePendingScriptAndDispatchEvent(m_parserBlockingScript,
                                          ScriptStreamer::ParsingBlocking);
 
     // 10. "Let the insertion point be undefined again."
@@ -458,8 +465,8 @@
   TRACE_EVENT0("blink", "HTMLParserScriptRunner::executeScriptsWaitingForLoad");
   DCHECK(!isExecutingScript());
   DCHECK(hasParserBlockingScript());
-  DCHECK_EQ(pendingScript, m_parserBlockingScript);
-  DCHECK(m_parserBlockingScript->isReady());
+  DCHECK_EQ(pendingScript, parserBlockingScript());
+  DCHECK(parserBlockingScript()->isReady());
   executeParsingBlockingScripts();
 }
 
@@ -522,20 +529,22 @@
   // "The element is the pending parsing-blocking script of the Document of
   //  the parser that created the element.
   //  (There can only be one such script per Document at a time.)"
-  if (!requestPendingScript(m_parserBlockingScript.get(), element))
+  CHECK(!parserBlockingScript());
+  m_parserBlockingScript = requestPendingScript(element);
+  if (!parserBlockingScript())
     return;
 
-  DCHECK(m_parserBlockingScript->resource());
+  DCHECK(parserBlockingScript()->resource());
 
   // We only care about a load callback if resource is not already in the cache.
   // Callers will attempt to run the m_parserBlockingScript if possible before
   // returning control to the parser.
-  if (!m_parserBlockingScript->isReady()) {
+  if (!parserBlockingScript()->isReady()) {
     if (m_document->frame()) {
       ScriptState* scriptState = ScriptState::forMainWorld(m_document->frame());
       if (scriptState) {
         ScriptStreamer::startStreaming(
-            m_parserBlockingScript.get(), ScriptStreamer::ParsingBlocking,
+            m_parserBlockingScript, ScriptStreamer::ParsingBlocking,
             m_document->frame()->settings(), scriptState,
             TaskRunnerHelper::get(TaskType::Networking, m_document));
       }
@@ -547,8 +556,8 @@
 
 // 1st Clause, Step 23 of https://html.spec.whatwg.org/#prepare-a-script
 void HTMLParserScriptRunner::requestDeferredScript(Element* element) {
-  PendingScript* pendingScript = PendingScript::create(nullptr, nullptr);
-  if (!requestPendingScript(pendingScript, element))
+  PendingScript* pendingScript = requestPendingScript(element);
+  if (!pendingScript)
     return;
 
   if (m_document->frame() && !pendingScript->isReady()) {
@@ -569,18 +578,16 @@
   m_scriptsToExecuteAfterParsing.append(pendingScript);
 }
 
-bool HTMLParserScriptRunner::requestPendingScript(PendingScript* pendingScript,
-                                                  Element* script) const {
-  DCHECK(!pendingScript->element());
-  pendingScript->setElement(script);
+PendingScript* HTMLParserScriptRunner::requestPendingScript(
+    Element* element) const {
   // This should correctly return 0 for empty or invalid srcValues.
-  ScriptResource* resource = toScriptLoaderIfPossible(script)->resource();
+  ScriptResource* resource = toScriptLoaderIfPossible(element)->resource();
   if (!resource) {
     DVLOG(1) << "Not implemented.";  // Dispatch error event.
-    return false;
+    return nullptr;
   }
-  pendingScript->setScriptResource(resource);
-  return true;
+
+  return PendingScript::create(element, resource);
 }
 
 // The initial steps for 'An end tag whose tag name is "script"'
@@ -637,15 +644,18 @@
         // "The element is the pending parsing-blocking script of the
         //  Document of the parser that created the element.
         //  (There can only be one such script per Document at a time.)"
-        m_parserBlockingScript->setElement(script);
-        m_parserBlockingScript->setStartingPosition(scriptStartPosition);
+        CHECK(!m_parserBlockingScript);
+        m_parserBlockingScript =
+            PendingScript::create(script, scriptStartPosition);
       } else {
         // 6th Clause of Step 23.
         // "Immediately execute the script block,
         //  even if other scripts are already executing."
         // TODO(hiroshige): Merge the block into ScriptLoader::prepareScript().
         DCHECK_GT(m_reentryPermit->scriptNestingLevel(), 1u);
-        m_parserBlockingScript->dispose();
+        if (m_parserBlockingScript)
+          m_parserBlockingScript->dispose();
+        m_parserBlockingScript = nullptr;
         ScriptSourceCode sourceCode(script->textContent(),
                                     documentURLForScriptExecution(m_document),
                                     scriptStartPosition);
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.h b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.h
index 2c9a3b9..69060e6 100644
--- a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.h
+++ b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.h
@@ -107,13 +107,17 @@
 
   void requestParsingBlockingScript(Element*);
   void requestDeferredScript(Element*);
-  bool requestPendingScript(PendingScript*, Element*) const;
+  PendingScript* requestPendingScript(Element*) const;
 
   // Processes the provided script element, but does not execute any
   // parsing-blocking scripts that may remain after execution.
   void processScriptElementInternal(Element*,
                                     const TextPosition& scriptStartPosition);
 
+  const PendingScript* parserBlockingScript() const {
+    return m_parserBlockingScript;
+  }
+
   bool isParserBlockingScriptReady();
 
   void possiblyFetchBlockedDocWriteScript(PendingScript*);