// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/views/controls/webview/webview.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "ipc/ipc_message.h" #include "ui/accessibility/ax_enums.h" #include "ui/accessibility/ax_view_state.h" #include "ui/aura/window.h" #include "ui/base/ui_base_switches_util.h" #include "ui/events/event.h" #include "ui/views/accessibility/native_view_accessibility.h" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/views_delegate.h" namespace views { // static const char WebView::kViewClassName[] = "WebView"; //////////////////////////////////////////////////////////////////////////////// // WebView, public: WebView::WebView(content::BrowserContext* browser_context) : holder_(new NativeViewHost()), embed_fullscreen_widget_mode_enabled_(false), is_embedding_fullscreen_widget_(false), browser_context_(browser_context), allow_accelerators_(false) { AddChildView(holder_); // Takes ownership of |holder_|. NativeViewAccessibility::RegisterWebView(this); } WebView::~WebView() { SetWebContents(NULL); // Make sure all necessary tear-down takes place. NativeViewAccessibility::UnregisterWebView(this); } content::WebContents* WebView::GetWebContents() { if (!web_contents()) { wc_owner_.reset(CreateWebContents(browser_context_)); wc_owner_->SetDelegate(this); SetWebContents(wc_owner_.get()); } return web_contents(); } void WebView::SetWebContents(content::WebContents* replacement) { if (replacement == web_contents()) return; DetachWebContents(); WebContentsObserver::Observe(replacement); // web_contents() now returns |replacement| from here onwards. SetFocusable(!!web_contents()); if (wc_owner_ != replacement) wc_owner_.reset(); if (embed_fullscreen_widget_mode_enabled_) { is_embedding_fullscreen_widget_ = web_contents() && web_contents()->GetFullscreenRenderWidgetHostView(); } else { DCHECK(!is_embedding_fullscreen_widget_); } AttachWebContents(); NotifyMaybeTextInputClientChanged(); } void WebView::SetEmbedFullscreenWidgetMode(bool enable) { DCHECK(!web_contents()) << "Cannot change mode while a WebContents is attached."; embed_fullscreen_widget_mode_enabled_ = enable; } void WebView::LoadInitialURL(const GURL& url) { GetWebContents()->GetController().LoadURL( url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); } void WebView::SetFastResize(bool fast_resize) { holder_->set_fast_resize(fast_resize); } void WebView::OnWebContentsFocused(content::WebContents* web_contents) { FocusManager* focus_manager = GetFocusManager(); if (focus_manager) focus_manager->SetFocusedView(this); } void WebView::SetPreferredSize(const gfx::Size& preferred_size) { preferred_size_ = preferred_size; PreferredSizeChanged(); } //////////////////////////////////////////////////////////////////////////////// // WebView, View overrides: const char* WebView::GetClassName() const { return kViewClassName; } ui::TextInputClient* WebView::GetTextInputClient() { // This function delegates the text input handling to the underlying // content::RenderWidgetHostView. So when the underlying RWHV is destroyed or // replaced with another one, we have to notify the FocusManager through // FocusManager::OnTextInputClientChanged() that the focused TextInputClient // needs to be updated. if (switches::IsTextInputFocusManagerEnabled() && web_contents() && !web_contents()->IsBeingDestroyed()) { content::RenderWidgetHostView* host_view = is_embedding_fullscreen_widget_ ? web_contents()->GetFullscreenRenderWidgetHostView() : web_contents()->GetRenderWidgetHostView(); if (host_view) return host_view->GetTextInputClient(); } return NULL; } scoped_ptr WebView::SwapWebContents( scoped_ptr new_web_contents) { if (wc_owner_) wc_owner_->SetDelegate(NULL); scoped_ptr old_web_contents(wc_owner_.Pass()); wc_owner_ = new_web_contents.Pass(); if (wc_owner_) wc_owner_->SetDelegate(this); SetWebContents(wc_owner_.get()); return old_web_contents.Pass(); } void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) { // In most cases, the holder is simply sized to fill this WebView's bounds. // Only WebContentses that are in fullscreen mode and being screen-captured // will engage the special layout/sizing behavior. gfx::Rect holder_bounds(bounds().size()); if (!embed_fullscreen_widget_mode_enabled_ || !web_contents() || web_contents()->GetCapturerCount() == 0 || web_contents()->GetPreferredSize().IsEmpty() || !(is_embedding_fullscreen_widget_ || (web_contents()->GetDelegate() && web_contents()->GetDelegate()-> IsFullscreenForTabOrPending(web_contents())))) { holder_->SetBoundsRect(holder_bounds); return; } // Size the holder to the capture video resolution and center it. If this // WebView is not large enough to contain the holder at the preferred size, // scale down to fit (preserving aspect ratio). const gfx::Size capture_size = web_contents()->GetPreferredSize(); if (capture_size.width() <= holder_bounds.width() && capture_size.height() <= holder_bounds.height()) { // No scaling, just centering. holder_bounds.ClampToCenteredSize(capture_size); } else { // Scale down, preserving aspect ratio, and center. // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it // looks like others have written this code elsewhere. Let's considate // into a shared function ui/gfx/geometry or around there. const int64 x = static_cast(capture_size.width()) * holder_bounds.height(); const int64 y = static_cast(capture_size.height()) * holder_bounds.width(); if (y < x) { holder_bounds.ClampToCenteredSize(gfx::Size( holder_bounds.width(), static_cast(y / capture_size.width()))); } else { holder_bounds.ClampToCenteredSize(gfx::Size( static_cast(x / capture_size.height()), holder_bounds.height())); } } holder_->SetBoundsRect(holder_bounds); } void WebView::ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) { if (details.is_add) AttachWebContents(); } bool WebView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { if (allow_accelerators_) return FocusManager::IsTabTraversalKeyEvent(event); // Don't look-up accelerators or tab-traversal if we are showing a non-crashed // TabContents. // We'll first give the page a chance to process the key events. If it does // not process them, they'll be returned to us and we'll treat them as // accelerators then. return web_contents() && !web_contents()->IsCrashed(); } void WebView::OnFocus() { if (web_contents()) web_contents()->Focus(); } void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) { if (web_contents()) web_contents()->FocusThroughTabTraversal(reverse); } void WebView::GetAccessibleState(ui::AXViewState* state) { state->role = ui::AX_ROLE_GROUP; } gfx::NativeViewAccessible WebView::GetNativeViewAccessible() { if (web_contents()) { content::RenderWidgetHostView* host_view = web_contents()->GetRenderWidgetHostView(); if (host_view) return host_view->GetNativeViewAccessible(); } return View::GetNativeViewAccessible(); } gfx::Size WebView::GetPreferredSize() const { if (preferred_size_ == gfx::Size()) return View::GetPreferredSize(); else return preferred_size_; } //////////////////////////////////////////////////////////////////////////////// // WebView, content::WebContentsDelegate implementation: void WebView::WebContentsFocused(content::WebContents* web_contents) { DCHECK(wc_owner_.get()); // The WebView is only the delegate of WebContentses it creates itself. OnWebContentsFocused(wc_owner_.get()); } bool WebView::EmbedsFullscreenWidget() const { DCHECK(wc_owner_.get()); return embed_fullscreen_widget_mode_enabled_; } //////////////////////////////////////////////////////////////////////////////// // WebView, content::WebContentsObserver implementation: void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) { NotifyMaybeTextInputClientChanged(); } void WebView::RenderProcessGone(base::TerminationStatus status) { NotifyMaybeTextInputClientChanged(); } void WebView::RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) { FocusManager* const focus_manager = GetFocusManager(); if (focus_manager && focus_manager->GetFocusedView() == this) OnFocus(); NotifyMaybeTextInputClientChanged(); } void WebView::DidShowFullscreenWidget(int routing_id) { if (embed_fullscreen_widget_mode_enabled_) ReattachForFullscreenChange(true); } void WebView::DidDestroyFullscreenWidget(int routing_id) { if (embed_fullscreen_widget_mode_enabled_) ReattachForFullscreenChange(false); } void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen) { if (embed_fullscreen_widget_mode_enabled_) ReattachForFullscreenChange(entered_fullscreen); } void WebView::DidAttachInterstitialPage() { NotifyMaybeTextInputClientChanged(); } void WebView::DidDetachInterstitialPage() { NotifyMaybeTextInputClientChanged(); } //////////////////////////////////////////////////////////////////////////////// // WebView, private: void WebView::AttachWebContents() { // Prevents attachment if the WebView isn't already in a Widget, or it's // already attached. if (!GetWidget() || !web_contents()) return; const gfx::NativeView view_to_attach = is_embedding_fullscreen_widget_ ? web_contents()->GetFullscreenRenderWidgetHostView()->GetNativeView() : web_contents()->GetNativeView(); OnBoundsChanged(bounds()); if (holder_->native_view() == view_to_attach) return; // The WCV needs to be parented before making it visible. holder_->Attach(view_to_attach); // Fullscreen widgets are not parented by a WebContentsView. Their visibility // is controlled by content i.e. (RenderWidgetHost) if (!is_embedding_fullscreen_widget_) view_to_attach->Show(); // The view will not be focused automatically when it is attached, so we need // to pass on focus to it if the FocusManager thinks the view is focused. Note // that not every Widget has a focus manager. FocusManager* const focus_manager = GetFocusManager(); if (focus_manager && focus_manager->GetFocusedView() == this) OnFocus(); #if defined(OS_WIN) if (!is_embedding_fullscreen_widget_) { web_contents()->SetParentNativeViewAccessible( parent()->GetNativeViewAccessible()); } #endif } void WebView::DetachWebContents() { if (web_contents()) { // Fullscreen widgets are not parented by a WebContentsView. Their // visibility is controlled by content i.e. (RenderWidgetHost). if (!is_embedding_fullscreen_widget_) web_contents()->GetNativeView()->Hide(); holder_->Detach(); #if defined(OS_WIN) if (!is_embedding_fullscreen_widget_) web_contents()->SetParentNativeViewAccessible(NULL); #endif } } void WebView::ReattachForFullscreenChange(bool enter_fullscreen) { DCHECK(embed_fullscreen_widget_mode_enabled_); const bool web_contents_has_separate_fs_widget = web_contents() && web_contents()->GetFullscreenRenderWidgetHostView(); if (is_embedding_fullscreen_widget_ || web_contents_has_separate_fs_widget) { // Shutting down or starting up the embedding of the separate fullscreen // widget. Need to detach and re-attach to a different native view. DetachWebContents(); is_embedding_fullscreen_widget_ = enter_fullscreen && web_contents_has_separate_fs_widget; AttachWebContents(); } else { // Entering or exiting "non-Flash" fullscreen mode, where the native view is // the same. So, do not change attachment. OnBoundsChanged(bounds()); } NotifyMaybeTextInputClientChanged(); } void WebView::NotifyMaybeTextInputClientChanged() { // Update the TextInputClient as needed; see GetTextInputClient(). FocusManager* const focus_manager = GetFocusManager(); if (focus_manager) focus_manager->OnTextInputClientChanged(this); } content::WebContents* WebView::CreateWebContents( content::BrowserContext* browser_context) { content::WebContents* contents = NULL; if (ViewsDelegate::views_delegate) { contents = ViewsDelegate::views_delegate->CreateWebContents( browser_context, NULL); } if (!contents) { content::WebContents::CreateParams create_params( browser_context, NULL); return content::WebContents::Create(create_params); } return contents; } } // namespace views