Index: tonic/dart_library_loader.cc |
diff --git a/tonic/dart_library_loader.cc b/tonic/dart_library_loader.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a6492ad8835ba97c85850bc94b0d58c6e51a0924 |
--- /dev/null |
+++ b/tonic/dart_library_loader.cc |
@@ -0,0 +1,305 @@ |
+// 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 "tonic/dart_library_loader.h" |
+ |
+#include "base/callback.h" |
+#include "base/trace_event/trace_event.h" |
+#include "mojo/common/data_pipe_drainer.h" |
+#include "tonic/dart_api_scope.h" |
+#include "tonic/dart_converter.h" |
+#include "tonic/dart_dependency_catcher.h" |
+#include "tonic/dart_error.h" |
+#include "tonic/dart_isolate_scope.h" |
+#include "tonic/dart_library_provider.h" |
+#include "tonic/dart_state.h" |
+ |
+using mojo::common::DataPipeDrainer; |
+ |
+namespace blink { |
+ |
+namespace { |
+ |
+// Helper to erase a T* from a container of std::unique_ptr<T>s. |
+template<typename T, typename C> |
+void EraseUniquePtr(C& container, T* item) { |
+ std::unique_ptr<T> key = std::unique_ptr<T>(item); |
+ container.erase(key); |
+ key.release(); |
+} |
+ |
+} |
+ |
+// A DartLibraryLoader::Job represents a network load. It fetches data from the |
+// network and buffers the data in std::vector. To cancel the job, delete this |
+// object. |
+class DartLibraryLoader::Job : public DartDependency, |
+ public DataPipeDrainer::Client { |
+ public: |
+ Job(DartLibraryLoader* loader, const std::string& name) |
+ : loader_(loader), name_(name), weak_factory_(this) { |
+ loader->library_provider()->GetLibraryAsStream( |
+ name, base::Bind(&Job::OnStreamAvailable, weak_factory_.GetWeakPtr())); |
+ } |
+ |
+ const std::string& name() const { return name_; } |
+ |
+ protected: |
+ DartLibraryLoader* loader_; |
+ // TODO(abarth): Should we be using SharedBuffer to buffer the data? |
+ std::vector<uint8_t> buffer_; |
+ |
+ private: |
+ void OnStreamAvailable(mojo::ScopedDataPipeConsumerHandle pipe) { |
+ if (!pipe.is_valid()) { |
+ loader_->DidFailJob(this); |
+ return; |
+ } |
+ drainer_ = adoptPtr(new DataPipeDrainer(this, pipe.Pass())); |
+ } |
+ |
+ // DataPipeDrainer::Client |
+ void OnDataAvailable(const void* data, size_t num_bytes) override { |
+ const uint8_t* bytes = static_cast<const uint8_t*>(data); |
+ buffer_.insert(buffer_.end(), bytes, bytes + num_bytes); |
+ } |
+ // Subclasses must implement OnDataComplete. |
+ |
+ std::string name_; |
+ OwnPtr<DataPipeDrainer> drainer_; |
+ |
+ base::WeakPtrFactory<Job> weak_factory_; |
+}; |
+ |
+class DartLibraryLoader::ImportJob : public Job { |
+ public: |
+ ImportJob(DartLibraryLoader* loader, const std::string& name) : Job(loader, name) { |
+ TRACE_EVENT_ASYNC_BEGIN1("sky", "DartLibraryLoader::ImportJob", this, "url", |
+ name); |
+ } |
+ |
+ private: |
+ // DataPipeDrainer::Client |
+ void OnDataComplete() override { |
+ TRACE_EVENT_ASYNC_END0("sky", "DartLibraryLoader::ImportJob", this); |
+ loader_->DidCompleteImportJob(this, buffer_); |
+ } |
+}; |
+ |
+class DartLibraryLoader::SourceJob : public Job { |
+ public: |
+ SourceJob(DartLibraryLoader* loader, const std::string& name, Dart_Handle library) |
+ : Job(loader, name), library_(loader->dart_state(), library) { |
+ TRACE_EVENT_ASYNC_BEGIN1("sky", "DartLibraryLoader::SourceJob", this, "url", |
+ name); |
+ } |
+ |
+ Dart_PersistentHandle library() const { return library_.value(); } |
+ |
+ private: |
+ // DataPipeDrainer::Client |
+ void OnDataComplete() override { |
+ TRACE_EVENT_ASYNC_END0("sky", "DartLibraryLoader::SourceJob", this); |
+ 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 DartLibraryLoader::DependencyWatcher { |
+ public: |
+ DependencyWatcher(const std::unordered_set<DartDependency*>& dependencies, |
+ const base::Closure& callback) |
+ : dependencies_(dependencies), callback_(callback) { |
+ DCHECK(!dependencies_.empty()); |
+ } |
+ |
+ bool DidResolveDependency( |
+ DartDependency* resolved_dependency, |
+ const std::unordered_set<DartDependency*>& new_dependencies) { |
+ const auto& it = dependencies_.find(resolved_dependency); |
+ if (it == dependencies_.end()) |
+ return false; |
+ dependencies_.erase(it); |
+ for (const auto& dependency : new_dependencies) |
+ dependencies_.insert(dependency); |
+ return dependencies_.empty(); |
+ } |
+ |
+ const base::Closure& callback() const { return callback_; } |
+ |
+ private: |
+ std::unordered_set<DartDependency*> 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 DartLibraryLoader::WatcherSignaler { |
+ public: |
+ WatcherSignaler(DartLibraryLoader& loader, |
+ DartDependency* resolved_dependency) |
+ : loader_(loader), |
+ catcher_(adoptPtr(new DartDependencyCatcher(loader))), |
+ resolved_dependency_(resolved_dependency) {} |
+ |
+ ~WatcherSignaler() { |
+ std::vector<DependencyWatcher*> completed_watchers; |
+ for (const auto& watcher : loader_.dependency_watchers_) { |
+ if (watcher->DidResolveDependency(resolved_dependency_, |
+ catcher_->dependencies())) |
+ completed_watchers.push_back(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(); |
+ std::vector<base::Closure> callbacks; |
+ for (const auto& watcher : completed_watchers) { |
+ callbacks.push_back(watcher->callback()); |
+ EraseUniquePtr(loader_.dependency_watchers_, watcher); |
+ } |
+ |
+ // Finally, run all the callbacks while touching only data on the stack. |
+ for (const auto& callback : callbacks) |
+ callback.Run(); |
+ } |
+ |
+ private: |
+ DartLibraryLoader& loader_; |
+ OwnPtr<DartDependencyCatcher> catcher_; |
+ DartDependency* resolved_dependency_; |
+}; |
+ |
+DartLibraryLoader::DartLibraryLoader(DartState* dart_state) |
+ : dart_state_(dart_state), |
+ library_provider_(nullptr), |
+ dependency_catcher_(nullptr) { |
+} |
+ |
+DartLibraryLoader::~DartLibraryLoader() { |
+} |
+ |
+Dart_Handle DartLibraryLoader::HandleLibraryTag(Dart_LibraryTag tag, |
+ Dart_Handle library, |
+ Dart_Handle url) { |
+ DCHECK(Dart_IsLibrary(library)); |
+ DCHECK(Dart_IsString(url)); |
+ if (tag == Dart_kCanonicalizeUrl) |
+ return DartState::Current()->library_loader().CanonicalizeURL(library, url); |
+ if (tag == Dart_kImportTag) { |
+ return DartState::Current()->library_loader().Import(library, url); |
+ } |
+ if (tag == Dart_kSourceTag) { |
+ return DartState::Current()->library_loader().Source(library, url); |
+ } |
+ DCHECK(false); |
+ return Dart_NewApiError("Unknown library tag."); |
+} |
+ |
+void DartLibraryLoader::WaitForDependencies( |
+ const std::unordered_set<DartDependency*>& dependencies, |
+ const base::Closure& callback) { |
+ if (dependencies.empty()) |
+ return callback.Run(); |
+ dependency_watchers_.insert( |
+ std::unique_ptr<DependencyWatcher>( |
+ new DependencyWatcher(dependencies, callback))); |
+} |
+ |
+void DartLibraryLoader::LoadLibrary(const std::string& name) { |
+ const auto& result = pending_libraries_.insert(std::make_pair(name, nullptr)); |
+ if (result.second) { |
+ // New entry. |
+ std::unique_ptr<Job> job = std::unique_ptr<Job>(new ImportJob(this, name)); |
+ result.first->second = job.get(); |
+ jobs_.insert(std::move(job)); |
+ } |
+ if (dependency_catcher_) |
+ dependency_catcher_->AddDependency(result.first->second); |
+} |
+ |
+Dart_Handle DartLibraryLoader::Import(Dart_Handle library, Dart_Handle url) { |
+ LoadLibrary(StdStringFromDart(url)); |
+ return Dart_True(); |
+} |
+ |
+Dart_Handle DartLibraryLoader::Source(Dart_Handle library, Dart_Handle url) { |
+ std::unique_ptr<Job> job = std::unique_ptr<Job>( |
+ new SourceJob(this, StdStringFromDart(url), library)); |
+ if (dependency_catcher_) |
+ dependency_catcher_->AddDependency(job.get()); |
+ jobs_.insert(std::move(job)); |
+ return Dart_True(); |
+} |
+ |
+Dart_Handle DartLibraryLoader::CanonicalizeURL(Dart_Handle library, |
+ Dart_Handle url) { |
+ return library_provider_->CanonicalizeURL(library, url); |
+} |
+ |
+void DartLibraryLoader::DidCompleteImportJob( |
+ ImportJob* job, |
+ const std::vector<uint8_t>& buffer) { |
+ DartIsolateScope scope(dart_state_->isolate()); |
+ DartApiScope api_scope; |
+ |
+ WatcherSignaler watcher_signaler(*this, job); |
+ |
+ Dart_Handle result = Dart_LoadLibrary( |
+ StdStringToDart(job->name()), |
+ Dart_NewStringFromUTF8(buffer.data(), buffer.size()), 0, 0); |
+ if (Dart_IsError(result)) { |
+ LOG(ERROR) << "Error Loading " << job->name() << " " |
+ << Dart_GetError(result); |
+ } |
+ |
+ pending_libraries_.erase(job->name()); |
+ EraseUniquePtr<Job>(jobs_, job); |
+} |
+ |
+void DartLibraryLoader::DidCompleteSourceJob( |
+ SourceJob* job, |
+ const std::vector<uint8_t>& buffer) { |
+ DartIsolateScope scope(dart_state_->isolate()); |
+ DartApiScope api_scope; |
+ |
+ WatcherSignaler watcher_signaler(*this, job); |
+ |
+ Dart_Handle result = Dart_LoadSource( |
+ Dart_HandleFromPersistent(job->library()), |
+ StdStringToDart(job->name()), |
+ Dart_NewStringFromUTF8(buffer.data(), buffer.size()), 0, 0); |
+ |
+ if (Dart_IsError(result)) { |
+ LOG(ERROR) << "Error Loading " << job->name() << " " |
+ << Dart_GetError(result); |
+ } |
+ |
+ EraseUniquePtr<Job>(jobs_, job); |
+} |
+ |
+void DartLibraryLoader::DidFailJob(Job* job) { |
+ DartIsolateScope scope(dart_state_->isolate()); |
+ DartApiScope api_scope; |
+ |
+ WatcherSignaler watcher_signaler(*this, job); |
+ |
+ LOG(ERROR) << "Library Load failed: " << job->name(); |
+ // TODO(eseidel): Call Dart_LibraryHandleError in the SourceJob case? |
+ |
+ EraseUniquePtr<Job>(jobs_, job); |
+} |
+ |
+} // namespace blink |