Propagate networking errors correctly for import() operator
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2017 10:00:32 +0000 (10:00 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2017 10:00:32 +0000 (10:00 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167501

Reviewed by Ryosuke Niwa.

.:

* Source/ModuleFetchFailureKind.h: Added.

Source/WebCore:

We use the promise chain inside the module loader pipeline.
The problem is that,

1. The errors of loading module scripts are propagated through the promise's rejection.
2. Some module related errors like syntax error (when scanning module dependencies) are
   reported at loading time (evaluating time). These errors are propagated through the
   promise rejections and dumped to the console. LoadableModuleScript set catch handler
   for that and print these errors to the console for ScriptElement.
3. Some of errors are already reported to the console. For example, networking errors
   are reported by the CachedModuleLoader.
4. But there is no way to distinguish between (2) and (3) at the catch handler.

Previously, we propagate the special symbol for the already reported errors to distinguish
that. This was OK because these errors cannot be catched by the user code. However,
recent `import()` call needs to expose these errors to the user code. So now, accidentally,
the special purpose symbol is exposed.

Instead of propagating the special symbol, this patch just propagates the errors. But these
error objects are annotated with the private symbol. So these errors can be distinguished
from the other ones.

Currently, we do not have the way to expose the error details to the client of the
CachedScript. So we just report the error with the failed / canceled. This should be
more descriptive error messages in the separate patch[1].

[1]: https://bugs.webkit.org/show_bug.cgi?id=167553

Tests: http/tests/security/mixedContent/import-insecure-script-in-iframe.html
       js/dom/modules/import-not-found-error.html

* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSDOMPromise.h:
(WebCore::DeferredPromise::resolveWithCallback):
(WebCore::DeferredPromise::rejectWithCallback):
* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::setupModuleScriptHandlers):
* bindings/js/ScriptController.h:
(WebCore::ScriptController::moduleLoaderAlreadyReportedErrorSymbol): Deleted.
(WebCore::ScriptController::moduleLoaderFetchingIsCanceledSymbol): Deleted.
* bindings/js/ScriptModuleLoader.cpp:
(WebCore::rejectToPropagateNetworkError):
(WebCore::ScriptModuleLoader::fetch):
(WebCore::ScriptModuleLoader::notifyFinished):
* bindings/js/WebCoreBuiltinNames.h:

LayoutTests:

* http/tests/security/mixedContent/import-insecure-script-in-iframe-expected.txt: Added.
* http/tests/security/mixedContent/import-insecure-script-in-iframe.html: Added.
* http/tests/security/mixedContent/resources/frame-with-insecure-import.html: Added.
* js/dom/modules/import-not-found-error-expected.txt: Added.
* js/dom/modules/import-not-found-error.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@211473 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
ChangeLog
LayoutTests/ChangeLog
LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe.html [new file with mode: 0644]
LayoutTests/http/tests/security/mixedContent/resources/frame-with-insecure-import.html [new file with mode: 0644]
LayoutTests/js/dom/modules/import-not-found-error-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/modules/import-not-found-error.html [new file with mode: 0644]
Source/ModuleFetchFailureKind.h [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/JSDOMPromise.h
Source/WebCore/bindings/js/ScriptController.cpp
Source/WebCore/bindings/js/ScriptController.h
Source/WebCore/bindings/js/ScriptModuleLoader.cpp
Source/WebCore/bindings/js/WebCoreBuiltinNames.h

index 8135a71..eb2a676 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2017-02-01  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Propagate networking errors correctly for import() operator
+        https://bugs.webkit.org/show_bug.cgi?id=167501
+
+        Reviewed by Ryosuke Niwa.
+
+        * Source/ModuleFetchFailureKind.h: Added.
+
 2017-01-31  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Unreviewed. Update OptionsGTK.cmake and NEWS for 2.15.4 release.
index 4740d43..2ac2acc 100644 (file)
@@ -1,3 +1,16 @@
+2017-02-01  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Propagate networking errors correctly for import() operator
+        https://bugs.webkit.org/show_bug.cgi?id=167501
+
+        Reviewed by Ryosuke Niwa.
+
+        * http/tests/security/mixedContent/import-insecure-script-in-iframe-expected.txt: Added.
+        * http/tests/security/mixedContent/import-insecure-script-in-iframe.html: Added.
+        * http/tests/security/mixedContent/resources/frame-with-insecure-import.html: Added.
+        * js/dom/modules/import-not-found-error-expected.txt: Added.
+        * js/dom/modules/import-not-found-error.html: Added.
+
 2017-01-31  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Regression (Safari 10.1): Pressing Return in a contenteditable no longer inserts a line break under certain conditions
diff --git a/LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe-expected.txt b/LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe-expected.txt
new file mode 100644 (file)
index 0000000..00d002e
--- /dev/null
@@ -0,0 +1,12 @@
+CONSOLE MESSAGE: line 1: [blocked] The page at https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-import.html was not allowed to run insecure content from http://127.0.0.1:8080/security/mixedContent/resources/script.js.
+
+This test loads a secure iframe that attempt to import() a module script via HTTP. Since the iframe's content is of a secure origin, importing a module script via HTTP should result in a mixed content error.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS window.data is "TypeError: Importing a module script failed."
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe.html b/LayoutTests/http/tests/security/mixedContent/import-insecure-script-in-iframe.html
new file mode 100644 (file)
index 0000000..76948e8
--- /dev/null
@@ -0,0 +1,21 @@
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description(`This test loads a secure iframe that attempt to import() a module script via HTTP.
+Since the iframe's content is of a secure origin, importing a module script via HTTP should result
+in a mixed content error.
+`);
+window.jsTestIsAsync = true;
+window.data = null;
+window.addEventListener("message", function (error) {
+    window.data = error.data;
+    shouldBe(`window.data`, `"TypeError: Importing a module script failed."`);
+    finishJSTest();
+}, false);
+</script>
+<script src="../../resources/js-test-post.js"></script>
+<p></p>
+<iframe src="https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-import.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/mixedContent/resources/frame-with-insecure-import.html b/LayoutTests/http/tests/security/mixedContent/resources/frame-with-insecure-import.html
new file mode 100644 (file)
index 0000000..488b3d2
--- /dev/null
@@ -0,0 +1,8 @@
+<script>
+window.onload = function() {
+    import("http://127.0.0.1:8080/security/mixedContent/resources/script.js").catch(function (error) {
+        if (window.parent)
+            window.parent.postMessage(String(error), '*');
+    });
+};
+</script>
diff --git a/LayoutTests/js/dom/modules/import-not-found-error-expected.txt b/LayoutTests/js/dom/modules/import-not-found-error-expected.txt
new file mode 100644 (file)
index 0000000..7bcaf9d
--- /dev/null
@@ -0,0 +1,10 @@
+Test import with non existing module.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS String(error) is "TypeError: Importing a module script failed."
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/modules/import-not-found-error.html b/LayoutTests/js/dom/modules/import-not-found-error.html
new file mode 100644 (file)
index 0000000..45baabc
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description('Test import with non existing module.');
+
+window.jsTestIsAsync = true;
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+<script>
+var error = null;
+(async function () {
+    try {
+        await import(`./resources/import-not-found.js`);
+    } catch (e) {
+        error = e;
+    }
+    shouldBeEqualToString(`String(error)`, `TypeError: Importing a module script failed.`);
+    finishJSTest();
+}());
+</script>
+</body>
+</html>
diff --git a/Source/ModuleFetchFailureKind.h b/Source/ModuleFetchFailureKind.h
new file mode 100644 (file)
index 0000000..259ef1e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace WebCore {
+
+enum class ModuleFetchFailureKind {
+    WasErrored,
+    WasCanceled,
+};
+
+} // namespace WebCore
+
index b9d99a9..161af19 100644 (file)
@@ -1,3 +1,55 @@
+2017-02-01  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Propagate networking errors correctly for import() operator
+        https://bugs.webkit.org/show_bug.cgi?id=167501
+
+        Reviewed by Ryosuke Niwa.
+
+        We use the promise chain inside the module loader pipeline.
+        The problem is that,
+
+        1. The errors of loading module scripts are propagated through the promise's rejection.
+        2. Some module related errors like syntax error (when scanning module dependencies) are
+           reported at loading time (evaluating time). These errors are propagated through the
+           promise rejections and dumped to the console. LoadableModuleScript set catch handler
+           for that and print these errors to the console for ScriptElement.
+        3. Some of errors are already reported to the console. For example, networking errors
+           are reported by the CachedModuleLoader.
+        4. But there is no way to distinguish between (2) and (3) at the catch handler.
+
+        Previously, we propagate the special symbol for the already reported errors to distinguish
+        that. This was OK because these errors cannot be catched by the user code. However,
+        recent `import()` call needs to expose these errors to the user code. So now, accidentally,
+        the special purpose symbol is exposed.
+
+        Instead of propagating the special symbol, this patch just propagates the errors. But these
+        error objects are annotated with the private symbol. So these errors can be distinguished
+        from the other ones.
+
+        Currently, we do not have the way to expose the error details to the client of the
+        CachedScript. So we just report the error with the failed / canceled. This should be
+        more descriptive error messages in the separate patch[1].
+
+        [1]: https://bugs.webkit.org/show_bug.cgi?id=167553
+
+        Tests: http/tests/security/mixedContent/import-insecure-script-in-iframe.html
+               js/dom/modules/import-not-found-error.html
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/JSDOMPromise.h:
+        (WebCore::DeferredPromise::resolveWithCallback):
+        (WebCore::DeferredPromise::rejectWithCallback):
+        * bindings/js/ScriptController.cpp:
+        (WebCore::ScriptController::setupModuleScriptHandlers):
+        * bindings/js/ScriptController.h:
+        (WebCore::ScriptController::moduleLoaderAlreadyReportedErrorSymbol): Deleted.
+        (WebCore::ScriptController::moduleLoaderFetchingIsCanceledSymbol): Deleted.
+        * bindings/js/ScriptModuleLoader.cpp:
+        (WebCore::rejectToPropagateNetworkError):
+        (WebCore::ScriptModuleLoader::fetch):
+        (WebCore::ScriptModuleLoader::notifyFinished):
+        * bindings/js/WebCoreBuiltinNames.h:
+
 2017-01-31  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Regression (Safari 10.1): Pressing Return in a contenteditable no longer inserts a line break under certain conditions
index fedef87..c1a3710 100644 (file)
                6F995A391A70833700A735F4 /* JSWebGLVertexArrayObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6F995A2F1A70833700A735F4 /* JSWebGLVertexArrayObject.cpp */; };
                6F995A3A1A70833700A735F4 /* JSWebGLVertexArrayObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F995A301A70833700A735F4 /* JSWebGLVertexArrayObject.h */; };
                6FA4454E898F2FC168BC38C1 /* JSBeforeUnloadEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 29E04A27BED2F81F98E9022B /* JSBeforeUnloadEvent.h */; };
