| // 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 |