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 |