+               709A01FE1E3D0BDD006B0D4C /* ModuleFetchFailureKind.h in Headers */ = {isa = PBXBuildFile; fileRef = 709A01FD1E3D0BCC006B0D4C /* ModuleFetchFailureKind.h */; settings = {ATTRIBUTES = (Private, ); }; };
                70F546E8B8B5D7DC54EE144E /* JSBeforeUnloadEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8225432CA9D4B4CF4628EC7F /* JSBeforeUnloadEvent.cpp */; };
                7117445914BC34EC00EE5FC8 /* SVGTextMetricsBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 7117445714BC34E200EE5FC8 /* SVGTextMetricsBuilder.h */; };
                7118FED415685CC60030B79A /* JSSVGViewSpec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7118FED215685CC60030B79A /* JSSVGViewSpec.cpp */; };
                6F995A2F1A70833700A735F4 /* JSWebGLVertexArrayObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebGLVertexArrayObject.cpp; sourceTree = "<group>"; };
                6F995A301A70833700A735F4 /* JSWebGLVertexArrayObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWebGLVertexArrayObject.h; sourceTree = "<group>"; };
                6FAD4A561A9D0FAE009F7D3C /* OpenGLESSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenGLESSPI.h; sourceTree = "<group>"; };
+               709A01FD1E3D0BCC006B0D4C /* ModuleFetchFailureKind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModuleFetchFailureKind.h; sourceTree = "<group>"; };
                71004B9A1DC1109300A52A38 /* elapsed-time-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "elapsed-time-support.js"; sourceTree = "<group>"; };
                71004B9B1DC1109300A52A38 /* remaining-time-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "remaining-time-support.js"; sourceTree = "<group>"; };
                71004B9D1DC1398800A52A38 /* playback-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "playback-support.js"; sourceTree = "<group>"; };
                                BC53DA2D1143121E000D817E /* DOMWrapperWorld.h */,
                                1432E8480C51493F00B1500F /* GCController.cpp */,
                                1432E8460C51493800B1500F /* GCController.h */,
