blob: 6e49da5a4c4350ee3013a789b99526d8e7c68547 [file] [log] [blame]
// 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 "base/profiler/scoped_tracker.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/common/content_switches_internal.h"
#include "content/public/browser/render_widget_host_view_frame_subscriber.h"
#include "ui/gfx/display.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/screen.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
namespace content {
#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) {
// TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 PluginWrapperWindowProc"));
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<PluginWrapperWindowProc>,
CS_DBLCLKS,
0,
0,
NULL,
// xxx reinterpret_cast<HBRUSH>(COLOR_WINDOW+1),
reinterpret_cast<HBRUSH>(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<gfx::Rect*>(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<WebPluginGeometry>& moves) {
if (moves.empty())
return;
bool oop_plugins = !base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSingleProcess);
HDWP defer_window_pos_info =
::BeginDeferWindowPos(static_cast<int>(moves.size()));
if (!defer_window_pos_info) {
NOTREACHED();
return;
}
#if defined(USE_AURA)
std::vector<RECT> 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() && (flags & SWP_NOREDRAW) == 0);
#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<POINT*>(&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<LPARAM>(&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<LPARAM>(&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),
background_color_(SK_ColorWHITE),
mouse_locked_(false),
showing_context_menu_(false),
selection_text_offset_(0),
selection_range_(gfx::Range::InvalidRange()),
current_device_scale_factor_(0),
current_display_rotation_(gfx::Display::ROTATE_0),
pinch_zoom_enabled_(content::IsPinchToZoomEnabled()),
renderer_frame_number_(0),
weak_factory_(this) {
}
RenderWidgetHostViewBase::~RenderWidgetHostViewBase() {
DCHECK(!mouse_locked_);
}
bool RenderWidgetHostViewBase::OnMessageReceived(const IPC::Message& msg){
return false;
}
void RenderWidgetHostViewBase::SetBackgroundColor(SkColor color) {
background_color_ = color;
}
void RenderWidgetHostViewBase::SetBackgroundColorToDefault() {
SetBackgroundColor(SK_ColorWHITE);
}
bool RenderWidgetHostViewBase::GetBackgroundOpaque() {
return SkColorGetA(background_color_) == SK_AlphaOPAQUE;
}
gfx::Size RenderWidgetHostViewBase::GetPhysicalBackingSize() const {
gfx::NativeView view = GetNativeView();
gfx::Display display =
gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
return gfx::ToCeiledSize(gfx::ScaleSize(GetRequestedRendererSize(),
display.device_scale_factor()));
}
bool RenderWidgetHostViewBase::DoTopControlsShrinkBlinkSize() const {
return false;
}
float RenderWidgetHostViewBase::GetTopControlsHeight() 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());
}
gfx::Size RenderWidgetHostViewBase::GetRequestedRendererSize() const {
return GetViewBounds().size();
}
ui::TextInputClient* RenderWidgetHostViewBase::GetTextInputClient() {
NOTREACHED();
return NULL;
}
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_;
}
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::WheelEventAck(
const blink::WebMouseWheelEvent& event,
InputEventAckState ack_result) {
}
void RenderWidgetHostViewBase::GestureEventAck(
const blink::WebGestureEvent& event,
InputEventAckState ack_result) {
}
void RenderWidgetHostViewBase::SetPopupType(blink::WebPopupType popup_type) {
popup_type_ = popup_type;
}
blink::WebPopupType RenderWidgetHostViewBase::GetPopupType() {
return popup_type_;
}
BrowserAccessibilityManager*
RenderWidgetHostViewBase::CreateBrowserAccessibilityManager(
BrowserAccessibilityDelegate* delegate) {
NOTREACHED();
return NULL;
}
void RenderWidgetHostViewBase::AccessibilityShowMenu(const gfx::Point& point) {
}
gfx::Point RenderWidgetHostViewBase::AccessibilityOriginInScreen(
const gfx::Rect& bounds) {
return bounds.origin();
}
gfx::AcceleratedWidget
RenderWidgetHostViewBase::AccessibilityGetAcceleratedWidget() {
return gfx::kNullAcceleratedWidget;
}
gfx::NativeViewAccessible
RenderWidgetHostViewBase::AccessibilityGetNativeViewAccessible() {
return NULL;
}
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() &&
current_display_rotation_ == display.rotation()) {
return false;
}
current_display_area_ = display.work_area();
current_device_scale_factor_ = display.device_scale_factor();
current_display_rotation_ = display.rotation();
return true;
}
base::WeakPtr<RenderWidgetHostViewBase> RenderWidgetHostViewBase::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
scoped_ptr<SyntheticGestureTarget>
RenderWidgetHostViewBase::CreateSyntheticGestureTarget() {
RenderWidgetHostImpl* host =
RenderWidgetHostImpl::From(GetRenderWidgetHost());
return scoped_ptr<SyntheticGestureTarget>(
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<RenderWidgetHostViewFrameSubscriber> subscriber) {
RenderWidgetHostImpl* impl = NULL;
if (GetRenderWidgetHost())
impl = RenderWidgetHostImpl::From(GetRenderWidgetHost());
if (!impl)
return;
RenderProcessHostImpl* render_process_host =
static_cast<RenderProcessHostImpl*>(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<RenderProcessHostImpl*>(impl->GetProcess());
render_process_host->EndFrameSubscription(impl->GetRoutingID());
}
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();
}
SkColorType RenderWidgetHostViewBase::PreferredReadbackFormat() {
return kN32_SkColorType;
}
void RenderWidgetHostViewBase::OnTextSurroundingSelectionResponse(
const base::string16& content,
size_t start_offset,
size_t end_offset) {
NOTIMPLEMENTED();
}
void RenderWidgetHostViewBase::ShowDisambiguationPopup(
const gfx::Rect& rect_pixels,
const SkBitmap& zoomed_bitmap) {
NOTIMPLEMENTED();
}
gfx::Size RenderWidgetHostViewBase::GetVisibleViewportSize() const {
return GetViewBounds().size();
}
void RenderWidgetHostViewBase::SetInsets(const gfx::Insets& insets) {
NOTIMPLEMENTED();
}
// static
blink::WebScreenOrientationType
RenderWidgetHostViewBase::GetOrientationTypeForMobile(
const gfx::Display& display) {
int angle = display.RotationAsDegree();
const gfx::Rect& bounds = display.bounds();
// Whether the device's natural orientation is portrait.
bool natural_portrait = false;
if (angle == 0 || angle == 180) // The device is in its natural orientation.
natural_portrait = bounds.height() >= bounds.width();
else
natural_portrait = bounds.height() <= bounds.width();
switch (angle) {
case 0:
return natural_portrait ? blink::WebScreenOrientationPortraitPrimary
: blink::WebScreenOrientationLandscapePrimary;
case 90:
return natural_portrait ? blink::WebScreenOrientationLandscapePrimary
: blink::WebScreenOrientationPortraitSecondary;
case 180:
return natural_portrait ? blink::WebScreenOrientationPortraitSecondary
: blink::WebScreenOrientationLandscapeSecondary;
case 270:
return natural_portrait ? blink::WebScreenOrientationLandscapeSecondary
: blink::WebScreenOrientationPortraitPrimary;
default:
NOTREACHED();
return blink::WebScreenOrientationPortraitPrimary;
}
}
// static
blink::WebScreenOrientationType
RenderWidgetHostViewBase::GetOrientationTypeForDesktop(
const gfx::Display& display) {
static int primary_landscape_angle = -1;
static int primary_portrait_angle = -1;
int angle = display.RotationAsDegree();
const gfx::Rect& bounds = display.bounds();
bool is_portrait = bounds.height() >= bounds.width();
if (is_portrait && primary_portrait_angle == -1)
primary_portrait_angle = angle;
if (!is_portrait && primary_landscape_angle == -1)
primary_landscape_angle = angle;
if (is_portrait) {
return primary_portrait_angle == angle
? blink::WebScreenOrientationPortraitPrimary
: blink::WebScreenOrientationPortraitSecondary;
}
return primary_landscape_angle == angle
? blink::WebScreenOrientationLandscapePrimary
: blink::WebScreenOrientationLandscapeSecondary;
}
void RenderWidgetHostViewBase::OnDidNavigateMainFrameToNewPage() {
}
} // namespace content