blob: c74aca0c08602e775bf12b33a2cf185e2a4e15bc [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "JSAPIGlobalObject.h"
#if JSC_OBJC_API_ENABLED
#import "APICast.h"
#import "CallFrameInlines.h"
#import "CatchScope.h"
#import "Completion.h"
#import "Error.h"
#import "Exception.h"
#import "JSContextInternal.h"
#import "JSInternalPromise.h"
#import "JSModuleLoader.h"
#import "JSNativeStdFunction.h"
#import "JSPromise.h"
#import "JSScriptInternal.h"
#import "JSSourceCode.h"
#import "JSValueInternal.h"
#import "JSVirtualMachineInternal.h"
#import "JavaScriptCore.h"
#import "ObjectConstructor.h"
#import "SourceOrigin.h"
#import <wtf/URL.h>
namespace JSC {
const ClassInfo JSAPIGlobalObject::s_info = { "GlobalObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSAPIGlobalObject) };
const GlobalObjectMethodTable JSAPIGlobalObject::s_globalObjectMethodTable = {
&supportsRichSourceInfo,
&shouldInterruptScript,
&javaScriptRuntimeFlags,
nullptr, // queueTaskToEventLoop
&shouldInterruptScriptBeforeTimeout,
&moduleLoaderImportModule, // moduleLoaderImportModule
&moduleLoaderResolve, // moduleLoaderResolve
&moduleLoaderFetch, // moduleLoaderFetch
&moduleLoaderCreateImportMetaProperties, // moduleLoaderCreateImportMetaProperties
moduleLoaderEvaluate, // moduleLoaderEvaluate
nullptr, // promiseRejectionTracker
nullptr, // defaultLanguage
nullptr, // compileStreaming
nullptr, // instantiateStreaming
};
static Expected<URL, String> computeValidImportSpecifier(const URL& base, const String& specifier)
{
URL absoluteURL(URL(), specifier);
if (absoluteURL.isValid())
return absoluteURL;
if (!specifier.startsWith('/') && !specifier.startsWith("./") && !specifier.startsWith("../"))
return makeUnexpected(makeString("Module specifier: "_s, specifier, " does not start with \"/\", \"./\", or \"../\"."_s));
if (specifier.startsWith('/')) {
absoluteURL = URL(URL({ }, "file://"), specifier);
if (absoluteURL.isValid())
return absoluteURL;
}
if (base == URL())
return makeUnexpected("Could not determine the base URL for loading."_s);
if (!base.isValid())
return makeUnexpected(makeString("Referrering script's url is not valid: "_s, base.string()));
absoluteURL = URL(base, specifier);
if (absoluteURL.isValid())
return absoluteURL;
return makeUnexpected(makeString("Could not form valid URL from identifier and base. Tried:"_s, absoluteURL.string()));
}
Identifier JSAPIGlobalObject::moduleLoaderResolve(JSGlobalObject* globalObject, JSModuleLoader*, JSValue key, JSValue referrer, JSValue)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT_UNUSED(globalObject, globalObject == globalObject);
ASSERT(key.isString() || key.isSymbol());
String name = key.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
URL base;
if (JSString* referrerString = jsDynamicCast<JSString*>(vm, referrer)) {
String value = referrerString->value(globalObject);
RETURN_IF_EXCEPTION(scope, { });
URL referrerURL({ }, value);
RELEASE_ASSERT(referrerURL.isValid());
base = WTFMove(referrerURL);
}
auto result = computeValidImportSpecifier(base, name);
if (result)
return Identifier::fromString(vm, result.value());
throwVMError(globalObject, scope, createError(globalObject, result.error()));
return { };
}
JSInternalPromise* JSAPIGlobalObject::moduleLoaderImportModule(JSGlobalObject* globalObject, JSModuleLoader*, JSString* specifierValue, JSValue, const SourceOrigin& sourceOrigin)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
auto reject = [&] (JSValue exception) -> JSInternalPromise* {
scope.clearException();
auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
// FIXME: We could have error since any JS call can throw stack-overflow errors.
// https://bugs.webkit.org/show_bug.cgi?id=203402
promise->reject(globalObject, exception);
scope.clearException();
return promise;
};
auto import = [&] (URL& url) {
auto result = importModule(globalObject, Identifier::fromString(vm, url), jsUndefined(), jsUndefined());
if (UNLIKELY(scope.exception()))
return reject(scope.exception());
return result;
};
auto specifier = specifierValue->value(globalObject);
if (UNLIKELY(scope.exception())) {
JSValue exception = scope.exception();
scope.clearException();
return reject(exception);
}
String referrer = !sourceOrigin.isNull() ? sourceOrigin.string() : String();
URL baseURL(URL(), referrer);
auto result = computeValidImportSpecifier(baseURL, specifier);
if (result)
return import(result.value());
return reject(createError(globalObject, result.error()));
}
JSInternalPromise* JSAPIGlobalObject::moduleLoaderFetch(JSGlobalObject* globalObject, JSModuleLoader*, JSValue key, JSValue, JSValue)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
ASSERT(globalObject == globalObject);
JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];
JSInternalPromise* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
Identifier moduleKey = key.toPropertyKey(globalObject);
if (UNLIKELY(scope.exception())) {
JSValue exception = scope.exception();
scope.clearException();
promise->reject(globalObject, exception);
scope.clearException();
return promise;
}
if (UNLIKELY(![context moduleLoaderDelegate])) {
promise->reject(globalObject, createError(globalObject, "No module loader provided."));
return promise;
}
auto strongPromise = Strong<JSInternalPromise>(vm, promise);
auto* resolve = JSNativeStdFunction::create(vm, globalObject, 1, "resolve", [=] (JSGlobalObject* globalObject, CallFrame* callFrame) {
// This captures the globalObject but that's ok because our structure keeps it alive anyway.
VM& vm = globalObject->vm();
JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];
id script = valueToObject(context, toRef(globalObject, callFrame->argument(0)));
MarkedArgumentBuffer args;
auto rejectPromise = [&] (String message) {
strongPromise.get()->reject(globalObject, createTypeError(globalObject, message));
return encodedJSUndefined();
};
if (UNLIKELY(![script isKindOfClass:[JSScript class]]))
return rejectPromise("First argument of resolution callback is not a JSScript"_s);
JSScript* jsScript = static_cast<JSScript *>(script);
JSSourceCode* source = [jsScript jsSourceCode];
if (UNLIKELY([jsScript type] != kJSScriptTypeModule))
return rejectPromise("The JSScript that was provided did not have expected type of kJSScriptTypeModule."_s);
NSURL *sourceURL = [jsScript sourceURL];
String oldModuleKey { [sourceURL absoluteString] };
if (UNLIKELY(Identifier::fromString(vm, oldModuleKey) != moduleKey))
return rejectPromise(makeString("The same JSScript was provided for two different identifiers, previously: ", oldModuleKey, " and now: ", moduleKey.string()));
strongPromise.get()->resolve(globalObject, source);
return encodedJSUndefined();
});
auto* reject = JSNativeStdFunction::create(vm, globalObject, 1, "reject", [=] (JSGlobalObject*, CallFrame* callFrame) {
strongPromise.get()->reject(globalObject, callFrame->argument(0));
return encodedJSUndefined();
});
[[context moduleLoaderDelegate] context:context fetchModuleForIdentifier:[::JSValue valueWithJSValueRef:toRef(globalObject, key) inContext:context] withResolveHandler:[::JSValue valueWithJSValueRef:toRef(globalObject, resolve) inContext:context] andRejectHandler:[::JSValue valueWithJSValueRef:toRef(globalObject, reject) inContext:context]];
if (context.exception) {
promise->reject(globalObject, toJS(globalObject, [context.exception JSValueRef]));
context.exception = nil;
}
return promise;
}
JSObject* JSAPIGlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObject* globalObject, JSModuleLoader*, JSValue key, JSModuleRecord*, JSValue)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* metaProperties = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
RETURN_IF_EXCEPTION(scope, nullptr);
metaProperties->putDirect(vm, Identifier::fromString(vm, "filename"), key);
RETURN_IF_EXCEPTION(scope, nullptr);
return metaProperties;
}
JSValue JSAPIGlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject, JSModuleLoader* moduleLoader, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];
id <JSModuleLoaderDelegate> moduleLoaderDelegate = [context moduleLoaderDelegate];
NSURL *url = nil;
if ([moduleLoaderDelegate respondsToSelector:@selector(willEvaluateModule:)] || [moduleLoaderDelegate respondsToSelector:@selector(didEvaluateModule:)]) {
String moduleKey = key.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
url = [NSURL URLWithString:static_cast<NSString *>(moduleKey)];
}
if ([moduleLoaderDelegate respondsToSelector:@selector(willEvaluateModule:)])
[moduleLoaderDelegate willEvaluateModule:url];
scope.release();
JSValue result = moduleLoader->evaluateNonVirtual(globalObject, key, moduleRecordValue, scriptFetcher);
if ([moduleLoaderDelegate respondsToSelector:@selector(didEvaluateModule:)])
[moduleLoaderDelegate didEvaluateModule:url];
return result;
}
JSValue JSAPIGlobalObject::loadAndEvaluateJSScriptModule(const JSLockHolder&, JSScript *script)
{
ASSERT(script.type == kJSScriptTypeModule);
VM& vm = this->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Identifier key = Identifier::fromString(vm, String { [[script sourceURL] absoluteString] });
JSInternalPromise* promise = importModule(this, key, jsUndefined(), jsUndefined());
RETURN_IF_EXCEPTION(scope, { });
auto* result = JSPromise::create(vm, this->promiseStructure());
result->resolve(this, promise);
RETURN_IF_EXCEPTION(scope, { });
return result;
}
}
#endif // JSC_OBJC_API_ENABLED