+                               709A01FD1E3D0BCC006B0D4C /* ModuleFetchFailureKind.h */,
                                C585A66011D4FAC5004C3E4B /* IDBBindingUtilities.cpp */,
                                C585A66111D4FAC5004C3E4B /* IDBBindingUtilities.h */,
                                E157A8EE18185425009F821D /* JSCryptoAlgorithmBuilder.cpp */,
                                A81369D0097374F600D74463 /* HTMLFormElement.h in Headers */,
                                A871DE2B0A152AC800B12A68 /* HTMLFrameElement.h in Headers */,
                                14FFE31D0AE1963300136BF5 /* HTMLFrameElementBase.h in Headers */,
+                               709A01FE1E3D0BDD006B0D4C /* ModuleFetchFailureKind.h in Headers */,
                                93E241FF0B2B4E4000C732A1 /* HTMLFrameOwnerElement.h in Headers */,
                                A871DE280A152AC800B12A68 /* HTMLFrameSetElement.h in Headers */,
                                A871DE2D0A152AC800B12A68 /* HTMLHeadElement.h in Headers */,
index d864e25..39530cc 100644 (file)
@@ -94,8 +94,8 @@ public:
     void reject(ExceptionCode, const String& = { });
     void reject(const JSC::PrivateName&);
 
