Better error messages for module loader SPI
[WebKit-https.git] / Source / JavaScriptCore / API / JSAPIGlobalObject.mm
1 /*
2  * Copyright (C) 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "JSAPIGlobalObject.h"
28
29 #if JSC_OBJC_API_ENABLED
30
31 #import "APICast.h"
32 #import "CatchScope.h"
33 #import "Completion.h"
34 #import "Error.h"
35 #import "Exception.h"
36 #import "JSContextInternal.h"
37 #import "JSInternalPromiseDeferred.h"
38 #import "JSNativeStdFunction.h"
39 #import "JSScriptInternal.h"
40 #import "JSSourceCode.h"
41 #import "JSValueInternal.h"
42 #import "JSVirtualMachineInternal.h"
43 #import "JavaScriptCore.h"
44 #import "ObjectConstructor.h"
45 #import "SourceOrigin.h"
46
47 #import <wtf/URL.h>
48
49 namespace JSC {
50
51 const ClassInfo JSAPIGlobalObject::s_info = { "GlobalObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSAPIGlobalObject) };
52
53 const GlobalObjectMethodTable JSAPIGlobalObject::s_globalObjectMethodTable = {
54     &supportsRichSourceInfo,
55     &shouldInterruptScript,
56     &javaScriptRuntimeFlags,
57     nullptr, // queueTaskToEventLoop
58     &shouldInterruptScriptBeforeTimeout,
59     &moduleLoaderImportModule, // moduleLoaderImportModule
60     &moduleLoaderResolve, // moduleLoaderResolve
61     &moduleLoaderFetch, // moduleLoaderFetch
62     &moduleLoaderCreateImportMetaProperties, // moduleLoaderCreateImportMetaProperties
63     nullptr, // moduleLoaderEvaluate
64     nullptr, // promiseRejectionTracker
65     nullptr, // defaultLanguage
66     nullptr, // compileStreaming
67     nullptr, // instantiateStreaming
68 };
69
70 Identifier JSAPIGlobalObject::moduleLoaderResolve(JSGlobalObject* globalObject, ExecState* exec, JSModuleLoader*, JSValue key, JSValue referrer, JSValue)
71 {
72     VM& vm = exec->vm();
73     auto scope = DECLARE_THROW_SCOPE(vm);
74     ASSERT_UNUSED(globalObject, globalObject == exec->lexicalGlobalObject());
75     ASSERT(key.isString() || key.isSymbol());
76     String name =  key.toWTFString(exec);
77
78     URL referrerURL(URL(), jsCast<JSString*>(referrer)->tryGetValue());
79     RELEASE_ASSERT(referrerURL.isValid());
80
81     URL url = URL(referrerURL, name);
82     if (url.isValid())
83         return Identifier::fromString(exec, url);
84
85     throwVMError(exec, scope, "Could not form valid URL from identifier and base"_s);
86     return { };
87 }
88
89 JSInternalPromise* JSAPIGlobalObject::moduleLoaderImportModule(JSGlobalObject* globalObject, ExecState* exec, JSModuleLoader*, JSString* specifierValue, JSValue, const SourceOrigin& sourceOrigin)
90 {
91     VM& vm = globalObject->vm();
92     auto scope = DECLARE_CATCH_SCOPE(vm);
93     auto reject = [&] (JSValue exception) -> JSInternalPromise* {
94         scope.clearException();
95         auto* promise = JSInternalPromiseDeferred::tryCreate(exec, globalObject);
96         scope.clearException();
97         return promise->reject(exec, exception);
98     };
99
100     auto import = [&] (URL& url) {
101         auto result = importModule(exec, Identifier::fromString(&vm, url), jsUndefined(), jsUndefined());
102         if (UNLIKELY(scope.exception()))
103             return reject(scope.exception());
104         return result;
105     };
106
107     auto specifier = specifierValue->value(exec);
108     if (UNLIKELY(scope.exception())) {
109         JSValue exception = scope.exception();
110         scope.clearException();
111         return reject(exception);
112     }
113
114     URL absoluteURL(URL(), specifier);
115     if (absoluteURL.isValid())
116         return import(absoluteURL);
117
118     if (!specifier.startsWith('/') && !specifier.startsWith("./") && !specifier.startsWith("../"))
119         return reject(createError(exec, makeString("Module specifier: ", specifier, " does not start with \"/\", \"./\", or \"../\"."_s)));
120
121     if (specifier.startsWith('/')) {
122         absoluteURL = URL(URL({ }, "file://"), specifier);
123         if (absoluteURL.isValid())
124             return import(absoluteURL);
125     }
126
127     auto noBaseErrorMessage = "Could not determine the base URL for loading."_s;
128     if (sourceOrigin.isNull())
129         return reject(createError(exec, makeString(noBaseErrorMessage, " Referring script has no URL."_s)));
130
131     auto referrer = sourceOrigin.string();
132     URL baseURL(URL(), referrer);
133     if (!baseURL.isValid())
134         return reject(createError(exec, makeString(noBaseErrorMessage, " Referring script's URL is not valid: "_s, baseURL.string())));
135
136     URL url(baseURL, specifier);
137     if (!url.isValid())
138         return reject(createError(exec, makeString("could not determine a valid URL for module specifier. Tried: "_s, url.string())));
139
140     return import(url);
141 }
142
143 JSInternalPromise* JSAPIGlobalObject::moduleLoaderFetch(JSGlobalObject* globalObject, ExecState* exec, JSModuleLoader*, JSValue key, JSValue, JSValue)
144 {
145     VM& vm = globalObject->vm();
146     auto scope = DECLARE_CATCH_SCOPE(vm);
147
148     ASSERT(globalObject == exec->lexicalGlobalObject());
149     JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject->globalExec())];
150
151     JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::tryCreate(exec, globalObject);
152     RETURN_IF_EXCEPTION(scope, nullptr);
153
154     Identifier moduleKey = key.toPropertyKey(exec);
155     if (UNLIKELY(scope.exception())) {
156         JSValue exception = scope.exception();
157         scope.clearException();
158         return deferred->reject(exec, exception);
159     }
160
161     if (UNLIKELY(![context moduleLoaderDelegate]))
162         return deferred->reject(exec, createError(exec, "No module loader provided."));
163
164     auto deferredPromise = Strong<JSInternalPromiseDeferred>(vm, deferred);
165     auto strongKey = Strong<JSString>(vm, jsSecureCast<JSString*>(vm, key));
166     auto* resolve = JSNativeStdFunction::create(vm, globalObject, 1, "resolve", [=] (ExecState* exec) {
167         // This captures the globalObject but that's ok because our structure keeps it alive anyway.
168         JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject->globalExec())];
169         id script = valueToObject(context, toRef(exec, exec->argument(0)));
170
171         MarkedArgumentBuffer args;
172         if (UNLIKELY(![script isKindOfClass:[JSScript class]])) {
173             args.append(createTypeError(exec, "First argument of resolution callback is not a JSScript"));
174             call(exec, deferredPromise->JSPromiseDeferred::reject(), args, "This should never be seen...");
175             return encodedJSUndefined();
176         }
177
178         args.append([static_cast<JSScript *>(script) jsSourceCode:moduleKey]);
179         call(exec, deferredPromise->JSPromiseDeferred::resolve(), args, "This should never be seen...");
180         return encodedJSUndefined();
181     });
182
183     auto* reject = JSNativeStdFunction::create(vm, globalObject, 1, "reject", [=] (ExecState* exec) {
184         MarkedArgumentBuffer args;
185         args.append(exec->argument(0));
186
187         call(exec, deferredPromise->JSPromiseDeferred::reject(), args, "This should never be seen...");
188         return encodedJSUndefined();
189     });
190
191     [[context moduleLoaderDelegate] context:context fetchModuleForIdentifier:[::JSValue valueWithJSValueRef:toRef(exec, key) inContext:context] withResolveHandler:[::JSValue valueWithJSValueRef:toRef(exec, resolve) inContext:context] andRejectHandler:[::JSValue valueWithJSValueRef:toRef(exec, reject) inContext:context]];
192     if (context.exception) {
193         deferred->reject(exec, toJS(exec, [context.exception JSValueRef]));
194         context.exception = nil;
195     }
196     return deferred->promise();
197 }
198
199 JSObject* JSAPIGlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObject* globalObject, ExecState* exec, JSModuleLoader*, JSValue key, JSModuleRecord*, JSValue)
200 {
201     VM& vm = exec->vm();
202     auto scope = DECLARE_THROW_SCOPE(vm);
203
204     JSObject* metaProperties = constructEmptyObject(exec, globalObject->nullPrototypeObjectStructure());
205     RETURN_IF_EXCEPTION(scope, nullptr);
206
207     metaProperties->putDirect(vm, Identifier::fromString(&vm, "filename"), key);
208     RETURN_IF_EXCEPTION(scope, nullptr);
209
210     return metaProperties;
211 }
212
213 }
214
215 #endif // JSC_OBJC_API_ENABLED