| // 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/notification_details.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/notification_types.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 "content/public/browser/web_contents_view.h" |
| #include "ipc/ipc_message.h" |
| #include "ui/base/accessibility/accessibility_types.h" |
| #include "ui/base/accessibility/accessible_view_state.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) |
| : wcv_holder_(new NativeViewHost), |
| web_contents_(NULL), |
| embed_fullscreen_widget_mode_enabled_(false), |
| is_embedding_fullscreen_widget_(false), |
| browser_context_(browser_context), |
| allow_accelerators_(false) { |
| AddChildView(wcv_holder_); |
| NativeViewAccessibility::RegisterWebView(this); |
| } |
| |
| WebView::~WebView() { |
| NativeViewAccessibility::UnregisterWebView(this); |
| } |
| |
| content::WebContents* WebView::GetWebContents() { |
| CreateWebContentsWithSiteInstance(NULL); |
| return web_contents_; |
| } |
| |
| void WebView::CreateWebContentsWithSiteInstance( |
| content::SiteInstance* site_instance) { |
| if (!web_contents_) { |
| wc_owner_.reset(CreateWebContents(browser_context_, site_instance)); |
| web_contents_ = wc_owner_.get(); |
| web_contents_->SetDelegate(this); |
| AttachWebContents(); |
| } |
| } |
| |
| void WebView::SetWebContents(content::WebContents* web_contents) { |
| if (web_contents == web_contents_) |
| return; |
| DetachWebContents(); |
| if (wc_owner_ != web_contents) |
| wc_owner_.reset(); |
| web_contents_ = web_contents; |
| if (embed_fullscreen_widget_mode_enabled_) { |
| WebContentsObserver::Observe(web_contents_); |
| is_embedding_fullscreen_widget_ = |
| web_contents_ && web_contents_->GetFullscreenRenderWidgetHostView(); |
| } else { |
| is_embedding_fullscreen_widget_ = false; |
| } |
| AttachWebContents(); |
| } |
| |
| void WebView::SetEmbedFullscreenWidgetMode(bool enable) { |
| bool should_be_embedded = enable; |
| if (!embed_fullscreen_widget_mode_enabled_ && enable) { |
| DCHECK(!is_embedding_fullscreen_widget_); |
| embed_fullscreen_widget_mode_enabled_ = true; |
| WebContentsObserver::Observe(web_contents_); |
| should_be_embedded = |
| web_contents_ && web_contents_->GetFullscreenRenderWidgetHostView(); |
| } else if (embed_fullscreen_widget_mode_enabled_ && !enable) { |
| embed_fullscreen_widget_mode_enabled_ = false; |
| WebContentsObserver::Observe(NULL); |
| } |
| if (should_be_embedded != is_embedding_fullscreen_widget_) |
| ReattachForFullscreenChange(should_be_embedded); |
| } |
| |
| void WebView::LoadInitialURL(const GURL& url) { |
| GetWebContents()->GetController().LoadURL( |
| url, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| std::string()); |
| } |
| |
| void WebView::SetFastResize(bool fast_resize) { |
| wcv_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; |
| } |
| |
| void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| wcv_holder_->SetSize(bounds().size()); |
| } |
| |
| 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(); |
| } |
| |
| bool WebView::IsFocusable() const { |
| // We need to be focusable when our contents is not a view hierarchy, as |
| // clicking on the contents needs to focus us. |
| return !!web_contents_; |
| } |
| |
| void WebView::OnFocus() { |
| if (!web_contents_) |
| return; |
| if (is_embedding_fullscreen_widget_) { |
| content::RenderWidgetHostView* const current_fs_view = |
| web_contents_->GetFullscreenRenderWidgetHostView(); |
| if (current_fs_view) |
| current_fs_view->Focus(); |
| } else { |
| web_contents_->GetView()->Focus(); |
| } |
| } |
| |
| void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| if (web_contents_) |
| web_contents_->FocusThroughTabTraversal(reverse); |
| } |
| |
| void WebView::GetAccessibleState(ui::AccessibleViewState* state) { |
| state->role = ui::AccessibilityTypes::ROLE_GROUPING; |
| } |
| |
| 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() { |
| if (preferred_size_ == gfx::Size()) |
| return View::GetPreferredSize(); |
| else |
| return preferred_size_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // WebView, content::NotificationObserver implementation: |
| |
| void WebView::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| if (type == content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED) { |
| FocusManager* const focus_manager = GetFocusManager(); |
| if (focus_manager && focus_manager->GetFocusedView() == this) |
| OnFocus(); |
| } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { |
| DCHECK(content::Source<content::WebContents>(source).ptr() == |
| web_contents_); |
| SetWebContents(NULL); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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(web_contents_); |
| } |
| |
| bool WebView::EmbedsFullscreenWidget() const { |
| DCHECK(wc_owner_.get()); |
| return embed_fullscreen_widget_mode_enabled_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // WebView, content::WebContentsObserver implementation: |
| |
| void WebView::DidShowFullscreenWidget(int routing_id) { |
| DCHECK(embed_fullscreen_widget_mode_enabled_); |
| ReattachForFullscreenChange(true); |
| } |
| |
| void WebView::DidDestroyFullscreenWidget(int routing_id) { |
| DCHECK(embed_fullscreen_widget_mode_enabled_); |
| ReattachForFullscreenChange(false); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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_->GetView()->GetNativeView(); |
| if (wcv_holder_->native_view() == view_to_attach) |
| return; |
| wcv_holder_->Attach(view_to_attach); |
| |
| // 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(); |
| |
| registrar_.Add( |
| this, |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, |
| content::Source<content::NavigationController>( |
| &web_contents_->GetController())); |
| registrar_.Add( |
| this, |
| content::NOTIFICATION_WEB_CONTENTS_DESTROYED, |
| content::Source<content::WebContents>(web_contents_)); |
| |
| #if defined(OS_WIN) && defined(USE_AURA) |
| if (!is_embedding_fullscreen_widget_) { |
| web_contents_->SetParentNativeViewAccessible( |
| parent()->GetNativeViewAccessible()); |
| } |
| #endif |
| } |
| |
| void WebView::DetachWebContents() { |
| if (web_contents_) { |
| wcv_holder_->Detach(); |
| #if defined(OS_WIN) |
| if (!is_embedding_fullscreen_widget_) { |
| #if !defined(USE_AURA) |
| // TODO(beng): This should either not be necessary, or be done implicitly |
| // by NativeViewHostWin on Detach(). As it stands, this is needed so that |
| // the of the detached contents knows to tell the renderer it's been |
| // hidden. |
| // |
| // Moving this out of here would also mean we wouldn't be potentially |
| // calling member functions on a half-destroyed WebContents. |
| ShowWindow(web_contents_->GetView()->GetNativeView(), SW_HIDE); |
| #else |
| web_contents_->SetParentNativeViewAccessible(NULL); |
| #endif |
| } |
| #endif |
| } |
| registrar_.RemoveAll(); |
| } |
| |
| void WebView::ReattachForFullscreenChange(bool enter_fullscreen) { |
| DetachWebContents(); |
| is_embedding_fullscreen_widget_ = enter_fullscreen && |
| web_contents_ && web_contents_->GetFullscreenRenderWidgetHostView(); |
| AttachWebContents(); |
| } |
| |
| content::WebContents* WebView::CreateWebContents( |
| content::BrowserContext* browser_context, |
| content::SiteInstance* site_instance) { |
| content::WebContents* contents = NULL; |
| if (ViewsDelegate::views_delegate) { |
| contents = ViewsDelegate::views_delegate->CreateWebContents( |
| browser_context, site_instance); |
| } |
| |
| if (!contents) { |
| content::WebContents::CreateParams create_params( |
| browser_context, site_instance); |
| return content::WebContents::Create(create_params); |
| } |
| |
| return contents; |
| } |
| |
| } // namespace views |