-    template<typename Callback, typename Value>
-    void resolveWithCallback(Callback callback, Value value)
+    template<typename Callback>
+    void resolveWithCallback(Callback callback)
     {
         if (isSuspended())
             return;
@@ -103,7 +103,19 @@ public:
         ASSERT(m_globalObject);
         JSC::ExecState* exec = m_globalObject->globalExec();
         JSC::JSLockHolder locker(exec);
-        resolve(*exec, callback(*exec, *m_globalObject.get(), std::forward<Value>(value)));
+        resolve(*exec, callback(*exec, *m_globalObject.get()));
+    }
+
+    template<typename Callback>
+    void rejectWithCallback(Callback callback)
+    {
+        if (isSuspended())
+            return;
+        ASSERT(m_deferred);
+        ASSERT(m_globalObject);
+        JSC::ExecState* exec = m_globalObject->globalExec();
+        JSC::JSLockHolder locker(exec);
+        reject(*exec, callback(*exec, *m_globalObject.get()));
     }
 
     JSC::JSValue promise() const;
index d092780..68eb91b 100644 (file)
@@ -40,6 +40,7 @@
 #include "LoadableModuleScript.h"
 #include "MainFrame.h"
 #include "MemoryPressureHandler.h"
+#include "ModuleFetchFailureKind.h"
 #include "NP_jsobject.h"
 #include "Page.h"
 #include "PageConsoleClient.h"
@@ -379,9 +380,6 @@ void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScr
     // For example, if the page load is canceled, the DeferredPromise used in the module loader pipeline will stop executing JS code.
     // Thus the promise returned from this function could remain unresolved.
 
-    JSC::PrivateName moduleLoaderAlreadyReportedErrorSymbol = m_moduleLoaderAlreadyReportedErrorSymbol;
-    JSC::PrivateName moduleLoaderFetchingIsCanceledSymbol = m_moduleLoaderFetchingIsCanceledSymbol;
-
     RefPtr<LoadableModuleScript> moduleScript(&moduleScriptRef);
 
     auto& fulfillHandler = *JSNativeStdFunction::create(state.vm(), shell.window(), 1, String(), [moduleScript](ExecState* exec) {
@@ -390,20 +388,24 @@ void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScr
         return JSValue::encode(jsUndefined());
     });
 
