ScriptModuleLoader.cpp [plain text]
#include "config.h"
#include "ScriptModuleLoader.h"
#include "CachedModuleScriptLoader.h"
#include "CachedScript.h"
#include "Document.h"
#include "Frame.h"
#include "JSDOMBinding.h"
#include "JSElement.h"
#include "LoadableModuleScript.h"
#include "MIMETypeRegistry.h"
#include "ScriptController.h"
#include "ScriptElement.h"
#include "ScriptSourceCode.h"
#include <runtime/JSInternalPromise.h>
#include <runtime/JSInternalPromiseDeferred.h>
#include <runtime/JSModuleRecord.h>
#include <runtime/JSString.h>
#include <runtime/Symbol.h>
namespace WebCore {
ScriptModuleLoader::ScriptModuleLoader(Document& document)
: m_document(document)
{
}
ScriptModuleLoader::~ScriptModuleLoader()
{
for (auto& loader : m_loaders)
const_cast<CachedModuleScriptLoader&>(loader.get()).clearClient();
}
static bool isRootModule(JSC::JSValue importerModuleKey)
{
return importerModuleKey.isSymbol() || importerModuleKey.isUndefined();
}
JSC::JSInternalPromise* ScriptModuleLoader::resolve(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleNameValue, JSC::JSValue importerModuleKey, JSC::JSValue)
{
auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
auto& jsPromise = *JSC::JSInternalPromiseDeferred::create(exec, &globalObject);
auto promise = DeferredPromise::create(globalObject, jsPromise);
if (moduleNameValue.isSymbol()) {
promise->resolve<IDLAny>(toJS(exec, &globalObject, asSymbol(moduleNameValue)->privateName()));
return jsPromise.promise();
}
if (!moduleNameValue.isString()) {
promise->reject(TypeError, ASCIILiteral("Module specifier is not Symbol or String."));
return jsPromise.promise();
}
String specifier = asString(moduleNameValue)->value(exec);
URL absoluteURL(URL(), specifier);
if (absoluteURL.isValid()) {
promise->resolve<IDLDOMString>(absoluteURL.string());
return jsPromise.promise();
}
if (!specifier.startsWith('/') && !specifier.startsWith("./") && !specifier.startsWith("../")) {
promise->reject(TypeError, ASCIILiteral("Module specifier does not start with \"/\", \"./\", or \"../\"."));
return jsPromise.promise();
}
URL completedURL;
if (isRootModule(importerModuleKey))
completedURL = m_document.completeURL(specifier);
else if (importerModuleKey.isString()) {
URL importerModuleRequestURL(URL(), asString(importerModuleKey)->value(exec));
if (!importerModuleRequestURL.isValid()) {
promise->reject(TypeError, ASCIILiteral("Importer module key is an invalid URL."));
return jsPromise.promise();
}
URL importerModuleResponseURL = m_requestURLToResponseURLMap.get(importerModuleRequestURL);
if (!importerModuleResponseURL.isValid()) {
promise->reject(TypeError, ASCIILiteral("Importer module has an invalid response URL."));
return jsPromise.promise();
}
completedURL = m_document.completeURL(specifier, importerModuleResponseURL);
} else {
promise->reject(TypeError, ASCIILiteral("Importer module key is not Symbol or String."));
return jsPromise.promise();
}
if (!completedURL.isValid()) {
promise->reject(TypeError, ASCIILiteral("Module name constructs an invalid URL."));
return jsPromise.promise();
}
promise->resolve<IDLDOMString>(completedURL.string());
return jsPromise.promise();
}
JSC::JSInternalPromise* ScriptModuleLoader::fetch(JSC::JSGlobalObject* jsGlobalObject, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue initiator)
{
auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
auto& jsPromise = *JSC::JSInternalPromiseDeferred::create(exec, &globalObject);
auto deferred = DeferredPromise::create(globalObject, jsPromise);
if (moduleKeyValue.isSymbol()) {
deferred->reject(TypeError, ASCIILiteral("Symbol module key should be already fulfilled with the inlined resource."));
return jsPromise.promise();
}
if (!moduleKeyValue.isString()) {
deferred->reject(TypeError, ASCIILiteral("Module key is not Symbol or String."));
return jsPromise.promise();
}
URL completedURL(URL(), asString(moduleKeyValue)->value(exec));
if (!completedURL.isValid()) {
deferred->reject(TypeError, ASCIILiteral("Module key is a valid URL."));
return jsPromise.promise();
}
ASSERT_WITH_MESSAGE(JSC::jsDynamicCast<JSElement*>(initiator), "Initiator should be an JSElement");
auto* scriptElement = toScriptElementIfPossible(&JSC::jsCast<JSElement*>(initiator)->wrapped());
ASSERT_WITH_MESSAGE(scriptElement, "Initiator should be ScriptElement.");
if (auto* frame = m_document.frame()) {
auto loader = CachedModuleScriptLoader::create(*this, deferred.get());
m_loaders.add(loader.copyRef());
if (!loader->load(*scriptElement, completedURL)) {
loader->clearClient();
m_loaders.remove(WTFMove(loader));
deferred->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
return jsPromise.promise();
}
}
return jsPromise.promise();
}
JSC::JSValue ScriptModuleLoader::evaluate(JSC::JSGlobalObject*, JSC::ExecState* exec, JSC::JSModuleLoader*, JSC::JSValue moduleKeyValue, JSC::JSValue moduleRecordValue, JSC::JSValue)
{
JSC::VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* moduleRecord = jsDynamicDowncast<JSC::JSModuleRecord*>(moduleRecordValue);
if (!moduleRecord)
return JSC::jsUndefined();
URL sourceURL;
if (moduleKeyValue.isSymbol())
sourceURL = m_document.url();
else if (moduleKeyValue.isString())
sourceURL = URL(URL(), asString(moduleKeyValue)->value(exec));
else
return JSC::throwTypeError(exec, scope, ASCIILiteral("Module key is not Symbol or String."));
if (!sourceURL.isValid())
return JSC::throwTypeError(exec, scope, ASCIILiteral("Module key is an invalid URL."));
if (auto* frame = m_document.frame())
return frame->script().evaluateModule(sourceURL, *moduleRecord);
return JSC::jsUndefined();
}
void ScriptModuleLoader::notifyFinished(CachedModuleScriptLoader& loader, RefPtr<DeferredPromise> promise)
{
if (!m_loaders.remove(&loader))
return;
loader.clearClient();
auto& cachedScript = *loader.cachedScript();
bool failed = false;
if (cachedScript.resourceError().isAccessControl()) {
promise->reject(TypeError, ASCIILiteral("Cross-origin script load denied by Cross-Origin Resource Sharing policy."));
return;
}
if (cachedScript.errorOccurred())
failed = true;
else if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(cachedScript.response().mimeType())) {
promise->reject(TypeError, makeString("'", cachedScript.response().mimeType(), "' is not a valid JavaScript MIME type."));
return;
}
auto* frame = m_document.frame();
if (!frame)
return;
if (failed) {
promise->reject(frame->script().moduleLoaderAlreadyReportedErrorSymbol());
return;
}
if (cachedScript.wasCanceled()) {
promise->reject(frame->script().moduleLoaderFetchingIsCanceledSymbol());
return;
}
m_requestURLToResponseURLMap.add(cachedScript.url(), cachedScript.response().url());
promise->resolve<IDLDOMString>(ScriptSourceCode(&cachedScript, JSC::SourceProviderSourceType::Module).source().toString());
}
}