Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(273)

Unified Diff: third_party/WebKit/Source/core/dom/ModuleMap.cpp

Issue 2555653002: [WIP Prototype] ES6 https://html.spec.whatwg.org/#fetch-a-single-module-script implementation (Closed)
Patch Set: snapshot Dec19 Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698