Title: [224662] trunk
Revision
224662
Author
utatane....@gmail.com
Date
2017-11-09 21:28:34 -0800 (Thu, 09 Nov 2017)

Log Message

[JSC] Retry module fetching if previous request fails
https://bugs.webkit.org/show_bug.cgi?id=178168

Reviewed by Saam Barati.

Source/_javascript_Core:

According to the latest spec, the failed fetching operation can be retried if it is requested again.
For example,

    <script type="module" integrity="shaXXX-bad" src=""
    <script type="module" integrity="shaXXX-correct" src=""

When performing the first module fetching, integrity check fails, and the load of this module becomes failed.
But when loading the second module, we do not use the cached failure result in the first module loading.
We retry fetching for "./A.js". In this case, we have a correct integrity and module fetching succeeds.
This is specified in whatwg/HTML[1]. If the fetching fails, we do not cache it.

Interestingly, fetching result and instantiation result will be cached if they succeeds. This is because we would
like to cache modules based on their URLs. As a result,

    <script type="module" integrity="shaXXX-correct" src=""
    <script type="module" integrity="shaXXX-bad" src=""

In the above case, the first loading succeeds. And the second loading also succeeds since the succeeded fetching and
instantiation are cached in the module pipeline.

This patch implements the above semantics. Previously, our module pipeline always caches the result. If the fetching
failed, all the subsequent fetching for the same URL fails even if we have different integrity values. We retry fetching
if the previous one fails. As an overview of our change,

1. Fetching result should be cached only if it succeeds. Two or more on-the-fly fetching requests to the same URLs should
   be unified. But if currently executing one fails, other attempts should retry fetching.

2. Instantiation should be cached if fetching succeeds.

3. Satisfying should be cached if it succeeds.

[1]: https://html.spec.whatwg.org/#fetch-a-single-module-script

* builtins/ModuleLoaderPrototype.js:
(requestFetch):
(requestInstantiate):
(requestSatisfy):
(link):
(loadModule):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):

LayoutTests:

* js/dom/modules/module-fetch-failure-not-cached-expected.txt: Added.
* js/dom/modules/module-fetch-failure-not-cached.html: Added.
* js/dom/modules/module-integrity-bad-value-success-with-cache-expected.txt: Added.
* js/dom/modules/module-integrity-bad-value-success-with-cache.html: Added.
* js/dom/modules/script-tests/module-fetch-failure-not-cached.js: Added.
* js/dom/modules/script-tests/module-integrity-bad-value-success-with-cache.js: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (224661 => 224662)


--- trunk/LayoutTests/ChangeLog	2017-11-10 03:21:23 UTC (rev 224661)
+++ trunk/LayoutTests/ChangeLog	2017-11-10 05:28:34 UTC (rev 224662)
@@ -1,3 +1,17 @@
+2017-11-09  Yusuke Suzuki  <utatane....@gmail.com>
+
+        [JSC] Retry module fetching if previous request fails
+        https://bugs.webkit.org/show_bug.cgi?id=178168
+
+        Reviewed by Saam Barati.
+
+        * js/dom/modules/module-fetch-failure-not-cached-expected.txt: Added.
+        * js/dom/modules/module-fetch-failure-not-cached.html: Added.
+        * js/dom/modules/module-integrity-bad-value-success-with-cache-expected.txt: Added.
+        * js/dom/modules/module-integrity-bad-value-success-with-cache.html: Added.
+        * js/dom/modules/script-tests/module-fetch-failure-not-cached.js: Added.
+        * js/dom/modules/script-tests/module-integrity-bad-value-success-with-cache.js: Added.
+
 2017-11-09  Ryan Haddad  <ryanhad...@apple.com>
 
         Mark multiple service worker tests as flaky.

