| // 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/dom/window.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/cssom/css_computed_style_declaration.h" |
| #include "cobalt/cssom/user_agent_style_sheet.h" |
| #include "cobalt/dom/camera_3d.h" |
| #include "cobalt/dom/console.h" |
| #include "cobalt/dom/device_orientation_event.h" |
| #include "cobalt/dom/document.h" |
| #include "cobalt/dom/dom_settings.h" |
| #include "cobalt/dom/element.h" |
| #include "cobalt/dom/event.h" |
| #include "cobalt/dom/history.h" |
| #include "cobalt/dom/html_element.h" |
| #include "cobalt/dom/html_element_context.h" |
| #include "cobalt/dom/keyboard_event.h" |
| #include "cobalt/dom/location.h" |
| #include "cobalt/dom/media_source.h" |
| #include "cobalt/dom/mouse_event.h" |
| #include "cobalt/dom/mutation_observer_task_manager.h" |
| #include "cobalt/dom/navigator.h" |
| #include "cobalt/dom/performance.h" |
| #include "cobalt/dom/pointer_event.h" |
| #include "cobalt/dom/screen.h" |
| #include "cobalt/dom/storage.h" |
| #include "cobalt/dom/wheel_event.h" |
| #include "cobalt/dom/window_timers.h" |
| #include "cobalt/media_session/media_session_client.h" |
| #include "cobalt/script/javascript_engine.h" |
| #include "cobalt/speech/speech_synthesis.h" |
| |
| using cobalt::media_session::MediaSession; |
| |
| namespace cobalt { |
| namespace dom { |
| |
| // This class fires the window's load event when the document is loaded. |
| class Window::RelayLoadEvent : public DocumentObserver { |
| public: |
| explicit RelayLoadEvent(Window* window) : window_(window) {} |
| |
| // From DocumentObserver. |
| void OnLoad() OVERRIDE { |
| window_->PostToDispatchEvent(FROM_HERE, base::Tokens::load()); |
| } |
| void OnMutation() OVERRIDE {} |
| void OnFocusChanged() OVERRIDE {} |
| |
| private: |
| Window* window_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RelayLoadEvent); |
| }; |
| |
| Window::Window(int width, int height, |
| base::ApplicationState initial_application_state, |
| cssom::CSSParser* css_parser, Parser* dom_parser, |
| loader::FetcherFactory* fetcher_factory, |
| render_tree::ResourceProvider** resource_provider, |
| loader::image::AnimatedImageTracker* animated_image_tracker, |
| loader::image::ImageCache* image_cache, |
| loader::image::ReducedCacheCapacityManager* |
| reduced_image_cache_capacity_manager, |
| loader::font::RemoteTypefaceCache* remote_typeface_cache, |
| loader::mesh::MeshCache* mesh_cache, |
| LocalStorageDatabase* local_storage_database, |
| media::CanPlayTypeHandler* can_play_type_handler, |
| media::WebMediaPlayerFactory* web_media_player_factory, |
| script::ExecutionState* execution_state, |
| script::ScriptRunner* script_runner, |
| script::ScriptValueFactory* script_value_factory, |
| MediaSource::Registry* media_source_registry, |
| DomStatTracker* dom_stat_tracker, const GURL& url, |
| const std::string& user_agent, const std::string& language, |
| const base::Callback<void(const GURL&)> navigation_callback, |
| const base::Callback<void(const std::string&)>& error_callback, |
| network_bridge::CookieJar* cookie_jar, |
| const network_bridge::PostSender& post_sender, |
| const std::string& default_security_policy, |
| CspEnforcementType csp_enforcement_mode, |
| const base::Closure& csp_policy_changed_callback, |
| const base::Closure& ran_animation_frame_callbacks_callback, |
| const base::Closure& window_close_callback, |
| const base::Closure& window_minimize_callback, |
| system_window::SystemWindow* system_window, |
| const scoped_refptr<input::Camera3D>& camera_3d, |
| const scoped_refptr<MediaSession>& media_session, |
| int csp_insecure_allowed_token, int dom_max_element_depth, |
| float video_playback_rate_multiplier) |
| : width_(width), |
| height_(height), |
| html_element_context_(new HTMLElementContext( |
| fetcher_factory, css_parser, dom_parser, can_play_type_handler, |
| web_media_player_factory, script_runner, script_value_factory, |
| media_source_registry, resource_provider, animated_image_tracker, |
| image_cache, reduced_image_cache_capacity_manager, |
| remote_typeface_cache, mesh_cache, dom_stat_tracker, language, |
| initial_application_state, video_playback_rate_multiplier)), |
| performance_(new Performance(new base::SystemMonotonicClock())), |
| ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document( |
| html_element_context_.get(), |
| Document::Options( |
| url, this, |
| base::Bind(&Window::FireHashChangeEvent, base::Unretained(this)), |
| performance_->timing()->GetNavigationStartClock(), |
| navigation_callback, ParseUserAgentStyleSheet(css_parser), |
| math::Size(width_, height_), cookie_jar, post_sender, |
| default_security_policy, csp_enforcement_mode, |
| csp_policy_changed_callback, csp_insecure_allowed_token, |
| dom_max_element_depth)))), |
| document_loader_(NULL), |
| history_(new History()), |
| navigator_(new Navigator(user_agent, language, media_session, |
| script_value_factory)), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| relay_on_load_event_(new RelayLoadEvent(this))), |
| console_(new Console(execution_state)), |
| camera_3d_(new Camera3D(camera_3d)), |
| ALLOW_THIS_IN_INITIALIZER_LIST(window_timers_(new WindowTimers(this))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(animation_frame_request_callback_list_( |
| new AnimationFrameRequestCallbackList(this))), |
| crypto_(new Crypto()), |
| speech_synthesis_(new speech::SpeechSynthesis(navigator_)), |
| ALLOW_THIS_IN_INITIALIZER_LIST(local_storage_( |
| new Storage(this, Storage::kLocalStorage, local_storage_database))), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| session_storage_(new Storage(this, Storage::kSessionStorage, NULL))), |
| screen_(new Screen(width, height)), |
| ran_animation_frame_callbacks_callback_( |
| ran_animation_frame_callbacks_callback), |
| window_close_callback_(window_close_callback), |
| window_minimize_callback_(window_minimize_callback), |
| system_window_(system_window) { |
| #if defined(ENABLE_TEST_RUNNER) |
| test_runner_ = new TestRunner(); |
| #endif // ENABLE_TEST_RUNNER |
| document_->AddObserver(relay_on_load_event_.get()); |
| html_element_context_->page_visibility_state()->AddObserver(this); |
| |
| if (system_window_) { |
| SbWindow sb_window = system_window_->GetSbWindow(); |
| SbWindowSize size; |
| if (SbWindowGetSize(sb_window, &size)) { |
| device_pixel_ratio_ = size.video_pixel_ratio; |
| } else { |
| device_pixel_ratio_ = 1.0f; |
| } |
| } else { |
| device_pixel_ratio_ = 1.0f; |
| } |
| |
| // Document load start is deferred from this constructor so that we can be |
| // guaranteed that this Window object is fully constructed before document |
| // loading begins. |
| MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&Window::StartDocumentLoad, this, fetcher_factory, |
| url, dom_parser, error_callback)); |
| camera_3d_->StartOrientationEvents(base::AsWeakPtr(this)); |
| } |
| |
| void Window::StartDocumentLoad( |
| loader::FetcherFactory* fetcher_factory, const GURL& url, |
| Parser* dom_parser, |
| const base::Callback<void(const std::string&)>& error_callback) { |
| document_loader_.reset( |
| new loader::Loader(base::Bind(&loader::FetcherFactory::CreateFetcher, |
| base::Unretained(fetcher_factory), url), |
| dom_parser->ParseDocumentAsync( |
| document_, base::SourceLocation(url.spec(), 1, 1)), |
| error_callback)); |
| } |
| |
| const scoped_refptr<Document>& Window::document() const { return document_; } |
| |
| const scoped_refptr<Location>& Window::location() const { |
| return document_->location(); |
| } |
| |
| const scoped_refptr<History>& Window::history() const { return history_; } |
| |
| // https://www.w3.org/TR/html5/browsers.html#dom-window-close |
| void Window::Close() { |
| if (!window_close_callback_.is_null()) { |
| window_close_callback_.Run(); |
| } |
| } |
| |
| void Window::Minimize() { |
| if (!window_minimize_callback_.is_null()) { |
| window_minimize_callback_.Run(); |
| } |
| } |
| |
| const scoped_refptr<Navigator>& Window::navigator() const { return navigator_; } |
| |
| scoped_refptr<cssom::CSSStyleDeclaration> Window::GetComputedStyle( |
| const scoped_refptr<Element>& elt) { |
| scoped_refptr<HTMLElement> html_element = elt->AsHTMLElement(); |
| if (html_element) { |
| document_->UpdateComputedStyles(); |
| return html_element->css_computed_style_declaration(); |
| } |
| return NULL; |
| } |
| |
| scoped_refptr<cssom::CSSStyleDeclaration> Window::GetComputedStyle( |
| const scoped_refptr<Element>& elt, const std::string& pseudoElt) { |
| // The getComputedStyle(elt, pseudoElt) method must run these steps: |
| // https://www.w3.org/TR/2013/WD-cssom-20131205/#dom-window-getcomputedstyle |
| |
| // 1. Let doc be the Document associated with the Window object on which the |
| // method was invoked. |
| DCHECK_EQ(document_, elt->node_document()) |
| << "getComputedStyle not supported for elements outside of the document"; |
| |
| scoped_refptr<HTMLElement> html_element = elt->AsHTMLElement(); |
| scoped_refptr<cssom::CSSComputedStyleDeclaration> obj; |
| if (html_element) { |
| document_->UpdateComputedStyles(); |
| |
| // 2. Let obj be elt. |
| obj = html_element->css_computed_style_declaration(); |
| |
| // 3. If pseudoElt is as an ASCII case-insensitive match for either |
| // ':before' or '::before' let obj be the ::before pseudo-element of elt. |
| if (LowerCaseEqualsASCII(pseudoElt, ":before") || |
| LowerCaseEqualsASCII(pseudoElt, "::before")) { |
| PseudoElement* pseudo_element = |
| html_element->pseudo_element(kBeforePseudoElementType); |
| obj = pseudo_element ? pseudo_element->css_computed_style_declaration() |
| : NULL; |
| } |
| |
| // 4. If pseudoElt is as an ASCII case-insensitive match for either ':after' |
| // or '::after' let obj be the ::after pseudo-element of elt. |
| if (LowerCaseEqualsASCII(pseudoElt, ":after") || |
| LowerCaseEqualsASCII(pseudoElt, "::after")) { |
| PseudoElement* pseudo_element = |
| html_element->pseudo_element(kAfterPseudoElementType); |
| obj = pseudo_element ? pseudo_element->css_computed_style_declaration() |
| : NULL; |
| } |
| } |
| // 5. Return a live CSS declaration block. |
| return obj; |
| } |
| |
| int32 Window::RequestAnimationFrame( |
| const AnimationFrameRequestCallbackList::FrameRequestCallbackArg& |
| callback) { |
| return animation_frame_request_callback_list_->RequestAnimationFrame( |
| callback); |
| } |
| |
| void Window::CancelAnimationFrame(int32 handle) { |
| animation_frame_request_callback_list_->CancelAnimationFrame(handle); |
| } |
| |
| scoped_refptr<MediaQueryList> Window::MatchMedia(const std::string& query) { |
| DCHECK(html_element_context_->css_parser()); |
| scoped_refptr<cssom::MediaList> media_list = |
| html_element_context_->css_parser()->ParseMediaList( |
| query, GetInlineSourceLocation()); |
| return make_scoped_refptr(new MediaQueryList(media_list, screen_)); |
| } |
| |
| const scoped_refptr<Screen>& Window::screen() { return screen_; } |
| |
| scoped_refptr<Crypto> Window::crypto() const { return crypto_; } |
| |
| int Window::SetTimeout(const WindowTimers::TimerCallbackArg& handler, |
| int timeout) { |
| DLOG_IF(WARNING, timeout < 0) |
| << "Window::SetTimeout received negative timeout: " << timeout; |
| timeout = std::max(timeout, 0); |
| |
| int return_value = 0; |
| if (window_timers_) { |
| return_value = window_timers_->SetTimeout(handler, timeout); |
| } else { |
| DLOG(WARNING) << "window_timers_ does not exist. Already destroyed?"; |
| } |
| |
| return return_value; |
| } |
| |
| void Window::ClearTimeout(int handle) { |
| if (window_timers_) { |
| window_timers_->ClearTimeout(handle); |
| } else { |
| DLOG(WARNING) << "window_timers_ does not exist. Already destroyed?"; |
| } |
| } |
| |
| int Window::SetInterval(const WindowTimers::TimerCallbackArg& handler, |
| int timeout) { |
| DLOG_IF(WARNING, timeout < 0) |
| << "Window::SetInterval received negative timeout: " << timeout; |
| timeout = std::max(timeout, 0); |
| |
| int return_value = 0; |
| if (window_timers_) { |
| return_value = window_timers_->SetInterval(handler, timeout); |
| } else { |
| DLOG(WARNING) << "window_timers_ does not exist. Already destroyed?"; |
| } |
| |
| return return_value; |
| } |
| |
| void Window::ClearInterval(int handle) { |
| if (window_timers_) { |
| window_timers_->ClearInterval(handle); |
| } else { |
| DLOG(WARNING) << "window_timers_ does not exist. Already destroyed?"; |
| } |
| } |
| |
| void Window::DestroyTimers() { window_timers_.reset(); } |
| |
| scoped_refptr<Storage> Window::local_storage() const { return local_storage_; } |
| |
| scoped_refptr<Storage> Window::session_storage() const { |
| return session_storage_; |
| } |
| |
| const scoped_refptr<Performance>& Window::performance() const { |
| return performance_; |
| } |
| |
| scoped_refptr<speech::SpeechSynthesis> Window::speech_synthesis() const { |
| return speech_synthesis_; |
| } |
| |
| const scoped_refptr<Console>& Window::console() const { return console_; } |
| |
| const scoped_refptr<Camera3D>& Window::camera_3d() const { return camera_3d_; } |
| |
| #if defined(ENABLE_TEST_RUNNER) |
| const scoped_refptr<TestRunner>& Window::test_runner() const { |
| return test_runner_; |
| } |
| #endif // ENABLE_TEST_RUNNER |
| |
| void Window::Gc(script::EnvironmentSettings* settings) { |
| if (settings) { |
| DOMSettings* dom_settings = |
| base::polymorphic_downcast<dom::DOMSettings*>(settings); |
| dom_settings->javascript_engine()->CollectGarbage(); |
| } |
| } |
| |
| HTMLElementContext* Window::html_element_context() const { |
| return html_element_context_.get(); |
| } |
| |
| void Window::RunAnimationFrameCallbacks() { |
| // Scope the StopWatch. It should not include any processing from |
| // |ran_animation_frame_callbacks_callback_|. |
| { |
| base::StopWatch stop_watch_run_animation_frame_callbacks( |
| DomStatTracker::kStopWatchTypeRunAnimationFrameCallbacks, |
| base::StopWatch::kAutoStartOn, |
| html_element_context()->dom_stat_tracker()); |
| |
| // First grab the current list of frame request callbacks and hold on to it |
| // here locally. |
| scoped_ptr<AnimationFrameRequestCallbackList> frame_request_list = |
| animation_frame_request_callback_list_.Pass(); |
| |
| // Then setup the Window's frame request callback list with a freshly |
| // created and empty one. |
| animation_frame_request_callback_list_.reset( |
| new AnimationFrameRequestCallbackList(this)); |
| |
| // Now, iterate through each of the callbacks and call them. |
| frame_request_list->RunCallbacks(*document_->timeline()->current_time()); |
| } |
| |
| // Run the callback if one exists. |
| if (!ran_animation_frame_callbacks_callback_.is_null()) { |
| ran_animation_frame_callbacks_callback_.Run(); |
| } |
| } |
| |
| bool Window::HasPendingAnimationFrameCallbacks() const { |
| return animation_frame_request_callback_list_->HasPendingCallbacks(); |
| } |
| |
| void Window::InjectEvent(const scoped_refptr<Event>& event) { |
| // Forward the event on to the correct object in DOM. |
| if (event->GetWrappableType() == base::GetTypeId<KeyboardEvent>()) { |
| // Event.target:focused element processing the key event or if no element |
| // focused, then the body element if available, otherwise the root element. |
| // https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keydown |
| // https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keypress |
| // https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keyup |
| if (document_->active_element()) { |
| document_->active_element()->DispatchEvent(event); |
| } else { |
| document_->DispatchEvent(event); |
| } |
| } else if (event->GetWrappableType() == base::GetTypeId<PointerEvent>() || |
| event->GetWrappableType() == base::GetTypeId<MouseEvent>() || |
| event->GetWrappableType() == base::GetTypeId<WheelEvent>()) { |
| document_->QueuePointerEvent(event); |
| } else { |
| SB_NOTREACHED(); |
| } |
| } |
| |
| void Window::SetApplicationState(base::ApplicationState state) { |
| html_element_context_->page_visibility_state()->SetApplicationState(state); |
| } |
| |
| void Window::SetSynchronousLayoutCallback( |
| const base::Closure& synchronous_layout_callback) { |
| document_->set_synchronous_layout_callback(synchronous_layout_callback); |
| } |
| |
| void Window::OnWindowFocusChanged(bool has_focus) { |
| DispatchEvent( |
| new Event(has_focus ? base::Tokens::focus() : base::Tokens::blur())); |
| } |
| |
| void Window::OnVisibilityStateChanged( |
| page_visibility::VisibilityState visibility_state) { |
| UNREFERENCED_PARAMETER(visibility_state); |
| } |
| |
| void Window::TraceMembers(script::Tracer* tracer) { |
| tracer->Trace(performance_); |
| tracer->Trace(document_); |
| tracer->Trace(history_); |
| tracer->Trace(navigator_); |
| tracer->Trace(console_); |
| tracer->Trace(camera_3d_); |
| tracer->Trace(crypto_); |
| tracer->Trace(speech_synthesis_); |
| tracer->Trace(local_storage_); |
| tracer->Trace(session_storage_); |
| tracer->Trace(screen_); |
| } |
| |
| Window::~Window() { |
| html_element_context_->page_visibility_state()->RemoveObserver(this); |
| } |
| |
| void Window::FireHashChangeEvent() { |
| PostToDispatchEvent(FROM_HERE, base::Tokens::hashchange()); |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |