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/aura/window.h"
17#include "ui/base/ui_base_switches_util.h"
18#include "ui/events/event.h"
19#include "ui/views/accessibility/native_view_accessibility.h"
20#include "ui/views/controls/native/native_view_host.h"
21#include "ui/views/focus/focus_manager.h"
22#include "ui/views/views_delegate.h"
23
24namespace views {
25
26// static
27const char WebView::kViewClassName[] = "WebView";
28
29////////////////////////////////////////////////////////////////////////////////
30// WebView, public:
31
32WebView::WebView(content::BrowserContext* browser_context)
33    : holder_(new NativeViewHost()),
34      embed_fullscreen_widget_mode_enabled_(false),
35      is_embedding_fullscreen_widget_(false),
36      browser_context_(browser_context),
37      allow_accelerators_(false) {
38  AddChildView(holder_);  // Takes ownership of |holder_|.
39  NativeViewAccessibility::RegisterWebView(this);
40}
41
42WebView::~WebView() {
43  SetWebContents(NULL);  // Make sure all necessary tear-down takes place.
44  NativeViewAccessibility::UnregisterWebView(this);
45}
46
47content::WebContents* WebView::GetWebContents() {
48  if (!web_contents()) {
49    wc_owner_.reset(CreateWebContents(browser_context_));
50    wc_owner_->SetDelegate(this);
51    SetWebContents(wc_owner_.get());
52  }
53  return web_contents();
54}
55
56void WebView::SetWebContents(content::WebContents* replacement) {
57  if (replacement == web_contents())
58    return;
59  DetachWebContents();
60  WebContentsObserver::Observe(replacement);
61  // web_contents() now returns |replacement| from here onwards.
62  SetFocusable(!!web_contents());
63  if (wc_owner_ != replacement)
64    wc_owner_.reset();
65  if (embed_fullscreen_widget_mode_enabled_) {
66    is_embedding_fullscreen_widget_ =
67        web_contents() && web_contents()->GetFullscreenRenderWidgetHostView();
68  } else {
69    DCHECK(!is_embedding_fullscreen_widget_);
70  }
71  AttachWebContents();
72  NotifyMaybeTextInputClientChanged();
73}
74
75void WebView::SetEmbedFullscreenWidgetMode(bool enable) {
76  DCHECK(!web_contents())
77      << "Cannot change mode while a WebContents is attached.";
78  embed_fullscreen_widget_mode_enabled_ = enable;
79}
80
81void WebView::LoadInitialURL(const GURL& url) {
82  GetWebContents()->GetController().LoadURL(
83      url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
84      std::string());
85}
86
87void WebView::SetFastResize(bool fast_resize) {
88  holder_->set_fast_resize(fast_resize);
89}
90
91void WebView::OnWebContentsFocused(content::WebContents* web_contents) {
92  FocusManager* focus_manager = GetFocusManager();
93  if (focus_manager)
94    focus_manager->SetFocusedView(this);
95}
96
97void WebView::SetPreferredSize(const gfx::Size& preferred_size) {
98  preferred_size_ = preferred_size;
99  PreferredSizeChanged();
100}
101
102////////////////////////////////////////////////////////////////////////////////
103// WebView, View overrides:
104
105const char* WebView::GetClassName() const {
106  return kViewClassName;
107}
108
109ui::TextInputClient* WebView::GetTextInputClient() {
110  // This function delegates the text input handling to the underlying
111  // content::RenderWidgetHostView.  So when the underlying RWHV is destroyed or
112  // replaced with another one, we have to notify the FocusManager through
113  // FocusManager::OnTextInputClientChanged() that the focused TextInputClient
114  // needs to be updated.
115  if (switches::IsTextInputFocusManagerEnabled() &&
116      web_contents() && !web_contents()->IsBeingDestroyed()) {
117    content::RenderWidgetHostView* host_view =
118        is_embedding_fullscreen_widget_ ?
119        web_contents()->GetFullscreenRenderWidgetHostView() :
120        web_contents()->GetRenderWidgetHostView();
121    if (host_view)
122      return host_view->GetTextInputClient();
123  }
124  return NULL;
125}
126
127scoped_ptr<content::WebContents> WebView::SwapWebContents(
128    scoped_ptr<content::WebContents> new_web_contents) {
129  if (wc_owner_)
130    wc_owner_->SetDelegate(NULL);
131  scoped_ptr<content::WebContents> old_web_contents(wc_owner_.Pass());
132  wc_owner_ = new_web_contents.Pass();
133  if (wc_owner_)
134    wc_owner_->SetDelegate(this);
135  SetWebContents(wc_owner_.get());
136  return old_web_contents.Pass();
137}
138
139void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
140  // In most cases, the holder is simply sized to fill this WebView's bounds.
141  // Only WebContentses that are in fullscreen mode and being screen-captured
142  // will engage the special layout/sizing behavior.
143  gfx::Rect holder_bounds(bounds().size());
144  if (!embed_fullscreen_widget_mode_enabled_ ||
145      !web_contents() ||
146      web_contents()->GetCapturerCount() == 0 ||
147      web_contents()->GetPreferredSize().IsEmpty() ||
148      !(is_embedding_fullscreen_widget_ ||
149        (web_contents()->GetDelegate() &&
150         web_contents()->GetDelegate()->
151             IsFullscreenForTabOrPending(web_contents())))) {
152    holder_->SetBoundsRect(holder_bounds);
153    return;
154  }
155
156  // Size the holder to the capture video resolution and center it.  If this
157  // WebView is not large enough to contain the holder at the preferred size,
158  // scale down to fit (preserving aspect ratio).
159  const gfx::Size capture_size = web_contents()->GetPreferredSize();
160  if (capture_size.width() <= holder_bounds.width() &&
161      capture_size.height() <= holder_bounds.height()) {
162    // No scaling, just centering.
163    holder_bounds.ClampToCenteredSize(capture_size);
164  } else {
165    // Scale down, preserving aspect ratio, and center.
166    // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
167    // looks like others have written this code elsewhere.  Let's considate
168    // into a shared function ui/gfx/geometry or around there.
169    const int64 x = static_cast<int64>(capture_size.width()) *
170        holder_bounds.height();
171    const int64 y = static_cast<int64>(capture_size.height()) *
172        holder_bounds.width();
173    if (y < x) {
174      holder_bounds.ClampToCenteredSize(gfx::Size(
175          holder_bounds.width(), static_cast<int>(y / capture_size.width())));
176    } else {
177      holder_bounds.ClampToCenteredSize(gfx::Size(
178          static_cast<int>(x / capture_size.height()), holder_bounds.height()));
179    }
180  }
181
182  holder_->SetBoundsRect(holder_bounds);
183}
184
185void WebView::ViewHierarchyChanged(
186    const ViewHierarchyChangedDetails& details) {
187  if (details.is_add)
188    AttachWebContents();
189}
190
191bool WebView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
192  if (allow_accelerators_)
193    return FocusManager::IsTabTraversalKeyEvent(event);
194
195  // Don't look-up accelerators or tab-traversal if we are showing a non-crashed
196  // TabContents.
197  // We'll first give the page a chance to process the key events.  If it does
198  // not process them, they'll be returned to us and we'll treat them as
199  // accelerators then.
200  return web_contents() && !web_contents()->IsCrashed();
201}
202
203void WebView::OnFocus() {
204  if (web_contents())
205    web_contents()->Focus();
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::RenderProcessGone(base::TerminationStatus status) {
256  NotifyMaybeTextInputClientChanged();
257}
258
259void WebView::RenderViewHostChanged(content::RenderViewHost* old_host,
260                                    content::RenderViewHost* new_host) {
261  FocusManager* const focus_manager = GetFocusManager();
262  if (focus_manager && focus_manager->GetFocusedView() == this)
263    OnFocus();
264  NotifyMaybeTextInputClientChanged();
265}
266
267void WebView::DidShowFullscreenWidget(int routing_id) {
268  if (embed_fullscreen_widget_mode_enabled_)
269    ReattachForFullscreenChange(true);
270}
271
272void WebView::DidDestroyFullscreenWidget(int routing_id) {
273  if (embed_fullscreen_widget_mode_enabled_)
274    ReattachForFullscreenChange(false);
275}
276
277void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen) {
278  if (embed_fullscreen_widget_mode_enabled_)
279    ReattachForFullscreenChange(entered_fullscreen);
280}
281
282void WebView::DidAttachInterstitialPage() {
283  NotifyMaybeTextInputClientChanged();
284}
285
286void WebView::DidDetachInterstitialPage() {
287  NotifyMaybeTextInputClientChanged();
288}
289
290////////////////////////////////////////////////////////////////////////////////
291// WebView, private:
292
293void WebView::AttachWebContents() {
294  // Prevents attachment if the WebView isn't already in a Widget, or it's
295  // already attached.
296  if (!GetWidget() || !web_contents())
297    return;
298
299  const gfx::NativeView view_to_attach = is_embedding_fullscreen_widget_ ?
300      web_contents()->GetFullscreenRenderWidgetHostView()->GetNativeView() :
301      web_contents()->GetNativeView();
302  OnBoundsChanged(bounds());
303  if (holder_->native_view() == view_to_attach)
304    return;
305
306  // The WCV needs to be parented before making it visible.
307  holder_->Attach(view_to_attach);
308
309  // Fullscreen widgets are not parented by a WebContentsView. Their visibility
310  // is controlled by content i.e. (RenderWidgetHost)
311  if (!is_embedding_fullscreen_widget_)
312    view_to_attach->Show();
313
314  // The view will not be focused automatically when it is attached, so we need
315  // to pass on focus to it if the FocusManager thinks the view is focused. Note
316  // that not every Widget has a focus manager.
317  FocusManager* const focus_manager = GetFocusManager();
318  if (focus_manager && focus_manager->GetFocusedView() == this)
319    OnFocus();
320
321#if defined(OS_WIN)
322  if (!is_embedding_fullscreen_widget_) {
323    web_contents()->SetParentNativeViewAccessible(
324        parent()->GetNativeViewAccessible());
325  }
326#endif
327}
328
329void WebView::DetachWebContents() {
330  if (web_contents()) {
331    // Fullscreen widgets are not parented by a WebContentsView. Their
332    // visibility is controlled by content i.e. (RenderWidgetHost).
333    if (!is_embedding_fullscreen_widget_)
334      web_contents()->GetNativeView()->Hide();
335
336    holder_->Detach();
337#if defined(OS_WIN)
338    if (!is_embedding_fullscreen_widget_)
339      web_contents()->SetParentNativeViewAccessible(NULL);
340#endif
341  }
342}
343
344void WebView::ReattachForFullscreenChange(bool enter_fullscreen) {
345  DCHECK(embed_fullscreen_widget_mode_enabled_);
346  const bool web_contents_has_separate_fs_widget =
347      web_contents() && web_contents()->GetFullscreenRenderWidgetHostView();
348  if (is_embedding_fullscreen_widget_ || web_contents_has_separate_fs_widget) {
349    // Shutting down or starting up the embedding of the separate fullscreen
350    // widget.  Need to detach and re-attach to a different native view.
351    DetachWebContents();
352    is_embedding_fullscreen_widget_ =
353        enter_fullscreen && web_contents_has_separate_fs_widget;
354    AttachWebContents();
355  } else {
356    // Entering or exiting "non-Flash" fullscreen mode, where the native view is
357    // the same.  So, do not change attachment.
358    OnBoundsChanged(bounds());
359  }
360  NotifyMaybeTextInputClientChanged();
361}
362
363void WebView::NotifyMaybeTextInputClientChanged() {
364  // Update the TextInputClient as needed; see GetTextInputClient().
365  FocusManager* const focus_manager = GetFocusManager();
366  if (focus_manager)
367    focus_manager->OnTextInputClientChanged(this);
368}
369
370content::WebContents* WebView::CreateWebContents(
371      content::BrowserContext* browser_context) {
372  content::WebContents* contents = NULL;
373  if (ViewsDelegate::views_delegate) {
374    contents = ViewsDelegate::views_delegate->CreateWebContents(
375        browser_context, NULL);
376  }
377
378  if (!contents) {
379    content::WebContents::CreateParams create_params(
380        browser_context, NULL);
381    return content::WebContents::Create(create_params);
382  }
383
384  return contents;
385}
386
387}  // namespace views
388