Added: trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached-expected.txt (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached-expected.txt	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1,4 @@
+CONSOLE MESSAGE: TypeError: Cannot load script module-fetch-failure-not-cached.js. Failed integrity metadata check.
+
+PASS Module fetch failure is not cached in module pipeline 
+

Added: trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached.html (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/module-fetch-failure-not-cached.html	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Module fetch failure is not cached in module pipeline</title>
+<script src=""
+<script src=""
+</head>
+<body>
+<script>
+promise_test(() => {
+    return new Promise(function (resolve, reject) {
+        var scriptWithBadValue = document.createElement('script');
+        scriptWithBadValue.type = 'module';
+        scriptWithBadValue.src = '';
+        scriptWithBadValue.integrity = 'sha256-badbeef';
+        scriptWithBadValue._onload_ = reject;
+        scriptWithBadValue._onerror_ = function () {
+            assert_equals(window.moduleIsLoaded, undefined);
+            var script = document.createElement('script');
+            script.type = 'module';
+            script.src = '';
+            script.integrity = 'sha256-7iiaipciOq3/cXnCpuOPyoC9GgCQw2F6y84mH4CJrGk=';
+            script._onload_ = function () {
+                assert_equals(window.moduleIsLoaded, true);
+                resolve();
+            };
+            script._onerror_ = reject;
+            document.body.appendChild(script);
+        };
+        document.body.appendChild(scriptWithBadValue);
+    });
+}, 'Module fetch failure is not cached in module pipeline');
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache-expected.txt (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache-expected.txt	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1,3 @@
+
+PASS Module integrity check is ignored if target module is already cached 
+

Added: trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache.html (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/module-integrity-bad-value-success-with-cache.html	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Module integrity check is ignored if target module is already cached</title>
+<script src=""
+<script src=""
+</head>
+<body>
+<script>
+promise_test(() => {
+    return new Promise(function (resolve, reject) {
+        var script = document.createElement('script');
+        script.type = 'module';
+        script.src = '';
+        script.integrity = 'sha256-7iiaipciOq3/cXnCpuOPyoC9GgCQw2F6y84mH4CJrGk=';
+        script._onload_ = function () {
+            assert_equals(window.moduleIsLoaded, true);
+            var scriptWithBadValue = document.createElement('script');
+            scriptWithBadValue.type = 'module';
+            scriptWithBadValue.src = '';
+            scriptWithBadValue.integrity = 'sha256-badbeef';
+            scriptWithBadValue._onload_ = resolve;
+            scriptWithBadValue._onerror_ = reject;
+            document.body.appendChild(scriptWithBadValue);
+        };
+        script._onerror_ = reject;
+        document.body.appendChild(script);
+    });
+}, 'Module integrity check is ignored if target module is already cached');
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/modules/script-tests/module-fetch-failure-not-cached.js (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/script-tests/module-fetch-failure-not-cached.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/script-tests/module-fetch-failure-not-cached.js	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1 @@
+window.moduleIsLoaded = true;

Added: trunk/LayoutTests/js/dom/modules/script-tests/module-integrity-bad-value-success-with-cache.js (0 => 224662)


--- trunk/LayoutTests/js/dom/modules/script-tests/module-integrity-bad-value-success-with-cache.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/modules/script-tests/module-integrity-bad-value-success-with-cache.js	2017-11-10 05:28:34 UTC (rev 224662)
@@ -0,0 +1 @@
+window.moduleIsLoaded = true;

Modified: trunk/Source/_javascript_Core/ChangeLog (224661 => 224662)


--- trunk/Source/_javascript_Core/ChangeLog	2017-11-10 03:21:23 UTC (rev 224661)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-11-10 05:28:34 UTC (rev 224662)
@@ -1,3 +1,52 @@
+2017-11-09  Yusuke Suzuki  <utatane....@gmail.com>
+
+        [JSC] Retry module fetching if previous request fails
+        https://bugs.webkit.org/show_bug.cgi?id=178168
+
+        Reviewed by Saam Barati.
+
+        According to the latest spec, the failed fetching operation can be retried if it is requested again.
+        For example,
+
+            <script type="module" integrity="shaXXX-bad" src=""
+            <script type="module" integrity="shaXXX-correct" src=""
+
+        When performing the first module fetching, integrity check fails, and the load of this module becomes failed.
+        But when loading the second module, we do not use the cached failure result in the first module loading.
+        We retry fetching for "./A.js". In this case, we have a correct integrity and module fetching succeeds.
+        This is specified in whatwg/HTML[1]. If the fetching fails, we do not cache it.
+
+        Interestingly, fetching result and instantiation result will be cached if they succeeds. This is because we would
+        like to cache modules based on their URLs. As a result,
+
+            <script type="module" integrity="shaXXX-correct" src=""
+            <script type="module" integrity="shaXXX-bad" src=""
+
+        In the above case, the first loading succeeds. And the second loading also succeeds since the succeeded fetching and
+        instantiation are cached in the module pipeline.
+
+        This patch implements the above semantics. Previously, our module pipeline always caches the result. If the fetching
+        failed, all the subsequent fetching for the same URL fails even if we have different integrity values. We retry fetching
+        if the previous one fails. As an overview of our change,
+
+        1. Fetching result should be cached only if it succeeds. Two or more on-the-fly fetching requests to the same URLs should
+           be unified. But if currently executing one fails, other attempts should retry fetching.
+
+        2. Instantiation should be cached if fetching succeeds.
+
+        3. Satisfying should be cached if it succeeds.
+
+        [1]: https://html.spec.whatwg.org/#fetch-a-single-module-script
+
+        * builtins/ModuleLoaderPrototype.js:
+        (requestFetch):
+        (requestInstantiate):
+        (requestSatisfy):
+        (link):
+        (loadModule):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+
 2017-11-09  Devin Rousso  <web...@devinrousso.com>
 
         Web Inspector: support undo/redo of insertAdjacentHTML

Modified: trunk/Source/_javascript_Core/builtins/ModuleLoaderPrototype.js (224661 => 224662)


--- trunk/Source/_javascript_Core/builtins/ModuleLoaderPrototype.js	2017-11-10 03:21:23 UTC (rev 224661)
+++ trunk/Source/_javascript_Core/builtins/ModuleLoaderPrototype.js	2017-11-10 05:28:34 UTC (rev 224662)
@@ -147,9 +147,23 @@
 
     "use strict";
 
-    if (entry.fetch)
-        return entry.fetch;
+    if (entry.fetch) {
+        var currentAttempt = entry.fetch;
+        if (entry.state !== @ModuleFetch)
+            return currentAttempt;
 
+        return currentAttempt.catch((error) => {
+            // Even if the existing fetching request failed, this attempt may succeed.
+            // For example, previous attempt used invalid integrity="" value. But this
+            // request could have the correct integrity="" value. In that case, we should
+            // retry fetching for this request.
+            // https://html.spec.whatwg.org/#fetch-a-single-module-script
+            if (currentAttempt === entry.fetch)
+                entry.fetch = @undefined;
+            return this.requestFetch(entry, parameters, fetcher);
+        });
+    }
+
     // Hook point.
     // 2. Loader.fetch
     //     https://whatwg.github.io/loader/#browser-fetch
@@ -170,20 +184,20 @@
 
     "use strict";
 
+    // entry.instantiate is set if fetch succeeds.
     if (entry.instantiate)
         return entry.instantiate;
 
     var instantiatePromise = this.requestFetch(entry, parameters, fetcher).then((source) => {
+        // https://html.spec.whatwg.org/#fetch-a-single-module-script
+        // Now fetching request succeeds. Then even if instantiation fails, we should cache it.
+        // Instantiation won't be retried.
+        if (entry.instantiate)
+            return entry;
+        entry.instantiate = instantiatePromise;
+
         var key = entry.key;
         var moduleRecord = this.parseModule(key, source);
-
-        // FIXME: Described in the draft,
-        //   4. Fulfill entry.[[Instantiate]] with instance.
-        // But, instantiate promise should be fulfilled with the entry.
-        // We remove this statement because instantiate promise will be
-        // fulfilled without this "force fulfill" operation.
-        // https://github.com/whatwg/loader/pull/67
-
         var dependenciesMap = moduleRecord.dependenciesMap;
         var requestedModules = this.requestedModules(moduleRecord);
         var dependencies = @newArrayWithSize(requestedModules.length);
@@ -200,13 +214,12 @@
         @setStateToMax(entry, @ModuleSatisfy);
         return entry;
     });
-    entry.instantiate = instantiatePromise;
     return instantiatePromise;
 }
 
-function requestSatisfy(entry, parameters, fetcher)
+function requestSatisfy(entry, parameters, fetcher, visited)
 {
-    // https://whatwg.github.io/loader/#satisfy-instance
+    // https://html.spec.whatwg.org/#internal-module-script-graph-fetching-procedure
 
     "use strict";
 
@@ -213,11 +226,15 @@
     if (entry.satisfy)
         return entry.satisfy;
 
+    visited.@add(entry);
     var satisfyPromise = this.requestInstantiate(entry, parameters, fetcher).then((entry) => {
+        if (entry.satisfy)
+            return entry;
+
         var depLoads = @newArrayWithSize(entry.dependencies.length);
         for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
             var depEntry = entry.dependencies[i];
-            var promise = @undefined;
+            var promise;
 
             // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
             // Stop tracing the circular dependencies.
@@ -228,22 +245,24 @@
             // If we wait for the Satisfy for this module, it construct the circular promise chain and
             // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
             // the Satisfy for this module, we just wait Instantiate for this.
-            if (depEntry.satisfy)
-                promise = depEntry.instantiate;
+            if (visited.@has(depEntry))
+                promise = this.requestInstantiate(depEntry, @undefined, fetcher);
             else {
                 // Currently, module loader do not pass any information for non-top-level module fetching.
-                promise = this.requestSatisfy(depEntry, @undefined, fetcher);
+                promise = this.requestSatisfy(depEntry, @undefined, fetcher, visited);
             }
             @putByValDirect(depLoads, i, promise);
         }
 
-        return @InternalPromise.internalAll(depLoads).then((modules) => {
+        return @InternalPromise.internalAll(depLoads).then((entries) => {
+            if (entry.satisfy)
+                return entry;
             @setStateToMax(entry, @ModuleLink);
+            entry.satisfy = satisfyPromise;
             return entry;
         });
     });
 
-    entry.satisfy = satisfyPromise;
     return satisfyPromise;
 }
 
@@ -251,7 +270,7 @@
 
 function link(entry, fetcher)
 {
-    // https://whatwg.github.io/loader/#link
+    // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-instantiate-a-module-script
 
     "use strict";
 
@@ -319,7 +338,7 @@
     // Take the name and resolve it to the unique identifier for the resource location.
     // For example, take the "jquery" and return the URL for the resource.
     return this.resolve(moduleName, @undefined, fetcher).then((key) => {
-        return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher);
+        return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher, new @Set);
     }).then((entry) => {
         return entry.key;
     });
@@ -350,7 +369,7 @@
 {
     "use strict";
 
-    return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher).then((entry) => {
+    return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher, new @Set).then((entry) => {
         this.linkAndEvaluateModule(entry.key, fetcher);
         return this.getModuleNamespaceObject(entry.module);
     });

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (224661 => 224662)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2017-11-10 03:21:23 UTC (rev 224661)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2017-11-10 05:28:34 UTC (rev 224662)
@@ -563,7 +563,7 @@
     m_regExpMatchesArrayStructure.set(vm, this, createRegExpMatchesArrayStructure(vm, this));
     m_regExpMatchesArrayWithGroupsStructure.set(vm, this, createRegExpMatchesArrayWithGroupsStructure(vm, this));
 
-    m_moduleRecordStructure.set(vm, this, JSModuleRecord::createStructure(vm, this, m_objectPrototype.get()));
+    m_moduleRecordStructure.set(vm, this, JSModuleRecord::createStructure(vm, this, jsNull()));
     m_moduleNamespaceObjectStructure.set(vm, this, JSModuleNamespaceObject::createStructure(vm, this, jsNull()));
     {
         bool isCallable = false;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to