[DeferAllScript] Deactivate ScriptRunnerDelayer on parser detach

Previously, HTMLParserScriptRunner continued to delay
ScriptRunner after detached (e.g. on `document.write()`)
and thus caused DCHECK failures due to conflicts with
new HTMLParserScriptRunner.
This CL fix this by deactivating HTMLParserScriptRunner's
ScriptRunnerDelayer on parser detach.

This CL adds WPTs for `document.write()`-related cases
that would DCHECK-fail before this CL, and
refactors tests using `helper.js`.

Bug: 1340837
Change-Id: I168914447a876afe3fdfb257d6a089a7d11385b6
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html b/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html
index 077d4f6..f7377d8 100644
--- a/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html
+++ b/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html
@@ -5,25 +5,11 @@
     <meta charset="utf-8">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/helper.js"></script>
 </head>
 <body>
   <script>
   setup({single_test: true});
-  window.result = [];
-  function log(msg) {
-    window.result.push(msg);
-  }
-  function checkIfReachedBodyEnd() {
-    const endelement = document.getElementById("bodyend");
-    if (endelement && endelement.textContent === "End") {
-      log("EndOfBody");
-      endelement.textContent = "Detected";
-    }
-  }
-  function logScript(msg) {
-    checkIfReachedBodyEnd();
-    log(msg);
-  }
   function finish() {
     assert_array_equals(
         window.result,
@@ -42,7 +28,6 @@
   }
   logScript("Inline1");
   window.addEventListener("load", finish);
-  document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });
   </script>
   <script src="resources/sync-script-1.js"></script>
   <!-- To test the async script loaded before force-deferred scripts
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/async-script.html b/html/semantics/scripting-1/the-script-element/defer-script/async-script.html
index 0e84e2f..ea6c546 100644
--- a/html/semantics/scripting-1/the-script-element/defer-script/async-script.html
+++ b/html/semantics/scripting-1/the-script-element/defer-script/async-script.html
@@ -5,25 +5,11 @@
     <meta charset="utf-8">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/helper.js"></script>
 </head>
 <body>
   <script>
   setup({single_test: true});
-  window.result = [];
-  function log(msg) {
-    window.result.push(msg);
-  }
-  function checkIfReachedBodyEnd() {
-    const endelement = document.getElementById("bodyend");
-    if (endelement && endelement.textContent === "End") {
-      log("EndOfBody");
-      endelement.textContent = "Detected";
-    }
-  }
-  function logScript(msg) {
-    checkIfReachedBodyEnd();
-    log(msg);
-  }
   function finish() {
     assert_array_equals(
         window.result,
@@ -35,7 +21,6 @@
   }
   logScript("Inline1");
   window.addEventListener("load", finish);
-  document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });
   </script>
   <script src="resources/sync-script-1.js"></script>
   <!-- Delays are added to make DOMContentLoaded be fired before
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml b/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml
index e64ff52..9d02ff3 100644
--- a/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml
+++ b/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml
@@ -5,29 +5,13 @@
   <title>Defer Script Execution Order</title>
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
+  <script src="resources/helper.js"></script>
 </head>
 <body>
   <div id="scriptlog"/>
   <input id="testElement"/>
   <script>
   setup({single_test: true});
-  window.result = [];
-  function log(msg) {
-    window.result.push(msg);
-  }
-  function checkIfReachedBodyEnd() {
-    const endelement = document.getElementById("bodyend");
-    if (endelement != null) {
-      if (endelement.textContent === "End") {
-        log("EndOfBody");
-        endelement.textContent = "Detected";
-      }
-    }
-  }
-  function logScript(msg) {
-    checkIfReachedBodyEnd();
-    log(msg);
-  }
   function finish() {
     assert_array_equals(
         window.result,
@@ -41,7 +25,6 @@
   }
   logScript("Inline1");
   window.addEventListener("load", finish);
-  document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });
   </script>
 
   <script src="resources/sync-script-1.js"></script>
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html b/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html
index c764aab..62c3a74 100644
--- a/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html
+++ b/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html
@@ -5,25 +5,11 @@
     <meta charset="utf-8">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/helper.js"></script>
 </head>
 <body>
   <script>
   setup({single_test: true});
