|  | // 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/data_pipe_utils/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 tonic { | 
|  |  | 
|  | 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_ = std::unique_ptr<DataPipeDrainer>( | 
|  | 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_; | 
|  | std::unique_ptr<DataPipeDrainer> drainer_; | 
|  |  | 
|  | base::WeakPtrFactory<Job> weak_factory_; | 
|  | }; | 
|  |  | 
|  | class DartLibraryLoader::ImportJob : public Job { | 
|  | public: | 
|  | ImportJob(DartLibraryLoader* loader, | 
|  | const std::string& name, | 
|  | bool load_script) : Job(loader, name), load_script_(load_script) { | 
|  | TRACE_EVENT_ASYNC_BEGIN1("sky", "DartLibraryLoader::ImportJob", this, "url", | 
|  | name); | 
|  | } | 
|  |  | 
|  | bool load_script() const { return load_script_; } | 
|  |  | 
|  | private: | 
|  | // DataPipeDrainer::Client | 
|  | void OnDataComplete() override { | 
|  | TRACE_EVENT_ASYNC_END0("sky", "DartLibraryLoader::ImportJob", this); | 
|  | loader_->DidCompleteImportJob(this, buffer_); | 
|  | } | 
|  |  | 
|  | bool load_script_; | 
|  | }; | 
|  |  | 
|  | 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_(std::unique_ptr<DartDependencyCatcher>( | 
|  | 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_.reset(); | 
|  | 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_; | 
|  | std::unique_ptr<DartDependencyCatcher> catcher_; | 
|  | DartDependency* resolved_dependency_; | 
|  | }; | 
|  |  | 
|  | DartLibraryLoader::DartLibraryLoader(DartState* dart_state) | 
|  | : dart_state_(dart_state), | 
|  | library_provider_(nullptr), | 
|  | dependency_catcher_(nullptr), | 
|  | magic_number_(nullptr), | 
|  | magic_number_len_(0), | 
|  | error_during_loading_(false) { | 
|  | } | 
|  |  | 
|  | 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, false)); | 
|  | result.first->second = job.get(); | 
|  | jobs_.insert(std::move(job)); | 
|  | } | 
|  | if (dependency_catcher_) | 
|  | dependency_catcher_->AddDependency(result.first->second); | 
|  | } | 
|  |  | 
|  | void DartLibraryLoader::LoadScript(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, true)); | 
|  | 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) { | 
|  | DCHECK(library_provider_ != nullptr); | 
|  | return library_provider_->CanonicalizeURL(library, url); | 
|  | } | 
|  |  | 
|  | const uint8_t* DartLibraryLoader::SniffForMagicNumber( | 
|  | const uint8_t* text_buffer, intptr_t* buffer_len, bool* is_snapshot) { | 
|  | if (magic_number_ == nullptr) { | 
|  | *is_snapshot = false; | 
|  | return text_buffer; | 
|  | } | 
|  | for (intptr_t i = 0; i < magic_number_len_; i++) { | 
|  | if (text_buffer[i] != magic_number_[i]) { | 
|  | *is_snapshot = false; | 
|  | return text_buffer; | 
|  | } | 
|  | } | 
|  | *is_snapshot = true; | 
|  | DCHECK_GT(*buffer_len, magic_number_len_); | 
|  | *buffer_len -= magic_number_len_; | 
|  | return text_buffer + magic_number_len_; | 
|  | } | 
|  |  | 
|  | void DartLibraryLoader::DidCompleteImportJob( | 
|  | ImportJob* job, | 
|  | const std::vector<uint8_t>& buffer) { | 
|  | TRACE_EVENT1("sky", "DartLibraryLoader::DidCompleteImportJob", "url", | 
|  | job->name()); | 
|  | DartIsolateScope scope(dart_state_->isolate()); | 
|  | DartApiScope api_scope; | 
|  |  | 
|  | WatcherSignaler watcher_signaler(*this, job); | 
|  |  | 
|  | Dart_Handle result; | 
|  |  | 
|  | if (job->load_script()) { | 
|  | // Sniff for magic number. Load script from snapshot if found. | 
|  | const uint8_t* buf = buffer.data(); | 
|  | intptr_t len = buffer.size(); | 
|  | bool is_snapshot = false; | 
|  | buf = SniffForMagicNumber(buf, &len, &is_snapshot); | 
|  | if (is_snapshot) { | 
|  | result = Dart_LoadScriptFromSnapshot(buf, len); | 
|  | } else { | 
|  | result = Dart_LoadScript( | 
|  | StdStringToDart(job->name()), Dart_NewStringFromUTF8(buf, len), 0, 0); | 
|  | } | 
|  | } else { | 
|  | 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); | 
|  | error_during_loading_ = true; | 
|  | } | 
|  |  | 
|  | pending_libraries_.erase(job->name()); | 
|  | EraseUniquePtr<Job>(jobs_, job); | 
|  | } | 
|  |  | 
|  | void DartLibraryLoader::DidCompleteSourceJob( | 
|  | SourceJob* job, | 
|  | const std::vector<uint8_t>& buffer) { | 
|  | TRACE_EVENT1("sky", "DartLibraryLoader::DidCompleteSourceJob", "url", | 
|  | job->name()); | 
|  | 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); | 
|  | error_during_loading_ = true; | 
|  | } | 
|  |  | 
|  | 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? | 
|  | error_during_loading_ = true; | 
|  |  | 
|  | EraseUniquePtr<Job>(jobs_, job); | 
|  | } | 
|  |  | 
|  | }  // namespace tonic |