blob: 8a4b23765f2d5a6be8bbf7f3b98c09b5958c0230 [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/browser/web_module.h"
#include <sstream>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/optional.h"
#include "base/stringprintf.h"
#include "cobalt/base/startup_timer.h"
#include "cobalt/base/tokens.h"
#include "cobalt/browser/stack_size_constants.h"
#include "cobalt/browser/switches.h"
#include "cobalt/browser/web_module_stat_tracker.h"
#include "cobalt/css_parser/parser.h"
#include "cobalt/debug/debug_server_module.h"
#include "cobalt/dom/blob.h"
#include "cobalt/dom/csp_delegate_factory.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/event.h"
#include "cobalt/dom/global_stats.h"
#include "cobalt/dom/keyboard_event.h"
#include "cobalt/dom/local_storage_database.h"
#include "cobalt/dom/mutation_observer_task_manager.h"
#include "cobalt/dom/pointer_event.h"
#include "cobalt/dom/storage.h"
#include "cobalt/dom/ui_event.h"
#include "cobalt/dom/url.h"
#include "cobalt/dom/wheel_event.h"
#include "cobalt/dom_parser/parser.h"
#include "cobalt/h5vcc/h5vcc.h"
#include "cobalt/layout/topmost_event_target.h"
#include "cobalt/loader/image/animated_image_tracker.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/page_visibility/visibility_state.h"
#include "cobalt/script/javascript_engine.h"
#include "cobalt/storage/storage_manager.h"
#include "starboard/accessibility.h"
#include "starboard/log.h"
namespace cobalt {
namespace browser {
namespace {
// The maximum number of element depth in the DOM tree. Elements at a level
// deeper than this could be discarded, and will not be rendered.
const int kDOMMaxElementDepth = 32;
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
// Help string for the 'partial_layout' command.
const char kPartialLayoutCommandShortHelp[] =
"Controls partial layout: on | off | wipe | wipe,off";
const char kPartialLayoutCommandLongHelp[] =
"Controls partial layout.\n"
"\n"
"Syntax:\n"
" debug.partial_layout('CMD [, CMD ...]')\n"
"\n"
"Where CMD can be:\n"
" on : turn partial layout on.\n"
" off : turn partial layout off.\n"
" wipe : wipe the box tree.\n"
"\n"
"Example:\n"
" debug.partial_layout('off,wipe')\n"
"\n"
"To wipe the box tree and turn partial layout off.";
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
} // namespace
// Private WebModule implementation. Each WebModule owns a single instance of
// this class, which performs all the actual work. All functions of this class
// must be called on the message loop of the WebModule thread, so they
// execute synchronously with respect to one another.
class WebModule::Impl {
public:
explicit Impl(const ConstructionData& data);
~Impl();
#if defined(ENABLE_DEBUG_CONSOLE)
debug::DebugServer* debug_server() const {
return debug_server_module_->debug_server();
}
#endif // ENABLE_DEBUG_CONSOLE
// Called to inject a keyboard event into the web module.
// Event is directed at a specific element if the element is non-null.
// Otherwise, the currently focused element receives the event.
// If element is specified, we must be on the WebModule's message loop
void InjectKeyboardEvent(scoped_refptr<dom::Element> element,
base::Token type,
const dom::KeyboardEventInit& event);
// Called to inject a pointer event into the web module.
// Event is directed at a specific element if the element is non-null.
// Otherwise, the currently focused element receives the event.
// If element is specified, we must be on the WebModule's message loop
void InjectPointerEvent(scoped_refptr<dom::Element> element, base::Token type,
const dom::PointerEventInit& event);
// Called to inject a wheel event into the web module.
// Event is directed at a specific element if the element is non-null.
// Otherwise, the currently focused element receives the event.
// If element is specified, we must be on the WebModule's message loop
void InjectWheelEvent(scoped_refptr<dom::Element> element, base::Token type,
const dom::WheelEventInit& event);
// Called to execute JavaScript in this WebModule. Sets the |result|
// output parameter and signals |got_result|.
void ExecuteJavascript(const std::string& script_utf8,
const base::SourceLocation& script_location,
base::WaitableEvent* got_result, std::string* result,
bool* out_succeeded);
// Clears disables timer related objects
// so that the message loop can easily exit
void ClearAllIntervalsAndTimeouts();
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
void OnPartialLayoutConsoleCommandReceived(const std::string& message);
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
#if defined(ENABLE_WEBDRIVER)
// Creates a new webdriver::WindowDriver that interacts with the Window that
// is owned by this WebModule instance.
void CreateWindowDriver(
const webdriver::protocol::WindowId& window_id,
scoped_ptr<webdriver::WindowDriver>* window_driver_out);
#endif
#if defined(ENABLE_DEBUG_CONSOLE)
void CreateDebugServerIfNull();
#endif // ENABLE_DEBUG_CONSOLE
void SetSize(math::Size window_dimensions, float video_pixel_ratio);
void SetCamera3D(const scoped_refptr<input::Camera3D>& camera_3d);
void SetMediaModule(media::MediaModule* media_module);
void SetImageCacheCapacity(int64_t bytes);
void SetRemoteTypefaceCacheCapacity(int64_t bytes);
void SetJavascriptGcThreshold(int64_t bytes);
// Sets the application state, asserts preconditions to transition to that
// state, and dispatches any precipitate web events.
void SetApplicationState(base::ApplicationState state);
// Suspension of the WebModule is a two-part process since a message loop
// gap is needed in order to give a chance to handle loader callbacks
// that were initiated from a loader thread.
//
// If |update_application_state| is false, then SetApplicationState will not
// be called, and no state transition events will be generated.
void SuspendLoaders(bool update_application_state);
void FinishSuspend();
// See LifecycleObserver. These functions do not implement the interface, but
// have the same basic function.
void Start(render_tree::ResourceProvider* resource_provider);
void Pause();
void Unpause();
void Resume(render_tree::ResourceProvider* resource_provider);
void ReportScriptError(const base::SourceLocation& source_location,
const std::string& error_message);
private:
class DocumentLoadedObserver;
// Purge all resource caches owned by the WebModule.
void PurgeResourceCaches();
// Disable callbacks in all resource caches owned by the WebModule.
void DisableCallbacksInResourceCaches();
// Injects a list of custom window attributes into the WebModule's window
// object.
void InjectCustomWindowAttributes(
const Options::InjectedWindowAttributes& attributes);
// Called by |layout_mananger_| after it runs the animation frame callbacks.
void OnRanAnimationFrameCallbacks();
// Called by |layout_mananger_| when it produces a render tree. May modify
// the render tree (e.g. to add a debug overlay), then runs the callback
// specified in the constructor, |render_tree_produced_callback_|.
void OnRenderTreeProduced(const LayoutResults& layout_results);
void OnCspPolicyChanged();
scoped_refptr<script::GlobalEnvironment> global_environment() {
DCHECK(thread_checker_.CalledOnValidThread());
return global_environment_;
}
void OnError(const std::string& error) {
error_callback_.Run(window_->location()->url(), error);
}
// Inject the DOM event object into the window or the element.
void InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<dom::Event>& event);
// Handle queued pointer events. Called by LayoutManager on_layout callback.
void HandlePointerEvents();
// Initializes the ResourceProvider and dependent resources.
void SetResourceProvider(render_tree::ResourceProvider* resource_provider);
// Thread checker ensures all calls to the WebModule are made from the same
// thread that it is created in.
base::ThreadChecker thread_checker_;
std::string name_;
// Simple flag used for basic error checking.
bool is_running_;
// Object that provides renderer resources like images and fonts.
render_tree::ResourceProvider* resource_provider_;
// CSS parser.
scoped_ptr<css_parser::Parser> css_parser_;
// DOM (HTML / XML) parser.
scoped_ptr<dom_parser::Parser> dom_parser_;
// FetcherFactory that is used to create a fetcher according to URL.
scoped_ptr<loader::FetcherFactory> fetcher_factory_;
// LoaderFactory that is used to acquire references to resources from a
// URL.
scoped_ptr<loader::LoaderFactory> loader_factory_;
scoped_ptr<loader::image::AnimatedImageTracker> animated_image_tracker_;
// ImageCache that is used to manage image cache logic.
scoped_ptr<loader::image::ImageCache> image_cache_;
// The reduced cache capacity manager can be used to force a reduced image
// cache over periods of time where memory is known to be restricted, such
// as when a video is playing.
scoped_ptr<loader::image::ReducedCacheCapacityManager>
reduced_image_cache_capacity_manager_;
// RemoteTypefaceCache that is used to manage loading and caching typefaces
// from URLs.
scoped_ptr<loader::font::RemoteTypefaceCache> remote_typeface_cache_;
// MeshCache that is used to manage mesh cache logic.
scoped_ptr<loader::mesh::MeshCache> mesh_cache_;
// Interface between LocalStorage and the Storage Manager.
scoped_ptr<dom::LocalStorageDatabase> local_storage_database_;
// Stats for the web module. Both the dom stat tracker and layout stat
// tracker are contained within it.
scoped_ptr<browser::WebModuleStatTracker> web_module_stat_tracker_;
// Post and run tasks to notify MutationObservers.
dom::MutationObserverTaskManager mutation_observer_task_manager_;
// JavaScript engine for the browser.
scoped_ptr<script::JavaScriptEngine> javascript_engine_;
// JavaScript Global Object for the browser. There should be one per window,
// but since there is only one window, we can have one per browser.
scoped_refptr<script::GlobalEnvironment> global_environment_;
// Used by |Console| to obtain a JavaScript stack trace.
scoped_ptr<script::ExecutionState> execution_state_;
// Interface for the document to execute JavaScript code.
scoped_ptr<script::ScriptRunner> script_runner_;
// Object to register and retrieve MediaSource object with a string key.
scoped_ptr<dom::MediaSource::Registry> media_source_registry_;
// Object to register and retrieve Blob objects with a string key.
scoped_ptr<dom::Blob::Registry> blob_registry_;
// The Window object wraps all DOM-related components.
scoped_refptr<dom::Window> window_;
// Cache a WeakPtr in the WebModule that is bound to the Window's message loop
// so we can ensure that all subsequently created WeakPtr's are also bound to
// the same loop.
// See the documentation in base/memory/weak_ptr.h for details.
base::WeakPtr<dom::Window> window_weak_;
// Environment Settings object
scoped_ptr<dom::DOMSettings> environment_settings_;
// Called by |OnRenderTreeProduced|.
OnRenderTreeProducedCallback render_tree_produced_callback_;
// Called by |OnError|.
OnErrorCallback error_callback_;
// Triggers layout whenever the document changes.
scoped_ptr<layout::LayoutManager> layout_manager_;
#if defined(ENABLE_DEBUG_CONSOLE)
// Allows the debugger to add render components to the web module.
// Used for DOM node highlighting and overlay messages.
scoped_ptr<debug::RenderOverlay> debug_overlay_;
// The core of the debugging system, described here:
// https://docs.google.com/document/d/1lZhrBTusQZJsacpt21J3kPgnkj7pyQObhFqYktvm40Y
// Created lazily when accessed via |GetDebugServer|.
scoped_ptr<debug::DebugServerModule> debug_server_module_;
#endif // ENABLE_DEBUG_CONSOLE
// DocumentObserver that observes the loading document.
scoped_ptr<DocumentLoadedObserver> document_load_observer_;
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
// Handles the 'partial_layout' command.
scoped_ptr<base::ConsoleCommandManager::CommandHandler>
partial_layout_command_handler_;
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
scoped_ptr<media_session::MediaSessionClient> media_session_client_;
scoped_ptr<layout::TopmostEventTarget> topmost_event_target_;
};
class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
public:
typedef std::vector<base::Closure> ClosureVector;
explicit DocumentLoadedObserver(const ClosureVector& loaded_callbacks)
: loaded_callbacks_(loaded_callbacks) {}
// Called at most once, when document and all referred resources are loaded.
void OnLoad() OVERRIDE {
for (size_t i = 0; i < loaded_callbacks_.size(); ++i) {
loaded_callbacks_[i].Run();
}
}
void OnMutation() OVERRIDE{};
void OnFocusChanged() OVERRIDE{};
private:
ClosureVector loaded_callbacks_;
};
WebModule::Impl::Impl(const ConstructionData& data)
: name_(data.options.name),
is_running_(false),
resource_provider_(data.resource_provider) {
// Currently we rely on a platform to explicitly specify that it supports
// the map-to-mesh filter via the ENABLE_MAP_TO_MESH define (and the
// 'enable_map_to_mesh' gyp variable). When we have better support for
// checking for decode to texture support, it would be nice to switch this
// logic to something like:
//
// supports_map_to_mesh =
// (resource_provider_->Supports3D() && SbPlayerSupportsDecodeToTexture()
// ? css_parser::Parser::kSupportsMapToMesh
// : css_parser::Parser::kDoesNotSupportMapToMesh);
//
// Note that it is important that we do not parse map-to-mesh filters if we
// cannot render them, since web apps may check for map-to-mesh support by
// testing whether it parses or not via the CSS.supports() Web API.
css_parser::Parser::SupportsMapToMeshFlag supports_map_to_mesh =
#if defined(ENABLE_MAP_TO_MESH)
css_parser::Parser::kSupportsMapToMesh;
#else
css_parser::Parser::kDoesNotSupportMapToMesh;
#endif
css_parser_ = css_parser::Parser::Create(supports_map_to_mesh);
DCHECK(css_parser_);
dom_parser_.reset(new dom_parser::Parser(
kDOMMaxElementDepth,
base::Bind(&WebModule::Impl::OnError, base::Unretained(this))));
DCHECK(dom_parser_);
blob_registry_.reset(new dom::Blob::Registry);
fetcher_factory_.reset(new loader::FetcherFactory(
data.network_module, data.options.extra_web_file_dir,
dom::URL::MakeBlobResolverCallback(blob_registry_.get())));
DCHECK(fetcher_factory_);
loader_factory_.reset(
new loader::LoaderFactory(fetcher_factory_.get(), resource_provider_,
data.options.loader_thread_priority));
animated_image_tracker_.reset(new loader::image::AnimatedImageTracker(
data.options.animated_image_decode_thread_priority));
DCHECK_LE(0, data.options.image_cache_capacity);
image_cache_ = loader::image::CreateImageCache(
base::StringPrintf("%s.ImageCache", name_.c_str()),
static_cast<uint32>(data.options.image_cache_capacity),
loader_factory_.get());
DCHECK(image_cache_);
reduced_image_cache_capacity_manager_.reset(
new loader::image::ReducedCacheCapacityManager(
image_cache_.get(),
data.options.image_cache_capacity_multiplier_when_playing_video));
DCHECK_LE(0, data.options.remote_typeface_cache_capacity);
remote_typeface_cache_ = loader::font::CreateRemoteTypefaceCache(
base::StringPrintf("%s.RemoteTypefaceCache", name_.c_str()),
static_cast<uint32>(data.options.remote_typeface_cache_capacity),
loader_factory_.get());
DCHECK(remote_typeface_cache_);
DCHECK_LE(0, data.options.mesh_cache_capacity);
mesh_cache_ = loader::mesh::CreateMeshCache(
base::StringPrintf("%s.MeshCache", name_.c_str()),
static_cast<uint32>(data.options.mesh_cache_capacity),
loader_factory_.get());
DCHECK(mesh_cache_);
local_storage_database_.reset(
new dom::LocalStorageDatabase(data.network_module->storage_manager()));
DCHECK(local_storage_database_);
web_module_stat_tracker_.reset(
new browser::WebModuleStatTracker(name_, data.options.track_event_stats));
DCHECK(web_module_stat_tracker_);
javascript_engine_ =
script::JavaScriptEngine::CreateEngine(data.options.javascript_options);
DCHECK(javascript_engine_);
#if defined(COBALT_ENABLE_JAVASCRIPT_ERROR_LOGGING)
script::JavaScriptEngine::ErrorHandler error_handler =
base::Bind(&WebModule::Impl::ReportScriptError, base::Unretained(this));
javascript_engine_->RegisterErrorHandler(error_handler);
#endif
global_environment_ = javascript_engine_->CreateGlobalEnvironment();
DCHECK(global_environment_);
execution_state_ =
script::ExecutionState::CreateExecutionState(global_environment_);
DCHECK(execution_state_);
script_runner_ =
script::ScriptRunner::CreateScriptRunner(global_environment_);
DCHECK(script_runner_);
media_source_registry_.reset(new dom::MediaSource::Registry);
media_session_client_ = media_session::MediaSessionClient::Create();
window_ = new dom::Window(
data.window_dimensions.width(), data.window_dimensions.height(),
data.video_pixel_ratio, data.initial_application_state, css_parser_.get(),
dom_parser_.get(), fetcher_factory_.get(), &resource_provider_,
animated_image_tracker_.get(), image_cache_.get(),
reduced_image_cache_capacity_manager_.get(), remote_typeface_cache_.get(),
mesh_cache_.get(), local_storage_database_.get(), data.media_module,
data.media_module, execution_state_.get(), script_runner_.get(),
global_environment_->script_value_factory(), media_source_registry_.get(),
web_module_stat_tracker_->dom_stat_tracker(), data.initial_url,
data.network_module->GetUserAgent(),
data.network_module->preferred_language(),
data.options.navigation_callback,
base::Bind(&WebModule::Impl::OnError, base::Unretained(this)),
data.network_module->cookie_jar(), data.network_module->GetPostSender(),
data.options.location_policy, data.options.csp_enforcement_mode,
base::Bind(&WebModule::Impl::OnCspPolicyChanged, base::Unretained(this)),
base::Bind(&WebModule::Impl::OnRanAnimationFrameCallbacks,
base::Unretained(this)),
data.window_close_callback, data.window_minimize_callback,
data.options.camera_3d, media_session_client_->GetMediaSession(),
data.options.csp_insecure_allowed_token, data.dom_max_element_depth,
data.options.video_playback_rate_multiplier,
#if defined(ENABLE_TEST_RUNNER)
data.options.layout_trigger == layout::LayoutManager::kTestRunnerMode
? dom::Window::kClockTypeTestRunner
: dom::Window::kClockTypeSystemTime
#else
dom::Window::kClockTypeSystemTime
#endif
); // NOLINT(whitespace/parens)
DCHECK(window_);
window_weak_ = base::AsWeakPtr(window_.get());
DCHECK(window_weak_);
environment_settings_.reset(new dom::DOMSettings(
kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module,
data.media_module, window_, media_source_registry_.get(),
blob_registry_.get(), data.media_module, javascript_engine_.get(),
global_environment_.get(), &mutation_observer_task_manager_,
data.options.dom_settings_options));
DCHECK(environment_settings_);
global_environment_->CreateGlobalObject(window_, environment_settings_.get());
render_tree_produced_callback_ = data.render_tree_produced_callback;
DCHECK(!render_tree_produced_callback_.is_null());
error_callback_ = data.error_callback;
DCHECK(!error_callback_.is_null());
layout_manager_.reset(new layout::LayoutManager(
name_, window_.get(),
base::Bind(&WebModule::Impl::OnRenderTreeProduced,
base::Unretained(this)),
base::Bind(&WebModule::Impl::HandlePointerEvents, base::Unretained(this)),
data.options.layout_trigger, data.dom_max_element_depth,
data.layout_refresh_rate, data.network_module->preferred_language(),
data.options.enable_image_animations,
web_module_stat_tracker_->layout_stat_tracker()));
DCHECK(layout_manager_);
#if defined(ENABLE_DEBUG_CONSOLE)
debug_overlay_.reset(
new debug::RenderOverlay(data.render_tree_produced_callback));
#endif // ENABLE_DEBUG_CONSOLE
#if !defined(COBALT_FORCE_CSP)
if (data.options.csp_enforcement_mode == dom::kCspEnforcementDisable) {
// If CSP is disabled, enable eval(). Otherwise, it will be enabled by
// a CSP directive.
global_environment_->EnableEval();
}
#endif
global_environment_->SetReportEvalCallback(
base::Bind(&dom::CspDelegate::ReportEval,
base::Unretained(window_->document()->csp_delegate())));
InjectCustomWindowAttributes(data.options.injected_window_attributes);
if (!data.options.loaded_callbacks.empty()) {
document_load_observer_.reset(
new DocumentLoadedObserver(data.options.loaded_callbacks));
window_->document()->AddObserver(document_load_observer_.get());
}
is_running_ = true;
}
WebModule::Impl::~Impl() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
is_running_ = false;
global_environment_->SetReportEvalCallback(base::Closure());
window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
document_load_observer_.reset();
media_session_client_.reset();
#if defined(ENABLE_DEBUG_CONSOLE)
debug_overlay_.reset();
debug_server_module_.reset();
#endif // ENABLE_DEBUG_CONSOLE
// Disable callbacks for the resource caches. Otherwise, it is possible for a
// callback to occur into a DOM object that is being kept alive by a JS engine
// reference even after the DOM tree has been destroyed. This can result in a
// crash when the callback attempts to access a stale Document pointer.
DisableCallbacksInResourceCaches();
topmost_event_target_.reset();
layout_manager_.reset();
environment_settings_.reset();
window_weak_.reset();
window_ = NULL;
media_source_registry_.reset();
blob_registry_.reset();
script_runner_.reset();
execution_state_.reset();
global_environment_ = NULL;
javascript_engine_.reset();
web_module_stat_tracker_.reset();
local_storage_database_.reset();
mesh_cache_.reset();
remote_typeface_cache_.reset();
image_cache_.reset();
animated_image_tracker_.reset();
fetcher_factory_.reset();
dom_parser_.reset();
css_parser_.reset();
}
void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<dom::Event>& event) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(window_);
web_module_stat_tracker_->OnStartInjectEvent(event);
if (element) {
element->DispatchEvent(event);
} else {
window_->InjectEvent(event);
}
web_module_stat_tracker_->OnEndInjectEvent(
window_->HasPendingAnimationFrameCallbacks(),
layout_manager_->IsRenderTreePending());
}
void WebModule::Impl::InjectKeyboardEvent(scoped_refptr<dom::Element> element,
base::Token type,
const dom::KeyboardEventInit& event) {
scoped_refptr<dom::KeyboardEvent> keyboard_event(
new dom::KeyboardEvent(type, window_, event));
InjectInputEvent(element, keyboard_event);
}
void WebModule::Impl::InjectPointerEvent(scoped_refptr<dom::Element> element,
base::Token type,
const dom::PointerEventInit& event) {
scoped_refptr<dom::PointerEvent> pointer_event(
new dom::PointerEvent(type, window_, event));
InjectInputEvent(element, pointer_event);
}
void WebModule::Impl::InjectWheelEvent(scoped_refptr<dom::Element> element,
base::Token type,
const dom::WheelEventInit& event) {
scoped_refptr<dom::WheelEvent> wheel_event(
new dom::WheelEvent(type, window_, event));
InjectInputEvent(element, wheel_event);
}
void WebModule::Impl::ExecuteJavascript(
const std::string& script_utf8, const base::SourceLocation& script_location,
base::WaitableEvent* got_result, std::string* result, bool* out_succeeded) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(script_runner_);
// JavaScript is being run. Track it in the global stats.
dom::GlobalStats::GetInstance()->StartJavaScriptEvent();
*result = script_runner_->Execute(script_utf8, script_location,
out_succeeded);
// JavaScript is done running. Stop tracking it in the global stats.
dom::GlobalStats::GetInstance()->StopJavaScriptEvent();
got_result->Signal();
}
void WebModule::Impl::ClearAllIntervalsAndTimeouts() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(window_);
window_->DestroyTimers();
}
void WebModule::Impl::OnRanAnimationFrameCallbacks() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
// Notify the stat tracker that the animation frame callbacks have finished.
// This may end the current event being tracked.
web_module_stat_tracker_->OnRanAnimationFrameCallbacks(
layout_manager_->IsRenderTreePending());
}
void WebModule::Impl::OnRenderTreeProduced(
const LayoutResults& layout_results) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
// Notify the stat tracker that a render tree has been produced.
web_module_stat_tracker_->OnRenderTreeProduced();
#if defined(ENABLE_DEBUG_CONSOLE)
debug_overlay_->OnRenderTreeProduced(layout_results);
#else // ENABLE_DEBUG_CONSOLE
render_tree_produced_callback_.Run(layout_results);
#endif // ENABLE_DEBUG_CONSOLE
}
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
void WebModule::Impl::OnPartialLayoutConsoleCommandReceived(
const std::string& message) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->document());
window_->document()->SetPartialLayout(message);
}
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
void WebModule::Impl::OnCspPolicyChanged() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->document());
DCHECK(window_->document()->csp_delegate());
std::string eval_disabled_message;
bool allow_eval =
window_->document()->csp_delegate()->AllowEval(&eval_disabled_message);
if (allow_eval) {
global_environment_->EnableEval();
} else {
global_environment_->DisableEval(eval_disabled_message);
}
}
#if defined(ENABLE_WEBDRIVER)
void WebModule::Impl::CreateWindowDriver(
const webdriver::protocol::WindowId& window_id,
scoped_ptr<webdriver::WindowDriver>* window_driver_out) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_weak_);
DCHECK(window_->document());
DCHECK(global_environment_);
window_driver_out->reset(new webdriver::WindowDriver(
window_id, window_weak_,
base::Bind(&WebModule::Impl::global_environment, base::Unretained(this)),
base::Bind(&WebModule::Impl::InjectKeyboardEvent, base::Unretained(this)),
base::Bind(&WebModule::Impl::InjectPointerEvent, base::Unretained(this)),
base::Bind(&WebModule::Impl::InjectWheelEvent, base::Unretained(this)),
base::MessageLoopProxy::current()));
}
#endif // defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUG_CONSOLE)
void WebModule::Impl::CreateDebugServerIfNull() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_running_);
DCHECK(window_);
DCHECK(global_environment_);
DCHECK(resource_provider_);
if (debug_server_module_) {
return;
}
debug_server_module_.reset(new debug::DebugServerModule(
window_->console(), global_environment_, debug_overlay_.get(),
resource_provider_, window_));
}
#endif // defined(ENABLE_DEBUG_CONSOLE)
void WebModule::Impl::InjectCustomWindowAttributes(
const Options::InjectedWindowAttributes& attributes) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(global_environment_);
for (Options::InjectedWindowAttributes::const_iterator iter =
attributes.begin();
iter != attributes.end(); ++iter) {
global_environment_->Bind(
iter->first,
iter->second.Run(window_, &mutation_observer_task_manager_));
}
}
void WebModule::Impl::SetImageCacheCapacity(int64_t bytes) {
image_cache_->SetCapacity(static_cast<uint32>(bytes));
}
void WebModule::Impl::SetRemoteTypefaceCacheCapacity(int64_t bytes) {
remote_typeface_cache_->SetCapacity(static_cast<uint32>(bytes));
}
void WebModule::Impl::SetJavascriptGcThreshold(int64_t bytes) {
javascript_engine_->SetGcThreshold(bytes);
}
void WebModule::Impl::SetSize(math::Size window_dimensions,
float video_pixel_ratio) {
window_->SetSize(window_dimensions.width(), window_dimensions.height(),
video_pixel_ratio);
}
void WebModule::Impl::SetCamera3D(
const scoped_refptr<input::Camera3D>& camera_3d) {
window_->SetCamera3D(camera_3d);
}
void WebModule::Impl::SetMediaModule(media::MediaModule* media_module) {
window_->set_can_play_type_handler(media_module);
window_->set_web_media_player_factory(media_module);
environment_settings_->set_media_module(media_module);
environment_settings_->set_can_play_type_handler(media_module);
}
void WebModule::Impl::SetApplicationState(base::ApplicationState state) {
window_->SetApplicationState(state);
}
void WebModule::Impl::SetResourceProvider(
render_tree::ResourceProvider* resource_provider) {
resource_provider_ = resource_provider;
if (resource_provider_) {
loader_factory_->Resume(resource_provider_);
// Permit render trees to be generated again. Layout will have been
// invalidated with the call to Suspend(), so the layout manager's first
// task will be to perform a full re-layout.
layout_manager_->Resume();
}
}
void WebModule::Impl::Start(render_tree::ResourceProvider* resource_provider) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Start()");
SetResourceProvider(resource_provider);
SetApplicationState(base::kApplicationStateStarted);
}
void WebModule::Impl::Pause() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Pause()");
SetApplicationState(base::kApplicationStatePaused);
}
void WebModule::Impl::Unpause() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Unpause()");
SetApplicationState(base::kApplicationStateStarted);
}
void WebModule::Impl::SuspendLoaders(bool update_application_state) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::SuspendLoaders()");
if (update_application_state) {
SetApplicationState(base::kApplicationStateSuspended);
}
// Purge the resource caches before running any suspend logic. This will force
// any pending callbacks that the caches are batching to run.
PurgeResourceCaches();
// Stop the generation of render trees.
layout_manager_->Suspend();
// Purge the cached resources prior to the suspend. That may cancel pending
// loads, allowing the suspend to occur faster and preventing unnecessary
// callbacks.
window_->document()->PurgeCachedResources();
// Clear out the loader factory's resource provider, possibly aborting any
// in-progress loads.
loader_factory_->Suspend();
// Clear out any currently tracked animating images.
animated_image_tracker_->Reset();
}
void WebModule::Impl::FinishSuspend() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::FinishSuspend()");
DCHECK(resource_provider_);
// Ensure the document is not holding onto any more image cached resources so
// that they are eligible to be purged.
window_->document()->PurgeCachedResources();
// Clear out all resource caches. We need to do this after we abort all
// in-progress loads, and after we clear all document references, or they will
// still be referenced and won't be cleared from the cache.
PurgeResourceCaches();
#if defined(ENABLE_DEBUG_CONSOLE)
// The debug overlay may be holding onto a render tree, clear that out.
debug_overlay_->ClearInput();
#endif
resource_provider_ = NULL;
// Force garbage collection in |javascript_engine_|.
if (javascript_engine_) {
javascript_engine_->CollectGarbage();
}
}
void WebModule::Impl::Resume(render_tree::ResourceProvider* resource_provider) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Resume()");
SetResourceProvider(resource_provider);
SetApplicationState(base::kApplicationStatePaused);
}
void WebModule::Impl::ReportScriptError(
const base::SourceLocation& source_location,
const std::string& error_message) {
std::string file_name =
FilePath(source_location.file_path).BaseName().value();
std::stringstream ss;
base::TimeDelta dt = base::StartupTimer::TimeElapsed();
// Create the error output.
// Example:
// JS:50250:file.js(29,80): ka(...) is not iterable
// JS:<time millis><js-file-name>(<line>,<column>):<message>
ss << "JS:" << dt.InMilliseconds() << ":" << file_name << "("
<< source_location.line_number << ","
<< source_location.column_number << "): " << error_message << "\n";
SbLogRaw(ss.str().c_str());
}
void WebModule::Impl::PurgeResourceCaches() {
image_cache_->Purge();
remote_typeface_cache_->Purge();
mesh_cache_->Purge();
}
void WebModule::Impl::DisableCallbacksInResourceCaches() {
image_cache_->DisableCallbacks();
remote_typeface_cache_->DisableCallbacks();
mesh_cache_->DisableCallbacks();
}
WebModule::DestructionObserver::DestructionObserver(WebModule* web_module)
: web_module_(web_module) {}
void WebModule::DestructionObserver::WillDestroyCurrentMessageLoop() {
web_module_->impl_.reset();
}
WebModule::Options::Options()
: name("WebModule"),
layout_trigger(layout::LayoutManager::kOnDocumentMutation),
image_cache_capacity(32 * 1024 * 1024),
remote_typeface_cache_capacity(4 * 1024 * 1024),
mesh_cache_capacity(COBALT_MESH_CACHE_SIZE_IN_BYTES),
csp_enforcement_mode(dom::kCspEnforcementEnable),
csp_insecure_allowed_token(0),
track_event_stats(false),
image_cache_capacity_multiplier_when_playing_video(1.0f),
thread_priority(base::kThreadPriority_Normal),
loader_thread_priority(base::kThreadPriority_Low),
animated_image_decode_thread_priority(base::kThreadPriority_Low),
video_playback_rate_multiplier(1.f),
enable_image_animations(true) {}
WebModule::WebModule(
const GURL& initial_url, base::ApplicationState initial_application_state,
const OnRenderTreeProducedCallback& render_tree_produced_callback,
const OnErrorCallback& error_callback,
const base::Closure& window_close_callback,
const base::Closure& window_minimize_callback,
media::MediaModule* media_module, network::NetworkModule* network_module,
const math::Size& window_dimensions, float video_pixel_ratio,
render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
const Options& options)
: thread_(options.name.c_str()) {
ConstructionData construction_data(
initial_url, initial_application_state, render_tree_produced_callback,
error_callback, window_close_callback, window_minimize_callback,
media_module, network_module, window_dimensions, video_pixel_ratio,
resource_provider, kDOMMaxElementDepth, layout_refresh_rate, options);
// Start the dedicated thread and create the internal implementation
// object on that thread.
thread_.StartWithOptions(base::Thread::Options(
MessageLoop::TYPE_DEFAULT, cobalt::browser::kWebModuleStackSize,
options.thread_priority));
DCHECK(message_loop());
// Block this thread until the initialization is complete.
// TODO: Figure out why this is necessary.
// It would be preferable to return immediately and let the WebModule
// continue in its own time, but without this wait there is a race condition
// such that inline scripts may be executed before the document elements they
// operate on are present.
message_loop()->PostBlockingTask(
FROM_HERE, base::Bind(&WebModule::Initialize, base::Unretained(this),
construction_data));
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(browser::switches::kPartialLayout)) {
const std::string partial_layout_string =
command_line->GetSwitchValueASCII(browser::switches::kPartialLayout);
OnPartialLayoutConsoleCommandReceived(partial_layout_string);
}
partial_layout_command_handler_.reset(
new base::ConsoleCommandManager::CommandHandler(
browser::switches::kPartialLayout,
base::Bind(&WebModule::OnPartialLayoutConsoleCommandReceived,
base::Unretained(this)),
kPartialLayoutCommandShortHelp, kPartialLayoutCommandLongHelp));
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
}
WebModule::~WebModule() {
DCHECK(message_loop());
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
partial_layout_command_handler_.reset();
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
// Create a destruction observer to shut down the WebModule once all pending
// tasks have been executed and the message loop is about to be destroyed.
// This allows us to safely stop the thread, drain the task queue, then
// destroy the internal components before the message loop is set to NULL.
// No posted tasks will be executed once the thread is stopped.
DestructionObserver destruction_observer(this);
message_loop()->PostBlockingTask(
FROM_HERE,
base::Bind(&MessageLoop::AddDestructionObserver,
base::Unretained(message_loop()),
base::Unretained(&destruction_observer)));
// This will cancel the timers for tasks, which help the thread exit
ClearAllIntervalsAndTimeouts();
// Stop the thread. This will cause the destruction observer to be notified.
thread_.Stop();
}
void WebModule::Initialize(const ConstructionData& data) {
DCHECK_EQ(MessageLoop::current(), message_loop());
impl_.reset(new Impl(data));
}
void WebModule::InjectKeyboardEvent(base::Token type,
const dom::KeyboardEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
type.c_str());
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::InjectKeyboardEvent,
base::Unretained(impl_.get()),
scoped_refptr<dom::Element>(), type, event));
}
void WebModule::InjectPointerEvent(base::Token type,
const dom::PointerEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectPointerEvent()", "type",
type.c_str());
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::InjectPointerEvent,
base::Unretained(impl_.get()),
scoped_refptr<dom::Element>(), type, event));
}
void WebModule::InjectWheelEvent(base::Token type,
const dom::WheelEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectWheelEvent()", "type",
type.c_str());
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::InjectWheelEvent,
base::Unretained(impl_.get()),
scoped_refptr<dom::Element>(), type, event));
}
std::string WebModule::ExecuteJavascript(
const std::string& script_utf8,
const base::SourceLocation& script_location,
bool* out_succeeded) {
TRACE_EVENT0("cobalt::browser", "WebModule::ExecuteJavascript()");
DCHECK(message_loop());
DCHECK(impl_);
base::WaitableEvent got_result(true, false);
std::string result;
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::ExecuteJavascript,
base::Unretained(impl_.get()), script_utf8,
script_location, &got_result, &result,
out_succeeded));
got_result.Wait();
return result;
}
void WebModule::ClearAllIntervalsAndTimeouts() {
TRACE_EVENT0("cobalt::browser", "WebModule::ClearAllIntervalsAndTimeouts()");
DCHECK(message_loop());
DCHECK(impl_);
if (impl_) {
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::ClearAllIntervalsAndTimeouts,
base::Unretained(impl_.get())));
}
}
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
void WebModule::OnPartialLayoutConsoleCommandReceived(
const std::string& message) {
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->PostTask(
FROM_HERE,
base::Bind(&WebModule::Impl::OnPartialLayoutConsoleCommandReceived,
base::Unretained(impl_.get()), message));
}
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
#if defined(ENABLE_WEBDRIVER)
scoped_ptr<webdriver::WindowDriver> WebModule::CreateWindowDriver(
const webdriver::protocol::WindowId& window_id) {
DCHECK(message_loop());
DCHECK(impl_);
scoped_ptr<webdriver::WindowDriver> window_driver;
message_loop()->PostBlockingTask(
FROM_HERE,
base::Bind(&WebModule::Impl::CreateWindowDriver,
base::Unretained(impl_.get()), window_id,
base::Unretained(&window_driver)));
return window_driver.Pass();
}
#endif // defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUG_CONSOLE)
// May be called from any thread.
debug::DebugServer* WebModule::GetDebugServer() {
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->PostBlockingTask(
FROM_HERE,
base::Bind(&WebModule::Impl::CreateDebugServerIfNull,
base::Unretained(impl_.get())));
return impl_->debug_server();
}
#endif // defined(ENABLE_DEBUG_CONSOLE)
void WebModule::SetSize(const math::Size& window_dimensions,
float video_pixel_ratio) {
message_loop()->PostTask(
FROM_HERE,
base::Bind(&WebModule::Impl::SetSize, base::Unretained(impl_.get()),
window_dimensions, video_pixel_ratio));
}
void WebModule::SetCamera3D(const scoped_refptr<input::Camera3D>& camera_3d) {
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::SetCamera3D,
base::Unretained(impl_.get()), camera_3d));
}
void WebModule::SetMediaModule(media::MediaModule* media_module) {
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::SetMediaModule,
base::Unretained(impl_.get()), media_module));
}
void WebModule::SetImageCacheCapacity(int64_t bytes) {
message_loop()->PostTask(FROM_HERE,
base::Bind(&WebModule::Impl::SetImageCacheCapacity,
base::Unretained(impl_.get()), bytes));
}
void WebModule::SetRemoteTypefaceCacheCapacity(int64_t bytes) {
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::SetRemoteTypefaceCacheCapacity,
base::Unretained(impl_.get()), bytes));
}
void WebModule::SetJavascriptGcThreshold(int64_t bytes) {
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::SetJavascriptGcThreshold,
base::Unretained(impl_.get()), bytes));
}
void WebModule::Prestart() {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that we don't queue the finish until after
// SuspendLoaders has run to completion, and therefore has already queued any
// precipitate tasks.
message_loop()->PostBlockingTask(
FROM_HERE, base::Bind(&WebModule::Impl::SuspendLoaders,
base::Unretained(impl_.get()),
false /*update_application_state*/));
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostBlockingTask(FROM_HERE,
base::Bind(&WebModule::Impl::FinishSuspend,
base::Unretained(impl_.get())));
}
void WebModule::Start(render_tree::ResourceProvider* resource_provider) {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::Start,
base::Unretained(impl_.get()), resource_provider));
}
void WebModule::Pause() {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostBlockingTask(
FROM_HERE,
base::Bind(&WebModule::Impl::Pause, base::Unretained(impl_.get())));
}
void WebModule::Unpause() {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostTask(
FROM_HERE,
base::Bind(&WebModule::Impl::Unpause, base::Unretained(impl_.get())));
}
void WebModule::Suspend() {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that we don't queue the finish until after
// SuspendLoaders has run to completion, and therefore has already queued any
// precipitate tasks.
message_loop()->PostBlockingTask(
FROM_HERE, base::Bind(&WebModule::Impl::SuspendLoaders,
base::Unretained(impl_.get()),
true /*update_application_state*/));
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostBlockingTask(FROM_HERE,
base::Bind(&WebModule::Impl::FinishSuspend,
base::Unretained(impl_.get())));
}
void WebModule::Resume(render_tree::ResourceProvider* resource_provider) {
// Must only be called by a thread external from the WebModule thread.
DCHECK_NE(MessageLoop::current(), message_loop());
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
message_loop()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::Resume,
base::Unretained(impl_.get()), resource_provider));
}
void WebModule::Impl::HandlePointerEvents() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::HandlePointerEvents");
const scoped_refptr<dom::Document>& document = window_->document();
scoped_refptr<dom::Event> event;
do {
event = document->GetNextQueuedPointerEvent();
if (event) {
SB_DCHECK(
window_ ==
base::polymorphic_downcast<const dom::UIEvent* const>(event.get())
->view());
if (!topmost_event_target_) {
topmost_event_target_.reset(new layout::TopmostEventTarget());
}
topmost_event_target_->MaybeSendPointerEvents(event);
}
} while (event && !layout_manager_->IsRenderTreePending());
}
} // namespace browser
} // namespace cobalt