-  window.result = [];
-  function log(msg) {
-    window.result.push(msg);
-  }
-  function checkIfReachedBodyEnd() {
-    const endelement = document.getElementById("bodyend");
-    if (endelement && endelement.textContent === "End") {
-      log("EndOfBody");
-      endelement.textContent = "Detected";
-    }
-  }
-  function logScript(msg) {
-    checkIfReachedBodyEnd();
-    log(msg);
-  }
   function finish() {
     assert_array_equals(
         window.result,
@@ -37,7 +23,6 @@
   }
   logScript("Inline1");
   window.addEventListener("load", finish);
-  document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });
   </script>
 
   <script src="resources/sync-script-1.js"></script>
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/document-write.html b/html/semantics/scripting-1/the-script-element/defer-script/document-write.html
new file mode 100644
index 0000000..63e251b
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/document-write.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>DeferAllScript: document.write()</title>
+<html>
+<head>
+    <meta charset="utf-8">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script>
+    const t1 = async_test("document.write()");
+    const t2 = async_test("document.write(),close()");
+    const t3 = async_test("document.open(),write()");
+    const t4 = async_test("document.open(),write(),close()");
+    function finish() {
+      const expected = ["Inline1", "Sync2", "Async1", "Sync1",
+                        "EndOfBody", "DOMContentLoaded", "WindowLoad"];
+      t1.step_func_done(() => {
+        assert_array_equals(
+          document.getElementById("document-write").contentWindow.result,
+          expected,
+          "Execution order");
+      })();
+
+      t2.step_func_done(() => {
+        assert_array_equals(
+          document.getElementById("document-write-close").contentWindow.result,
+          expected,
+          "Execution order");
+      })();
+
+      t3.step_func_done(() => {
+        assert_array_equals(
+          document.getElementById("document-open-write").contentWindow.result,
+          expected,
+          "Execution order");
+      })();
+
+      t4.step_func_done(() => {
+        assert_array_equals(
+          document.getElementById(
+              "document-open-write-close").contentWindow.result,
+          expected,
+          "Execution order");
+      })();
+      // For cases where documents are kept open, call `document.close()` here
+      // to finish the test harness.
+      for (const iframe of document.querySelectorAll("iframe")) {
+        iframe.contentDocument.close();
+      }
+    }
+
+    // For cases where documents are kept open (that should never occur in
+    // non-intervention cases), schedule `finish()` because Window load events
+    // might be not fired.
+    setTimeout(finish, 5000);
+    </script>
+</head>
+<body onload="finish()">
+<iframe id="document-write"
+        src="resources/document-write-iframe.sub.html?script=document-write.js"></iframe>
+<iframe id="document-write-close"
+        src="resources/document-write-iframe.sub.html?script=document-write-close.js"></iframe>
+<iframe id="document-open-write"
+        src="resources/document-write-iframe.sub.html?script=document-open-write.js"></iframe>
+<iframe id="document-open-write-close"
+        src="resources/document-write-iframe.sub.html?script=document-open-write-close.js"></iframe>
+</body>
+</html>
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js
new file mode 100644
index 0000000..80703d5
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js
@@ -0,0 +1,3 @@
+document.open();
+document.write(`<script src="sync-script-2.js"></script>`);
+document.close();
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js
new file mode 100644
index 0000000..178c374
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js
@@ -0,0 +1,2 @@
+document.open();
+document.write(`<script src="sync-script-2.js"></script>`);
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js
new file mode 100644
index 0000000..7cdde0d
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js
@@ -0,0 +1,2 @@
+document.write(`<script src="sync-script-2.js"></script>`);
+document.close();
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html
new file mode 100644
index 0000000..e3022e3
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<body>
+<script src="helper.js"></script>
+<script>
+logScript("Inline1");
+window.addEventListener("load", () => logScript("WindowLoad"));
+</script>
+<script src="{{GET[script]}}?pipe=trickle(d1)"></script>
+<script src="async-script-1.js" async></script>
+<script src="sync-script-1.js?pipe=trickle(d2)"></script>
+<pre id="bodyend">End</pre>
+</body>
+</html>
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js
new file mode 100644
index 0000000..413a9bc
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js
@@ -0,0 +1 @@
+document.write(`<script src="sync-script-2.js"></script>`);
diff --git a/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js b/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js
new file mode 100644
index 0000000..89c6d1e
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js
@@ -0,0 +1,17 @@
+window.result = [];
+function log(msg) {
+  window.result.push(msg);
+}
+function checkIfReachedBodyEnd() {
+  const endelement = document.getElementById("bodyend");
+  // `<pre id="bodyend">End</pre>` is needed at the end of HTML.
+  if (endelement && endelement.textContent === "End") {
+    log("EndOfBody");
+    endelement.textContent = "Detected";
+  }
+}
+function logScript(msg) {
+  checkIfReachedBodyEnd();
+  log(msg);
+}
+document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });