[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/API/JSAPIGlobalObject.mm [Back]  [Original]

/**
 * Copyright (C) 2019-2023 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 "Completion.h"
#import "Error.h"
#import "Exception.h"
#import "GlobalObjectMethodTable.h"
#import "IdentifierInlines.h"
#import "JSContextInternal.h"
#import "JSModuleLoader.h"
#import "JSNativeStdFunction.h"
#import "JSObjectInlines.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 "StrongInlines.h"
#import "TopExceptionScope.h"
#import 
#import 

namespace JSC {

const GlobalObjectMethodTable* JSAPIGlobalObject::globalObjectMethodTable()
{
    static constexpr GlobalObjectMethodTable table = {
        &supportsRichSourceInfo,
        &shouldInterruptScript,
        &javaScriptRuntimeFlags,
        &shouldInterruptScriptBeforeTimeout,
        &moduleLoaderImportModule, // moduleLoaderImportModule
        &moduleLoaderResolve, // moduleLoaderResolve
        &moduleLoaderFetch, // moduleLoaderFetch
        &moduleLoaderCreateImportMetaProperties, // moduleLoaderCreateImportMetaProperties
        &moduleLoaderEvaluate, // moduleLoaderEvaluate
        &promiseRejectionTracker,
        &reportUncaughtExceptionAtEventLoop,
        ¤tScriptExecutionOwner,
        &scriptExecutionStatus,
        &reportViolationForUnsafeEval,
        nullptr, // defaultLanguage
        nullptr, // compileStreaming
        nullptr, // instantiateStreaming
        &deriveShadowRealmGlobalObject,
        &codeForEval,
        &canCompileStrings,
        &trustedScriptStructure,
    };
    return &table;
};

void JSAPIGlobalObject::reportUncaughtExceptionAtEventLoop(JSGlobalObject* globalObject, Exception* exception)
{
    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];
    [context notifyException:toRef(globalObject->vm(), exception->value())];
}

static Expected computeValidImportSpecifier(const URL& base, const String& specifier)
{
    URL absoluteURL(specifier);
    if (absoluteURL.isValid())
        return absoluteURL;

    if (!specifier.startsWith('/') && !specifier.startsWith("./"_s) && !specifier.startsWith("../"_s))
        return makeUnexpected(makeString("Module specifier: "_s, specifier, " does not start with \"/\", \"./\", or \"../\". Referenced from: "_s, base.string()));

    if (specifier.startsWith('/')) {
        absoluteURL = URL(URL("file://"_s), 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, RefPtr, bool)
{
    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 = dynamicDowncast(referrer)) {
        String value = referrerString->value(globalObject);
        RETURN_IF_EXCEPTION(scope, { });
        // It can be invalid URL because dynamic-import will be resolved with caller's source origin (this becomes referrer), and it can be non valid URL.
        // But this is handled well in computeValidImportSpecifier.
        base = URL { { }, value };
    }

    auto result = computeValidImportSpecifier(base, name);
    if (result)
        return Identifier::fromString(vm, result.value().string());

    throwVMError(globalObject, scope, createError(globalObject, result.error()));
    return { };
}

JSPromise* JSAPIGlobalObject::moduleLoaderImportModule(JSGlobalObject* globalObject, JSModuleLoader*, JSString* specifierValue, RefPtr fetchParams, const SourceOrigin& sourceOrigin, bool deferred)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);
    auto reject = [&] (ThrowScope& scope) -> JSPromise* {
        auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
        return promise->rejectWithCaughtException(vm, scope);
    };

    auto specifier = specifierValue->value(globalObject);
    RETURN_IF_EXCEPTION(scope, reject(scope));

    Identifier originURL;
    if (const String& originString = sourceOrigin.string(); !originString.isNull())
        originURL = Identifier::fromString(vm, originString);
    auto result = importModule(globalObject, Identifier::fromString(vm, specifier), originURL, WTF::move(fetchParams), nullptr, deferred);
    RETURN_IF_EXCEPTION(scope, reject(scope));
    return result;
}

JSPromise* JSAPIGlobalObject::moduleLoaderFetch(JSGlobalObject* globalObject, JSModuleLoader*, JSValue key, RefPtr, RefPtr)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    ASSERT(globalObject == globalObject);
    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];

    JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());

    Identifier moduleKey = key.toPropertyKey(globalObject);
    RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope));

    if (![context moduleLoaderDelegate]) [[unlikely]] {
        scope.release();
        promise->reject(vm, createError(globalObject, "No module loader provided."_s));
        return promise;
    }

    auto* resolve = JSNativeStdFunction::create(vm, globalObject, 1, "resolve"_s, [promise, moduleKey] (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)));

        auto rejectPromise = [&] (String message) {
            promise->reject(vm, createTypeError(globalObject, message));
            return encodedJSUndefined();
        };

        if (![script isKindOfClass:[JSScript class]]) [[unlikely]]
            return rejectPromise("First argument of resolution callback is not a JSScript"_s);

        RetainPtr jsScript = static_cast(script);

        JSSourceCode* source = [jsScript.get() jsSourceCode];
        if ([jsScript.get() type] != kJSScriptTypeModule) [[unlikely]]
            return rejectPromise("The JSScript that was provided did not have expected type of kJSScriptTypeModule."_s);

        NSURL *sourceURL = [jsScript.get() sourceURL];
        String oldModuleKey { [sourceURL absoluteString] };
        if (Identifier::fromString(vm, oldModuleKey) != moduleKey) [[unlikely]]
            return rejectPromise(makeString("The same JSScript was provided for two different identifiers, previously: "_s, oldModuleKey, " and now: "_s, moduleKey.string()));

        promise->resolve(globalObject, vm, source);
        return encodedJSUndefined();
    }, promise);

    auto* reject = JSNativeStdFunction::create(vm, globalObject, 1, "reject"_s, [promise] (JSGlobalObject* globalObject, CallFrame* callFrame) {
        promise->reject(globalObject->vm(), callFrame->argument(0));
        return encodedJSUndefined();
    }, promise);

    [[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) {
        scope.release();
        promise->reject(vm, toJS(globalObject, [context.exception JSValueRef]));
        context.exception = nil;
    }
    return promise;
}

JSObject* JSAPIGlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObject* globalObject, JSModuleLoader*, JSValue key, JSModuleRecord*, RefPtr)
{
    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"_s), key);
    RETURN_IF_EXCEPTION(scope, nullptr);

    metaProperties->putDirect(vm, JSC::Identifier::fromString(vm, "url"_s), key);
    RETURN_IF_EXCEPTION(scope, nullptr);

    return metaProperties;
}

JSValue JSAPIGlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject, JSModuleLoader* moduleLoader, JSValue key, JSValue moduleRecordValue, RefPtr scriptFetcher, JSValue sentValue, JSValue resumeMode)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject)];
    id  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:moduleKey.createNSString().get()];
    }

    if ([moduleLoaderDelegate respondsToSelector:@selector(willEvaluateModule:)])
        [moduleLoaderDelegate willEvaluateModule:url];

    scope.release();
    // FIXME: We should update the delegate callbacks for async modules. https://bugs.webkit.org/show_bug.cgi?id=222253
    JSValue result = moduleLoader->evaluateNonVirtual(globalObject, key, moduleRecordValue, scriptFetcher, sentValue, resumeMode);

    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] });
    JSPromise* promise = importModule(this, key, { }, nullptr, nullptr);
    RETURN_IF_EXCEPTION(scope, { });
    auto* result = JSPromise::create(vm, this->promiseStructure());
    result->resolve(this, vm, promise);
    RETURN_IF_EXCEPTION(scope, { });
    return result;
}

}

#endif // JSC_OBJC_API_ENABLED

Web Proxy Viewer  |  New URL  |  Original Page