Clean up some includes to make the build a bit faster: DOMPromise
[WebKit-https.git] / Source / WebCore / bindings / js / ScriptModuleLoader.cpp
1 /*
2  * Copyright (C) 2015-2019 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 "CachedScriptFetcher.h"
32 #include "Document.h"
33 #include "Frame.h"
34 #include "JSDOMBinding.h"
35 #include "JSDOMPromiseDeferred.h"
36 #include "LoadableModuleScript.h"
37 #include "MIMETypeRegistry.h"
38 #include "ModuleFetchFailureKind.h"
39 #include "ModuleFetchParameters.h"
40 #include "ScriptController.h"
41 #include "ScriptSourceCode.h"
42 #include "SubresourceIntegrity.h"
43 #include "WebCoreJSClientData.h"
44 #include <JavaScriptCore/Completion.h>
45 #include <JavaScriptCore/JSInternalPromise.h>
46 #include <JavaScriptCore/JSInternalPromiseDeferred.h>
47 #include <JavaScriptCore/JSModuleRecord.h>
48 #include <JavaScriptCore/JSScriptFetchParameters.h>
49 #include <JavaScriptCore/JSScriptFetcher.h>
50 #include <JavaScriptCore/JSSourceCode.h>
51 #include <JavaScriptCore/JSString.h>
52 #include <JavaScriptCore/Symbol.h>
53
54 namespace WebCore {
55
56 ScriptModuleLoader::ScriptModuleLoader(Document& document)
57     : m_document(document)
58 {
59 }
60
61 ScriptModuleLoader::~ScriptModuleLoader()
62 {
63     for (auto& loader : m_loaders)
64         loader->clearClient();
65 }
66
67 static bool isRootModule(JSC::JSValue importerModuleKey)
68 {
69     return importerModuleKey.isSymbol() || importerModuleKey.isUndefined();
70 }
71
72 static Expected<URL, ASCIILiteral> resolveModuleSpecifier(Document& document, const String& specifier, const URL& baseURL)
73 {
74     // https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
75
76     URL absoluteURL(URL(), specifier);
77     if (absoluteURL.isValid())
78         return absoluteURL;
79
80     if (!specifier.startsWith('/') && !specifier.startsWith("./") && !specifier.startsWith("../"))
81         return makeUnexpected("Module specifier does not start with \"/\", \"./\", or \"../\"."_s);
82
83     auto result = document.completeURL(specifier, baseURL);
84     if (!result.isValid())
85         return makeUnexpected("Module name does not resolve to a valid URL."_s);
86     return result;
87 }
88
89 JSC::Identifier ScriptModuleLoader::resolve(JSC::JSGlobalObject*, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleNameValue, JSC::JSValue importerModuleKey, JSC::JSValue)
90 {
91     JSC::VM& vm = exec->vm();
92     auto scope = DECLARE_THROW_SCOPE(vm);
93
94     // We use a Symbol as a special purpose; It means this module is an inline module.
95     // So there is no correct URL to retrieve the module source code. If the module name
96     // value is a Symbol, it is used directly as a module key.
97     if (moduleNameValue.isSymbol())
98         return JSC::Identifier::fromUid(asSymbol(moduleNameValue)->privateName());
99
100     if (!moduleNameValue.isString()) {
101         JSC::throwTypeError(exec, scope, "Importer module key is not a Symbol or a String."_s);
102         return { };
103     }
104
105     String specifier = asString(moduleNameValue)->value(exec);
106     RETURN_IF_EXCEPTION(scope, { });
107
108     URL baseURL;
109     if (isRootModule(importerModuleKey))
110         baseURL = m_document.baseURL();
111     else {
112         ASSERT(importerModuleKey.isString());
113         URL importerModuleRequestURL(URL(), asString(importerModuleKey)->value(exec));
114         ASSERT_WITH_MESSAGE(importerModuleRequestURL.isValid(), "Invalid module referrer never starts importing dependent modules.");
115
116         auto iterator = m_requestURLToResponseURLMap.find(importerModuleRequestURL);
117         ASSERT_WITH_MESSAGE(iterator != m_requestURLToResponseURLMap.end(), "Module referrer must register itself to the map before starting importing dependent modules.");
118         baseURL = iterator->value;
119     }
120
121     auto result = resolveModuleSpecifier(m_document, specifier, baseURL);
122     if (!result) {
123         JSC::throwTypeError(exec, scope, result.error());
124         return { };
125     }
126
127     return JSC::Identifier::fromString(vm, result->string());
128 }
129
130 static void rejectToPropagateNetworkError(DeferredPromise& deferred, ModuleFetchFailureKind failureKind, ASCIILiteral message)
131 {
132     deferred.rejectWithCallback([&] (JSC::ExecState& state, JSDOMGlobalObject&) {
133         // We annotate exception with special private symbol. It allows us to distinguish these errors from the user thrown ones.
134         JSC::VM& vm = state.vm();
135         // FIXME: Propagate more descriptive error.
136         // https://bugs.webkit.org/show_bug.cgi?id=167553
137         auto* error = JSC::createTypeError(&state, message);
138         ASSERT(error);
139         error->putDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName(), JSC::jsNumber(static_cast<int32_t>(failureKind)));
140         return error;
141     });
142 }
143
144 JSC::JSInternalPromise* ScriptModuleLoader::fetch(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue parameters, JSC::JSValue scriptFetcher)
145 {
146     JSC::VM& vm = exec->vm();
147     ASSERT(JSC::jsDynamicCast<JSC::JSScriptFetcher*>(vm, scriptFetcher));
148
149     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
150     auto* jsPromise = JSC::JSInternalPromiseDeferred::tryCreate(exec, &globalObject);
151     RELEASE_ASSERT(jsPromise);
152     auto deferred = DeferredPromise::create(globalObject, *jsPromise);
153     if (moduleKeyValue.isSymbol()) {
154         deferred->reject(TypeError, "Symbol module key should be already fulfilled with the inlined resource."_s);
155         return jsPromise->promise();
156     }
157
158     if (!moduleKeyValue.isString()) {
159         deferred->reject(TypeError, "Module key is not Symbol or String."_s);
160         return jsPromise->promise();
161     }
162
163     // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
164
165     URL completedURL(URL(), asString(moduleKeyValue)->value(exec));
166     if (!completedURL.isValid()) {
167         deferred->reject(TypeError, "Module key is a valid URL."_s);
168         return jsPromise->promise();
169     }
170
171     RefPtr<ModuleFetchParameters> topLevelFetchParameters;
172     if (auto* scriptFetchParameters = JSC::jsDynamicCast<JSC::JSScriptFetchParameters*>(vm, parameters))
173         topLevelFetchParameters = static_cast<ModuleFetchParameters*>(&scriptFetchParameters->parameters());
174
175     auto loader = CachedModuleScriptLoader::create(*this, deferred.get(), *static_cast<CachedScriptFetcher*>(JSC::jsCast<JSC::JSScriptFetcher*>(scriptFetcher)->fetcher()), WTFMove(topLevelFetchParameters));
176     m_loaders.add(loader.copyRef());
177     if (!loader->load(m_document, completedURL)) {
178         loader->clearClient();
179         m_loaders.remove(WTFMove(loader));
180         rejectToPropagateNetworkError(deferred.get(), ModuleFetchFailureKind::WasErrored, "Importing a module script failed."_s);
181         return jsPromise->promise();
182     }
183
184     return jsPromise->promise();
185 }
186
187 URL ScriptModuleLoader::moduleURL(JSC::ExecState& state, JSC::JSValue moduleKeyValue)
188 {
189     if (moduleKeyValue.isSymbol())
190         return m_document.url();
191
192     ASSERT(moduleKeyValue.isString());
193     return URL(URL(), asString(moduleKeyValue)->value(&state));
194 }
195
196 JSC::JSValue ScriptModuleLoader::evaluate(JSC::JSGlobalObject*, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue moduleRecordValue, JSC::JSValue)
197 {
198     JSC::VM& vm = exec->vm();
199     auto scope = DECLARE_THROW_SCOPE(vm);
200
201     // FIXME: Currently, we only support JSModuleRecord.
202     // Once the reflective part of the module loader is supported, we will handle arbitrary values.
203     // https://whatwg.github.io/loader/#registry-prototype-provide
204     auto* moduleRecord = JSC::jsDynamicCast<JSC::JSModuleRecord*>(vm, moduleRecordValue);
205     if (!moduleRecord)
206         return JSC::jsUndefined();
207
208     URL sourceURL = moduleURL(*exec, moduleKeyValue);
209     if (!sourceURL.isValid())
210         return JSC::throwTypeError(exec, scope, "Module key is an invalid URL."_s);
211
212     if (auto* frame = m_document.frame())
213         return frame->script().evaluateModule(sourceURL, *moduleRecord);
214     return JSC::jsUndefined();
215 }
216
217 static JSC::JSInternalPromise* rejectPromise(JSC::ExecState& state, JSDOMGlobalObject& globalObject, ExceptionCode ec, ASCIILiteral message)
218 {
219     auto* jsPromise = JSC::JSInternalPromiseDeferred::tryCreate(&state, &globalObject);
220     RELEASE_ASSERT(jsPromise);
221     auto deferred = DeferredPromise::create(globalObject, *jsPromise);
222     deferred->reject(ec, WTFMove(message));
223     return jsPromise->promise();
224 }
225
226 JSC::JSInternalPromise* ScriptModuleLoader::importModule(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin)
227 {
228     auto& state = *exec;
229     JSC::VM& vm = exec->vm();
230     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
231
232     // If SourceOrigin and/or CachedScriptFetcher is null, we import the module with the default fetcher.
233     // SourceOrigin can be null if the source code is not coupled with the script file.
234     // The examples,
235     //     1. The code evaluated by the inspector.
236     //     2. The other unusual code execution like the evaluation through the NPAPI.
237     //     3. The code from injected bundle's script.
238     //     4. The code from extension script.
239     URL baseURL;
240     RefPtr<JSC::ScriptFetcher> scriptFetcher;
241     if (sourceOrigin.isNull()) {
242         baseURL = m_document.baseURL();
243         scriptFetcher = CachedScriptFetcher::create(m_document.charset());
244     } else {
245         baseURL = URL(URL(), sourceOrigin.string());
246         if (!baseURL.isValid())
247             return rejectPromise(state, globalObject, TypeError, "Importer module key is not a Symbol or a String."_s);
248
249         if (sourceOrigin.fetcher())
250             scriptFetcher = sourceOrigin.fetcher();
251         else
252             scriptFetcher = CachedScriptFetcher::create(m_document.charset());
253     }
254     ASSERT(baseURL.isValid());
255     ASSERT(scriptFetcher);
256
257     auto specifier = moduleName->value(exec);
258     auto result = resolveModuleSpecifier(m_document, specifier, baseURL);
259     if (!result)
260         return rejectPromise(state, globalObject, TypeError, result.error());
261
262     return JSC::importModule(exec, JSC::Identifier::fromString(vm, result->string()), parameters, JSC::JSScriptFetcher::create(vm, WTFMove(scriptFetcher) ));
263 }
264
265 JSC::JSObject* ScriptModuleLoader::createImportMetaProperties(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSModuleRecord*, JSC::JSValue)
266 {
267     auto& vm = exec->vm();
268     auto scope = DECLARE_THROW_SCOPE(vm);
269
270     URL sourceURL = moduleURL(*exec, moduleKeyValue);
271     ASSERT(sourceURL.isValid());
272
273     auto* metaProperties = JSC::constructEmptyObject(exec, globalObject->nullPrototypeObjectStructure());
274     RETURN_IF_EXCEPTION(scope, nullptr);
275
276     metaProperties->putDirect(vm, JSC::Identifier::fromString(vm, "url"), JSC::jsString(vm, sourceURL.string()));
277     RETURN_IF_EXCEPTION(scope, nullptr);
278
279     return metaProperties;
280 }
281
282 void ScriptModuleLoader::notifyFinished(CachedModuleScriptLoader& loader, RefPtr<DeferredPromise> promise)
283 {
284     // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
285
286     if (!m_loaders.remove(&loader))
287         return;
288     loader.clearClient();
289
290     auto& cachedScript = *loader.cachedScript();
291
292     if (cachedScript.resourceError().isAccessControl()) {
293         promise->reject(TypeError, "Cross-origin script load denied by Cross-Origin Resource Sharing policy."_s);
294         return;
295     }
296
297     if (cachedScript.errorOccurred()) {
298         rejectToPropagateNetworkError(*promise, ModuleFetchFailureKind::WasErrored, "Importing a module script failed."_s);
299         return;
300     }
301
302     if (cachedScript.wasCanceled()) {
303         rejectToPropagateNetworkError(*promise, ModuleFetchFailureKind::WasCanceled, "Importing a module script is canceled."_s);
304         return;
305     }
306
307     if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(cachedScript.response().mimeType())) {
308         // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
309         // The result of extracting a MIME type from response's header list (ignoring parameters) is not a JavaScript MIME type.
310         // 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.
311         promise->reject(TypeError, makeString("'", cachedScript.response().mimeType(), "' is not a valid JavaScript MIME type."));
312         return;
313     }
314
315     if (auto* parameters = loader.parameters()) {
316         if (!matchIntegrityMetadata(cachedScript, parameters->integrity())) {
317             promise->reject(TypeError, makeString("Cannot load script ", cachedScript.url().stringCenterEllipsizedToLength(), ". Failed integrity metadata check."));
318             return;
319         }
320     }
321
322     m_requestURLToResponseURLMap.add(cachedScript.url(), cachedScript.response().url());
323     promise->resolveWithCallback([&] (JSC::ExecState& state, JSDOMGlobalObject&) {
324         return JSC::JSSourceCode::create(state.vm(),
325             JSC::SourceCode { ScriptSourceCode { &cachedScript, JSC::SourceProviderSourceType::Module, loader.scriptFetcher() }.jsSourceCode() });
326     });
327 }
328
329 }