blob: 330de1e694a36e788a4cb5bd967cda326a34dd1b [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. 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 <memory>
#include <sstream>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/optional.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/c_val.h"
#include "cobalt/base/debugger_hooks.h"
#include "cobalt/base/language.h"
#include "cobalt/base/startup_timer.h"
#include "cobalt/base/tokens.h"
#include "cobalt/base/type_id.h"
#include "cobalt/browser/splash_screen_cache.h"
#include "cobalt/browser/stack_size_constants.h"
#include "cobalt/browser/switches.h"
#include "cobalt/browser/web_module_stat_tracker.h"
#include "cobalt/configuration/configuration.h"
#include "cobalt/css_parser/parser.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/global_stats.h"
#include "cobalt/dom/html_script_element.h"
#include "cobalt/dom/input_event.h"
#include "cobalt/dom/input_event_init.h"
#include "cobalt/dom/keyboard_event.h"
#include "cobalt/dom/keyboard_event_init.h"
#include "cobalt/dom/local_storage_database.h"
#include "cobalt/dom/mutation_observer_task_manager.h"
#include "cobalt/dom/navigation_type.h"
#include "cobalt/dom/navigator.h"
#include "cobalt/dom/pointer_event.h"
#include "cobalt/dom/storage.h"
#include "cobalt/dom/ui_event.h"
#include "cobalt/dom/visibility_state.h"
#include "cobalt/dom/wheel_event.h"
#include "cobalt/dom/window.h"
#include "cobalt/dom_parser/parser.h"
#include "cobalt/layout/topmost_event_target.h"
#include "cobalt/loader/image/animated_image_tracker.h"
#include "cobalt/loader/loader_factory.h"
#include "cobalt/loader/switches.h"
#include "cobalt/media/decoder_buffer_allocator.h"
#include "cobalt/media/media_module.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/storage/storage_manager.h"
#include "cobalt/web/blob.h"
#include "cobalt/web/context.h"
#include "cobalt/web/csp_delegate_factory.h"
#include "cobalt/web/environment_settings.h"
#include "cobalt/web/event.h"
#include "cobalt/web/url.h"
#include "starboard/accessibility.h"
#include "starboard/common/log.h"
#include "starboard/gles.h"
#if defined(ENABLE_DEBUGGER)
#include "cobalt/debug/backend/debug_module.h" // nogncheck
#endif // defined(ENABLE_DEBUGGER)
namespace cobalt {
namespace browser {
using cobalt::cssom::ViewportSize;
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;
void CacheUrlContent(SplashScreenCache* splash_screen_cache,
const std::string& content,
const base::Optional<std::string>& topic) {
splash_screen_cache->SplashScreenCache::CacheSplashScreen(content, topic);
}
base::Callback<void(const std::string&, const base::Optional<std::string>&)>
CacheUrlContentCallback(SplashScreenCache* splash_screen_cache) {
// This callback takes in first the url, then the content string.
if (splash_screen_cache) {
return base::Bind(CacheUrlContent, base::Unretained(splash_screen_cache));
} else {
return base::Callback<void(const std::string&,
const base::Optional<std::string>&)>();
}
}
} // 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:
Impl(web::Context* web_context, const ConstructionData& data);
~Impl();
#if defined(ENABLE_DEBUGGER)
debug::backend::DebugDispatcher* debug_dispatcher() {
DCHECK(debug_module_);
return debug_module_->debug_dispatcher();
}
#endif // ENABLE_DEBUGGER
// Injects an on screen keyboard input 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 InjectOnScreenKeyboardInputEvent(
base::Token type, const dom::InputEventInit& event,
scoped_refptr<dom::Element> element = scoped_refptr<dom::Element>());
// Injects an on screen keyboard shown event into the web module. Event is
// directed at the on screen keyboard element.
void InjectOnScreenKeyboardShownEvent(int ticket);
// Injects an on screen keyboard hidden event into the web module. Event is
// directed at the on screen keyboard element.
void InjectOnScreenKeyboardHiddenEvent(int ticket);
// Injects an on screen keyboard focused event into the web module. Event is
// directed at the on screen keyboard element.
void InjectOnScreenKeyboardFocusedEvent(int ticket);
// Injects an on screen keyboard blurred event into the web module. Event is
// directed at the on screen keyboard element.
void InjectOnScreenKeyboardBlurredEvent(int ticket);
// Injects an on screen keyboard suggestions updated event into the web
// module. Event is directed at the on screen keyboard element.
void InjectOnScreenKeyboardSuggestionsUpdatedEvent(int ticket);
// Injects 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(
base::Token type, const dom::KeyboardEventInit& event,
scoped_refptr<dom::Element> element = scoped_refptr<dom::Element>());
// Injects 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(
base::Token type, const dom::PointerEventInit& event,
scoped_refptr<dom::Element> element = scoped_refptr<dom::Element>());
// Injects 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(
base::Token type, const dom::WheelEventInit& event,
scoped_refptr<dom::Element> element = scoped_refptr<dom::Element>());
// Injects a beforeunload event into the web module. If this event is not
// handled by the web application, |on_before_unload_fired_but_not_handled_|
// will be called. The event is not directed at a specific element.
void InjectBeforeUnloadEvent();
void InjectCaptionSettingsChangedEvent();
void InjectWindowOnOnlineEvent();
void InjectWindowOnOfflineEvent();
void UpdateDateTimeConfiguration();
// Executes 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,
std::string* result, bool* out_succeeded);
// Clears disables timer related objects
// so that the message loop can easily exit
void ClearAllIntervalsAndTimeouts();
#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,
std::unique_ptr<webdriver::WindowDriver>* window_driver_out);
#endif // defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUGGER)
void WaitForWebDebugger();
void FreezeDebugger(
std::unique_ptr<debug::backend::DebuggerState>* debugger_state) {
if (debugger_state) *debugger_state = debug_module_->Freeze();
}
#endif // defined(ENABLE_DEBUGGER)
void SetSize(cssom::ViewportSize viewport_size);
void UpdateCamera3D(const scoped_refptr<input::Camera3D>& camera_3d);
void SetMediaModule(media::MediaModule* media_module);
void SetImageCacheCapacity(int64_t bytes);
void SetRemoteTypefaceCacheCapacity(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,
SbTimeMonotonic timestamp);
// See LifecycleObserver. These functions do not implement the interface, but
// have the same basic function.
void Blur(SbTimeMonotonic timestamp);
void Conceal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp);
void Freeze(SbTimeMonotonic timestamp);
void Unfreeze(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp);
void Reveal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp);
void Focus(SbTimeMonotonic timestamp);
void ReduceMemory();
void LogScriptError(const base::SourceLocation& source_location,
const std::string& error_message);
void IsReadyToFreeze(volatile bool* is_ready_to_freeze) {
if (window_->media_session()->media_session_client() == NULL) {
*is_ready_to_freeze = true;
return;
}
*is_ready_to_freeze =
!window_->media_session()->media_session_client()->is_active();
}
void DoSynchronousLayoutAndGetRenderTree(
scoped_refptr<render_tree::Node>* render_tree);
void SetApplicationStartOrPreloadTimestamp(bool is_preload,
SbTimeMonotonic timestamp);
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
void SetUnloadEventTimingInfo(base::TimeTicks start_time,
base::TimeTicks end_time);
private:
class DocumentLoadedObserver;
// Purge all resource caches owned by the WebModule.
void PurgeResourceCaches(bool should_retain_remote_typeface_cache);
// Disable callbacks in all resource caches owned by the WebModule.
void DisableCallbacksInResourceCaches();
// Called by |layout_manager_| after it runs the animation frame callbacks.
void OnRanAnimationFrameCallbacks();
// Called by |layout_manager_| 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);
// Called by the Renderer on the Renderer thread when it rasterizes a render
// tree with this callback attached. It includes the time the render tree was
// produced.
void OnRenderTreeRasterized(
scoped_refptr<base::SingleThreadTaskRunner> web_module_message_loop,
const base::TimeTicks& produced_time);
// WebModule thread handling of the OnRenderTreeRasterized() callback. It
// includes the time that the render tree was produced and the time that the
// render tree was rasterized.
void ProcessOnRenderTreeRasterized(const base::TimeTicks& produced_time,
const base::TimeTicks& rasterized_time);
void OnCspPolicyChanged();
scoped_refptr<script::GlobalEnvironment> global_environment() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return web_context_->global_environment();
}
void OnLoadComplete(const base::Optional<std::string>& error) {
if (error) error_callback_.Run(window_->location()->url(), *error);
// Create Performance navigation timing info after document loading
// completed.
DCHECK(window_);
DCHECK(window_->performance());
if (window_->GetDocumentLoader()) {
net::LoadTimingInfo load_timing_info =
window_->GetDocumentLoader()->get_load_timing_info();
// Check if the load happens through net mdoule.
bool is_load_timing_info_valid =
!load_timing_info.request_start.is_null();
if (is_load_timing_info_valid) {
window_->document()->CreatePerformanceNavigationTiming(
window_->performance(), load_timing_info);
}
}
}
// Report an error encountered while running JS.
// Returns whether or not the error was handled.
bool ReportScriptError(const script::ErrorReport& error_report);
// Inject the DOM event object into the window or the element.
void InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<web::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);
void OnStartDispatchEvent(const scoped_refptr<web::Event>& event);
void OnStopDispatchEvent(const scoped_refptr<web::Event>& event);
// Thread checker ensures all calls to the WebModule are made from the same
// thread that it is created in.
THREAD_CHECKER(thread_checker_);
web::Context* web_context_;
// Simple flag used for basic error checking.
bool is_running_;
// The most recent time that a new render tree was produced.
base::TimeTicks last_render_tree_produced_time_;
// Whether or not a render tree has been produced but not yet rasterized.
base::CVal<bool, base::CValPublic> is_render_tree_rasterization_pending_;
// Object that provides renderer resources like images and fonts.
render_tree::ResourceProvider* resource_provider_;
// The type id of resource provider being used by the WebModule. Whenever this
// changes, the caches may have obsolete data and must be blown away.
base::TypeId resource_provider_type_id_;
// CSS parser.
std::unique_ptr<css_parser::Parser> css_parser_;
// DOM (HTML / XML) parser.
std::unique_ptr<dom_parser::Parser> dom_parser_;
// LoaderFactory that is used to acquire references to resources from a
// URL.
std::unique_ptr<loader::LoaderFactory> loader_factory_;
std::unique_ptr<loader::image::AnimatedImageTracker> animated_image_tracker_;
// ImageCache that is used to manage image cache logic.
std::unique_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.
std::unique_ptr<loader::image::ReducedCacheCapacityManager>
reduced_image_cache_capacity_manager_;
// RemoteTypefaceCache that is used to manage loading and caching typefaces
// from URLs.
std::unique_ptr<loader::font::RemoteTypefaceCache> remote_typeface_cache_;
// MeshCache that is used to manage mesh cache logic.
std::unique_ptr<loader::mesh::MeshCache> mesh_cache_;
// Interface between LocalStorage and the Storage Manager.
std::unique_ptr<dom::LocalStorageDatabase> local_storage_database_;
// Stats for the web module. Both the dom stat tracker and layout stat
// tracker are contained within it.
std::unique_ptr<browser::WebModuleStatTracker> web_module_stat_tracker_;
// Post and run tasks to notify MutationObservers.
dom::MutationObserverTaskManager mutation_observer_task_manager_;
// Object to register and retrieve MediaSource object with a string key.
std::unique_ptr<dom::MediaSource::Registry> media_source_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_;
// Used only when MediaModule is null
std::unique_ptr<media::DecoderBufferMemoryInfo>
stub_decoder_buffer_memory_info_;
// Called by |OnRenderTreeProduced|.
OnRenderTreeProducedCallback render_tree_produced_callback_;
// Called by |OnError|.
OnErrorCallback error_callback_;
// Triggers layout whenever the document changes.
std::unique_ptr<layout::LayoutManager> layout_manager_;
#if defined(ENABLE_DEBUGGER)
// Allows the debugger to add render components to the web module.
// Used for DOM node highlighting and overlay messages.
std::unique_ptr<debug::backend::RenderOverlay> debug_overlay_;
// The core of the debugging system.
std::unique_ptr<debug::backend::DebugModule> debug_module_;
// Used to avoid a deadlock when running |Impl::Pause| while waiting for the
// web debugger to connect.
starboard::atomic_bool* waiting_for_web_debugger_;
// Interface to report behaviour relevant to the web debugger.
debug::backend::DebuggerHooksImpl debugger_hooks_;
#else
// Null implementation used in gold builds without checking ENABLE_DEBUGGER.
base::NullDebuggerHooks debugger_hooks_;
#endif // ENABLE_DEBUGGER
// DocumentObserver that observes the loading document.
std::unique_ptr<DocumentLoadedObserver> document_load_observer_;
std::unique_ptr<layout::TopmostEventTarget> topmost_event_target_;
base::Closure on_before_unload_fired_but_not_handled_;
bool should_retain_remote_typeface_cache_on_freeze_;
scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>
system_caption_settings_;
// This event is used to interrupt the loader when JavaScript is loaded
// synchronously. It is manually reset so that events like Freeze can be
// correctly execute, even if there are multiple synchronous loads in queue
// before the freeze (or other) event handlers.
base::WaitableEvent* synchronous_loader_interrupt_;
base::Callback<void(base::TimeTicks, base::TimeTicks)>
report_unload_timing_info_callback_;
};
void WebModule::WillDestroyCurrentMessageLoop() { impl_.reset(); }
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(web::Context* web_context, const ConstructionData& data)
: web_context_(web_context),
is_running_(false),
is_render_tree_rasterization_pending_(
base::StringPrintf("%s.IsRenderTreeRasterizationPending",
web_context_->name().c_str()),
false, "True when a render tree is produced but not yet rasterized."),
resource_provider_(data.resource_provider),
resource_provider_type_id_(data.resource_provider->GetTypeId()),
#if defined(ENABLE_DEBUGGER)
waiting_for_web_debugger_(data.waiting_for_web_debugger),
#endif // defined(ENABLE_DEBUGGER)
synchronous_loader_interrupt_(data.synchronous_loader_interrupt) {
DCHECK(web_context_);
#if defined(COBALT_ENABLE_JAVASCRIPT_ERROR_LOGGING)
script::JavaScriptEngine::ErrorHandler error_handler =
base::Bind(&WebModule::Impl::LogScriptError, base::Unretained(this));
web_context_->javascript_engine()->RegisterErrorHandler(error_handler);
#endif
css_parser::Parser::SupportsMapToMeshFlag supports_map_to_mesh =
data.options.enable_map_to_mesh
? css_parser::Parser::kSupportsMapToMesh
: css_parser::Parser::kDoesNotSupportMapToMesh;
css_parser_ =
css_parser::Parser::Create(debugger_hooks_, supports_map_to_mesh);
DCHECK(css_parser_);
dom_parser_.reset(new dom_parser::Parser(
kDOMMaxElementDepth,
base::Bind(&WebModule::Impl::OnLoadComplete, base::Unretained(this)),
data.options.require_csp));
DCHECK(dom_parser_);
on_before_unload_fired_but_not_handled_ =
data.options.on_before_unload_fired_but_not_handled;
should_retain_remote_typeface_cache_on_freeze_ =
data.options.should_retain_remote_typeface_cache_on_freeze;
DCHECK_LE(0, data.options.encoded_image_cache_capacity);
loader_factory_.reset(new loader::LoaderFactory(
web_context_->name().c_str(), web_context_->fetcher_factory(),
resource_provider_, debugger_hooks_,
data.options.encoded_image_cache_capacity,
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", web_context_->name().c_str()),
debugger_hooks_, 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",
web_context_->name().c_str()),
debugger_hooks_,
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", web_context_->name().c_str()),
debugger_hooks_, static_cast<uint32>(data.options.mesh_cache_capacity),
loader_factory_.get());
DCHECK(mesh_cache_);
local_storage_database_.reset(new dom::LocalStorageDatabase(
web_context_->network_module()->storage_manager()));
DCHECK(local_storage_database_);
web_module_stat_tracker_.reset(new browser::WebModuleStatTracker(
web_context_->name(), data.options.track_event_stats));
DCHECK(web_module_stat_tracker_);
media_source_registry_.reset(new dom::MediaSource::Registry);
const media::DecoderBufferMemoryInfo* memory_info = nullptr;
if (data.media_module) {
memory_info = data.media_module->GetDecoderBufferAllocator();
} else {
stub_decoder_buffer_memory_info_.reset(
new media::StubDecoderBufferMemoryInfo);
memory_info = stub_decoder_buffer_memory_info_.get();
}
web_context_->setup_environment_settings(new dom::DOMSettings(
debugger_hooks_, kDOMMaxElementDepth, media_source_registry_.get(),
data.can_play_type_handler, memory_info, &mutation_observer_task_manager_,
data.options.dom_settings_options));
DCHECK(web_context_->environment_settings());
web_context_->environment_settings()->set_base_url(data.initial_url);
system_caption_settings_ = new cobalt::dom::captions::SystemCaptionSettings(
web_context_->environment_settings());
dom::Window::CacheCallback splash_screen_cache_callback =
CacheUrlContentCallback(data.options.splash_screen_cache);
// These members will reference other |Traceable|s, however are not
// accessible from |Window|, so we must explicitly add them as roots.
web_context_->global_environment()->AddRoot(&mutation_observer_task_manager_);
web_context_->global_environment()->AddRoot(media_source_registry_.get());
#if defined(ENABLE_DEBUGGER)
if (data.options.wait_for_web_debugger) {
// Post a task that blocks the message loop and waits for the web debugger.
// This must be posted before the the window's task to load the document.
waiting_for_web_debugger_->store(true);
base::MessageLoop::current()->task_runner()->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::WaitForWebDebugger,
base::Unretained(this)));
}
#endif // defined(ENABLE_DEBUGGER)
bool log_tts = false;
#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
log_tts =
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseTTS);
#endif
DCHECK(web_context_->network_module());
window_ = new dom::Window(
web_context_->environment_settings(), data.window_dimensions,
data.initial_application_state, css_parser_.get(), dom_parser_.get(),
web_context_->fetcher_factory(), loader_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.can_play_type_handler, data.media_module,
web_context_->execution_state(), web_context_->script_runner(),
web_context_->global_environment()->script_value_factory(),
media_source_registry_.get(),
web_module_stat_tracker_->dom_stat_tracker(),
base::GetSystemLanguageScript(), data.options.navigation_callback,
base::Bind(&WebModule::Impl::OnLoadComplete, base::Unretained(this)),
web_context_->network_module()->cookie_jar(),
web_context_->network_module()->GetPostSender(), data.options.require_csp,
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.on_screen_keyboard_bridge, data.options.camera_3d,
base::Bind(&WebModule::Impl::OnStartDispatchEvent,
base::Unretained(this)),
base::Bind(&WebModule::Impl::OnStopDispatchEvent, base::Unretained(this)),
data.options.provide_screenshot_function, synchronous_loader_interrupt_,
data.options.enable_inline_script_warnings, data.ui_nav_root,
data.options.enable_map_to_mesh, 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
: (data.options.limit_performance_timer_resolution
? dom::Window::kClockTypeResolutionLimitedSystemTime
: dom::Window::kClockTypeSystemTime),
#else
dom::Window::kClockTypeSystemTime,
#endif
splash_screen_cache_callback, system_caption_settings_, log_tts);
DCHECK(window_);
window_weak_ = base::AsWeakPtr(window_.get());
DCHECK(window_weak_);
dom::DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(
web_context_->environment_settings());
dom_settings->set_window(window_);
web_context_->global_environment()->CreateGlobalObject(
window_, web_context_->environment_settings());
DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->IsWindow());
DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsDedicatedWorker());
DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker());
DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->GetWrappableType() ==
base::GetTypeId<dom::Window>());
DCHECK_EQ(window_, base::polymorphic_downcast<dom::Window*>(
web_context_->GetWindowOrWorkerGlobalScope()));
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());
window_->navigator()->set_maybefreeze_callback(
data.options.maybe_freeze_callback);
window_->navigator()->set_media_player_factory(data.media_module);
bool is_concealed =
(data.initial_application_state == base::kApplicationStateConcealed);
layout_manager_.reset(new layout::LayoutManager(
is_concealed, web_context_->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,
web_context_->network_module()->preferred_language(),
data.options.enable_image_animations,
web_module_stat_tracker_->layout_stat_tracker(),
data.options.clear_window_with_background_color));
DCHECK(layout_manager_);
#if !defined(COBALT_FORCE_CSP)
if (data.options.csp_enforcement_mode == web::kCspEnforcementDisable) {
// If CSP is disabled, enable eval(). Otherwise, it will be enabled by
// a CSP directive.
web_context_->global_environment()->EnableEval();
}
#endif
web_context_->global_environment()->SetReportEvalCallback(
base::Bind(&web::CspDelegate::ReportEval,
base::Unretained(window_->document()->csp_delegate())));
web_context_->global_environment()->SetReportErrorCallback(
base::Bind(&WebModule::Impl::ReportScriptError, base::Unretained(this)));
if (!data.options.loaded_callbacks.empty()) {
document_load_observer_.reset(
new DocumentLoadedObserver(data.options.loaded_callbacks));
window_->document()->AddObserver(document_load_observer_.get());
}
#if defined(ENABLE_DEBUGGER)
debug_overlay_.reset(
new debug::backend::RenderOverlay(render_tree_produced_callback_));
debug_module_.reset(new debug::backend::DebugModule(
&debugger_hooks_, web_context_->global_environment(),
debug_overlay_.get(), resource_provider_, window_,
data.options.debugger_state));
#endif // ENABLE_DEBUGGER
report_unload_timing_info_callback_ =
data.options.collect_unload_event_time_callback;
is_running_ = true;
}
WebModule::Impl::~Impl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
is_running_ = false;
// Collect document's unload event start time.
base::TimeTicks unload_event_start_time = base::TimeTicks::Now();
window_->DispatchEvent(new web::Event(base::Tokens::unload()));
// Collect document's unload event end time.
base::TimeTicks unload_event_end_time = base::TimeTicks::Now();
// Send the unload event start/end time back to application.
if (!report_unload_timing_info_callback_.is_null()) {
report_unload_timing_info_callback_.Run(unload_event_start_time,
unload_event_end_time);
}
document_load_observer_.reset();
#if defined(ENABLE_DEBUGGER)
debug_module_.reset();
debug_overlay_.reset();
#endif // ENABLE_DEBUGGER
// 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();
stub_decoder_buffer_memory_info_.reset();
window_weak_.reset();
window_->ClearPointerStateForShutdown();
window_ = NULL;
media_source_registry_.reset();
web_context_->ShutDownJavaScriptEngine();
local_storage_database_.reset();
mesh_cache_.reset();
remote_typeface_cache_.reset();
animated_image_tracker_.reset();
dom_parser_.reset();
css_parser_.reset();
image_cache_.reset();
web_module_stat_tracker_.reset();
}
void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<web::Event>& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::Impl::InjectInputEvent()",
"event", TRACE_STR_COPY(event->type().c_str()));
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
if (element) {
element->DispatchEvent(event);
} else {
if (dom::PointerState::CanQueueEvent(event)) {
// As an optimization we batch together pointer/mouse events for as long
// as we can get away with it (e.g. until a non-pointer event is received
// or whenever the next layout occurs).
window_->document()->pointer_state()->QueuePointerEvent(event);
} else {
// In order to maintain the correct input event ordering, we first
// dispatch any queued pending pointer events.
HandlePointerEvents();
window_->InjectEvent(event);
}
}
}
void WebModule::Impl::InjectOnScreenKeyboardInputEvent(
base::Token type, const dom::InputEventInit& event,
scoped_refptr<dom::Element> element) {
scoped_refptr<dom::InputEvent> input_event(
new dom::InputEvent(type, window_, event));
InjectInputEvent(element, input_event);
}
void WebModule::Impl::InjectOnScreenKeyboardShownEvent(int ticket) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->on_screen_keyboard());
window_->on_screen_keyboard()->DispatchShowEvent(ticket);
}
void WebModule::Impl::InjectOnScreenKeyboardHiddenEvent(int ticket) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->on_screen_keyboard());
window_->on_screen_keyboard()->DispatchHideEvent(ticket);
}
void WebModule::Impl::InjectOnScreenKeyboardFocusedEvent(int ticket) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->on_screen_keyboard());
window_->on_screen_keyboard()->DispatchFocusEvent(ticket);
}
void WebModule::Impl::InjectOnScreenKeyboardBlurredEvent(int ticket) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->on_screen_keyboard());
window_->on_screen_keyboard()->DispatchBlurEvent(ticket);
}
void WebModule::Impl::InjectOnScreenKeyboardSuggestionsUpdatedEvent(
int ticket) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_->on_screen_keyboard());
window_->on_screen_keyboard()->DispatchSuggestionsUpdatedEvent(ticket);
}
void WebModule::Impl::InjectKeyboardEvent(base::Token type,
const dom::KeyboardEventInit& event,
scoped_refptr<dom::Element> element) {
scoped_refptr<dom::KeyboardEvent> keyboard_event(
new dom::KeyboardEvent(type, window_, event));
InjectInputEvent(element, keyboard_event);
}
void WebModule::Impl::InjectPointerEvent(base::Token type,
const dom::PointerEventInit& event,
scoped_refptr<dom::Element> element) {
scoped_refptr<dom::PointerEvent> pointer_event(
new dom::PointerEvent(type, window_, event));
InjectInputEvent(element, pointer_event);
}
void WebModule::Impl::InjectWheelEvent(base::Token type,
const dom::WheelEventInit& event,
scoped_refptr<dom::Element> element) {
scoped_refptr<dom::WheelEvent> wheel_event(
new dom::WheelEvent(type, window_, event));
InjectInputEvent(element, wheel_event);
}
void WebModule::Impl::UpdateDateTimeConfiguration() {
if (web_context_ && web_context_->javascript_engine()) {
web_context_->javascript_engine()->UpdateDateTimeConfiguration();
}
}
void WebModule::Impl::ExecuteJavascript(
const std::string& script_utf8, const base::SourceLocation& script_location,
std::string* result, bool* out_succeeded) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(web_context_->script_runner());
// JavaScript is being run. Track it in the global stats.
dom::GlobalStats::GetInstance()->StartJavaScriptEvent();
// This should only be called for Cobalt JavaScript, error reports are
// allowed.
if (result) {
*result = web_context_->script_runner()->Execute(
script_utf8, script_location, false /*mute_errors*/, out_succeeded);
} else {
web_context_->script_runner()->Execute(
script_utf8, script_location, false /*mute_errors*/, out_succeeded);
}
// JavaScript is done running. Stop tracking it in the global stats.
dom::GlobalStats::GetInstance()->StopJavaScriptEvent();
}
void WebModule::Impl::ClearAllIntervalsAndTimeouts() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(window_);
window_->DestroyTimers();
}
void WebModule::Impl::OnRanAnimationFrameCallbacks() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
last_render_tree_produced_time_ = base::TimeTicks::Now();
is_render_tree_rasterization_pending_ = true;
web_module_stat_tracker_->OnRenderTreeProduced(
last_render_tree_produced_time_);
LayoutResults layout_results_with_callback(
layout_results.render_tree, layout_results.layout_time,
base::Bind(&WebModule::Impl::OnRenderTreeRasterized,
base::Unretained(this),
base::MessageLoop::current()->task_runner(),
last_render_tree_produced_time_));
#if defined(ENABLE_DEBUGGER)
debug_overlay_->OnRenderTreeProduced(layout_results_with_callback);
#else // ENABLE_DEBUGGER
render_tree_produced_callback_.Run(layout_results_with_callback);
#endif // ENABLE_DEBUGGER
}
void WebModule::Impl::OnRenderTreeRasterized(
scoped_refptr<base::SingleThreadTaskRunner> web_module_message_loop,
const base::TimeTicks& produced_time) {
web_module_message_loop->PostTask(
FROM_HERE, base::Bind(&WebModule::Impl::ProcessOnRenderTreeRasterized,
base::Unretained(this), produced_time,
base::TimeTicks::Now()));
}
void WebModule::Impl::ProcessOnRenderTreeRasterized(
const base::TimeTicks& produced_time,
const base::TimeTicks& rasterized_time) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
web_module_stat_tracker_->OnRenderTreeRasterized(produced_time,
rasterized_time);
if (produced_time >= last_render_tree_produced_time_) {
is_render_tree_rasterization_pending_ = false;
}
}
void WebModule::Impl::DoSynchronousLayoutAndGetRenderTree(
scoped_refptr<render_tree::Node>* render_tree) {
TRACE_EVENT0("cobalt::browser",
"WebModule::Impl::DoSynchronousLayoutAndGetRenderTree()");
DCHECK(render_tree);
scoped_refptr<render_tree::Node> tree =
window_->document()->DoSynchronousLayoutAndGetRenderTree();
if (render_tree) *render_tree = tree;
}
void WebModule::Impl::SetApplicationStartOrPreloadTimestamp(
bool is_preload, SbTimeMonotonic timestamp) {
DCHECK(window_);
DCHECK(window_->performance());
window_->performance()->SetApplicationStartOrPreloadTimestamp(is_preload,
timestamp);
}
void WebModule::Impl::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
DCHECK(window_);
window_->performance()->SetDeepLinkTimestamp(timestamp);
}
void WebModule::Impl::SetUnloadEventTimingInfo(base::TimeTicks start_time,
base::TimeTicks end_time) {
DCHECK(window_);
if (window_->document()) {
window_->document()->SetUnloadEventTimingInfo(start_time, end_time);
}
}
void WebModule::Impl::OnCspPolicyChanged() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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) {
web_context_->global_environment()->EnableEval();
} else {
web_context_->global_environment()->DisableEval(eval_disabled_message);
}
}
bool WebModule::Impl::ReportScriptError(
const script::ErrorReport& error_report) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
return window_->ReportScriptError(error_report);
}
#if defined(ENABLE_WEBDRIVER)
void WebModule::Impl::CreateWindowDriver(
const webdriver::protocol::WindowId& window_id,
std::unique_ptr<webdriver::WindowDriver>* window_driver_out) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
DCHECK(window_weak_);
DCHECK(window_->document());
DCHECK(web_context_->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::MessageLoop::current()->task_runner()));
}
#endif // defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUGGER)
void WebModule::Impl::WaitForWebDebugger() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(debug_module_);
LOG(WARNING) << "\n-------------------------------------"
"\n Waiting for web debugger to connect "
"\n-------------------------------------";
// This blocks until the web debugger connects.
debug_module_->debug_dispatcher()->SetPaused(true);
waiting_for_web_debugger_->store(false);
}
#endif // defined(ENABLE_DEBUGGER)
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::SetSize(cssom::ViewportSize viewport_size) {
window_->SetSize(viewport_size);
}
void WebModule::Impl::UpdateCamera3D(
const scoped_refptr<input::Camera3D>& camera_3d) {
window_->UpdateCamera3D(camera_3d);
}
void WebModule::Impl::SetMediaModule(media::MediaModule* media_module) {
SB_DCHECK(media_module);
dom::DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(
web_context_->environment_settings());
dom_settings->set_decoder_buffer_memory_info(
media_module->GetDecoderBufferAllocator());
window_->set_web_media_player_factory(media_module);
}
void WebModule::Impl::SetApplicationState(base::ApplicationState state,
SbTimeMonotonic timestamp) {
window_->SetApplicationState(state, timestamp);
}
void WebModule::Impl::SetResourceProvider(
render_tree::ResourceProvider* resource_provider) {
resource_provider_ = resource_provider;
if (resource_provider_) {
base::TypeId resource_provider_type_id = resource_provider_->GetTypeId();
// Check for if the resource provider type id has changed. If it has, then
// anything contained within the caches is invalid and must be purged.
if (resource_provider_type_id_ != resource_provider_type_id) {
PurgeResourceCaches(should_retain_remote_typeface_cache_on_freeze_);
}
resource_provider_type_id_ = resource_provider_type_id;
}
}
void WebModule::Impl::OnStartDispatchEvent(
const scoped_refptr<web::Event>& event) {
web_module_stat_tracker_->OnStartDispatchEvent(event);
}
void WebModule::Impl::OnStopDispatchEvent(
const scoped_refptr<web::Event>& event) {
web_module_stat_tracker_->OnStopDispatchEvent(
event, window_->HasPendingAnimationFrameCallbacks(),
layout_manager_->IsRenderTreePending());
}
void WebModule::Impl::Blur(SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Blur()");
SetApplicationState(base::kApplicationStateBlurred, timestamp);
}
void WebModule::Impl::Conceal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Conceal()");
SetResourceProvider(resource_provider);
SetApplicationState(base::kApplicationStateConcealed, timestamp);
layout_manager_->Suspend();
// Purge the cached resources prior to the freeze. That may cancel pending
// loads, allowing the freeze to occur faster and preventing unnecessary
// callbacks.
window_->document()->PurgeCachedResources();
// Clear out any currently tracked animating images.
animated_image_tracker_->Reset();
// Purge the resource caches before running any freeze logic. This will force
// any pending callbacks that the caches are batching to run.
PurgeResourceCaches(should_retain_remote_typeface_cache_on_freeze_);
#if defined(ENABLE_DEBUGGER)
// The debug overlay may be holding onto a render tree, clear that out.
debug_overlay_->ClearInput();
#endif
// Force garbage collection in |javascript_engine|.
if (web_context_ && web_context_->javascript_engine()) {
web_context_->javascript_engine()->CollectGarbage();
}
loader_factory_->UpdateResourceProvider(resource_provider_);
if (window_->media_session()->media_session_client() != NULL) {
window_->media_session()
->media_session_client()
->PostDelayedTaskForMaybeFreezeCallback();
}
}
void WebModule::Impl::Freeze(SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Freeze()");
SetApplicationState(base::kApplicationStateFrozen, timestamp);
// Clear out the loader factory's resource provider, possibly aborting any
// in-progress loads.
loader_factory_->Suspend();
}
void WebModule::Impl::Unfreeze(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Unfreeze()");
synchronous_loader_interrupt_->Reset();
DCHECK(resource_provider);
loader_factory_->Resume(resource_provider);
SetApplicationState(base::kApplicationStateConcealed, timestamp);
}
void WebModule::Impl::Reveal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Reveal()");
synchronous_loader_interrupt_->Reset();
DCHECK(resource_provider);
SetResourceProvider(resource_provider);
window_->document()->PurgeCachedResources();
PurgeResourceCaches(should_retain_remote_typeface_cache_on_freeze_);
loader_factory_->UpdateResourceProvider(resource_provider_);
layout_manager_->Resume();
SetApplicationState(base::kApplicationStateBlurred, timestamp);
}
void WebModule::Impl::Focus(SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Focus()");
synchronous_loader_interrupt_->Reset();
SetApplicationState(base::kApplicationStateStarted, timestamp);
}
void WebModule::Impl::ReduceMemory() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::ReduceMemory()");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!is_running_) {
return;
}
synchronous_loader_interrupt_->Reset();
layout_manager_->Purge();
// Retain the remote typeface cache when reducing memory.
PurgeResourceCaches(true /*should_retain_remote_typeface_cache*/);
window_->document()->PurgeCachedResources();
// Force garbage collection in |javascript_engine|.
if (web_context_ && web_context_->javascript_engine()) {
web_context_->javascript_engine()->CollectGarbage();
}
}
void WebModule::Impl::LogScriptError(
const base::SourceLocation& source_location,
const std::string& error_message) {
std::string file_name =
base::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::InjectBeforeUnloadEvent() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (window_ && window_->HasEventListener(base::Tokens::beforeunload())) {
window_->DispatchEvent(new web::Event(base::Tokens::beforeunload()));
} else if (!on_before_unload_fired_but_not_handled_.is_null()) {
on_before_unload_fired_but_not_handled_.Run();
}
}
void WebModule::Impl::InjectCaptionSettingsChangedEvent() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
system_caption_settings_->OnCaptionSettingsChanged();
}
void WebModule::Impl::InjectWindowOnOnlineEvent() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
window_->OnWindowOnOnlineEvent();
}
void WebModule::Impl::InjectWindowOnOfflineEvent() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
window_->OnWindowOnOfflineEvent();
}
void WebModule::Impl::PurgeResourceCaches(
bool should_retain_remote_typeface_cache) {
image_cache_->Purge();
if (should_retain_remote_typeface_cache) {
remote_typeface_cache_->ProcessPendingCallbacks();
} else {
remote_typeface_cache_->Purge();
}
mesh_cache_->Purge();
}
void WebModule::Impl::DisableCallbacksInResourceCaches() {
image_cache_->DisableCallbacks();
remote_typeface_cache_->DisableCallbacks();
mesh_cache_->DisableCallbacks();
}
void WebModule::Impl::HandlePointerEvents() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::HandlePointerEvents");
const scoped_refptr<dom::Document>& document = window_->document();
scoped_refptr<web::Event> event;
do {
event = document->pointer_state()->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());
}
WebModule::Options::Options(const std::string& name)
: web_options(name),
layout_trigger(layout::LayoutManager::kOnDocumentMutation),
mesh_cache_capacity(configuration::Configuration::GetInstance()
->CobaltMeshCacheSizeInBytes()) {
web_options.stack_size = cobalt::browser::kWebModuleStackSize;
}
WebModule::WebModule(
const GURL& initial_url, base::ApplicationState initial_application_state,
const OnRenderTreeProducedCallback& render_tree_produced_callback,
OnErrorCallback error_callback, const CloseCallback& window_close_callback,
const base::Closure& window_minimize_callback,
media::CanPlayTypeHandler* can_play_type_handler,
media::MediaModule* media_module, const ViewportSize& window_dimensions,
render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
const Options& options)
: ui_nav_root_(new ui_navigation::NavItem(
ui_navigation::kNativeItemTypeContainer,
// Currently, events do not need to be processed for the root item.
base::Closure(), base::Closure(), base::Closure())) {
ConstructionData construction_data(
initial_url, initial_application_state, render_tree_produced_callback,
error_callback, window_close_callback, window_minimize_callback,
can_play_type_handler, media_module, window_dimensions, resource_provider,
kDOMMaxElementDepth, layout_refresh_rate, ui_nav_root_,
#if defined(ENABLE_DEBUGGER)
&waiting_for_web_debugger_,
#endif // defined(ENABLE_DEBUGGER)
&synchronous_loader_interrupt_, options);
web_agent_.reset(
new web::Agent(options.web_options,
base::Bind(&WebModule::Initialize, base::Unretained(this),
construction_data),
this));
}
WebModule::~WebModule() {
DCHECK(message_loop());
DCHECK(web_agent_);
// This will cancel the timers for tasks, which help the thread exit
ClearAllIntervalsAndTimeouts();
web_agent_->WaitUntilDone();
web_agent_.reset();
}
void WebModule::Initialize(const ConstructionData& data,
web::Context* context) {
DCHECK_EQ(base::MessageLoop::current(), message_loop());
impl_.reset(new Impl(context, data));
}
// These are helper macros for jumping to the Impl thread without referencing
// impl_ outside of the thread.
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a task to the given function if we are not.
#define TASK_TO_ENSURE_IMPL_ON_THREAD0(task_function, function) \
DCHECK(message_loop()); \
if (base::MessageLoop::current() != message_loop()) { \
message_loop()->task_runner()->task_function( \
FROM_HERE, base::Bind(&WebModule::function, base::Unretained(this))); \
return; \
} else { \
DCHECK(impl_); \
}
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a task to the given function if we are not.
#define TASK_TO_ENSURE_IMPL_ON_THREAD(task_function, function, ...) \
DCHECK(message_loop()); \
if (base::MessageLoop::current() != message_loop()) { \
message_loop()->task_runner()->task_function( \
FROM_HERE, base::Bind(&WebModule::function, base::Unretained(this), \
##__VA_ARGS__)); \
return; \
} else { \
DCHECK(impl_); \
}
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a task to the given function if we are not.
#define POST_TO_ENSURE_IMPL_ON_THREAD0(function) \
TASK_TO_ENSURE_IMPL_ON_THREAD0(PostTask, function)
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a task to the given function if we are not.
#define POST_TO_ENSURE_IMPL_ON_THREAD(function, ...) \
TASK_TO_ENSURE_IMPL_ON_THREAD(PostTask, function, ##__VA_ARGS__)
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a blocking task to the given function if we are not.
#define POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD0(function) \
TASK_TO_ENSURE_IMPL_ON_THREAD0(PostBlockingTask, function)
// Ensure that we are on the WebModule thread where we can dereference impl_.
// Post a blocking task to the given function if we are not.
#define POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(function, ...) \
TASK_TO_ENSURE_IMPL_ON_THREAD(PostBlockingTask, function, ##__VA_ARGS__)
void WebModule::InjectOnScreenKeyboardInputEvent(
base::Token type, const dom::InputEventInit& event) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardInputEvent()", "type",
TRACE_STR_COPY(type.c_str()));
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardInputEvent, type, event);
impl_->InjectOnScreenKeyboardInputEvent(type, event);
}
void WebModule::InjectOnScreenKeyboardShownEvent(int ticket) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardShownEvent()", "ticket",
ticket);
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardShownEvent, ticket);
impl_->InjectOnScreenKeyboardShownEvent(ticket);
}
void WebModule::InjectOnScreenKeyboardHiddenEvent(int ticket) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardHiddenEvent()", "ticket",
ticket);
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardHiddenEvent, ticket);
impl_->InjectOnScreenKeyboardHiddenEvent(ticket);
}
void WebModule::InjectOnScreenKeyboardFocusedEvent(int ticket) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardFocusedEvent()", "ticket",
ticket);
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardFocusedEvent, ticket);
impl_->InjectOnScreenKeyboardFocusedEvent(ticket);
}
void WebModule::InjectOnScreenKeyboardBlurredEvent(int ticket) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardBlurredEvent()", "ticket",
ticket);
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardBlurredEvent, ticket);
impl_->InjectOnScreenKeyboardBlurredEvent(ticket);
}
void WebModule::InjectOnScreenKeyboardSuggestionsUpdatedEvent(int ticket) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardSuggestionsUpdatedEvent()",
"ticket", ticket);
POST_TO_ENSURE_IMPL_ON_THREAD(InjectOnScreenKeyboardSuggestionsUpdatedEvent,
ticket);
impl_->InjectOnScreenKeyboardSuggestionsUpdatedEvent(ticket);
}
void WebModule::InjectKeyboardEvent(base::Token type,
const dom::KeyboardEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
TRACE_STR_COPY(type.c_str()));
POST_TO_ENSURE_IMPL_ON_THREAD(InjectKeyboardEvent, type, event);
impl_->InjectKeyboardEvent(type, event);
}
void WebModule::InjectPointerEvent(base::Token type,
const dom::PointerEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectPointerEvent()", "type",
TRACE_STR_COPY(type.c_str()));
POST_TO_ENSURE_IMPL_ON_THREAD(InjectPointerEvent, type, event);
impl_->InjectPointerEvent(type, event);
}
void WebModule::InjectWheelEvent(base::Token type,
const dom::WheelEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectWheelEvent()", "type",
TRACE_STR_COPY(type.c_str()));
POST_TO_ENSURE_IMPL_ON_THREAD(InjectWheelEvent, type, event);
impl_->InjectWheelEvent(type, event);
}
void WebModule::InjectBeforeUnloadEvent() {
TRACE_EVENT0("cobalt::browser", "WebModule::InjectBeforeUnloadEvent()");
POST_TO_ENSURE_IMPL_ON_THREAD0(InjectBeforeUnloadEvent);
impl_->InjectBeforeUnloadEvent();
}
void WebModule::InjectCaptionSettingsChangedEvent() {
TRACE_EVENT0("cobalt::browser",
"WebModule::InjectCaptionSettingsChangedEvent()");
POST_TO_ENSURE_IMPL_ON_THREAD0(InjectCaptionSettingsChangedEvent);
impl_->InjectCaptionSettingsChangedEvent();
}
void WebModule::InjectWindowOnOnlineEvent(const base::Event* event) {
TRACE_EVENT0("cobalt::browser", "WebModule::InjectWindowOnOnlineEvent()");
POST_TO_ENSURE_IMPL_ON_THREAD(InjectWindowOnOnlineEvent, event);
impl_->InjectWindowOnOnlineEvent();
}
void WebModule::InjectWindowOnOfflineEvent(const base::Event* event) {
TRACE_EVENT0("cobalt::browser", "WebModule::InjectWindowOnOfflineEvent()");
POST_TO_ENSURE_IMPL_ON_THREAD(InjectWindowOnOfflineEvent, event);
impl_->InjectWindowOnOfflineEvent();
}
void WebModule::UpdateDateTimeConfiguration() {
TRACE_EVENT0("cobalt::browser", "WebModule::UpdateDateTimeConfiguration()");
POST_TO_ENSURE_IMPL_ON_THREAD0(UpdateDateTimeConfiguration);
impl_->UpdateDateTimeConfiguration();
}
void WebModule::ExecuteJavascript(const std::string& script_utf8,
const base::SourceLocation& script_location,
std::string* out_result,
bool* out_succeeded) {
TRACE_EVENT0("cobalt::browser", "WebModule::ExecuteJavascript()");
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(ExecuteJavascript, script_utf8,
script_location, out_result,
out_succeeded);
impl_->ExecuteJavascript(script_utf8, script_location, out_result,
out_succeeded);
}
void WebModule::ClearAllIntervalsAndTimeouts() {
TRACE_EVENT0("cobalt::browser", "WebModule::ClearAllIntervalsAndTimeouts()");
POST_TO_ENSURE_IMPL_ON_THREAD0(ClearAllIntervalsAndTimeouts);
impl_->ClearAllIntervalsAndTimeouts();
}
#if defined(ENABLE_WEBDRIVER)
void WebModule::CreateWindowDriver(
const webdriver::protocol::WindowId& window_id,
std::unique_ptr<webdriver::WindowDriver>* window_driver_out) {
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(CreateWindowDriver, window_id,
window_driver_out);
impl_->CreateWindowDriver(window_id, window_driver_out);
}
#endif // defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUGGER)
// May be called from any thread.
void WebModule::GetDebugDispatcher(
debug::backend::DebugDispatcher** dispatcher) {
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(GetDebugDispatcher, dispatcher);
if (dispatcher) *dispatcher = impl_->debug_dispatcher();
}
void WebModule::FreezeDebugger(
std::unique_ptr<debug::backend::DebuggerState>* debugger_state) {
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(FreezeDebugger, debugger_state);
impl_->FreezeDebugger(debugger_state);
}
#endif // defined(ENABLE_DEBUGGER)
void WebModule::SetSize(const ViewportSize& viewport_size) {
POST_TO_ENSURE_IMPL_ON_THREAD(SetSize, viewport_size);
impl_->SetSize(viewport_size);
}
void WebModule::UpdateCamera3D(
const scoped_refptr<input::Camera3D>& camera_3d) {
POST_TO_ENSURE_IMPL_ON_THREAD(UpdateCamera3D, camera_3d);
impl_->UpdateCamera3D(camera_3d);
}
void WebModule::SetMediaModule(media::MediaModule* media_module) {
POST_TO_ENSURE_IMPL_ON_THREAD(SetMediaModule, media_module);
impl_->SetMediaModule(media_module);
}
void WebModule::SetImageCacheCapacity(int64_t bytes) {
POST_TO_ENSURE_IMPL_ON_THREAD(SetImageCacheCapacity, bytes);
impl_->SetImageCacheCapacity(bytes);
}
void WebModule::SetRemoteTypefaceCacheCapacity(int64_t bytes) {
POST_TO_ENSURE_IMPL_ON_THREAD(SetRemoteTypefaceCacheCapacity, bytes);
impl_->SetRemoteTypefaceCacheCapacity(bytes);
}
void WebModule::Blur(SbTimeMonotonic timestamp) {
synchronous_loader_interrupt_.Signal();
#if defined(ENABLE_DEBUGGER)
// We normally need to block here so that the call doesn't return until the
// web application has had a chance to process the whole event. However, our
// message loop is blocked while waiting for the web debugger to connect, so
// we would deadlock here if the user switches to Chrome to run devtools on
// the same machine where Cobalt is running. Therefore, while we're still
// waiting for the debugger we post the pause task without blocking on it,
// letting it eventually run when the debugger connects and the message loop
// is unblocked again.
if (waiting_for_web_debugger_.load()) {
POST_TO_ENSURE_IMPL_ON_THREAD(Blur, timestamp);
impl_->Blur(timestamp);
return;
}
#endif // defined(ENABLE_DEBUGGER)
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Blur, timestamp);
impl_->Blur(timestamp);
}
void WebModule::Conceal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
synchronous_loader_interrupt_.Signal();
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Conceal, resource_provider,
timestamp);
impl_->Conceal(resource_provider, timestamp);
}
void WebModule::Freeze(SbTimeMonotonic timestamp) {
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Freeze, timestamp);
impl_->Freeze(timestamp);
}
void WebModule::Unfreeze(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Unfreeze, resource_provider,
timestamp);
impl_->Unfreeze(resource_provider, timestamp);
}
void WebModule::Reveal(render_tree::ResourceProvider* resource_provider,
SbTimeMonotonic timestamp) {
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Reveal, resource_provider, timestamp);
impl_->Reveal(resource_provider, timestamp);
}
void WebModule::Focus(SbTimeMonotonic timestamp) {
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(Focus, timestamp);
impl_->Focus(timestamp);
}
void WebModule::ReduceMemory() {
synchronous_loader_interrupt_.Signal();
// We must block here so that the call doesn't return until the web
// application has had a chance to process the whole event.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD0(ReduceMemory);
impl_->ReduceMemory();
}
void WebModule::RequestJavaScriptHeapStatistics(
const web::Agent::JavaScriptHeapStatisticsCallback& callback) {
web_agent_->RequestJavaScriptHeapStatistics(callback);
}
void WebModule::GetIsReadyToFreeze(volatile bool* is_ready_to_freeze) {
// We must block here so that the call doesn't return until the thread
// has had a chance to fill in the return value.
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(GetIsReadyToFreeze,
is_ready_to_freeze);
impl_->IsReadyToFreeze(is_ready_to_freeze);
}
bool WebModule::IsReadyToFreeze() {
volatile bool is_ready_to_freeze = false;
GetIsReadyToFreeze(&is_ready_to_freeze);
return is_ready_to_freeze;
}
void WebModule::DoSynchronousLayoutAndGetRenderTree(
scoped_refptr<render_tree::Node>* render_tree) {
TRACE_EVENT0("cobalt::browser",
"WebModule::DoSynchronousLayoutAndGetRenderTree()");
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(DoSynchronousLayoutAndGetRenderTree,
render_tree);
impl_->DoSynchronousLayoutAndGetRenderTree(render_tree);
}
void WebModule::SetApplicationStartOrPreloadTimestamp(
bool is_preload, SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser",
"WebModule::SetApplicationStartOrPreloadTimestamp()");
POST_TO_ENSURE_IMPL_ON_THREAD(SetApplicationStartOrPreloadTimestamp,
is_preload, timestamp);
impl_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp);
}
void WebModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
TRACE_EVENT0("cobalt::browser", "WebModule::SetDeepLinkTimestamp()");
POST_AND_BLOCK_TO_ENSURE_IMPL_ON_THREAD(SetDeepLinkTimestamp, timestamp);
impl_->SetDeepLinkTimestamp(timestamp);
}
void WebModule::SetUnloadEventTimingInfo(base::TimeTicks start_time,
base::TimeTicks end_time) {
TRACE_EVENT0("cobalt::browser", "WebModule::SetUnloadEventTimingInfo()");
POST_TO_ENSURE_IMPL_ON_THREAD(SetUnloadEventTimingInfo, start_time, end_time);
impl_->SetUnloadEventTimingInfo(start_time, end_time);
}
} // namespace browser
} // namespace cobalt