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 "content/browser/renderer_host/render_widget_host_view_base.h" 6 7 #include "base/logging.h" 8 #include "content/browser/accessibility/browser_accessibility_manager.h" 9 #include "content/browser/gpu/gpu_data_manager_impl.h" 10 #include "content/browser/renderer_host/input/synthetic_gesture_target_base.h" 11 #include "content/browser/renderer_host/render_process_host_impl.h" 12 #include "content/browser/renderer_host/render_widget_host_impl.h" 13 #include "content/common/content_switches_internal.h" 14 #include "content/public/browser/render_widget_host_view_frame_subscriber.h" 15 #include "third_party/WebKit/public/platform/WebScreenInfo.h" 16 #include "ui/gfx/display.h" 17 #include "ui/gfx/screen.h" 18 #include "ui/gfx/size_conversions.h" 19 #include "ui/gfx/size_f.h" 20 21 #if defined(OS_WIN) 22 #include "base/command_line.h" 23 #include "base/message_loop/message_loop.h" 24 #include "base/win/wrapped_window_proc.h" 25 #include "content/browser/plugin_process_host.h" 26 #include "content/browser/plugin_service_impl.h" 27 #include "content/common/plugin_constants_win.h" 28 #include "content/common/webplugin_geometry.h" 29 #include "content/public/browser/browser_thread.h" 30 #include "content/public/browser/child_process_data.h" 31 #include "content/public/common/content_switches.h" 32 #include "ui/gfx/gdi_util.h" 33 #include "ui/gfx/win/dpi.h" 34 #include "ui/gfx/win/hwnd_util.h" 35 #endif 36 37 namespace content { 38 39 #if defined(OS_WIN) 40 41 namespace { 42 43 // |window| is the plugin HWND, created and destroyed in the plugin process. 44 // |parent| is the parent HWND, created and destroyed on the browser UI thread. 45 void NotifyPluginProcessHostHelper(HWND window, HWND parent, int tries) { 46 // How long to wait between each try. 47 static const int kTryDelayMs = 200; 48 49 DWORD plugin_process_id; 50 bool found_starting_plugin_process = false; 51 GetWindowThreadProcessId(window, &plugin_process_id); 52 for (PluginProcessHostIterator iter; !iter.Done(); ++iter) { 53 if (!iter.GetData().handle) { 54 found_starting_plugin_process = true; 55 continue; 56 } 57 if (base::GetProcId(iter.GetData().handle) == plugin_process_id) { 58 iter->AddWindow(parent); 59 return; 60 } 61 } 62 63 if (found_starting_plugin_process) { 64 // A plugin process has started but we don't have its handle yet. Since 65 // it's most likely the one for this plugin, try a few more times after a 66 // delay. 67 if (tries > 0) { 68 base::MessageLoop::current()->PostDelayedTask( 69 FROM_HERE, 70 base::Bind(&NotifyPluginProcessHostHelper, window, parent, tries - 1), 71 base::TimeDelta::FromMilliseconds(kTryDelayMs)); 72 return; 73 } 74 } 75 76 // The plugin process might have died in the time to execute the task, don't 77 // leak the HWND. 78 PostMessage(parent, WM_CLOSE, 0, 0); 79 } 80 81 // The plugin wrapper window which lives in the browser process has this proc 82 // as its window procedure. We only handle the WM_PARENTNOTIFY message sent by 83 // windowed plugins for mouse input. This is forwarded off to the wrappers 84 // parent which is typically the RVH window which turns on user gesture. 85 LRESULT CALLBACK PluginWrapperWindowProc(HWND window, unsigned int message, 86 WPARAM wparam, LPARAM lparam) { 87 if (message == WM_PARENTNOTIFY) { 88 switch (LOWORD(wparam)) { 89 case WM_LBUTTONDOWN: 90 case WM_RBUTTONDOWN: 91 case WM_MBUTTONDOWN: 92 ::SendMessage(GetParent(window), message, wparam, lparam); 93 return 0; 94 default: 95 break; 96 } 97 } 98 return ::DefWindowProc(window, message, wparam, lparam); 99 } 100 101 bool IsPluginWrapperWindow(HWND window) { 102 return gfx::GetClassNameW(window) == 103 base::string16(kWrapperNativeWindowClassName); 104 } 105 106 // Create an intermediate window between the given HWND and its parent. 107 HWND ReparentWindow(HWND window, HWND parent) { 108 static ATOM atom = 0; 109 static HMODULE instance = NULL; 110 if (!atom) { 111 WNDCLASSEX window_class; 112 base::win::InitializeWindowClass( 113 kWrapperNativeWindowClassName, 114 &base::win::WrappedWindowProc<PluginWrapperWindowProc>, 115 CS_DBLCLKS, 116 0, 117 0, 118 NULL, 119 // xxx reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), 120 reinterpret_cast<HBRUSH>(COLOR_GRAYTEXT+1), 121 NULL, 122 NULL, 123 NULL, 124 &window_class); 125 instance = window_class.hInstance; 126 atom = RegisterClassEx(&window_class); 127 } 128 DCHECK(atom); 129 130 HWND new_parent = CreateWindowEx( 131 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, 132 MAKEINTATOM(atom), 0, 133 WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 134 0, 0, 0, 0, parent, 0, instance, 0); 135 gfx::CheckWindowCreated(new_parent); 136 ::SetParent(window, new_parent); 137 // How many times we try to find a PluginProcessHost whose process matches 138 // the HWND. 139 static const int kMaxTries = 5; 140 BrowserThread::PostTask( 141 BrowserThread::IO, 142 FROM_HERE, 143 base::Bind(&NotifyPluginProcessHostHelper, window, new_parent, 144 kMaxTries)); 145 return new_parent; 146 } 147 148 BOOL CALLBACK PaintEnumChildProc(HWND hwnd, LPARAM lparam) { 149 if (!PluginServiceImpl::GetInstance()->IsPluginWindow(hwnd)) 150 return TRUE; 151 152 gfx::Rect* rect = reinterpret_cast<gfx::Rect*>(lparam); 153 gfx::Rect rect_in_pixels = gfx::win::DIPToScreenRect(*rect); 154 static UINT msg = RegisterWindowMessage(kPaintMessageName); 155 WPARAM wparam = MAKEWPARAM(rect_in_pixels.x(), rect_in_pixels.y()); 156 lparam = MAKELPARAM(rect_in_pixels.width(), rect_in_pixels.height()); 157 158 // SendMessage gets the message across much quicker than PostMessage, since it 159 // doesn't get queued. When the plugin thread calls PeekMessage or other 160 // Win32 APIs, sent messages are dispatched automatically. 161 SendNotifyMessage(hwnd, msg, wparam, lparam); 162 163 return TRUE; 164 } 165 166 // Windows callback for OnDestroy to detach the plugin windows. 167 BOOL CALLBACK DetachPluginWindowsCallbackInternal(HWND window, LPARAM param) { 168 RenderWidgetHostViewBase::DetachPluginWindowsCallback(window); 169 return TRUE; 170 } 171 172 } // namespace 173 174 // static 175 void RenderWidgetHostViewBase::DetachPluginWindowsCallback(HWND window) { 176 if (PluginServiceImpl::GetInstance()->IsPluginWindow(window) && 177 !IsHungAppWindow(window)) { 178 ::ShowWindow(window, SW_HIDE); 179 SetParent(window, NULL); 180 } 181 } 182 183 // static 184 void RenderWidgetHostViewBase::MovePluginWindowsHelper( 185 HWND parent, 186 const std::vector<WebPluginGeometry>& moves) { 187 if (moves.empty()) 188 return; 189 190 bool oop_plugins = 191 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); 192 193 HDWP defer_window_pos_info = 194 ::BeginDeferWindowPos(static_cast<int>(moves.size())); 195 196 if (!defer_window_pos_info) { 197 NOTREACHED(); 198 return; 199 } 200 201 #if defined(USE_AURA) 202 std::vector<RECT> invalidate_rects; 203 #endif 204 205 for (size_t i = 0; i < moves.size(); ++i) { 206 unsigned long flags = 0; 207 const WebPluginGeometry& move = moves[i]; 208 HWND window = move.window; 209 210 // As the plugin parent window which lives on the browser UI thread is 211 // destroyed asynchronously, it is possible that we have a stale window 212 // sent in by the renderer for moving around. 213 // Note: get the parent before checking if the window is valid, to avoid a 214 // race condition where the window is destroyed after the check but before 215 // the GetParent call. 216 HWND cur_parent = ::GetParent(window); 217 if (!::IsWindow(window)) 218 continue; 219 220 if (!PluginServiceImpl::GetInstance()->IsPluginWindow(window)) { 221 // The renderer should only be trying to move plugin windows. However, 222 // this may happen as a result of a race condition (i.e. even after the 223 // check right above), so we ignore it. 224 continue; 225 } 226 227 if (oop_plugins) { 228 if (cur_parent == GetDesktopWindow()) { 229 // The plugin window hasn't been parented yet, add an intermediate 230 // window that lives on this thread to speed up scrolling. Note this 231 // only works with out of process plugins since we depend on 232 // PluginProcessHost to destroy the intermediate HWNDs. 233 cur_parent = ReparentWindow(window, parent); 234 ::ShowWindow(window, SW_SHOW); // Window was created hidden. 235 } else if (!IsPluginWrapperWindow(cur_parent)) { 236 continue; // Race if plugin process is shutting down. 237 } 238 239 // We move the intermediate parent window which doesn't result in cross- 240 // process synchronous Windows messages. 241 window = cur_parent; 242 } else { 243 if (cur_parent == GetDesktopWindow()) 244 SetParent(window, parent); 245 } 246 247 if (move.visible) 248 flags |= SWP_SHOWWINDOW; 249 else 250 flags |= SWP_HIDEWINDOW; 251 252 #if defined(USE_AURA) 253 if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { 254 // Without this flag, Windows repaints the parent area uncovered by this 255 // move. However when software compositing is used the clipping region is 256 // ignored. Since in Aura the browser chrome could be under the plugin, if 257 // if Windows tries to paint it synchronously inside EndDeferWindowsPos 258 // then it won't have the data and it will flash white. So instead we 259 // manually redraw the plugin. 260 // Why not do this for native Windows? Not sure if there are any 261 // performance issues with this. 262 flags |= SWP_NOREDRAW; 263 } 264 #endif 265 266 if (move.rects_valid) { 267 gfx::Rect clip_rect_in_pixel = gfx::win::DIPToScreenRect(move.clip_rect); 268 HRGN hrgn = ::CreateRectRgn(clip_rect_in_pixel.x(), 269 clip_rect_in_pixel.y(), 270 clip_rect_in_pixel.right(), 271 clip_rect_in_pixel.bottom()); 272 gfx::SubtractRectanglesFromRegion(hrgn, move.cutout_rects); 273 274 // Note: System will own the hrgn after we call SetWindowRgn, 275 // so we don't need to call DeleteObject(hrgn) 276 ::SetWindowRgn(window, hrgn, 277 !move.clip_rect.IsEmpty() && (flags & SWP_NOREDRAW) == 0); 278 279 #if defined(USE_AURA) 280 // When using the software compositor, if the clipping rectangle is empty 281 // then DeferWindowPos won't redraw the newly uncovered area under the 282 // plugin. 283 if (clip_rect_in_pixel.IsEmpty() && 284 !GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { 285 RECT r; 286 GetClientRect(window, &r); 287 MapWindowPoints(window, parent, reinterpret_cast<POINT*>(&r), 2); 288 invalidate_rects.push_back(r); 289 } 290 #endif 291 } else { 292 flags |= SWP_NOMOVE; 293 flags |= SWP_NOSIZE; 294 } 295 296 gfx::Rect window_rect_in_pixel = 297 gfx::win::DIPToScreenRect(move.window_rect); 298 defer_window_pos_info = ::DeferWindowPos(defer_window_pos_info, 299 window, NULL, 300 window_rect_in_pixel.x(), 301 window_rect_in_pixel.y(), 302 window_rect_in_pixel.width(), 303 window_rect_in_pixel.height(), 304 flags); 305 306 if (!defer_window_pos_info) { 307 DCHECK(false) << "DeferWindowPos failed, so all plugin moves ignored."; 308 return; 309 } 310 } 311 312 ::EndDeferWindowPos(defer_window_pos_info); 313 314 #if defined(USE_AURA) 315 if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { 316 for (size_t i = 0; i < moves.size(); ++i) { 317 const WebPluginGeometry& move = moves[i]; 318 RECT r; 319 GetWindowRect(move.window, &r); 320 gfx::Rect gr(r); 321 PaintEnumChildProc(move.window, reinterpret_cast<LPARAM>(&gr)); 322 } 323 } else { 324 for (size_t i = 0; i < invalidate_rects.size(); ++i) { 325 ::RedrawWindow( 326 parent, &invalidate_rects[i], NULL, 327 // These flags are from WebPluginDelegateImpl::NativeWndProc. 328 RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME | RDW_UPDATENOW); 329 } 330 } 331 #endif 332 } 333 334 // static 335 void RenderWidgetHostViewBase::PaintPluginWindowsHelper( 336 HWND parent, const gfx::Rect& damaged_screen_rect) { 337 LPARAM lparam = reinterpret_cast<LPARAM>(&damaged_screen_rect); 338 EnumChildWindows(parent, PaintEnumChildProc, lparam); 339 } 340 341 // static 342 void RenderWidgetHostViewBase::DetachPluginsHelper(HWND parent) { 343 // When a tab is closed all its child plugin windows are destroyed 344 // automatically. This happens before plugins get any notification that its 345 // instances are tearing down. 346 // 347 // Plugins like Quicktime assume that their windows will remain valid as long 348 // as they have plugin instances active. Quicktime crashes in this case 349 // because its windowing code cleans up an internal data structure that the 350 // handler for NPP_DestroyStream relies on. 351 // 352 // The fix is to detach plugin windows from web contents when it is going 353 // away. This will prevent the plugin windows from getting destroyed 354 // automatically. The detached plugin windows will get cleaned up in proper 355 // sequence as part of the usual cleanup when the plugin instance goes away. 356 EnumChildWindows(parent, DetachPluginWindowsCallbackInternal, NULL); 357 } 358 359 #endif // OS_WIN 360 361 namespace { 362 363 // How many microseconds apart input events should be flushed. 364 const int kFlushInputRateInUs = 16666; 365 366 } 367 368 RenderWidgetHostViewBase::RenderWidgetHostViewBase() 369 : popup_type_(blink::WebPopupTypeNone), 370 background_opaque_(true), 371 mouse_locked_(false), 372 showing_context_menu_(false), 373 selection_text_offset_(0), 374 selection_range_(gfx::Range::InvalidRange()), 375 current_device_scale_factor_(0), 376 current_display_rotation_(gfx::Display::ROTATE_0), 377 pinch_zoom_enabled_(content::IsPinchToZoomEnabled()), 378 renderer_frame_number_(0) { 379 } 380 381 RenderWidgetHostViewBase::~RenderWidgetHostViewBase() { 382 DCHECK(!mouse_locked_); 383 } 384 385 bool RenderWidgetHostViewBase::OnMessageReceived(const IPC::Message& msg){ 386 return false; 387 } 388 389 void RenderWidgetHostViewBase::SetBackgroundOpaque(bool opaque) { 390 background_opaque_ = opaque; 391 } 392 393 bool RenderWidgetHostViewBase::GetBackgroundOpaque() { 394 return background_opaque_; 395 } 396 397 gfx::Size RenderWidgetHostViewBase::GetPhysicalBackingSize() const { 398 gfx::NativeView view = GetNativeView(); 399 gfx::Display display = 400 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); 401 return gfx::ToCeiledSize(gfx::ScaleSize(GetRequestedRendererSize(), 402 display.device_scale_factor())); 403 } 404 405 float RenderWidgetHostViewBase::GetOverdrawBottomHeight() const { 406 return 0.f; 407 } 408 409 void RenderWidgetHostViewBase::SelectionChanged(const base::string16& text, 410 size_t offset, 411 const gfx::Range& range) { 412 selection_text_ = text; 413 selection_text_offset_ = offset; 414 selection_range_.set_start(range.start()); 415 selection_range_.set_end(range.end()); 416 } 417 418 gfx::Size RenderWidgetHostViewBase::GetRequestedRendererSize() const { 419 return GetViewBounds().size(); 420 } 421 422 ui::TextInputClient* RenderWidgetHostViewBase::GetTextInputClient() { 423 NOTREACHED(); 424 return NULL; 425 } 426 427 bool RenderWidgetHostViewBase::IsShowingContextMenu() const { 428 return showing_context_menu_; 429 } 430 431 void RenderWidgetHostViewBase::SetShowingContextMenu(bool showing) { 432 DCHECK_NE(showing_context_menu_, showing); 433 showing_context_menu_ = showing; 434 } 435 436 base::string16 RenderWidgetHostViewBase::GetSelectedText() const { 437 if (!selection_range_.IsValid()) 438 return base::string16(); 439 return selection_text_.substr( 440 selection_range_.GetMin() - selection_text_offset_, 441 selection_range_.length()); 442 } 443 444 bool RenderWidgetHostViewBase::IsMouseLocked() { 445 return mouse_locked_; 446 } 447 448 InputEventAckState RenderWidgetHostViewBase::FilterInputEvent( 449 const blink::WebInputEvent& input_event) { 450 // By default, input events are simply forwarded to the renderer. 451 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; 452 } 453 454 void RenderWidgetHostViewBase::OnDidFlushInput() { 455 // The notification can safely be ignored by most implementations. 456 } 457 458 void RenderWidgetHostViewBase::OnSetNeedsFlushInput() { 459 if (flush_input_timer_.IsRunning()) 460 return; 461 462 flush_input_timer_.Start( 463 FROM_HERE, 464 base::TimeDelta::FromMicroseconds(kFlushInputRateInUs), 465 this, 466 &RenderWidgetHostViewBase::FlushInput); 467 } 468 469 void RenderWidgetHostViewBase::WheelEventAck( 470 const blink::WebMouseWheelEvent& event, 471 InputEventAckState ack_result) { 472 } 473 474 void RenderWidgetHostViewBase::GestureEventAck( 475 const blink::WebGestureEvent& event, 476 InputEventAckState ack_result) { 477 } 478 479 void RenderWidgetHostViewBase::SetPopupType(blink::WebPopupType popup_type) { 480 popup_type_ = popup_type; 481 } 482 483 blink::WebPopupType RenderWidgetHostViewBase::GetPopupType() { 484 return popup_type_; 485 } 486 487 BrowserAccessibilityManager* 488 RenderWidgetHostViewBase::GetBrowserAccessibilityManager() const { 489 return browser_accessibility_manager_.get(); 490 } 491 492 void RenderWidgetHostViewBase::CreateBrowserAccessibilityManagerIfNeeded() { 493 } 494 495 void RenderWidgetHostViewBase::SetBrowserAccessibilityManager( 496 BrowserAccessibilityManager* manager) { 497 browser_accessibility_manager_.reset(manager); 498 } 499 500 void RenderWidgetHostViewBase::OnAccessibilitySetFocus(int acc_obj_id) { 501 } 502 503 void RenderWidgetHostViewBase::AccessibilityShowMenu(int acc_obj_id) { 504 } 505 506 gfx::Point RenderWidgetHostViewBase::AccessibilityOriginInScreen( 507 const gfx::Rect& bounds) { 508 return bounds.origin(); 509 } 510 511 void RenderWidgetHostViewBase::UpdateScreenInfo(gfx::NativeView view) { 512 RenderWidgetHostImpl* impl = NULL; 513 if (GetRenderWidgetHost()) 514 impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); 515 516 if (impl) 517 impl->SendScreenRects(); 518 519 if (HasDisplayPropertyChanged(view) && impl) 520 impl->NotifyScreenInfoChanged(); 521 } 522 523 bool RenderWidgetHostViewBase::HasDisplayPropertyChanged(gfx::NativeView view) { 524 gfx::Display display = 525 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); 526 if (current_display_area_ == display.work_area() && 527 current_device_scale_factor_ == display.device_scale_factor() && 528 current_display_rotation_ == display.rotation()) { 529 return false; 530 } 531 532 current_display_area_ = display.work_area(); 533 current_device_scale_factor_ = display.device_scale_factor(); 534 current_display_rotation_ = display.rotation(); 535 return true; 536 } 537 538 scoped_ptr<SyntheticGestureTarget> 539 RenderWidgetHostViewBase::CreateSyntheticGestureTarget() { 540 RenderWidgetHostImpl* host = 541 RenderWidgetHostImpl::From(GetRenderWidgetHost()); 542 return scoped_ptr<SyntheticGestureTarget>( 543 new SyntheticGestureTargetBase(host)); 544 } 545 546 // Platform implementation should override this method to allow frame 547 // subscription. Frame subscriber is set to RenderProcessHost, which is 548 // platform independent. It should be set to the specific presenter on each 549 // platform. 550 bool RenderWidgetHostViewBase::CanSubscribeFrame() const { 551 NOTIMPLEMENTED(); 552 return false; 553 } 554 555 // Base implementation for this method sets the subscriber to RenderProcessHost, 556 // which is platform independent. Note: Implementation only support subscribing 557 // to accelerated composited frames. 558 void RenderWidgetHostViewBase::BeginFrameSubscription( 559 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) { 560 RenderWidgetHostImpl* impl = NULL; 561 if (GetRenderWidgetHost()) 562 impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); 563 if (!impl) 564 return; 565 RenderProcessHostImpl* render_process_host = 566 static_cast<RenderProcessHostImpl*>(impl->GetProcess()); 567 render_process_host->BeginFrameSubscription(impl->GetRoutingID(), 568 subscriber.Pass()); 569 } 570 571 void RenderWidgetHostViewBase::EndFrameSubscription() { 572 RenderWidgetHostImpl* impl = NULL; 573 if (GetRenderWidgetHost()) 574 impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); 575 if (!impl) 576 return; 577 RenderProcessHostImpl* render_process_host = 578 static_cast<RenderProcessHostImpl*>(impl->GetProcess()); 579 render_process_host->EndFrameSubscription(impl->GetRoutingID()); 580 } 581 582 uint32 RenderWidgetHostViewBase::RendererFrameNumber() { 583 return renderer_frame_number_; 584 } 585 586 void RenderWidgetHostViewBase::DidReceiveRendererFrame() { 587 ++renderer_frame_number_; 588 } 589 590 void RenderWidgetHostViewBase::FlushInput() { 591 RenderWidgetHostImpl* impl = NULL; 592 if (GetRenderWidgetHost()) 593 impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); 594 if (!impl) 595 return; 596 impl->FlushInput(); 597 } 598 599 SkBitmap::Config RenderWidgetHostViewBase::PreferredReadbackFormat() { 600 return SkBitmap::kARGB_8888_Config; 601 } 602 603 gfx::Size RenderWidgetHostViewBase::GetVisibleViewportSize() const { 604 return GetViewBounds().size(); 605 } 606 607 void RenderWidgetHostViewBase::SetInsets(const gfx::Insets& insets) { 608 NOTIMPLEMENTED(); 609 } 610 611 } // namespace content 612