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