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 24 namespace views { 25 26 // static 27 const char WebView::kViewClassName[] = "WebView"; 28 29 //////////////////////////////////////////////////////////////////////////////// 30 // WebView, public: 31 32 WebView::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 42 WebView::~WebView() { 43 SetWebContents(NULL); // Make sure all necessary tear-down takes place. 44 NativeViewAccessibility::UnregisterWebView(this); 45 } 46 47 content::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 56 void 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 75 void 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 81 void WebView::LoadInitialURL(const GURL& url) { 82 GetWebContents()->GetController().LoadURL( 83 url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL, 84 std::string()); 85 } 86 87 void WebView::SetFastResize(bool fast_resize) { 88 holder_->set_fast_resize(fast_resize); 89 } 90 91 void WebView::OnWebContentsFocused(content::WebContents* web_contents) { 92 FocusManager* focus_manager = GetFocusManager(); 93 if (focus_manager) 94 focus_manager->SetFocusedView(this); 95 } 96 97 void WebView::SetPreferredSize(const gfx::Size& preferred_size) { 98 preferred_size_ = preferred_size; 99 PreferredSizeChanged(); 100 } 101 102 //////////////////////////////////////////////////////////////////////////////// 103 // WebView, View overrides: 104 105 const char* WebView::GetClassName() const { 106 return kViewClassName; 107 } 108 109 ui::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 127 scoped_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 139 void 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 185 void WebView::ViewHierarchyChanged( 186 const ViewHierarchyChangedDetails& details) { 187 if (details.is_add) 188 AttachWebContents(); 189 } 190 191 bool 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 203 void WebView::OnFocus() { 204 if (web_contents()) 205 web_contents()->Focus(); 206 } 207 208 void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) { 209 if (web_contents()) 210 web_contents()->FocusThroughTabTraversal(reverse); 211 } 212 213 void WebView::GetAccessibleState(ui::AXViewState* state) { 214 state->role = ui::AX_ROLE_GROUP; 215 } 216 217 gfx::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 227 gfx::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 237 void 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 243 bool WebView::EmbedsFullscreenWidget() const { 244 DCHECK(wc_owner_.get()); 245 return embed_fullscreen_widget_mode_enabled_; 246 } 247 248 //////////////////////////////////////////////////////////////////////////////// 249 // WebView, content::WebContentsObserver implementation: 250 251 void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) { 252 NotifyMaybeTextInputClientChanged(); 253 } 254 255 void WebView::RenderProcessGone(base::TerminationStatus status) { 256 NotifyMaybeTextInputClientChanged(); 257 } 258 259 void 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 267 void WebView::DidShowFullscreenWidget(int routing_id) { 268 if (embed_fullscreen_widget_mode_enabled_) 269 ReattachForFullscreenChange(true); 270 } 271 272 void WebView::DidDestroyFullscreenWidget(int routing_id) { 273 if (embed_fullscreen_widget_mode_enabled_) 274 ReattachForFullscreenChange(false); 275 } 276 277 void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen) { 278 if (embed_fullscreen_widget_mode_enabled_) 279 ReattachForFullscreenChange(entered_fullscreen); 280 } 281 282 void WebView::DidAttachInterstitialPage() { 283 NotifyMaybeTextInputClientChanged(); 284 } 285 286 void WebView::DidDetachInterstitialPage() { 287 NotifyMaybeTextInputClientChanged(); 288 } 289 290 //////////////////////////////////////////////////////////////////////////////// 291 // WebView, private: 292 293 void 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 329 void 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 344 void 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 363 void 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 370 content::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