Chromium Code Reviews| Index: third_party/WebKit/Source/core/dom/ModuleMap.cpp |
| diff --git a/third_party/WebKit/Source/core/dom/ModuleMap.cpp b/third_party/WebKit/Source/core/dom/ModuleMap.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..86e6412fa1274ec67a217c72a8af6d5249027888 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/core/dom/ModuleMap.cpp |
| @@ -0,0 +1,310 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "core/dom/ModuleMap.h" |
| + |
| +#include "bindings/core/v8/ScriptController.h" |
| +#include "bindings/core/v8/V8PerIsolateData.h" |
| +#include "core/fetch/ResourceFetcher.h" |
| +#include "platform/network/mime/MIMETypeRegistry.h" |
| +#include "wtf/AutoReset.h" |
| +#include <v8.h> |
| + |
| +namespace blink { |
| + |
| +ModuleLoader* ModuleLoader::fetch(const KURL& url, |
| + ScriptController* scriptController, |
| + ResourceFetcher* fetcher, |
| + ModuleLoaderRegistry* registry, |
| + ModuleLoader::Client* client) { |
| + ModuleLoader* loader = |
| + new ModuleLoader(url, scriptController, registry, client); |
| + loader->fetchInternal(fetcher); |
|
dominicc (has gone to gerrit)
2016/12/19 10:07:51
I would feel more secure if ModuleLoaders which ne
kouhei (in TOK)
2016/12/19 11:19:59
Would you clarify here? Do you mean you want an AS
|
| + return loader; |
| +} |
| + |
| +ModuleLoader::ModuleLoader(const KURL& url, |
| + ScriptController* scriptController, |
| + ModuleLoaderRegistry* registry, |
| + ModuleLoader::Client* client) |
| + : m_url(url), |
| + m_scriptController(scriptController), |
| + m_registry(registry), |
| + m_client(client) { |
| + DCHECK(scriptController); |
| + DCHECK(client); |
| +} |
| + |
| +void ModuleLoader::advanceState(ModuleLoader::State newState) { |
| + switch (m_state) { |
| + case State::Initial: |
| + DCHECK_EQ(newState, State::Fetching); |
| + break; |
| + case State::Fetching: |
| + DCHECK_EQ(newState, State::Finished); |
| + break; |
| + case State::Finished: |
| + break; |
|
dominicc (has gone to gerrit)
2016/12/19 10:07:51
NOTREACHED?
kouhei (in TOK)
2016/12/19 11:19:59
Yes. Will address.
|
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + m_state = newState; |
| + |
| + if (m_state == State::Finished) { |
| + m_registry->releaseFinishedLoader(this); |
| + m_client->notifyFinished(m_scriptModule); |
| + } |
| +} |
| + |
| +void ModuleLoader::fetchInternal(ResourceFetcher* fetcher) { |
| + // Step 4. Set moduleMap[url] to "fetching". |
| + advanceState(State::Fetching); |
| + |
| + // Step 5. Let request be a new request whose url is url, destination is |
| + // destination, type is "script", mode is "cors", credentials mode is |
| + // credentials mode, cryptographic nonce metadata is cryptographic nonce, |
| + // parser metadata is parser state, referrer is referrer, and client is |
| + // fetch |
| + // client settings object. |
| + FetchRequest request(ResourceRequest(m_url), "module"); |
| + // request.setDefer(FetchRequest::LazyLoad); //? always async -> defer? |
| + Member<ScriptResource> resource = ScriptResource::fetch(request, fetcher); |
| + setResource(resource); |
| + |
| + // Step 6. If the caller specified custom steps to perform the fetch, |
| + // perform |
| + // them on request, setting the is top-level flag if the top-level module |
| + // fetch flag is set. Return from this algorithm, and when the custom |
| + // perform |
| + // the fetch steps complete with response response, run the remaining steps. |
| + // Otherwise, fetch request. Return from this algorithm, and run the |
| + // remaining |
| + // steps as part of the fetch's process response for the response response. |
| + // Note: response is always CORS-same-origin. |
| + |
| + // TODO(kouhei): I don't know what Step 6 means. ScriptLoader seems to |
| + // ignore |
| + // the step? |
| +} |
| + |
| +bool ModuleLoader::wasModuleLoadSuccessful(Resource* resource) { |
| + // Implements conditions in Step 7 of |
| + // https://html.spec.whatwg.org/#fetch-a-single-module-script |
| + |
| + // - response's type is "error" |
| + if (resource->errorOccurred()) |
| + return false; |
| + |
| + const auto& response = resource->response(); |
| + // - response's status is not an ok status |
| + if (response.isHTTP() && |
| + (response.httpStatusCode() < 200 || response.httpStatusCode() >= 300)) { |
| + return false; |
| + } |
| + |
| + // The result of extracting a MIME type from response's header list |
| + // (ignoring parameters) is not a JavaScript MIME type |
| + // Note: 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. |
| + if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(response.mimeType())) |
| + return false; |
| + |
| + return true; |
| +} |
| + |
| +// ScriptResourceClient callback handler |
| +void ModuleLoader::notifyFinished(Resource*) { |
| + printf("notifyFinished!\n"); |
| + |
| + // Note: "conditions" referred in Step 7 is implemented in |
| + // wasModuleLoadSuccessful(). |
| + // Step 7. If any of the following conditions are met, set moduleMap[url] to |
| + // null, asynchronously complete this algorithm with null, and abort these |
| + // steps. |
| + if (!wasModuleLoadSuccessful(resource())) { |
| + printf("notifyFinished but load was not successful\n"); |
|
dominicc (has gone to gerrit)
2016/12/19 10:07:51
I guess a tombstone goes in the map/entry and ther
kouhei (in TOK)
2016/12/19 11:19:59
Yes. advanceState() triggers notification, which m
|
| + |
| + m_wasLoadSuccessful = false; |
| + advanceState(State::Finished); |
| + setResource(nullptr); |
| + return; |
| + } |
| + |
| + // Step 8. Let source text be the result of UTF-8 decoding response's body. |
| + String script = resource()->script(); |
| + printf("fetched script: %s\n", script.utf8().data()); |
| + |
| + // Step 9. Let module script be the result of creating a module script given |
| + // source text, module map settings object, response's url, cryptographic |
| + // nonce, parser state, and credentials mode. |
| + |
| + // TODO(kouhei): post a task to compile module? |
| + m_scriptModule = m_scriptController->compileModule(script, m_url.getString()); |
| + m_wasLoadSuccessful = !m_scriptModule.isNull(); |
| + advanceState(State::Finished); |
| + setResource(nullptr); |
| +} |
| + |
| +DEFINE_TRACE(ModuleLoader) { |
| + visitor->trace(m_scriptController); |
| + visitor->trace(m_registry); |
| + visitor->trace(m_client); |
| + ResourceOwner<ScriptResource>::trace(visitor); |
| +} |
| + |
| +DEFINE_TRACE(ModuleLoaderRegistry) { |
| + visitor->trace(m_activeLoaders); |
| +} |
| + |
| +ModuleLoader* ModuleLoaderRegistry::fetch(const KURL& url, |
| + ScriptController* scriptController, |
| + ResourceFetcher* fetcher, |
| + ModuleLoader::Client* client) { |
| + ModuleLoader* loader = |
| + ModuleLoader::fetch(url, scriptController, fetcher, this, client); |
| + DCHECK(loader->isFetching()); |
| + m_activeLoaders.add(loader); |
| + return loader; |
| +} |
| + |
| +void ModuleLoaderRegistry::releaseFinishedLoader(ModuleLoader* loader) { |
| + DCHECK(loader->hasFinished()); |
| + |
| + auto it = m_activeLoaders.find(loader); |
| + DCHECK_NE(it, m_activeLoaders.end()); |
| + m_activeLoaders.remove(it); |
| +} |
| + |
| +// Entry struct represents a value in "module map" spec object. |
| +class ModuleMap::Entry : public GarbageCollectedFinalized<Entry>, |
| + public ModuleLoader::Client { |
| + USING_GARBAGE_COLLECTED_MIXIN(ModuleMap::Entry); |
| + |
| + public: |
| + static Entry* create(ModuleMap* map) { return new Entry(map); } |
| + virtual ~Entry() = default; |
| + |
| + DECLARE_TRACE(); |
| + |
| + // Run callbacks for the given client till current state, and register the |
| + // client to receive further callbacks if !isFinished() loading. |
| + void addClient(ModuleMap::Client*); |
|
dominicc (has gone to gerrit)
2016/12/19 10:07:51
Yay, this looks neat. I think it's clearer to sepa
|
| + |
| + private: |
| + Entry(ModuleMap* map) : m_map(map) { DCHECK(map); } |
| + |
| + // Implements ModuleLoader::Client |
| + void notifyFinished(ScriptModule) override; |
| + |
| + // Flushes m_pendingClients to m_clients, and ensure all clients |
| + // (including those added during notification callbacks) receives |
| + // notifyFinished() callback if module load has finished. |
| + void notifyAndFlushPendingClients(); |
| + |
| + ScriptModule m_module; |
| + Member<ModuleMap> m_map; |
| + |
| + // Correspond to the HTML spec: "fetching" state. |
| + bool m_isFetching = true; |
| + |
| + // TODO(kouhei): Isolate below and their related logic to |
| + // ModuleLoaderClientRegistry. |
| + bool m_isFlushingPendingClients = false; |
| + HeapHashSet<Member<ModuleMap::Client>> m_clients; |
| + HeapHashSet<Member<ModuleMap::Client>> m_pendingClients; |
| +}; |
| + |
| +void ModuleMap::Entry::addClient(ModuleMap::Client* newClient) { |
| + DCHECK(!m_pendingClients.contains(newClient)); |
| + DCHECK(!m_clients.contains(newClient)); |
| + |
| + m_pendingClients.add(newClient); |
| + notifyAndFlushPendingClients(); |
|
dominicc (has gone to gerrit)
2016/12/19 10:07:51
WDYT about posting a task to do this? Does HTML re
kouhei (in TOK)
2016/12/19 11:19:59
Yes. Posting a task would greatly simplify the cod
|
| +} |
| + |
| +void ModuleMap::Entry::notifyFinished(ScriptModule module) { |
| + m_module = module; |
|
kouhei (in TOK)
2016/12/19 11:19:59
TODO: Add a comment that module may isNull() if er
|
| + notifyAndFlushPendingClients(); |
| +} |
| + |
| +void ModuleMap::Entry::notifyAndFlushPendingClients() { |
| + if (m_isFlushingPendingClients) { |
| + // This notifyAndFlushPendingClients call was reentrant. |
| + // Defer to first notifyAndFlushPendingClients to do the job. |
| + return; |
| + } |
| + |
| + AutoReset<bool> flushingScope(&m_isFlushingPendingClients, true); |
| + |
| + if (!m_isFetching) { |
| + while (!m_pendingClients.isEmpty()) |
| + m_clients.add(m_pendingClients.takeAny()); |
| + return; |
| + } |
| + |
| + HeapHashSet<Member<ModuleMap::Client>> clientsToNotify; |
| + // Notify to existing clients in the first loop, notify to clients added while |
| + // calling callbacks in the subsequent loops. |
| + clientsToNotify.swap(m_clients); |
| + do { |
| + for (const auto& client : clientsToNotify) { |
| + client->notifyFinished(m_module); |
| + } |
| + DCHECK(m_clients.isEmpty()) |
| + << "m_clients shouldn't be modified while in the notification loop"; |
| + |
| + // Notify the clients added while in the above loop. |
| + clientsToNotify.swap(m_pendingClients); |
| + } while (!clientsToNotify.isEmpty()); |
| +} |
| + |
| +DEFINE_TRACE(ModuleMap::Entry) { |
| + visitor->trace(m_map); |
| + visitor->trace(m_pendingClients); |
| + visitor->trace(m_clients); |
| +} |
| + |
| +ModuleMap::ModuleMap(ScriptController* scriptController, |
| + ResourceFetcher* fetcher) |
| + : m_scriptController(scriptController), |
| + m_fetcher(fetcher), |
| + m_loaderRegistry(ModuleLoaderRegistry::create()) { |
| + DCHECK(m_scriptController); |
| + DCHECK(m_fetcher); |
| +} |
| + |
| +void ModuleMap::fetchSingle(const KURL& url, ModuleMap::Client* client) { |
| + printf("ModuleMap::fetch(%s)\n", url.getString().utf8().data()); |
| + |
| + MapImpl::AddResult result = m_map.set(url, nullptr); |
| + Member<Entry>& entry = result.storedValue->value; |
| + if (result.isNewEntry) { |
| + // Step 4. Set moduleMap[url] to "fetching". |
| + entry = Entry::create(this); |
| + |
| + // Delegate to ModuleLoader to process Steps 5-9. |
| + m_loaderRegistry->fetch(url, m_scriptController.get(), m_fetcher.get(), |
| + entry); |
| + } |
| + |
| + // Spec: If moduleMap[url] is "fetching", wait (in parallel) until that |
| + // loaderEntry's value changes, then proceed to the next step. |
| + // Spec: If moduleMap[url] exists, asynchronously complete this algorithm |
| + // with moduleMap[url], and abort these steps. |
| + // Spec: Step 10. Set moduleMap[url] to module script, and asynchronously |
| + // complete |
| + // this algorithm with module script. |
| + entry->addClient(client); |
| +} |
| + |
| +DEFINE_TRACE(ModuleMap) { |
| + visitor->trace(m_map); |
| + visitor->trace(m_scriptController); |
| + visitor->trace(m_fetcher); |
| + visitor->trace(m_loaderRegistry); |
| +} |
| + |
| +} // namespace blink |