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 23 namespace views { 24 25 // static 26 const char WebView::kViewClassName[] = "WebView"; 27 28 //////////////////////////////////////////////////////////////////////////////// 29 // WebView, public: 30 31 WebView::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 41 WebView::~WebView() { 42 SetWebContents(NULL); // Make sure all necessary tear-down takes place. 43 NativeViewAccessibility::UnregisterWebView(this); 44 } 45 46 content::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 55 void 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 73 void 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 79 void WebView::LoadInitialURL(const GURL& url) { 80 GetWebContents()->GetController().LoadURL( 81 url, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, 82 std::string()); 83 } 84 85 void WebView::SetFastResize(bool fast_resize) { 86 holder_->set_fast_resize(fast_resize); 87 } 88 89 void WebView::OnWebContentsFocused(content::WebContents* web_contents) { 90 FocusManager* focus_manager = GetFocusManager(); 91 if (focus_manager) 92 focus_manager->SetFocusedView(this); 93 } 94 95 void WebView::SetPreferredSize(const gfx::Size& preferred_size) { 96 preferred_size_ = preferred_size; 97 PreferredSizeChanged(); 98 } 99 100 //////////////////////////////////////////////////////////////////////////////// 101 // WebView, View overrides: 102 103 const char* WebView::GetClassName() const { 104 return kViewClassName; 105 } 106 107 ui::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 125 void 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 171 void WebView::ViewHierarchyChanged( 172 const ViewHierarchyChangedDetails& details) { 173 if (details.is_add) 174 AttachWebContents(); 175 } 176 177 bool 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 189 bool 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 195 void 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 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::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 263 void WebView::DidShowFullscreenWidget(int routing_id) { 264 if (embed_fullscreen_widget_mode_enabled_) 265 ReattachForFullscreenChange(true); 266 } 267 268 void WebView::DidDestroyFullscreenWidget(int routing_id) { 269 if (embed_fullscreen_widget_mode_enabled_) 270 ReattachForFullscreenChange(false); 271 } 272 273 void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen) { 274 if (embed_fullscreen_widget_mode_enabled_) 275 ReattachForFullscreenChange(entered_fullscreen); 276 } 277 278 //////////////////////////////////////////////////////////////////////////////// 279 // WebView, private: 280 281 void 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 310 void 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 320 void 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 339 void 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 346 content::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