webview.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/controls/webview/webview.h"
6
7#include "content/public/browser/browser_accessibility_state.h"
8#include "content/public/browser/browser_context.h"
9#include "content/public/browser/navigation_controller.h"
10#include "content/public/browser/render_view_host.h"
11#include "content/public/browser/render_widget_host_view.h"
12#include "content/public/browser/web_contents.h"
13#include "ipc/ipc_message.h"
14#include "ui/accessibility/ax_enums.h"
15#include "ui/accessibility/ax_view_state.h"
16#include "ui/base/ui_base_switches_util.h"
17#include "ui/events/event.h"
18#include "ui/views/accessibility/native_view_accessibility.h"
19#include "ui/views/controls/native/native_view_host.h"
20#include "ui/views/focus/focus_manager.h"
21#include "ui/views/views_delegate.h"
22
23namespace views {
24
25// static
26const char WebView::kViewClassName[] = "WebView";
27
28////////////////////////////////////////////////////////////////////////////////
29// WebView, public:
30
31WebView::WebView(content::BrowserContext* browser_context)
32    : holder_(new NativeViewHost()),
33      embed_fullscreen_widget_mode_enabled_(false),
34      is_embedding_fullscreen_widget_(false),
35      browser_context_(browser_context),
36      allow_accelerators_(false) {
37  AddChildView(holder_);  // Takes ownership of |holder_|.
38  NativeViewAccessibility::RegisterWebView(this);
39}
40
41WebView::~WebView() {
42  SetWebContents(NULL);  // Make sure all necessary tear-down takes place.
43  NativeViewAccessibility::UnregisterWebView(this);
44}
45
46content::WebContents* WebView::GetWebContents() {
47  if (!web_contents()) {
48    wc_owner_.reset(CreateWebContents(browser_context_));
49    wc_owner_->SetDelegate(this);
50    SetWebContents(wc_owner_.get());
51  }
52  return web_contents();
53}
54
55void WebView::SetWebContents(content::WebContents* replacement) {
56  if (replacement == web_contents())
57    return;
58  DetachWebContents();
59  WebContentsObserver::Observe(replacement);
60  // web_contents() now returns |replacement| from here onwards.
61  if (wc_owner_ != replacement)
62    wc_owner_.reset();
63  if (embed_fullscreen_widget_mode_enabled_) {
64    is_embedding_fullscreen_widget_ =
65        web_contents() && web_contents()->GetFullscreenRenderWidgetHostView();
66  } else {
67    DCHECK(!is_embedding_fullscreen_widget_);
68  }
69  AttachWebContents();
70  NotifyMaybeTextInputClientChanged();
71}
72
73void WebView::SetEmbedFullscreenWidgetMode(bool enable) {
74  DCHECK(!web_contents())
75      << "Cannot change mode while a WebContents is attached.";
76  embed_fullscreen_widget_mode_enabled_ = enable;
77}
78
79void WebView::LoadInitialURL(const GURL& url) {
80  GetWebContents()->GetController().LoadURL(
81      url, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL,
82      std::string());
83}
84
85void WebView::SetFastResize(bool fast_resize) {
86  holder_->set_fast_resize(fast_resize);
87}
88
89void WebView::OnWebContentsFocused(content::WebContents* web_contents) {
90  FocusManager* focus_manager = GetFocusManager();
91  if (focus_manager)
92    focus_manager->SetFocusedView(this);
93}
94
95void WebView::SetPreferredSize(const gfx::Size& preferred_size) {
96  preferred_size_ = preferred_size;
97  PreferredSizeChanged();
98}
99
100////////////////////////////////////////////////////////////////////////////////
101// WebView, View overrides:
102
103const char* WebView::GetClassName() const {
104  return kViewClassName;
105}
106
107ui::TextInputClient* WebView::GetTextInputClient() {
108  // This function delegates the text input handling to the underlying
109  // content::RenderWidgetHostView.  So when the underlying RWHV is destroyed or
110  // replaced with another one, we have to notify the FocusManager through
111  // FocusManager::OnTextInputClientChanged() that the focused TextInputClient
112  // needs to be updated.
113  if (switches::IsTextInputFocusManagerEnabled() &&
114      web_contents() && !web_contents()->IsBeingDestroyed()) {
115    content::RenderWidgetHostView* host_view =
116        is_embedding_fullscreen_widget_ ?
117        web_contents()->GetFullscreenRenderWidgetHostView() :
118        web_contents()->GetRenderWidgetHostView();
119    if (host_view)
120      return host_view->GetTextInputClient();
121  }
122  return NULL;
123}
124
125void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
126  // In most cases, the holder is simply sized to fill this WebView's bounds.
127  // Only WebContentses that are in fullscreen mode and being screen-captured
128  // will engage the special layout/sizing behavior.
129  gfx::Rect holder_bounds(bounds().size());
130  if (!embed_fullscreen_widget_mode_enabled_ ||
131      !web_contents() ||
132      web_contents()->GetCapturerCount() == 0 ||
133      web_contents()->GetPreferredSize().IsEmpty() ||
134      !(is_embedding_fullscreen_widget_ ||
135        (web_contents()->GetDelegate() &&
136         web_contents()->GetDelegate()->
137             IsFullscreenForTabOrPending(web_contents())))) {
138    holder_->SetBoundsRect(holder_bounds);
139    return;
140  }
141
142  // Size the holder to the capture video resolution and center it.  If this
143  // WebView is not large enough to contain the holder at the preferred size,
144  // scale down to fit (preserving aspect ratio).
145  const gfx::Size capture_size = web_contents()->GetPreferredSize();
146  if (capture_size.width() <= holder_bounds.width() &&
147      capture_size.height() <= holder_bounds.height()) {
148    // No scaling, just centering.
149    holder_bounds.ClampToCenteredSize(capture_size);
150  } else {
151    // Scale down, preserving aspect ratio, and center.
152    // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
153    // looks like others have written this code elsewhere.  Let's considate
154    // into a shared function ui/gfx/geometry or around there.
155    const int64 x = static_cast<int64>(capture_size.width()) *
156        holder_bounds.height();
157    const int64 y = static_cast<int64>(capture_size.height()) *
158        holder_bounds.width();
159    if (y < x) {
160      holder_bounds.ClampToCenteredSize(gfx::Size(
161          holder_bounds.width(), static_cast<int>(y / capture_size.width())));
162    } else {
163      holder_bounds.ClampToCenteredSize(gfx::Size(
164          static_cast<int>(x / capture_size.height()), holder_bounds.height()));
165    }
166  }
167
168  holder_->SetBoundsRect(holder_bounds);
169}
170
171void WebView::ViewHierarchyChanged(
172    const ViewHierarchyChangedDetails& details) {
173  if (details.is_add)
174    AttachWebContents();
175}
176
177bool WebView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
178  if (allow_accelerators_)
179    return FocusManager::IsTabTraversalKeyEvent(event);
180
181  // Don't look-up accelerators or tab-traversal if we are showing a non-crashed
182  // TabContents.
183  // We'll first give the page a chance to process the key events.  If it does
184  // not process them, they'll be returned to us and we'll treat them as
185  // accelerators then.
186  return web_contents() && !web_contents()->IsCrashed();
187}
188
189bool WebView::IsFocusable() const {
190  // We need to be focusable when our contents is not a view hierarchy, as
191  // clicking on the contents needs to focus us.
192  return !!web_contents();
193}
194
195void WebView::OnFocus() {
196  if (!web_contents())
197    return;
198  if (is_embedding_fullscreen_widget_) {
199    content::RenderWidgetHostView* const current_fs_view =
200        web_contents()->GetFullscreenRenderWidgetHostView();
201    if (current_fs_view)
202      current_fs_view->Focus();
203  } else {
204    web_contents()->Focus();
205  }
206}
207
208void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) {
209  if (web_contents())
210    web_contents()->FocusThroughTabTraversal(reverse);
211}
212
213void WebView::GetAccessibleState(ui::AXViewState* state) {
214  state->role = ui::AX_ROLE_GROUP;
215}
216
217gfx::NativeViewAccessible WebView::GetNativeViewAccessible() {
218  if (web_contents()) {
219    content::RenderWidgetHostView* host_view =
220        web_contents()->GetRenderWidgetHostView();
221    if (host_view)
222      return host_view->GetNativeViewAccessible();
223  }
224  return View::GetNativeViewAccessible();
225}
226
227gfx::Size WebView::GetPreferredSize() const {
228  if (preferred_size_ == gfx::Size())
229    return View::GetPreferredSize();
230  else
231    return preferred_size_;
232}
233
234////////////////////////////////////////////////////////////////////////////////
235// WebView, content::WebContentsDelegate implementation:
236
237void WebView::WebContentsFocused(content::WebContents* web_contents) {
238  DCHECK(wc_owner_.get());
239  // The WebView is only the delegate of WebContentses it creates itself.
240  OnWebContentsFocused(wc_owner_.get());
241}
242
243bool WebView::EmbedsFullscreenWidget() const {
244  DCHECK(wc_owner_.get());
245  return embed_fullscreen_widget_mode_enabled_;
246}
247
248////////////////////////////////////////////////////////////////////////////////
249// WebView, content::WebContentsObserver implementation:
250
251void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) {
252  NotifyMaybeTextInputClientChanged();
253}
254
255void WebView::RenderViewHostChanged(content::RenderViewHost* old_host,
256                                    content::RenderViewHost* new_host) {
257  FocusManager* const focus_manager = GetFocusManager();
258  if (focus_manager && focus_manager->GetFocusedView() == this)
259    OnFocus();
260  NotifyMaybeTextInputClientChanged();
261}
262
263void WebView::DidShowFullscreenWidget(int routing_id) {
264  if (embed_fullscreen_widget_mode_enabled_)
265    ReattachForFullscreenChange(true);
266}
267
268void WebView::DidDestroyFullscreenWidget(int routing_id) {
269  if (embed_fullscreen_widget_mode_enabled_)
270    ReattachForFullscreenChange(false);
271}
272
273void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen) {
274  if (embed_fullscreen_widget_mode_enabled_)
275    ReattachForFullscreenChange(entered_fullscreen);
276}
277
278////////////////////////////////////////////////////////////////////////////////
279// WebView, private:
280
281void WebView::AttachWebContents() {
282  // Prevents attachment if the WebView isn't already in a Widget, or it's
283  // already attached.
284  if (!GetWidget() || !web_contents())
285    return;
286
287  const gfx::NativeView view_to_attach = is_embedding_fullscreen_widget_ ?
288      web_contents()->GetFullscreenRenderWidgetHostView()->GetNativeView() :
289      web_contents()->GetNativeView();
290  OnBoundsChanged(bounds());
291  if (holder_->native_view() == view_to_attach)
292    return;
293  holder_->Attach(view_to_attach);
294
295  // The view will not be focused automatically when it is attached, so we need
296  // to pass on focus to it if the FocusManager thinks the view is focused. Note
297  // that not every Widget has a focus manager.
298  FocusManager* const focus_manager = GetFocusManager();
299  if (focus_manager && focus_manager->GetFocusedView() == this)
300    OnFocus();
301
302#if defined(OS_WIN)
303  if (!is_embedding_fullscreen_widget_) {
304    web_contents()->SetParentNativeViewAccessible(
305        parent()->GetNativeViewAccessible());
306  }
307#endif
308}
309
310void WebView::DetachWebContents() {
311  if (web_contents()) {
312    holder_->Detach();
313#if defined(OS_WIN)
314    if (!is_embedding_fullscreen_widget_)
315      web_contents()->SetParentNativeViewAccessible(NULL);
316#endif
317  }
318}
319
320void WebView::ReattachForFullscreenChange(bool enter_fullscreen) {
321  DCHECK(embed_fullscreen_widget_mode_enabled_);
322  const bool web_contents_has_separate_fs_widget =
323      web_contents() && web_contents()->GetFullscreenRenderWidgetHostView();
324  if (is_embedding_fullscreen_widget_ || web_contents_has_separate_fs_widget) {
325    // Shutting down or starting up the embedding of the separate fullscreen
326    // widget.  Need to detach and re-attach to a different native view.
327    DetachWebContents();
328    is_embedding_fullscreen_widget_ =
329        enter_fullscreen && web_contents_has_separate_fs_widget;
330    AttachWebContents();
331  } else {
332    // Entering or exiting "non-Flash" fullscreen mode, where the native view is
333    // the same.  So, do not change attachment.
334    OnBoundsChanged(bounds());
335  }
336  NotifyMaybeTextInputClientChanged();
337}
338
339void WebView::NotifyMaybeTextInputClientChanged() {
340  // Update the TextInputClient as needed; see GetTextInputClient().
341  FocusManager* const focus_manager = GetFocusManager();
342  if (focus_manager)
343    focus_manager->OnTextInputClientChanged(this);
344}
345
346content::WebContents* WebView::CreateWebContents(
347      content::BrowserContext* browser_context) {
348  content::WebContents* contents = NULL;
349  if (ViewsDelegate::views_delegate) {
350    contents = ViewsDelegate::views_delegate->CreateWebContents(
351        browser_context, NULL);
352  }
353
354  if (!contents) {
355    content::WebContents::CreateParams create_params(
356        browser_context, NULL);
357    return content::WebContents::Create(create_params);
358  }
359
360  return contents;
361}
362
363}  // namespace views
364