-    auto& rejectHandler = *JSNativeStdFunction::create(state.vm(), shell.window(), 1, String(), [moduleScript, moduleLoaderAlreadyReportedErrorSymbol, moduleLoaderFetchingIsCanceledSymbol](ExecState* exec) {
-        JSValue error = exec->argument(0);
+    auto& rejectHandler = *JSNativeStdFunction::create(state.vm(), shell.window(), 1, String(), [moduleScript](ExecState* exec) {
         VM& vm = exec->vm();
-        if (auto* symbol = jsDynamicCast<Symbol*>(vm, error)) {
-            if (symbol->privateName() == moduleLoaderAlreadyReportedErrorSymbol) {
-                moduleScript->notifyLoadFailed(LoadableScript::Error {
-                    LoadableScript::ErrorType::CachedScript,
-                    std::nullopt
-                });
-                return JSValue::encode(jsUndefined());
-            }
-
-            if (symbol->privateName() == moduleLoaderFetchingIsCanceledSymbol) {
-                moduleScript->notifyLoadWasCanceled();
+        JSValue errorValue = exec->argument(0);
+        if (errorValue.isObject()) {
+            auto* object = JSC::asObject(errorValue);
+            if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
+                // This is host propagated error in the module loader pipeline.
+                switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) {
+                case ModuleFetchFailureKind::WasErrored:
+                    moduleScript->notifyLoadFailed(LoadableScript::Error {
+                        LoadableScript::ErrorType::CachedScript,
+                        std::nullopt
+                    });
+                    break;
+                case ModuleFetchFailureKind::WasCanceled:
+                    moduleScript->notifyLoadWasCanceled();
+                    break;
+                }
                 return JSValue::encode(jsUndefined());
             }
         }
@@ -414,7 +416,7 @@ void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScr
             LoadableScript::ConsoleMessage {
                 MessageSource::JS,
                 MessageLevel::Error,
-                retrieveErrorMessage(*exec, vm, error, scope),
+                retrieveErrorMessage(*exec, vm, errorValue, scope),
             }
         });
         return JSValue::encode(jsUndefined());
index d5b2b68..58fd49d 100644 (file)
@@ -147,9 +147,6 @@ public:
 
     const String* sourceURL() const { return m_sourceURL; } // 0 if we are not evaluating any script
 
-    const JSC::PrivateName& moduleLoaderAlreadyReportedErrorSymbol() const { return m_moduleLoaderAlreadyReportedErrorSymbol; }
-    const JSC::PrivateName& moduleLoaderFetchingIsCanceledSymbol() const { return m_moduleLoaderFetchingIsCanceledSymbol; }
-
     void clearWindowShellsNotMatchingDOMWindow(DOMWindow*, bool goingIntoPageCache);
     void setDOMWindowForWindowShell(DOMWindow*);
     void updateDocument();
@@ -192,8 +189,6 @@ private:
     const String* m_sourceURL;
 
     bool m_paused;
-    JSC::PrivateName m_moduleLoaderAlreadyReportedErrorSymbol;
-    JSC::PrivateName m_moduleLoaderFetchingIsCanceledSymbol;
 
     // The root object used for objects bound outside the context of a plugin, such
     // as NPAPI plugins. The plugins using these objects prevent a page from being cached so they
index 9fd418f..00cd0fb 100644 (file)
 #include "JSDOMBinding.h"
 #include "LoadableModuleScript.h"
 #include "MIMETypeRegistry.h"
+#include "ModuleFetchFailureKind.h"
 #include "ScriptController.h"
 #include "ScriptSourceCode.h"
+#include "WebCoreJSClientData.h"
 #include <runtime/Completion.h>
 #include <runtime/JSInternalPromise.h>
 #include <runtime/JSInternalPromiseDeferred.h>
@@ -123,6 +125,20 @@ JSC::JSInternalPromise* ScriptModuleLoader::resolve(JSC::JSGlobalObject* jsGloba
     return jsPromise.promise();
 }
 
+void rejectToPropagateNetworkError(DeferredPromise& deferred, ModuleFetchFailureKind failureKind, ASCIILiteral message)
+{
+    deferred.rejectWithCallback([&] (JSC::ExecState& state, JSDOMGlobalObject&) {
+        // We annotate exception with special private symbol. It allows us to distinguish these errors from the user thrown ones.
+        JSC::VM& vm = state.vm();
+        // FIXME: Propagate more descriptive error.
+        // https://bugs.webkit.org/show_bug.cgi?id=167553
+        auto* error = JSC::createTypeError(&state, message);
+        ASSERT(error);
+        error->putDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName(), JSC::jsNumber(static_cast<int32_t>(failureKind)));
+        return error;
+    });
+}
+
 JSC::JSInternalPromise* ScriptModuleLoader::fetch(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue scriptFetcher)
 {
     ASSERT(JSC::jsDynamicCast<JSC::JSScriptFetcher*>(exec->vm(), scriptFetcher));
@@ -148,15 +164,13 @@ JSC::JSInternalPromise* ScriptModuleLoader::fetch(JSC::JSGlobalObject* jsGlobalO
         return jsPromise.promise();
     }
 
-    if (auto* frame = m_document.frame()) {
-        auto loader = CachedModuleScriptLoader::create(*this, deferred.get(), *static_cast<CachedScriptFetcher*>(JSC::jsCast<JSC::JSScriptFetcher*>(scriptFetcher)->fetcher()));
-        m_loaders.add(loader.copyRef());
-        if (!loader->load(m_document, completedURL)) {
-            loader->clearClient();
-            m_loaders.remove(WTFMove(loader));
-            deferred->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
-            return jsPromise.promise();
-        }
+    auto loader = CachedModuleScriptLoader::create(*this, deferred.get(), *static_cast<CachedScriptFetcher*>(JSC::jsCast<JSC::JSScriptFetcher*>(scriptFetcher)->fetcher()));
+    m_loaders.add(loader.copyRef());
+    if (!loader->load(m_document, completedURL)) {
+        loader->clearClient();
+        m_loaders.remove(WTFMove(loader));
+        rejectToPropagateNetworkError(deferred.get(), ModuleFetchFailureKind::WasErrored, ASCIILiteral("Importing a module script failed."));
+        return jsPromise.promise();
     }
 
     return jsPromise.promise();
@@ -232,43 +246,34 @@ void ScriptModuleLoader::notifyFinished(CachedModuleScriptLoader& loader, RefPtr
 
     auto& cachedScript = *loader.cachedScript();
 
-    bool failed = false;
-
     if (cachedScript.resourceError().isAccessControl()) {
         promise->reject(TypeError, ASCIILiteral("Cross-origin script load denied by Cross-Origin Resource Sharing policy."));
         return;
     }
 
-    if (cachedScript.errorOccurred())
-        failed = true;
-    else if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(cachedScript.response().mimeType())) {
-        // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
-        // The result of extracting a MIME type from response's header list (ignoring parameters) is not a JavaScript MIME type.
-        // For historical reasons, fetching a classic script does not include MIME type checking. In contrast, module scripts will fail to load if they are not of a correct MIME type.
-        promise->reject(TypeError, makeString("'", cachedScript.response().mimeType(), "' is not a valid JavaScript MIME type."));
+    if (cachedScript.errorOccurred()) {
+        rejectToPropagateNetworkError(*promise, ModuleFetchFailureKind::WasErrored, ASCIILiteral("Importing a module script failed."));
         return;
     }
 
-    auto* frame = m_document.frame();
-    if (!frame)
-        return;
-
-    if (failed) {
-        // Reject a promise to propagate the error back all the way through module promise chains to the script element.
-        promise->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
+    if (cachedScript.wasCanceled()) {
+        rejectToPropagateNetworkError(*promise, ModuleFetchFailureKind::WasCanceled, ASCIILiteral("Importing a module script is canceled."));
         return;
     }
 
-    if (cachedScript.wasCanceled()) {
-        promise->reject(frame->script().moduleLoaderFetchingIsCanceledSymbol());
+    if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(cachedScript.response().mimeType())) {
+        // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
+        // The result of extracting a MIME type from response's header list (ignoring parameters) is not a JavaScript MIME type.
+        // For historical reasons, fetching a classic script does not include MIME type checking. In contrast, module scripts will fail to load if they are not of a correct MIME type.
+        promise->reject(TypeError, makeString("'", cachedScript.response().mimeType(), "' is not a valid JavaScript MIME type."));
         return;
     }
 
     m_requestURLToResponseURLMap.add(cachedScript.url(), cachedScript.response().url());
-    ScriptSourceCode scriptSourceCode(&cachedScript, JSC::SourceProviderSourceType::Module, loader.scriptFetcher());
-    promise->resolveWithCallback([] (JSC::ExecState& state, JSDOMGlobalObject&, JSC::SourceCode sourceCode) {
-        return JSC::JSSourceCode::create(state.vm(), WTFMove(sourceCode));
-    }, scriptSourceCode.jsSourceCode());
+    promise->resolveWithCallback([&] (JSC::ExecState& state, JSDOMGlobalObject&) {
+        return JSC::JSSourceCode::create(state.vm(),
+            JSC::SourceCode { ScriptSourceCode { &cachedScript, JSC::SourceProviderSourceType::Module, loader.scriptFetcher() }.jsSourceCode() });
+    });
 }
 
 }
index 0c0bab4..8ab00bc 100644 (file)
@@ -45,6 +45,7 @@ namespace WebCore {
     macro(controller) \
     macro(createReadableStreamSource) \
     macro(disturbed) \
+    macro(failureKind) \
     macro(fetchRequest) \
     macro(fillFromJS) \
     macro(finishConsumingStream) \