b3f65b2c332e469e148dbb532189ec9b1435afd4
[WebKit-https.git] / Source / WebCore / bindings / js / ScriptModuleLoader.cpp
1 /*
2  * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ScriptModuleLoader.h"
28
29 #include "CachedModuleScriptLoader.h"
30 #include "CachedScript.h"
31 #include "Document.h"
32 #include "Frame.h"
33 #include "JSDOMBinding.h"
34 #include "JSElement.h"
35 #include "LoadableModuleScript.h"
36 #include "MIMETypeRegistry.h"
37 #include "ScriptController.h"
38 #include "ScriptElement.h"
39 #include "ScriptSourceCode.h"
40 #include <runtime/JSInternalPromise.h>
41 #include <runtime/JSInternalPromiseDeferred.h>
42 #include <runtime/JSModuleRecord.h>
43 #include <runtime/JSSourceCode.h>
44 #include <runtime/JSString.h>
45 #include <runtime/Symbol.h>
46
47 namespace WebCore {
48
49 ScriptModuleLoader::ScriptModuleLoader(Document& document)
50     : m_document(document)
51 {
52 }
53
54 ScriptModuleLoader::~ScriptModuleLoader()
55 {
56     for (auto& loader : m_loaders)
57         const_cast<CachedModuleScriptLoader&>(loader.get()).clearClient();
58 }
59
60
61 static bool isRootModule(JSC::JSValue importerModuleKey)
62 {
63     return importerModuleKey.isSymbol() || importerModuleKey.isUndefined();
64 }
65
66 JSC::JSInternalPromise* ScriptModuleLoader::resolve(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleNameValue, JSC::JSValue importerModuleKey, JSC::JSValue)
67 {
68     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
69     auto& jsPromise = *JSC::JSInternalPromiseDeferred::create(exec, &globalObject);
70     auto promise = DeferredPromise::create(globalObject, jsPromise);
71
72     // We use a Symbol as a special purpose; It means this module is an inline module.
73     // So there is no correct URL to retrieve the module source code. If the module name
74     // value is a Symbol, it is used directly as a module key.
75     if (moduleNameValue.isSymbol()) {
76         promise->resolve<IDLAny>(toJS(exec, &globalObject, asSymbol(moduleNameValue)->privateName()));
77         return jsPromise.promise();
78     }
79
80     // https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
81
82     if (!moduleNameValue.isString()) {
83         promise->reject(TypeError, ASCIILiteral("Module specifier is not Symbol or String."));
84         return jsPromise.promise();
85     }
86
87     String specifier = asString(moduleNameValue)->value(exec);
88
89     // 1. Apply the URL parser to specifier. If the result is not failure, return the result.
90     URL absoluteURL(URL(), specifier);
91     if (absoluteURL.isValid()) {
92         promise->resolve<IDLDOMString>(absoluteURL.string());
93         return jsPromise.promise();
94     }
95
96     // 2. If specifier does not start with the character U+002F SOLIDUS (/), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./),
97     //    or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (../), return failure and abort these steps.
98     if (!specifier.startsWith('/') && !specifier.startsWith("./") && !specifier.startsWith("../")) {
99         promise->reject(TypeError, ASCIILiteral("Module specifier does not start with \"/\", \"./\", or \"../\"."));
100         return jsPromise.promise();
101     }
102
103     // 3. Return the result of applying the URL parser to specifier with script's base URL as the base URL.
104
105     URL completedURL;
106
107     if (isRootModule(importerModuleKey))
108         completedURL = m_document.completeURL(specifier);
109     else if (importerModuleKey.isString()) {
110         URL importerModuleRequestURL(URL(), asString(importerModuleKey)->value(exec));
111         if (!importerModuleRequestURL.isValid()) {
112             promise->reject(TypeError, ASCIILiteral("Importer module key is an invalid URL."));
113             return jsPromise.promise();
114         }
115
116         URL importerModuleResponseURL = m_requestURLToResponseURLMap.get(importerModuleRequestURL);
117         if (!importerModuleResponseURL.isValid()) {
118             promise->reject(TypeError, ASCIILiteral("Importer module has an invalid response URL."));
119             return jsPromise.promise();
120         }
121
122         completedURL = m_document.completeURL(specifier, importerModuleResponseURL);
123     } else {
124         promise->reject(TypeError, ASCIILiteral("Importer module key is not Symbol or String."));
125         return jsPromise.promise();
126     }
127
128     if (!completedURL.isValid()) {
129         promise->reject(TypeError, ASCIILiteral("Module name constructs an invalid URL."));
130         return jsPromise.promise();
131     }
132
133     promise->resolve<IDLDOMString>(completedURL.string());
134     return jsPromise.promise();
135 }
136
137 JSC::JSInternalPromise* ScriptModuleLoader::fetch(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue initiator)
138 {
139     // FIXME: What guarantees these are true? Why don't we need to check?
140     ASSERT(JSC::jsDynamicCast<JSElement*>(initiator));
141     ASSERT(isScriptElement(JSC::jsDynamicCast<JSElement*>(initiator)->wrapped()));
142
143     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
144     auto& jsPromise = *JSC::JSInternalPromiseDeferred::create(exec, &globalObject);
145     auto deferred = DeferredPromise::create(globalObject, jsPromise);
146     if (moduleKeyValue.isSymbol()) {
147         deferred->reject(TypeError, ASCIILiteral("Symbol module key should be already fulfilled with the inlined resource."));
148         return jsPromise.promise();
149     }
150
151     if (!moduleKeyValue.isString()) {
152         deferred->reject(TypeError, ASCIILiteral("Module key is not Symbol or String."));
153         return jsPromise.promise();
154     }
155
156     // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
157
158     URL completedURL(URL(), asString(moduleKeyValue)->value(exec));
159     if (!completedURL.isValid()) {
160         deferred->reject(TypeError, ASCIILiteral("Module key is a valid URL."));
161         return jsPromise.promise();
162     }
163
164     if (auto* frame = m_document.frame()) {
165         auto loader = CachedModuleScriptLoader::create(*this, deferred.get());
166         m_loaders.add(loader.copyRef());
167         if (!loader->load(downcastScriptElement(JSC::jsCast<JSElement*>(initiator)->wrapped()), completedURL)) {
168             loader->clearClient();
169             m_loaders.remove(WTFMove(loader));
170             deferred->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
171             return jsPromise.promise();
172         }
173     }
174
175     return jsPromise.promise();
176 }
177
178 JSC::JSValue ScriptModuleLoader::evaluate(JSC::JSGlobalObject*, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue moduleRecordValue, JSC::JSValue)
179 {
180     JSC::VM& vm = exec->vm();
181     auto scope = DECLARE_THROW_SCOPE(vm);
182
183     // FIXME: Currently, we only support JSModuleRecord.
184     // Once the reflective part of the module loader is supported, we will handle arbitrary values.
185     // https://whatwg.github.io/loader/#registry-prototype-provide
186     auto* moduleRecord = jsDynamicDowncast<JSC::JSModuleRecord*>(moduleRecordValue);
187     if (!moduleRecord)
188         return JSC::jsUndefined();
189
190     URL sourceURL;
191     if (moduleKeyValue.isSymbol())
192         sourceURL = m_document.url();
193     else if (moduleKeyValue.isString())
194         sourceURL = URL(URL(), asString(moduleKeyValue)->value(exec));
195     else
196         return JSC::throwTypeError(exec, scope, ASCIILiteral("Module key is not Symbol or String."));
197
198     if (!sourceURL.isValid())
199         return JSC::throwTypeError(exec, scope, ASCIILiteral("Module key is an invalid URL."));
200
201     if (auto* frame = m_document.frame())
202         return frame->script().evaluateModule(sourceURL, *moduleRecord);
203     return JSC::jsUndefined();
204 }
205
206 void ScriptModuleLoader::notifyFinished(CachedModuleScriptLoader& loader, RefPtr<DeferredPromise> promise)
207 {
208     // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
209
210     if (!m_loaders.remove(&loader))
211         return;
212     loader.clearClient();
213
214     auto& cachedScript = *loader.cachedScript();
215
216     bool failed = false;
217
218     if (cachedScript.resourceError().isAccessControl()) {
219         promise->reject(TypeError, ASCIILiteral("Cross-origin script load denied by Cross-Origin Resource Sharing policy."));
220         return;
221     }
222
223     if (cachedScript.errorOccurred())
224         failed = true;
225     else if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(cachedScript.response().mimeType())) {
226         // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
227         // The result of extracting a MIME type from response's header list (ignoring parameters) is not a JavaScript MIME type.
228         // 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.
229         promise->reject(TypeError, makeString("'", cachedScript.response().mimeType(), "' is not a valid JavaScript MIME type."));
230         return;
231     }
232
233     auto* frame = m_document.frame();
234     if (!frame)
235         return;
236
237     if (failed) {
238         // Reject a promise to propagate the error back all the way through module promise chains to the script element.
239         promise->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
240         return;
241     }
242
243     if (cachedScript.wasCanceled()) {
244         promise->reject(frame->script().moduleLoaderFetchingIsCanceledSymbol());
245         return;
246     }
247
248     m_requestURLToResponseURLMap.add(cachedScript.url(), cachedScript.response().url());
249     ScriptSourceCode scriptSourceCode(&cachedScript, JSC::SourceProviderSourceType::Module);
250     promise->resolveWithCallback([] (JSC::ExecState& state, JSDOMGlobalObject&, JSC::SourceCode sourceCode) {
251         return JSC::JSSourceCode::create(state.vm(), WTFMove(sourceCode));
252     }, scriptSourceCode.jsSourceCode());
253 }
254
255 }