// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/render_widget_host_view_base.h" #include "base/logging.h" #include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/gpu/gpu_data_manager_impl.h" #include "content/browser/renderer_host/input/synthetic_gesture_target_base.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/port/browser/render_widget_host_view_frame_subscriber.h" #include "third_party/WebKit/public/platform/WebScreenInfo.h" #include "ui/gfx/display.h" #include "ui/gfx/screen.h" #include "ui/gfx/size_conversions.h" #include "ui/gfx/size_f.h" #if defined(OS_WIN) #include "base/command_line.h" #include "base/message_loop/message_loop.h" #include "base/win/wrapped_window_proc.h" #include "content/browser/plugin_process_host.h" #include "content/browser/plugin_service_impl.h" #include "content/common/plugin_constants_win.h" #include "content/common/webplugin_geometry.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_data.h" #include "content/public/common/content_switches.h" #include "ui/gfx/gdi_util.h" #include "ui/gfx/win/dpi.h" #include "ui/gfx/win/hwnd_util.h" #endif #if defined(TOOLKIT_GTK) #include #include #include "content/browser/renderer_host/gtk_window_utils.h" #endif namespace content { // static RenderWidgetHostViewPort* RenderWidgetHostViewPort::FromRWHV( RenderWidgetHostView* rwhv) { return static_cast(rwhv); } // static RenderWidgetHostViewPort* RenderWidgetHostViewPort::CreateViewForWidget( RenderWidgetHost* widget) { return FromRWHV(RenderWidgetHostView::CreateViewForWidget(widget)); } #if defined(OS_WIN) namespace { // |window| is the plugin HWND, created and destroyed in the plugin process. // |parent| is the parent HWND, created and destroyed on the browser UI thread. void NotifyPluginProcessHostHelper(HWND window, HWND parent, int tries) { // How long to wait between each try. static const int kTryDelayMs = 200; DWORD plugin_process_id; bool found_starting_plugin_process = false; GetWindowThreadProcessId(window, &plugin_process_id); for (PluginProcessHostIterator iter; !iter.Done(); ++iter) { if (!iter.GetData().handle) { found_starting_plugin_process = true; continue; } if (base::GetProcId(iter.GetData().handle) == plugin_process_id) { iter->AddWindow(parent); return; } } if (found_starting_plugin_process) { // A plugin process has started but we don't have its handle yet. Since // it's most likely the one for this plugin, try a few more times after a // delay. if (tries > 0) { base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&NotifyPluginProcessHostHelper, window, parent, tries - 1), base::TimeDelta::FromMilliseconds(kTryDelayMs)); return; } } // The plugin process might have died in the time to execute the task, don't // leak the HWND. PostMessage(parent, WM_CLOSE, 0, 0); } // The plugin wrapper window which lives in the browser process has this proc // as its window procedure. We only handle the WM_PARENTNOTIFY message sent by // windowed plugins for mouse input. This is forwarded off to the wrappers // parent which is typically the RVH window which turns on user gesture. LRESULT CALLBACK PluginWrapperWindowProc(HWND window, unsigned int message, WPARAM wparam, LPARAM lparam) { if (message == WM_PARENTNOTIFY) { switch (LOWORD(wparam)) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: ::SendMessage(GetParent(window), message, wparam, lparam); return 0; default: break; } } return ::DefWindowProc(window, message, wparam, lparam); } bool IsPluginWrapperWindow(HWND window) { return gfx::GetClassNameW(window) == base::string16(kWrapperNativeWindowClassName); } // Create an intermediate window between the given HWND and its parent. HWND ReparentWindow(HWND window, HWND parent) { static ATOM atom = 0; static HMODULE instance = NULL; if (!atom) { WNDCLASSEX window_class; base::win::InitializeWindowClass( kWrapperNativeWindowClassName, &base::win::WrappedWindowProc, CS_DBLCLKS, 0, 0, NULL, // xxx reinterpret_cast(COLOR_WINDOW+1), reinterpret_cast(COLOR_GRAYTEXT+1), NULL, NULL, NULL, &window_class); instance = window_class.hInstance; atom = RegisterClassEx(&window_class); } DCHECK(atom); HWND new_parent = CreateWindowEx( WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, MAKEINTATOM(atom), 0, WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 0, 0, parent, 0, instance, 0); gfx::CheckWindowCreated(new_parent); ::SetParent(window, new_parent); // How many times we try to find a PluginProcessHost whose process matches // the HWND. static const int kMaxTries = 5; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&NotifyPluginProcessHostHelper, window, new_parent, kMaxTries)); return new_parent; } BOOL CALLBACK PaintEnumChildProc(HWND hwnd, LPARAM lparam) { if (!PluginServiceImpl::GetInstance()->IsPluginWindow(hwnd)) return TRUE; gfx::Rect* rect = reinterpret_cast(lparam); gfx::Rect rect_in_pixels = gfx::win::DIPToScreenRect(*rect); static UINT msg = RegisterWindowMessage(kPaintMessageName); WPARAM wparam = MAKEWPARAM(rect_in_pixels.x(), rect_in_pixels.y()); lparam = MAKELPARAM(rect_in_pixels.width(), rect_in_pixels.height()); // SendMessage gets the message across much quicker than PostMessage, since it // doesn't get queued. When the plugin thread calls PeekMessage or other // Win32 APIs, sent messages are dispatched automatically. SendNotifyMessage(hwnd, msg, wparam, lparam); return TRUE; } // Windows callback for OnDestroy to detach the plugin windows. BOOL CALLBACK DetachPluginWindowsCallbackInternal(HWND window, LPARAM param) { RenderWidgetHostViewBase::DetachPluginWindowsCallback(window); return TRUE; } } // namespace // static void RenderWidgetHostViewBase::DetachPluginWindowsCallback(HWND window) { if (PluginServiceImpl::GetInstance()->IsPluginWindow(window) && !IsHungAppWindow(window)) { ::ShowWindow(window, SW_HIDE); SetParent(window, NULL); } } // static void RenderWidgetHostViewBase::MovePluginWindowsHelper( HWND parent, const std::vector& moves) { if (moves.empty()) return; bool oop_plugins = !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) && !CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessPlugins); HDWP defer_window_pos_info = ::BeginDeferWindowPos(static_cast(moves.size())); if (!defer_window_pos_info) { NOTREACHED(); return; } #if defined(USE_AURA) std::vector invalidate_rects; #endif for (size_t i = 0; i < moves.size(); ++i) { unsigned long flags = 0; const WebPluginGeometry& move = moves[i]; HWND window = move.window; // As the plugin parent window which lives on the browser UI thread is // destroyed asynchronously, it is possible that we have a stale window // sent in by the renderer for moving around. // Note: get the parent before checking if the window is valid, to avoid a // race condition where the window is destroyed after the check but before // the GetParent call. HWND cur_parent = ::GetParent(window); if (!::IsWindow(window)) continue; if (!PluginServiceImpl::GetInstance()->IsPluginWindow(window)) { // The renderer should only be trying to move plugin windows. However, // this may happen as a result of a race condition (i.e. even after the // check right above), so we ignore it. continue; } if (oop_plugins) { if (cur_parent == GetDesktopWindow()) { // The plugin window hasn't been parented yet, add an intermediate // window that lives on this thread to speed up scrolling. Note this // only works with out of process plugins since we depend on // PluginProcessHost to destroy the intermediate HWNDs. cur_parent = ReparentWindow(window, parent); ::ShowWindow(window, SW_SHOW); // Window was created hidden. } else if (!IsPluginWrapperWindow(cur_parent)) { continue; // Race if plugin process is shutting down. } // We move the intermediate parent window which doesn't result in cross- // process synchronous Windows messages. window = cur_parent; } else { if (cur_parent == GetDesktopWindow()) SetParent(window, parent); } if (move.visible) flags |= SWP_SHOWWINDOW; else flags |= SWP_HIDEWINDOW; #if defined(USE_AURA) if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { // Without this flag, Windows repaints the parent area uncovered by this // move. However when software compositing is used the clipping region is // ignored. Since in Aura the browser chrome could be under the plugin, if // if Windows tries to paint it synchronously inside EndDeferWindowsPos // then it won't have the data and it will flash white. So instead we // manually redraw the plugin. // Why not do this for native Windows? Not sure if there are any // performance issues with this. flags |= SWP_NOREDRAW; } #endif if (move.rects_valid) { gfx::Rect clip_rect_in_pixel = gfx::win::DIPToScreenRect(move.clip_rect); HRGN hrgn = ::CreateRectRgn(clip_rect_in_pixel.x(), clip_rect_in_pixel.y(), clip_rect_in_pixel.right(), clip_rect_in_pixel.bottom()); gfx::SubtractRectanglesFromRegion(hrgn, move.cutout_rects); // Note: System will own the hrgn after we call SetWindowRgn, // so we don't need to call DeleteObject(hrgn) ::SetWindowRgn(window, hrgn, !move.clip_rect.IsEmpty()); #if defined(USE_AURA) // When using the software compositor, if the clipping rectangle is empty // then DeferWindowPos won't redraw the newly uncovered area under the // plugin. if (clip_rect_in_pixel.IsEmpty() && !GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { RECT r; GetClientRect(window, &r); MapWindowPoints(window, parent, reinterpret_cast(&r), 2); invalidate_rects.push_back(r); } #endif } else { flags |= SWP_NOMOVE; flags |= SWP_NOSIZE; } gfx::Rect window_rect_in_pixel = gfx::win::DIPToScreenRect(move.window_rect); defer_window_pos_info = ::DeferWindowPos(defer_window_pos_info, window, NULL, window_rect_in_pixel.x(), window_rect_in_pixel.y(), window_rect_in_pixel.width(), window_rect_in_pixel.height(), flags); if (!defer_window_pos_info) { DCHECK(false) << "DeferWindowPos failed, so all plugin moves ignored."; return; } } ::EndDeferWindowPos(defer_window_pos_info); #if defined(USE_AURA) if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) { for (size_t i = 0; i < moves.size(); ++i) { const WebPluginGeometry& move = moves[i]; RECT r; GetWindowRect(move.window, &r); gfx::Rect gr(r); PaintEnumChildProc(move.window, reinterpret_cast(&gr)); } } else { for (size_t i = 0; i < invalidate_rects.size(); ++i) { ::RedrawWindow( parent, &invalidate_rects[i], NULL, // These flags are from WebPluginDelegateImpl::NativeWndProc. RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME | RDW_UPDATENOW); } } #endif } // static void RenderWidgetHostViewBase::PaintPluginWindowsHelper( HWND parent, const gfx::Rect& damaged_screen_rect) { LPARAM lparam = reinterpret_cast(&damaged_screen_rect); EnumChildWindows(parent, PaintEnumChildProc, lparam); } // static void RenderWidgetHostViewBase::DetachPluginsHelper(HWND parent) { // When a tab is closed all its child plugin windows are destroyed // automatically. This happens before plugins get any notification that its // instances are tearing down. // // Plugins like Quicktime assume that their windows will remain valid as long // as they have plugin instances active. Quicktime crashes in this case // because its windowing code cleans up an internal data structure that the // handler for NPP_DestroyStream relies on. // // The fix is to detach plugin windows from web contents when it is going // away. This will prevent the plugin windows from getting destroyed // automatically. The detached plugin windows will get cleaned up in proper // sequence as part of the usual cleanup when the plugin instance goes away. EnumChildWindows(parent, DetachPluginWindowsCallbackInternal, NULL); } #endif // OS_WIN namespace { // How many microseconds apart input events should be flushed. const int kFlushInputRateInUs = 16666; } RenderWidgetHostViewBase::RenderWidgetHostViewBase() : popup_type_(blink::WebPopupTypeNone), mouse_locked_(false), showing_context_menu_(false), selection_text_offset_(0), selection_range_(gfx::Range::InvalidRange()), current_device_scale_factor_(0), renderer_frame_number_(0) { } RenderWidgetHostViewBase::~RenderWidgetHostViewBase() { DCHECK(!mouse_locked_); } bool RenderWidgetHostViewBase::OnMessageReceived(const IPC::Message& msg){ return false; } void RenderWidgetHostViewBase::SetBackground(const SkBitmap& background) { background_ = background; } const SkBitmap& RenderWidgetHostViewBase::GetBackground() { return background_; } gfx::Size RenderWidgetHostViewBase::GetPhysicalBackingSize() const { gfx::NativeView view = GetNativeView(); gfx::Display display = gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); return gfx::ToCeiledSize(gfx::ScaleSize(GetViewBounds().size(), display.device_scale_factor())); } float RenderWidgetHostViewBase::GetOverdrawBottomHeight() const { return 0.f; } void RenderWidgetHostViewBase::SelectionChanged(const base::string16& text, size_t offset, const gfx::Range& range) { selection_text_ = text; selection_text_offset_ = offset; selection_range_.set_start(range.start()); selection_range_.set_end(range.end()); } bool RenderWidgetHostViewBase::IsShowingContextMenu() const { return showing_context_menu_; } void RenderWidgetHostViewBase::SetShowingContextMenu(bool showing) { DCHECK_NE(showing_context_menu_, showing); showing_context_menu_ = showing; } base::string16 RenderWidgetHostViewBase::GetSelectedText() const { if (!selection_range_.IsValid()) return base::string16(); return selection_text_.substr( selection_range_.GetMin() - selection_text_offset_, selection_range_.length()); } bool RenderWidgetHostViewBase::IsMouseLocked() { return mouse_locked_; } void RenderWidgetHostViewBase::UnhandledWheelEvent( const blink::WebMouseWheelEvent& event) { // Most implementations don't need to do anything here. } InputEventAckState RenderWidgetHostViewBase::FilterInputEvent( const blink::WebInputEvent& input_event) { // By default, input events are simply forwarded to the renderer. return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; } void RenderWidgetHostViewBase::OnDidFlushInput() { // The notification can safely be ignored by most implementations. } void RenderWidgetHostViewBase::OnSetNeedsFlushInput() { if (flush_input_timer_.IsRunning()) return; flush_input_timer_.Start( FROM_HERE, base::TimeDelta::FromMicroseconds(kFlushInputRateInUs), this, &RenderWidgetHostViewBase::FlushInput); } void RenderWidgetHostViewBase::GestureEventAck(int gesture_event_type, InputEventAckState ack_result) {} void RenderWidgetHostViewBase::SetPopupType(blink::WebPopupType popup_type) { popup_type_ = popup_type; } blink::WebPopupType RenderWidgetHostViewBase::GetPopupType() { return popup_type_; } BrowserAccessibilityManager* RenderWidgetHostViewBase::GetBrowserAccessibilityManager() const { return browser_accessibility_manager_.get(); } void RenderWidgetHostViewBase::SetBrowserAccessibilityManager( BrowserAccessibilityManager* manager) { browser_accessibility_manager_.reset(manager); } void RenderWidgetHostViewBase::UpdateScreenInfo(gfx::NativeView view) { RenderWidgetHostImpl* impl = NULL; if (GetRenderWidgetHost()) impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); if (impl) impl->SendScreenRects(); if (HasDisplayPropertyChanged(view) && impl) impl->NotifyScreenInfoChanged(); } bool RenderWidgetHostViewBase::HasDisplayPropertyChanged(gfx::NativeView view) { gfx::Display display = gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); if (current_display_area_ == display.work_area() && current_device_scale_factor_ == display.device_scale_factor()) { return false; } current_display_area_ = display.work_area(); current_device_scale_factor_ = display.device_scale_factor(); return true; } void RenderWidgetHostViewBase::ProcessAckedTouchEvent( const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) { } scoped_ptr RenderWidgetHostViewBase::CreateSyntheticGestureTarget() { RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(GetRenderWidgetHost()); return scoped_ptr( new SyntheticGestureTargetBase(host)); } // Platform implementation should override this method to allow frame // subscription. Frame subscriber is set to RenderProcessHost, which is // platform independent. It should be set to the specific presenter on each // platform. bool RenderWidgetHostViewBase::CanSubscribeFrame() const { NOTIMPLEMENTED(); return false; } // Base implementation for this method sets the subscriber to RenderProcessHost, // which is platform independent. Note: Implementation only support subscribing // to accelerated composited frames. void RenderWidgetHostViewBase::BeginFrameSubscription( scoped_ptr subscriber) { RenderWidgetHostImpl* impl = NULL; if (GetRenderWidgetHost()) impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); if (!impl) return; RenderProcessHostImpl* render_process_host = static_cast(impl->GetProcess()); render_process_host->BeginFrameSubscription(impl->GetRoutingID(), subscriber.Pass()); } void RenderWidgetHostViewBase::EndFrameSubscription() { RenderWidgetHostImpl* impl = NULL; if (GetRenderWidgetHost()) impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); if (!impl) return; RenderProcessHostImpl* render_process_host = static_cast(impl->GetProcess()); render_process_host->EndFrameSubscription(impl->GetRoutingID()); } void RenderWidgetHostViewBase::OnOverscrolled( gfx::Vector2dF accumulated_overscroll, gfx::Vector2dF current_fling_velocity) { } uint32 RenderWidgetHostViewBase::RendererFrameNumber() { return renderer_frame_number_; } void RenderWidgetHostViewBase::DidReceiveRendererFrame() { ++renderer_frame_number_; } void RenderWidgetHostViewBase::FlushInput() { RenderWidgetHostImpl* impl = NULL; if (GetRenderWidgetHost()) impl = RenderWidgetHostImpl::From(GetRenderWidgetHost()); if (!impl) return; impl->FlushInput(); } } // namespace content