// Copyright 2015 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 "sky/engine/config.h" #include "sky/engine/core/script/dart_loader.h" #include "base/callback.h" #include "mojo/common/data_pipe_drainer.h" #include "sky/engine/core/script/dart_dependency_catcher.h" #include "sky/engine/core/script/dom_dart_state.h" #include "sky/engine/platform/fetcher/MojoFetcher.h" #include "sky/engine/platform/weborigin/KURL.h" #include "sky/engine/tonic/dart_api_scope.h" #include "sky/engine/tonic/dart_converter.h" #include "sky/engine/tonic/dart_error.h" #include "sky/engine/tonic/dart_isolate_scope.h" #include "sky/engine/wtf/MainThread.h" using mojo::common::DataPipeDrainer; namespace blink { namespace { Dart_Handle CanonicalizeURL(DartState* state, Dart_Handle library, Dart_Handle url) { String string = StringFromDart(url); if (string.startsWith("dart:") || string.startsWith("mojo:")) return url; // TODO(dart): Figure out how 'package:' should work in sky. if (string.startsWith("package:")) { string.replace("package:", "/gen/"); } String library_url_string = StringFromDart(Dart_LibraryUrl(library)); KURL library_url = KURL(ParsedURLString, library_url_string); KURL resolved_url = KURL(library_url, string); return StringToDart(state, resolved_url.string()); } } // namespace // A DartLoader::Job represents a network load. It fetches data from the network // and buffers the data in Vector. To cancel the job, delete this object. class DartLoader::Job : public DartDependency, public MojoFetcher::Client, public DataPipeDrainer::Client { public: Job(DartLoader* loader, const KURL& url) : loader_(loader), url_(url), fetcher_(this, url) {} const KURL& url() const { return url_; } protected: DartLoader* loader_; // TODO(abarth): Should we be using SharedBuffer to buffer the data? Vector buffer_; private: // MojoFetcher::Client void OnReceivedResponse(mojo::URLResponsePtr response) override { if (response->status_code != 200) { loader_->DidFailJob(this); return; } drainer_ = adoptPtr(new DataPipeDrainer(this, response->body.Pass())); } // DataPipeDrainer::Client void OnDataAvailable(const void* data, size_t num_bytes) override { buffer_.append(static_cast(data), num_bytes); } // Subclasses must implement OnDataComplete. KURL url_; MojoFetcher fetcher_; OwnPtr drainer_; }; class DartLoader::ImportJob : public Job { public: using Job::Job; private: // DataPipeDrainer::Client void OnDataComplete() override { loader_->DidCompleteImportJob(this, buffer_); } }; class DartLoader::SourceJob : public Job { public: SourceJob(DartLoader* loader, const KURL& url, Dart_Handle library) : Job(loader, url), library_(loader->dart_state(), library) {} Dart_PersistentHandle library() const { return library_.value(); } private: // DataPipeDrainer::Client void OnDataComplete() override { loader_->DidCompleteSourceJob(this, buffer_); } DartPersistentValue library_; }; // A DependencyWatcher represents a request to watch for when a given set of // dependencies (either libraries or parts of libraries) have finished loading. // When the dependencies are satisfied (including transitive dependencies), then // the |callback| will be invoked. class DartLoader::DependencyWatcher { public: DependencyWatcher(const HashSet& dependencies, const base::Closure& callback) : dependencies_(dependencies), callback_(callback) { DCHECK(!dependencies_.isEmpty()); } bool DidResolveDependency(DartDependency* resolved_dependency, const HashSet& new_dependencies) { const auto& it = dependencies_.find(resolved_dependency); if (it == dependencies_.end()) return false; dependencies_.remove(it); for (const auto& dependency : new_dependencies) dependencies_.add(dependency); return dependencies_.isEmpty(); } const base::Closure& callback() const { return callback_; } private: HashSet dependencies_; base::Closure callback_; }; // A WatcherSignaler is responsible for signaling DependencyWatchers when their // dependencies resolve and for calling the DependencyWatcher's callback. We use // a separate object of this task because we want to carefully manage when we // call the callbacks, which can call into us again reentrantly. // // WatcherSignaler is designed to be placed on the stack as a RAII. After its // destructor runs, we might have executed aribitrary script. class DartLoader::WatcherSignaler { public: WatcherSignaler(DartLoader& loader, DartDependency* resolved_dependency) : loader_(loader), catcher_(adoptPtr(new DartDependencyCatcher(loader))), resolved_dependency_(resolved_dependency) {} ~WatcherSignaler() { Vector completed_watchers; for (const auto& watcher : loader_.dependency_watchers_) { if (watcher->DidResolveDependency(resolved_dependency_, catcher_->dependencies())) completed_watchers.append(watcher.get()); } // Notice that we remove the dependency catcher and extract all the // callbacks before running any of them. We don't want to be re-entered // below the callbacks and end up in an inconsistent state. catcher_.clear(); Vector callbacks; for (const auto& watcher : completed_watchers) { callbacks.append(watcher->callback()); loader_.dependency_watchers_.remove(watcher); } // Finally, run all the callbacks while touching only data on the stack. for (const auto& callback : callbacks) callback.Run(); } private: DartLoader& loader_; OwnPtr catcher_; DartDependency* resolved_dependency_; }; DartLoader::DartLoader(DartState* dart_state) : dart_state_(dart_state->GetWeakPtr()), dependency_catcher_(nullptr) { } DartLoader::~DartLoader() { } Dart_Handle DartLoader::HandleLibraryTag(Dart_LibraryTag tag, Dart_Handle library, Dart_Handle url) { DCHECK(Dart_IsLibrary(library)); DCHECK(Dart_IsString(url)); if (tag == Dart_kCanonicalizeUrl) return CanonicalizeURL(DartState::Current(), library, url); if (tag == Dart_kImportTag) { CHECK(WTF::isMainThread()); String string = StringFromDart(url); if (string.startsWith("mojo:")) { Dart_Handle mojo_library = Dart_LookupLibrary(url); LogIfError(mojo_library); return mojo_library; } return DOMDartState::Current()->loader().Import(library, url); } if (tag == Dart_kSourceTag) { CHECK(WTF::isMainThread()); return DOMDartState::Current()->loader().Source(library, url); } DCHECK(false); return Dart_NewApiError("Unknown library tag."); } void DartLoader::WaitForDependencies( const HashSet& dependencies, const base::Closure& callback) { if (dependencies.isEmpty()) return callback.Run(); dependency_watchers_.add( adoptPtr(new DependencyWatcher(dependencies, callback))); } Dart_Handle DartLoader::Import(Dart_Handle library, Dart_Handle url) { KURL parsed_url(ParsedURLString, StringFromDart(url)); const auto& result = pending_libraries_.add(parsed_url.string(), nullptr); if (result.isNewEntry) { OwnPtr job = adoptPtr(new ImportJob(this, parsed_url)); result.storedValue->value = job.get(); jobs_.add(job.release()); } if (dependency_catcher_) dependency_catcher_->AddDependency(result.storedValue->value); return Dart_True(); } Dart_Handle DartLoader::Source(Dart_Handle library, Dart_Handle url) { KURL parsed_url(ParsedURLString, StringFromDart(url)); OwnPtr job = adoptPtr(new SourceJob(this, parsed_url, library)); if (dependency_catcher_) dependency_catcher_->AddDependency(job.get()); jobs_.add(job.release()); return Dart_True(); } void DartLoader::DidCompleteImportJob(ImportJob* job, const Vector& buffer) { DCHECK(dart_state_); DartIsolateScope scope(dart_state_->isolate()); DartApiScope api_scope; WatcherSignaler watcher_signaler(*this, job); String url_string = job->url().string(); LogIfError(Dart_LoadLibrary( StringToDart(dart_state_.get(), url_string), Dart_NewStringFromUTF8(buffer.data(), buffer.size()), 0, 0)); pending_libraries_.remove(url_string); jobs_.remove(job); } void DartLoader::DidCompleteSourceJob(SourceJob* job, const Vector& buffer) { DCHECK(dart_state_); DartIsolateScope scope(dart_state_->isolate()); DartApiScope api_scope; WatcherSignaler watcher_signaler(*this, job); LogIfError(Dart_LoadSource( Dart_HandleFromPersistent(job->library()), StringToDart(dart_state_.get(), job->url().string()), Dart_NewStringFromUTF8(buffer.data(), buffer.size()), 0, 0)); jobs_.remove(job); } void DartLoader::DidFailJob(Job* job) { DCHECK(dart_state_); DartIsolateScope scope(dart_state_->isolate()); DartApiScope api_scope; WatcherSignaler watcher_signaler(*this, job); LOG(ERROR) << "Library Load failed: " << job->url().string().utf8().data(); // TODO(eseidel): Call Dart_LibraryHandleError in the SourceJob case? jobs_.remove(job); } } // namespace blink