| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 // Need Win 7 headers for WM_GESTURE and ChangeWindowMessageFilterEx | |
| 6 // TODO(jschuh): See crbug.com/92941 for longterm fix. | |
| 7 #undef WINVER | |
| 8 #define WINVER _WIN32_WINNT_WIN7 | |
| 9 #undef _WIN32_WINNT | |
| 10 #define _WIN32_WINNT _WIN32_WINNT_WIN7 | |
| 11 #include <windows.h> | |
| 12 | |
| 13 #include "chrome/browser/renderer_host/render_widget_host_view_win.h" | |
| 14 | |
| 15 #include <algorithm> | |
| 16 | |
| 17 #include "base/command_line.h" | |
| 18 #include "base/i18n/rtl.h" | |
| 19 #include "base/metrics/histogram.h" | |
| 20 #include "base/process_util.h" | |
| 21 #include "base/threading/thread.h" | |
| 22 #include "base/win/scoped_comptr.h" | |
| 23 #include "base/win/scoped_gdi_object.h" | |
| 24 #include "base/win/wrapped_window_proc.h" | |
| 25 #include "content/browser/accessibility/browser_accessibility_manager.h" | |
| 26 #include "content/browser/accessibility/browser_accessibility_state.h" | |
| 27 #include "content/browser/accessibility/browser_accessibility_win.h" | |
| 28 #include "content/browser/browser_thread.h" | |
| 29 #include "content/browser/content_browser_client.h" | |
| 30 #include "content/browser/plugin_process_host.h" | |
| 31 #include "content/browser/renderer_host/backing_store.h" | |
| 32 #include "content/browser/renderer_host/backing_store_win.h" | |
| 33 #include "content/browser/renderer_host/render_process_host.h" | |
| 34 #include "content/browser/renderer_host/render_widget_host.h" | |
| 35 #include "content/common/content_switches.h" | |
| 36 #include "content/common/native_web_keyboard_event.h" | |
| 37 #include "content/common/notification_service.h" | |
| 38 #include "content/common/plugin_messages.h" | |
| 39 #include "content/common/view_messages.h" | |
| 40 #include "skia/ext/skia_utils_win.h" | |
| 41 #include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderli
ne.h" | |
| 42 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" | |
| 43 #include "third_party/WebKit/Source/WebKit/chromium/public/win/WebInputEventFact
ory.h" | |
| 44 #include "ui/base/ime/composition_text.h" | |
| 45 #include "ui/base/l10n/l10n_util.h" | |
| 46 #include "ui/base/l10n/l10n_util_win.h" | |
| 47 #include "ui/base/resource/resource_bundle.h" | |
| 48 #include "ui/base/text/text_elider.h" | |
| 49 #include "ui/base/view_prop.h" | |
| 50 #include "ui/base/win/hwnd_util.h" | |
| 51 #include "ui/gfx/canvas.h" | |
| 52 #include "ui/gfx/canvas_skia.h" | |
| 53 #include "ui/gfx/gdi_util.h" | |
| 54 #include "ui/gfx/rect.h" | |
| 55 #include "ui/gfx/screen.h" | |
| 56 #include "views/accessibility/native_view_accessibility_win.h" | |
| 57 #include "views/focus/focus_manager.h" | |
| 58 #include "views/focus/focus_util_win.h" | |
| 59 // Included for views::kReflectedMessage - TODO(beng): move this to win_util.h! | |
| 60 #include "views/widget/native_widget_win.h" | |
| 61 #include "webkit/glue/webaccessibility.h" | |
| 62 #include "webkit/glue/webcursor.h" | |
| 63 #include "webkit/plugins/npapi/plugin_constants_win.h" | |
| 64 #include "webkit/plugins/npapi/webplugin.h" | |
| 65 #include "webkit/plugins/npapi/webplugin_delegate_impl.h" | |
| 66 | |
| 67 using base::TimeDelta; | |
| 68 using base::TimeTicks; | |
| 69 using ui::ViewProp; | |
| 70 using WebKit::WebInputEvent; | |
| 71 using WebKit::WebInputEventFactory; | |
| 72 using WebKit::WebMouseEvent; | |
| 73 using WebKit::WebTextDirection; | |
| 74 using webkit::npapi::WebPluginGeometry; | |
| 75 | |
| 76 const wchar_t kRenderWidgetHostHWNDClass[] = L"Chrome_RenderWidgetHostHWND"; | |
| 77 | |
| 78 namespace { | |
| 79 | |
| 80 // Tooltips will wrap after this width. Yes, wrap. Imagine that! | |
| 81 const int kTooltipMaxWidthPixels = 300; | |
| 82 | |
| 83 // Maximum number of characters we allow in a tooltip. | |
| 84 const int kMaxTooltipLength = 1024; | |
| 85 | |
| 86 // A custom MSAA object id used to determine if a screen reader is actively | |
| 87 // listening for MSAA events. | |
| 88 const int kIdCustom = 1; | |
| 89 | |
| 90 // The delay before the compositor host window is destroyed. This gives the GPU | |
| 91 // process a grace period to stop referencing it. | |
| 92 const int kDestroyCompositorHostWindowDelay = 10000; | |
| 93 | |
| 94 const char* const kRenderWidgetHostViewKey = "__RENDER_WIDGET_HOST_VIEW__"; | |
| 95 | |
| 96 // A callback function for EnumThreadWindows to enumerate and dismiss | |
| 97 // any owned popop windows | |
| 98 BOOL CALLBACK DismissOwnedPopups(HWND window, LPARAM arg) { | |
| 99 const HWND toplevel_hwnd = reinterpret_cast<HWND>(arg); | |
| 100 | |
| 101 if (::IsWindowVisible(window)) { | |
| 102 const HWND owner = ::GetWindow(window, GW_OWNER); | |
| 103 if (toplevel_hwnd == owner) { | |
| 104 ::PostMessage(window, WM_CANCELMODE, 0, 0); | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 return TRUE; | |
| 109 } | |
| 110 | |
| 111 class NotifyPluginProcessHostTask : public Task { | |
| 112 public: | |
| 113 NotifyPluginProcessHostTask(HWND window, HWND parent) | |
| 114 : window_(window), parent_(parent), tries_(kMaxTries) { } | |
| 115 | |
| 116 private: | |
| 117 void Run() { | |
| 118 DWORD plugin_process_id; | |
| 119 bool found_starting_plugin_process = false; | |
| 120 GetWindowThreadProcessId(window_, &plugin_process_id); | |
| 121 for (BrowserChildProcessHost::Iterator iter( | |
| 122 ChildProcessInfo::PLUGIN_PROCESS); | |
| 123 !iter.Done(); ++iter) { | |
| 124 PluginProcessHost* plugin = static_cast<PluginProcessHost*>(*iter); | |
| 125 if (!plugin->handle()) { | |
| 126 found_starting_plugin_process = true; | |
| 127 continue; | |
| 128 } | |
| 129 if (base::GetProcId(plugin->handle()) == plugin_process_id) { | |
| 130 plugin->AddWindow(parent_); | |
| 131 return; | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 if (found_starting_plugin_process) { | |
| 136 // A plugin process has started but we don't have its handle yet. Since | |
| 137 // it's most likely the one for this plugin, try a few more times after a | |
| 138 // delay. | |
| 139 if (tries_--) { | |
| 140 MessageLoop::current()->PostDelayedTask(FROM_HERE, this, kTryDelayMs); | |
| 141 return; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // The plugin process might have died in the time to execute the task, don't | |
| 146 // leak the HWND. | |
| 147 PostMessage(parent_, WM_CLOSE, 0, 0); | |
| 148 } | |
| 149 | |
| 150 HWND window_; // Plugin HWND, created and destroyed in the plugin process. | |
| 151 HWND parent_; // Parent HWND, created and destroyed on the browser UI thread. | |
| 152 | |
| 153 int tries_; | |
| 154 | |
| 155 // How many times we try to find a PluginProcessHost whose process matches | |
| 156 // the HWND. | |
| 157 static const int kMaxTries = 5; | |
| 158 // How long to wait between each try. | |
| 159 static const int kTryDelayMs = 200; | |
| 160 }; | |
| 161 | |
| 162 // Windows callback for OnDestroy to detach the plugin windows. | |
| 163 BOOL CALLBACK DetachPluginWindowsCallback(HWND window, LPARAM param) { | |
| 164 if (webkit::npapi::WebPluginDelegateImpl::IsPluginDelegateWindow(window) && | |
| 165 !IsHungAppWindow(window)) { | |
| 166 ::ShowWindow(window, SW_HIDE); | |
| 167 SetParent(window, NULL); | |
| 168 } | |
| 169 return TRUE; | |
| 170 } | |
| 171 | |
| 172 // Draw the contents of |backing_store_dc| onto |paint_rect| with a 70% grey | |
| 173 // filter. | |
| 174 void DrawDeemphasized(const SkColor& color, | |
| 175 const gfx::Rect& paint_rect, | |
| 176 HDC backing_store_dc, | |
| 177 HDC paint_dc) { | |
| 178 gfx::CanvasSkia canvas(paint_rect.width(), paint_rect.height(), true); | |
| 179 { | |
| 180 skia::ScopedPlatformPaint scoped_platform_paint(&canvas); | |
| 181 HDC dc = scoped_platform_paint.GetPlatformSurface(); | |
| 182 BitBlt(dc, | |
| 183 0, | |
| 184 0, | |
| 185 paint_rect.width(), | |
| 186 paint_rect.height(), | |
| 187 backing_store_dc, | |
| 188 paint_rect.x(), | |
| 189 paint_rect.y(), | |
| 190 SRCCOPY); | |
| 191 } | |
| 192 canvas.FillRectInt(color, 0, 0, paint_rect.width(), paint_rect.height()); | |
| 193 skia::DrawToNativeContext(&canvas, paint_dc, paint_rect.x(), | |
| 194 paint_rect.y(), NULL); | |
| 195 } | |
| 196 | |
| 197 // The plugin wrapper window which lives in the browser process has this proc | |
| 198 // as its window procedure. We only handle the WM_PARENTNOTIFY message sent by | |
| 199 // windowed plugins for mouse input. This is forwarded off to the wrappers | |
| 200 // parent which is typically the RVH window which turns on user gesture. | |
| 201 LRESULT CALLBACK PluginWrapperWindowProc(HWND window, unsigned int message, | |
| 202 WPARAM wparam, LPARAM lparam) { | |
| 203 if (message == WM_PARENTNOTIFY) { | |
| 204 switch (LOWORD(wparam)) { | |
| 205 case WM_LBUTTONDOWN: | |
| 206 case WM_RBUTTONDOWN: | |
| 207 case WM_MBUTTONDOWN: | |
| 208 ::SendMessage(GetParent(window), message, wparam, lparam); | |
| 209 return 0; | |
| 210 default: | |
| 211 break; | |
| 212 } | |
| 213 } | |
| 214 return ::DefWindowProc(window, message, wparam, lparam); | |
| 215 } | |
| 216 | |
| 217 // Must be dynamically loaded to avoid startup failures on Win XP. | |
| 218 typedef BOOL (WINAPI *ChangeWindowMessageFilterExFunction)( | |
| 219 HWND hwnd, | |
| 220 UINT message, | |
| 221 DWORD action, | |
| 222 PCHANGEFILTERSTRUCT change_filter_struct); | |
| 223 ChangeWindowMessageFilterExFunction g_ChangeWindowMessageFilterEx; | |
| 224 | |
| 225 } // namespace | |
| 226 | |
| 227 /////////////////////////////////////////////////////////////////////////////// | |
| 228 // RenderWidgetHostViewWin, public: | |
| 229 | |
| 230 RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget) | |
| 231 : render_widget_host_(widget), | |
| 232 compositor_host_window_(NULL), | |
| 233 hide_compositor_window_at_next_paint_(false), | |
| 234 track_mouse_leave_(false), | |
| 235 ime_notification_(false), | |
| 236 capture_enter_key_(false), | |
| 237 is_hidden_(false), | |
| 238 about_to_validate_and_paint_(false), | |
| 239 close_on_deactivate_(false), | |
| 240 being_destroyed_(false), | |
| 241 tooltip_hwnd_(NULL), | |
| 242 tooltip_showing_(false), | |
| 243 shutdown_factory_(this), | |
| 244 parent_hwnd_(NULL), | |
| 245 is_loading_(false), | |
| 246 overlay_color_(0), | |
| 247 text_input_type_(ui::TEXT_INPUT_TYPE_NONE), | |
| 248 is_fullscreen_(false) { | |
| 249 render_widget_host_->SetView(this); | |
| 250 registrar_.Add(this, | |
| 251 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, | |
| 252 NotificationService::AllSources()); | |
| 253 } | |
| 254 | |
| 255 RenderWidgetHostViewWin::~RenderWidgetHostViewWin() { | |
| 256 ResetTooltip(); | |
| 257 } | |
| 258 | |
| 259 void RenderWidgetHostViewWin::CreateWnd(HWND parent) { | |
| 260 Create(parent); // ATL function to create the window. | |
| 261 } | |
| 262 | |
| 263 /////////////////////////////////////////////////////////////////////////////// | |
| 264 // RenderWidgetHostViewWin, RenderWidgetHostView implementation: | |
| 265 | |
| 266 void RenderWidgetHostViewWin::InitAsPopup( | |
| 267 RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) { | |
| 268 close_on_deactivate_ = true; | |
| 269 DoPopupOrFullscreenInit(parent_host_view->GetNativeView(), pos, | |
| 270 WS_EX_TOOLWINDOW); | |
| 271 } | |
| 272 | |
| 273 void RenderWidgetHostViewWin::InitAsFullscreen( | |
| 274 RenderWidgetHostView* reference_host_view) { | |
| 275 gfx::Rect pos = gfx::Screen::GetMonitorAreaNearestWindow( | |
| 276 reference_host_view->GetNativeView()); | |
| 277 is_fullscreen_ = true; | |
| 278 DoPopupOrFullscreenInit(GetDesktopWindow(), pos, 0); | |
| 279 } | |
| 280 | |
| 281 RenderWidgetHost* RenderWidgetHostViewWin::GetRenderWidgetHost() const { | |
| 282 return render_widget_host_; | |
| 283 } | |
| 284 | |
| 285 void RenderWidgetHostViewWin::DidBecomeSelected() { | |
| 286 if (!is_hidden_) | |
| 287 return; | |
| 288 | |
| 289 if (tab_switch_paint_time_.is_null()) | |
| 290 tab_switch_paint_time_ = TimeTicks::Now(); | |
| 291 is_hidden_ = false; | |
| 292 EnsureTooltip(); | |
| 293 render_widget_host_->WasRestored(); | |
| 294 } | |
| 295 | |
| 296 void RenderWidgetHostViewWin::WasHidden() { | |
| 297 if (is_hidden_) | |
| 298 return; | |
| 299 | |
| 300 // If we receive any more paint messages while we are hidden, we want to | |
| 301 // ignore them so we don't re-allocate the backing store. We will paint | |
| 302 // everything again when we become selected again. | |
| 303 is_hidden_ = true; | |
| 304 | |
| 305 ResetTooltip(); | |
| 306 | |
| 307 // If we have a renderer, then inform it that we are being hidden so it can | |
| 308 // reduce its resource utilization. | |
| 309 render_widget_host_->WasHidden(); | |
| 310 | |
| 311 // TODO(darin): what about constrained windows? it doesn't look like they | |
| 312 // see a message when their parent is hidden. maybe there is something more | |
| 313 // generic we can do at the TabContents API level instead of relying on | |
| 314 // Windows messages. | |
| 315 } | |
| 316 | |
| 317 void RenderWidgetHostViewWin::SetSize(const gfx::Size& size) { | |
| 318 SetBounds(gfx::Rect(GetViewBounds().origin(), size)); | |
| 319 } | |
| 320 | |
| 321 void RenderWidgetHostViewWin::SetBounds(const gfx::Rect& rect) { | |
| 322 if (is_hidden_) | |
| 323 return; | |
| 324 | |
| 325 // No SWP_NOREDRAW as autofill popups can move and the underneath window | |
| 326 // should redraw in that case. | |
| 327 UINT swp_flags = SWP_NOSENDCHANGING | SWP_NOOWNERZORDER | SWP_NOCOPYBITS | | |
| 328 SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE; | |
| 329 | |
| 330 // If the style is not popup, you have to convert the point to client | |
| 331 // coordinate. | |
| 332 POINT point = { rect.x(), rect.y() }; | |
| 333 if (GetStyle() & WS_CHILD) | |
| 334 ScreenToClient(&point); | |
| 335 | |
| 336 SetWindowPos(NULL, point.x, point.y, rect.width(), rect.height(), swp_flags); | |
| 337 render_widget_host_->WasResized(); | |
| 338 EnsureTooltip(); | |
| 339 } | |
| 340 | |
| 341 gfx::NativeView RenderWidgetHostViewWin::GetNativeView() const { | |
| 342 return m_hWnd; | |
| 343 } | |
| 344 | |
| 345 gfx::NativeViewId RenderWidgetHostViewWin::GetNativeViewId() const { | |
| 346 return reinterpret_cast<gfx::NativeViewId>(m_hWnd); | |
| 347 } | |
| 348 | |
| 349 void RenderWidgetHostViewWin::MovePluginWindows( | |
| 350 const std::vector<WebPluginGeometry>& plugin_window_moves) { | |
| 351 if (plugin_window_moves.empty()) | |
| 352 return; | |
| 353 | |
| 354 bool oop_plugins = | |
| 355 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) && | |
| 356 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessPlugins); | |
| 357 | |
| 358 HDWP defer_window_pos_info = | |
| 359 ::BeginDeferWindowPos(static_cast<int>(plugin_window_moves.size())); | |
| 360 | |
| 361 if (!defer_window_pos_info) { | |
| 362 NOTREACHED(); | |
| 363 return; | |
| 364 } | |
| 365 | |
| 366 for (size_t i = 0; i < plugin_window_moves.size(); ++i) { | |
| 367 unsigned long flags = 0; | |
| 368 const WebPluginGeometry& move = plugin_window_moves[i]; | |
| 369 HWND window = move.window; | |
| 370 | |
| 371 // As the plugin parent window which lives on the browser UI thread is | |
| 372 // destroyed asynchronously, it is possible that we have a stale window | |
| 373 // sent in by the renderer for moving around. | |
| 374 // Note: get the parent before checking if the window is valid, to avoid a | |
| 375 // race condition where the window is destroyed after the check but before | |
| 376 // the GetParent call. | |
| 377 HWND parent = ::GetParent(window); | |
| 378 if (!::IsWindow(window)) | |
| 379 continue; | |
| 380 | |
| 381 if (oop_plugins) { | |
| 382 if (parent == m_hWnd) { | |
| 383 // The plugin window is a direct child of this window, add an | |
| 384 // intermediate window that lives on this thread to speed up scrolling. | |
| 385 // Note this only works with out of process plugins since we depend on | |
| 386 // PluginProcessHost to destroy the intermediate HWNDs. | |
| 387 parent = ReparentWindow(window); | |
| 388 ::ShowWindow(window, SW_SHOW); // Window was created hidden. | |
| 389 } else if (::GetParent(parent) != m_hWnd) { | |
| 390 // The renderer should only be trying to move windows that are children | |
| 391 // of its render widget window. However, this may happen as a result of | |
| 392 // a race condition, so we ignore it and not kill the plugin process. | |
| 393 continue; | |
| 394 } | |
| 395 | |
| 396 // We move the intermediate parent window which doesn't result in cross- | |
| 397 // process synchronous Windows messages. | |
| 398 window = parent; | |
| 399 } | |
| 400 | |
| 401 if (move.visible) | |
| 402 flags |= SWP_SHOWWINDOW; | |
| 403 else | |
| 404 flags |= SWP_HIDEWINDOW; | |
| 405 | |
| 406 if (move.rects_valid) { | |
| 407 HRGN hrgn = ::CreateRectRgn(move.clip_rect.x(), | |
| 408 move.clip_rect.y(), | |
| 409 move.clip_rect.right(), | |
| 410 move.clip_rect.bottom()); | |
| 411 gfx::SubtractRectanglesFromRegion(hrgn, move.cutout_rects); | |
| 412 | |
| 413 // Note: System will own the hrgn after we call SetWindowRgn, | |
| 414 // so we don't need to call DeleteObject(hrgn) | |
| 415 ::SetWindowRgn(window, hrgn, !move.clip_rect.IsEmpty()); | |
| 416 } else { | |
| 417 flags |= SWP_NOMOVE; | |
| 418 flags |= SWP_NOSIZE; | |
| 419 } | |
| 420 | |
| 421 defer_window_pos_info = ::DeferWindowPos(defer_window_pos_info, | |
| 422 window, NULL, | |
| 423 move.window_rect.x(), | |
| 424 move.window_rect.y(), | |
| 425 move.window_rect.width(), | |
| 426 move.window_rect.height(), flags); | |
| 427 if (!defer_window_pos_info) { | |
| 428 DCHECK(false) << "DeferWindowPos failed, so all plugin moves ignored."; | |
| 429 return; | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 ::EndDeferWindowPos(defer_window_pos_info); | |
| 434 } | |
| 435 | |
| 436 HWND RenderWidgetHostViewWin::ReparentWindow(HWND window) { | |
| 437 static ATOM window_class = 0; | |
| 438 if (!window_class) { | |
| 439 WNDCLASSEX wcex; | |
| 440 wcex.cbSize = sizeof(WNDCLASSEX); | |
| 441 wcex.style = CS_DBLCLKS; | |
| 442 wcex.lpfnWndProc = base::win::WrappedWindowProc<PluginWrapperWindowProc>; | |
| 443 wcex.cbClsExtra = 0; | |
| 444 wcex.cbWndExtra = 0; | |
| 445 wcex.hInstance = GetModuleHandle(NULL); | |
| 446 wcex.hIcon = 0; | |
| 447 wcex.hCursor = 0; | |
| 448 wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); | |
| 449 wcex.lpszMenuName = 0; | |
| 450 wcex.lpszClassName = webkit::npapi::kWrapperNativeWindowClassName; | |
| 451 wcex.hIconSm = 0; | |
| 452 window_class = RegisterClassEx(&wcex); | |
| 453 } | |
| 454 DCHECK(window_class); | |
| 455 | |
| 456 HWND orig_parent = ::GetParent(window); | |
| 457 HWND parent = CreateWindowEx( | |
| 458 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, | |
| 459 MAKEINTATOM(window_class), 0, | |
| 460 WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, | |
| 461 0, 0, 0, 0, orig_parent, 0, GetModuleHandle(NULL), 0); | |
| 462 ui::CheckWindowCreated(parent); | |
| 463 // If UIPI is enabled we need to add message filters for parents with | |
| 464 // children that cross process boundaries. | |
| 465 if (::GetPropW(orig_parent, webkit::npapi::kNativeWindowClassFilterProp)) { | |
| 466 // Process-wide message filters required on Vista must be added to: | |
| 467 // chrome_content_client.cc ChromeContentClient::SandboxPlugin | |
| 468 if (!g_ChangeWindowMessageFilterEx) { | |
| 469 g_ChangeWindowMessageFilterEx = | |
| 470 reinterpret_cast<ChangeWindowMessageFilterExFunction>( | |
| 471 ::GetProcAddress(::GetModuleHandle(L"user32.dll"), | |
| 472 "ChangeWindowMessageFilterEx")); | |
| 473 } | |
| 474 // Process-wide message filters required on Vista must be added to: | |
| 475 // chrome_content_client.cc ChromeContentClient::SandboxPlugin | |
| 476 g_ChangeWindowMessageFilterEx(parent, WM_MOUSEWHEEL, MSGFLT_ALLOW, NULL); | |
| 477 g_ChangeWindowMessageFilterEx(parent, WM_GESTURE, MSGFLT_ALLOW, NULL); | |
| 478 ::SetPropW(orig_parent, webkit::npapi::kNativeWindowClassFilterProp, NULL); | |
| 479 } | |
| 480 ::SetParent(window, parent); | |
| 481 BrowserThread::PostTask( | |
| 482 BrowserThread::IO, FROM_HERE, | |
| 483 new NotifyPluginProcessHostTask(window, parent)); | |
| 484 return parent; | |
| 485 } | |
| 486 | |
| 487 static BOOL CALLBACK AddChildWindowToVector(HWND hwnd, LPARAM lparam) { | |
| 488 std::vector<HWND>* vector = reinterpret_cast<std::vector<HWND>*>(lparam); | |
| 489 vector->push_back(hwnd); | |
| 490 return TRUE; | |
| 491 } | |
| 492 | |
| 493 void RenderWidgetHostViewWin::CleanupCompositorWindow() { | |
| 494 if (!compositor_host_window_) | |
| 495 return; | |
| 496 | |
| 497 // Hide the compositor and parent it to the desktop rather than destroying | |
| 498 // it immediately. The GPU process has a grace period to stop accessing the | |
| 499 // window. TODO(apatrick): the GPU process should acknowledge that it has | |
| 500 // finished with the window handle and the browser process should destroy it | |
| 501 // at that point. | |
| 502 ::ShowWindow(compositor_host_window_, SW_HIDE); | |
| 503 ::SetParent(compositor_host_window_, NULL); | |
| 504 | |
| 505 BrowserThread::PostDelayedTask( | |
| 506 BrowserThread::UI, | |
| 507 FROM_HERE, | |
| 508 NewRunnableFunction(::DestroyWindow, compositor_host_window_), | |
| 509 kDestroyCompositorHostWindowDelay); | |
| 510 | |
| 511 compositor_host_window_ = NULL; | |
| 512 } | |
| 513 | |
| 514 bool RenderWidgetHostViewWin::IsActivatable() const { | |
| 515 // Popups should not be activated. | |
| 516 return popup_type_ == WebKit::WebPopupTypeNone; | |
| 517 } | |
| 518 | |
| 519 void RenderWidgetHostViewWin::Focus() { | |
| 520 if (IsWindow()) | |
| 521 SetFocus(); | |
| 522 } | |
| 523 | |
| 524 void RenderWidgetHostViewWin::Blur() { | |
| 525 views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(m_hWnd); | |
| 526 if (widget) { | |
| 527 views::FocusManager* focus_manager = widget->GetFocusManager(); | |
| 528 // We don't have a FocusManager if we are hidden. | |
| 529 if (focus_manager) | |
| 530 focus_manager->ClearFocus(); | |
| 531 } | |
| 532 } | |
| 533 | |
| 534 bool RenderWidgetHostViewWin::HasFocus() { | |
| 535 return ::GetFocus() == m_hWnd; | |
| 536 } | |
| 537 | |
| 538 void RenderWidgetHostViewWin::Show() { | |
| 539 if (!is_fullscreen_) { | |
| 540 DCHECK(parent_hwnd_); | |
| 541 DCHECK(parent_hwnd_ != GetDesktopWindow()); | |
| 542 SetParent(parent_hwnd_); | |
| 543 } | |
| 544 ShowWindow(SW_SHOW); | |
| 545 | |
| 546 DidBecomeSelected(); | |
| 547 } | |
| 548 | |
| 549 void RenderWidgetHostViewWin::Hide() { | |
| 550 if (!is_fullscreen_ && GetParent() == GetDesktopWindow()) { | |
| 551 LOG(WARNING) << "Hide() called twice in a row: " << this << ":" << | |
| 552 parent_hwnd_ << ":" << GetParent(); | |
| 553 return; | |
| 554 } | |
| 555 | |
| 556 if (::GetFocus() == m_hWnd) | |
| 557 ::SetFocus(NULL); | |
| 558 ShowWindow(SW_HIDE); | |
| 559 | |
| 560 if (!is_fullscreen_) { | |
| 561 // Cache the old parent, then orphan the window so we stop receiving | |
| 562 // messages. | |
| 563 parent_hwnd_ = GetParent(); | |
| 564 SetParent(NULL); | |
| 565 } | |
| 566 | |
| 567 WasHidden(); | |
| 568 } | |
| 569 | |
| 570 bool RenderWidgetHostViewWin::IsShowing() { | |
| 571 return !!IsWindowVisible(); | |
| 572 } | |
| 573 | |
| 574 gfx::Rect RenderWidgetHostViewWin::GetViewBounds() const { | |
| 575 CRect window_rect; | |
| 576 GetWindowRect(&window_rect); | |
| 577 return gfx::Rect(window_rect); | |
| 578 } | |
| 579 | |
| 580 void RenderWidgetHostViewWin::UpdateCursor(const WebCursor& cursor) { | |
| 581 current_cursor_ = cursor; | |
| 582 UpdateCursorIfOverSelf(); | |
| 583 } | |
| 584 | |
| 585 void RenderWidgetHostViewWin::UpdateCursorIfOverSelf() { | |
| 586 static HCURSOR kCursorArrow = LoadCursor(NULL, IDC_ARROW); | |
| 587 static HCURSOR kCursorAppStarting = LoadCursor(NULL, IDC_APPSTARTING); | |
| 588 static HINSTANCE module_handle = GetModuleHandle( | |
| 589 content::GetContentClient()->browser()->GetResourceDllName()); | |
| 590 | |
| 591 // If the mouse is over our HWND, then update the cursor state immediately. | |
| 592 CPoint pt; | |
| 593 GetCursorPos(&pt); | |
| 594 if (WindowFromPoint(pt) == m_hWnd) { | |
| 595 BOOL result = ::ScreenToClient(m_hWnd, &pt); | |
| 596 DCHECK(result); | |
| 597 // We cannot pass in NULL as the module handle as this would only work for | |
| 598 // standard win32 cursors. We can also receive cursor types which are | |
| 599 // defined as webkit resources. We need to specify the module handle of | |
| 600 // chrome.dll while loading these cursors. | |
| 601 HCURSOR display_cursor = current_cursor_.GetCursor(module_handle); | |
| 602 | |
| 603 // If a page is in the loading state, we want to show the Arrow+Hourglass | |
| 604 // cursor only when the current cursor is the ARROW cursor. In all other | |
| 605 // cases we should continue to display the current cursor. | |
| 606 if (is_loading_ && display_cursor == kCursorArrow) | |
| 607 display_cursor = kCursorAppStarting; | |
| 608 | |
| 609 SetCursor(display_cursor); | |
| 610 } | |
| 611 } | |
| 612 | |
| 613 void RenderWidgetHostViewWin::SetIsLoading(bool is_loading) { | |
| 614 is_loading_ = is_loading; | |
| 615 UpdateCursorIfOverSelf(); | |
| 616 } | |
| 617 | |
| 618 void RenderWidgetHostViewWin::ImeUpdateTextInputState( | |
| 619 ui::TextInputType type, | |
| 620 bool can_compose_inline, | |
| 621 const gfx::Rect& caret_rect) { | |
| 622 // TODO(kinaba): currently, can_compose_inline is ignored and always treated | |
| 623 // as true. We need to support "can_compose_inline=false" for PPAPI plugins | |
| 624 // that may want to avoid drawing composition-text by themselves and pass | |
| 625 // the responsibility to the browser. | |
| 626 bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE && | |
| 627 type != ui::TEXT_INPUT_TYPE_PASSWORD); | |
| 628 if (text_input_type_ != type) { | |
| 629 text_input_type_ = type; | |
| 630 if (is_enabled) | |
| 631 ime_input_.EnableIME(m_hWnd); | |
| 632 else | |
| 633 ime_input_.DisableIME(m_hWnd); | |
| 634 } | |
| 635 | |
| 636 // Only update caret position if the input method is enabled. | |
| 637 if (is_enabled) | |
| 638 ime_input_.UpdateCaretRect(m_hWnd, caret_rect); | |
| 639 } | |
| 640 | |
| 641 void RenderWidgetHostViewWin::ImeCancelComposition() { | |
| 642 ime_input_.CancelIME(m_hWnd); | |
| 643 } | |
| 644 | |
| 645 BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lparam) { | |
| 646 if (!webkit::npapi::WebPluginDelegateImpl::IsPluginDelegateWindow(hwnd)) | |
| 647 return TRUE; | |
| 648 | |
| 649 gfx::Rect* rect = reinterpret_cast<gfx::Rect*>(lparam); | |
| 650 static UINT msg = RegisterWindowMessage(webkit::npapi::kPaintMessageName); | |
| 651 WPARAM wparam = rect->x() << 16 | rect->y(); | |
| 652 lparam = rect->width() << 16 | rect->height(); | |
| 653 | |
| 654 // SendMessage gets the message across much quicker than PostMessage, since it | |
| 655 // doesn't get queued. When the plugin thread calls PeekMessage or other | |
| 656 // Win32 APIs, sent messages are dispatched automatically. | |
| 657 SendNotifyMessage(hwnd, msg, wparam, lparam); | |
| 658 | |
| 659 return TRUE; | |
| 660 } | |
| 661 | |
| 662 void RenderWidgetHostViewWin::Redraw() { | |
| 663 RECT damage_bounds; | |
| 664 GetUpdateRect(&damage_bounds, FALSE); | |
| 665 | |
| 666 base::win::ScopedGDIObject<HRGN> damage_region(CreateRectRgn(0, 0, 0, 0)); | |
| 667 GetUpdateRgn(damage_region, FALSE); | |
| 668 | |
| 669 // Paint the invalid region synchronously. Our caller will not paint again | |
| 670 // until we return, so by painting to the screen here, we ensure effective | |
| 671 // rate-limiting of backing store updates. This helps a lot on pages that | |
| 672 // have animations or fairly expensive layout (e.g., google maps). | |
| 673 // | |
| 674 // We paint this window synchronously, however child windows (i.e. plugins) | |
| 675 // are painted asynchronously. By avoiding synchronous cross-process window | |
| 676 // message dispatching we allow scrolling to be smooth, and also avoid the | |
| 677 // browser process locking up if the plugin process is hung. | |
| 678 // | |
| 679 RedrawWindow(NULL, damage_region, RDW_UPDATENOW | RDW_NOCHILDREN); | |
| 680 | |
| 681 // Send the invalid rect in screen coordinates. | |
| 682 gfx::Rect screen_rect = GetViewBounds(); | |
| 683 gfx::Rect invalid_screen_rect(damage_bounds); | |
| 684 invalid_screen_rect.Offset(screen_rect.x(), screen_rect.y()); | |
| 685 | |
| 686 LPARAM lparam = reinterpret_cast<LPARAM>(&invalid_screen_rect); | |
| 687 EnumChildWindows(m_hWnd, EnumChildProc, lparam); | |
| 688 } | |
| 689 | |
| 690 void RenderWidgetHostViewWin::DidUpdateBackingStore( | |
| 691 const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, | |
| 692 const std::vector<gfx::Rect>& copy_rects) { | |
| 693 if (is_hidden_) | |
| 694 return; | |
| 695 | |
| 696 // Schedule invalidations first so that the ScrollWindowEx call is closer to | |
| 697 // Redraw. That minimizes chances of "flicker" resulting if the screen | |
| 698 // refreshes before we have a chance to paint the exposed area. Somewhat | |
| 699 // surprisingly, this ordering matters. | |
| 700 | |
| 701 for (size_t i = 0; i < copy_rects.size(); ++i) | |
| 702 InvalidateRect(©_rects[i].ToRECT(), false); | |
| 703 | |
| 704 if (!scroll_rect.IsEmpty()) { | |
| 705 RECT clip_rect = scroll_rect.ToRECT(); | |
| 706 ScrollWindowEx(scroll_dx, scroll_dy, NULL, &clip_rect, NULL, NULL, | |
| 707 SW_INVALIDATE); | |
| 708 } | |
| 709 | |
| 710 if (!about_to_validate_and_paint_) | |
| 711 Redraw(); | |
| 712 } | |
| 713 | |
| 714 void RenderWidgetHostViewWin::RenderViewGone(base::TerminationStatus status, | |
| 715 int error_code) { | |
| 716 // TODO(darin): keep this around, and draw sad-tab into it. | |
| 717 UpdateCursorIfOverSelf(); | |
| 718 being_destroyed_ = true; | |
| 719 CleanupCompositorWindow(); | |
| 720 DestroyWindow(); | |
| 721 } | |
| 722 | |
| 723 void RenderWidgetHostViewWin::WillWmDestroy() { | |
| 724 CleanupCompositorWindow(); | |
| 725 } | |
| 726 | |
| 727 void RenderWidgetHostViewWin::Destroy() { | |
| 728 // We've been told to destroy. | |
| 729 // By clearing close_on_deactivate_, we prevent further deactivations | |
| 730 // (caused by windows messages resulting from the DestroyWindow) from | |
| 731 // triggering further destructions. The deletion of this is handled by | |
| 732 // OnFinalMessage(); | |
| 733 close_on_deactivate_ = false; | |
| 734 render_widget_host_ = NULL; | |
| 735 being_destroyed_ = true; | |
| 736 CleanupCompositorWindow(); | |
| 737 DestroyWindow(); | |
| 738 } | |
| 739 | |
| 740 void RenderWidgetHostViewWin::SetTooltipText(const std::wstring& tooltip_text) { | |
| 741 // Clamp the tooltip length to kMaxTooltipLength so that we don't | |
| 742 // accidentally DOS the user with a mega tooltip (since Windows doesn't seem | |
| 743 // to do this itself). | |
| 744 const std::wstring& new_tooltip_text = | |
| 745 ui::TruncateString(tooltip_text, kMaxTooltipLength); | |
| 746 | |
| 747 if (new_tooltip_text != tooltip_text_) { | |
| 748 tooltip_text_ = new_tooltip_text; | |
| 749 | |
| 750 // Need to check if the tooltip is already showing so that we don't | |
| 751 // immediately show the tooltip with no delay when we move the mouse from | |
| 752 // a region with no tooltip to a region with a tooltip. | |
| 753 if (::IsWindow(tooltip_hwnd_) && tooltip_showing_) { | |
| 754 ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
| 755 ::SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); | |
| 756 } | |
| 757 } else { | |
| 758 // Make sure the tooltip gets closed after TTN_POP gets sent. For some | |
| 759 // reason this doesn't happen automatically, so moving the mouse around | |
| 760 // within the same link/image/etc doesn't cause the tooltip to re-appear. | |
| 761 if (!tooltip_showing_) { | |
| 762 if (::IsWindow(tooltip_hwnd_)) | |
| 763 ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
| 764 } | |
| 765 } | |
| 766 } | |
| 767 | |
| 768 BackingStore* RenderWidgetHostViewWin::AllocBackingStore( | |
| 769 const gfx::Size& size) { | |
| 770 return new BackingStoreWin(render_widget_host_, size); | |
| 771 } | |
| 772 | |
| 773 void RenderWidgetHostViewWin::SetBackground(const SkBitmap& background) { | |
| 774 RenderWidgetHostView::SetBackground(background); | |
| 775 Send(new ViewMsg_SetBackground(render_widget_host_->routing_id(), | |
| 776 background)); | |
| 777 } | |
| 778 | |
| 779 void RenderWidgetHostViewWin::SetVisuallyDeemphasized(const SkColor* color, | |
| 780 bool animate) { | |
| 781 // |animate| is not yet implemented, and currently isn't used. | |
| 782 CHECK(!animate); | |
| 783 | |
| 784 SkColor overlay_color = color ? *color : 0; | |
| 785 if (overlay_color_ == overlay_color) | |
| 786 return; | |
| 787 overlay_color_ = overlay_color; | |
| 788 | |
| 789 InvalidateRect(NULL, FALSE); | |
| 790 } | |
| 791 | |
| 792 void RenderWidgetHostViewWin::UnhandledWheelEvent( | |
| 793 const WebKit::WebMouseWheelEvent& event) { | |
| 794 } | |
| 795 | |
| 796 void RenderWidgetHostViewWin::SetHasHorizontalScrollbar( | |
| 797 bool has_horizontal_scrollbar) { | |
| 798 } | |
| 799 | |
| 800 void RenderWidgetHostViewWin::SetScrollOffsetPinning( | |
| 801 bool is_pinned_to_left, bool is_pinned_to_right) { | |
| 802 } | |
| 803 | |
| 804 /////////////////////////////////////////////////////////////////////////////// | |
| 805 // RenderWidgetHostViewWin, private: | |
| 806 | |
| 807 LRESULT RenderWidgetHostViewWin::OnCreate(CREATESTRUCT* create_struct) { | |
| 808 // Call the WM_INPUTLANGCHANGE message handler to initialize the input locale | |
| 809 // of a browser process. | |
| 810 OnInputLangChange(0, 0); | |
| 811 // Marks that window as supporting mouse-wheel messages rerouting so it is | |
| 812 // scrolled when under the mouse pointer even if inactive. | |
| 813 props_.push_back(views::SetWindowSupportsRerouteMouseWheel(m_hWnd)); | |
| 814 props_.push_back(new ViewProp(m_hWnd, kRenderWidgetHostViewKey, | |
| 815 static_cast<RenderWidgetHostView*>(this))); | |
| 816 | |
| 817 return 0; | |
| 818 } | |
| 819 | |
| 820 void RenderWidgetHostViewWin::OnActivate(UINT action, BOOL minimized, | |
| 821 HWND window) { | |
| 822 // If the container is a popup, clicking elsewhere on screen should close the | |
| 823 // popup. | |
| 824 if (close_on_deactivate_ && action == WA_INACTIVE) { | |
| 825 // Send a windows message so that any derived classes | |
| 826 // will get a change to override the default handling | |
| 827 SendMessage(WM_CANCELMODE); | |
| 828 } | |
| 829 } | |
| 830 | |
| 831 void RenderWidgetHostViewWin::OnDestroy() { | |
| 832 // When a tab is closed all its child plugin windows are destroyed | |
| 833 // automatically. This happens before plugins get any notification that its | |
| 834 // instances are tearing down. | |
| 835 // | |
| 836 // Plugins like Quicktime assume that their windows will remain valid as long | |
| 837 // as they have plugin instances active. Quicktime crashes in this case | |
| 838 // because its windowing code cleans up an internal data structure that the | |
| 839 // handler for NPP_DestroyStream relies on. | |
| 840 // | |
| 841 // The fix is to detach plugin windows from web contents when it is going | |
| 842 // away. This will prevent the plugin windows from getting destroyed | |
| 843 // automatically. The detached plugin windows will get cleaned up in proper | |
| 844 // sequence as part of the usual cleanup when the plugin instance goes away. | |
| 845 EnumChildWindows(m_hWnd, DetachPluginWindowsCallback, NULL); | |
| 846 | |
| 847 props_.reset(); | |
| 848 | |
| 849 CleanupCompositorWindow(); | |
| 850 | |
| 851 ResetTooltip(); | |
| 852 TrackMouseLeave(false); | |
| 853 } | |
| 854 | |
| 855 void RenderWidgetHostViewWin::OnPaint(HDC unused_dc) { | |
| 856 if (!render_widget_host_) | |
| 857 return; | |
| 858 | |
| 859 DCHECK(render_widget_host_->process()->HasConnection()); | |
| 860 | |
| 861 // If the GPU process is rendering directly into the View, compositing is | |
| 862 // already triggered by damage to compositor_host_window_, so all we need to | |
| 863 // do here is clear borders during resize. | |
| 864 if (render_widget_host_->is_accelerated_compositing_active()) { | |
| 865 // We initialize paint_dc here so that BeginPaint()/EndPaint() | |
| 866 // get called to validate the region. | |
| 867 CPaintDC paint_dc(m_hWnd); | |
| 868 RECT host_rect, child_rect; | |
| 869 GetClientRect(&host_rect); | |
| 870 if (::GetClientRect(compositor_host_window_, &child_rect) && | |
| 871 (child_rect.right < host_rect.right || | |
| 872 child_rect.bottom < host_rect.bottom)) { | |
| 873 paint_dc.FillRect(&host_rect, | |
| 874 reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH))); | |
| 875 } | |
| 876 return; | |
| 877 } | |
| 878 | |
| 879 about_to_validate_and_paint_ = true; | |
| 880 BackingStoreWin* backing_store = static_cast<BackingStoreWin*>( | |
| 881 render_widget_host_->GetBackingStore(true)); | |
| 882 | |
| 883 // We initialize |paint_dc| (and thus call BeginPaint()) after calling | |
| 884 // GetBackingStore(), so that if it updates the invalid rect we'll catch the | |
| 885 // changes and repaint them. | |
| 886 about_to_validate_and_paint_ = false; | |
| 887 | |
| 888 // Grab the region to paint before creation of paint_dc since it clears the | |
| 889 // damage region. | |
| 890 base::win::ScopedGDIObject<HRGN> damage_region(CreateRectRgn(0, 0, 0, 0)); | |
| 891 GetUpdateRgn(damage_region, FALSE); | |
| 892 | |
| 893 if (hide_compositor_window_at_next_paint_) { | |
| 894 ::ShowWindow(compositor_host_window_, SW_HIDE); | |
| 895 hide_compositor_window_at_next_paint_ = false; | |
| 896 } | |
| 897 | |
| 898 CPaintDC paint_dc(m_hWnd); | |
| 899 | |
| 900 gfx::Rect damaged_rect(paint_dc.m_ps.rcPaint); | |
| 901 if (damaged_rect.IsEmpty()) | |
| 902 return; | |
| 903 | |
| 904 if (backing_store) { | |
| 905 gfx::Rect bitmap_rect(gfx::Point(), backing_store->size()); | |
| 906 | |
| 907 bool manage_colors = BackingStoreWin::ColorManagementEnabled(); | |
| 908 if (manage_colors) | |
| 909 SetICMMode(paint_dc.m_hDC, ICM_ON); | |
| 910 | |
| 911 // Blit only the damaged regions from the backing store. | |
| 912 DWORD data_size = GetRegionData(damage_region, 0, NULL); | |
| 913 scoped_array<char> region_data_buf(new char[data_size]); | |
| 914 RGNDATA* region_data = reinterpret_cast<RGNDATA*>(region_data_buf.get()); | |
| 915 GetRegionData(damage_region, data_size, region_data); | |
| 916 | |
| 917 RECT* region_rects = reinterpret_cast<RECT*>(region_data->Buffer); | |
| 918 for (DWORD i = 0; i < region_data->rdh.nCount; ++i) { | |
| 919 gfx::Rect paint_rect = bitmap_rect.Intersect(gfx::Rect(region_rects[i])); | |
| 920 if (!paint_rect.IsEmpty()) { | |
| 921 if (SkColorGetA(overlay_color_) > 0) { | |
| 922 DrawDeemphasized(overlay_color_, | |
| 923 paint_rect, | |
| 924 backing_store->hdc(), | |
| 925 paint_dc.m_hDC); | |
| 926 } else { | |
| 927 BitBlt(paint_dc.m_hDC, | |
| 928 paint_rect.x(), | |
| 929 paint_rect.y(), | |
| 930 paint_rect.width(), | |
| 931 paint_rect.height(), | |
| 932 backing_store->hdc(), | |
| 933 paint_rect.x(), | |
| 934 paint_rect.y(), | |
| 935 SRCCOPY); | |
| 936 } | |
| 937 } | |
| 938 } | |
| 939 | |
| 940 if (manage_colors) | |
| 941 SetICMMode(paint_dc.m_hDC, ICM_OFF); | |
| 942 | |
| 943 // Fill the remaining portion of the damaged_rect with the background | |
| 944 if (damaged_rect.right() > bitmap_rect.right()) { | |
| 945 RECT r; | |
| 946 r.left = std::max(bitmap_rect.right(), damaged_rect.x()); | |
| 947 r.right = damaged_rect.right(); | |
| 948 r.top = damaged_rect.y(); | |
| 949 r.bottom = std::min(bitmap_rect.bottom(), damaged_rect.bottom()); | |
| 950 DrawBackground(r, &paint_dc); | |
| 951 } | |
| 952 if (damaged_rect.bottom() > bitmap_rect.bottom()) { | |
| 953 RECT r; | |
| 954 r.left = damaged_rect.x(); | |
| 955 r.right = damaged_rect.right(); | |
| 956 r.top = std::max(bitmap_rect.bottom(), damaged_rect.y()); | |
| 957 r.bottom = damaged_rect.bottom(); | |
| 958 DrawBackground(r, &paint_dc); | |
| 959 } | |
| 960 if (!whiteout_start_time_.is_null()) { | |
| 961 TimeDelta whiteout_duration = TimeTicks::Now() - whiteout_start_time_; | |
| 962 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration); | |
| 963 | |
| 964 // Reset the start time to 0 so that we start recording again the next | |
| 965 // time the backing store is NULL... | |
| 966 whiteout_start_time_ = TimeTicks(); | |
| 967 } | |
| 968 if (!tab_switch_paint_time_.is_null()) { | |
| 969 TimeDelta tab_switch_paint_duration = TimeTicks::Now() - | |
| 970 tab_switch_paint_time_; | |
| 971 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", | |
| 972 tab_switch_paint_duration); | |
| 973 // Reset tab_switch_paint_time_ to 0 so future tab selections are | |
| 974 // recorded. | |
| 975 tab_switch_paint_time_ = TimeTicks(); | |
| 976 } | |
| 977 } else { | |
| 978 DrawBackground(paint_dc.m_ps.rcPaint, &paint_dc); | |
| 979 if (whiteout_start_time_.is_null()) | |
| 980 whiteout_start_time_ = TimeTicks::Now(); | |
| 981 } | |
| 982 } | |
| 983 | |
| 984 void RenderWidgetHostViewWin::DrawBackground(const RECT& dirty_rect, | |
| 985 CPaintDC* dc) { | |
| 986 if (!background_.empty()) { | |
| 987 gfx::CanvasSkia canvas(dirty_rect.right - dirty_rect.left, | |
| 988 dirty_rect.bottom - dirty_rect.top, | |
| 989 true); // opaque | |
| 990 canvas.TranslateInt(-dirty_rect.left, -dirty_rect.top); | |
| 991 | |
| 992 const RECT& dc_rect = dc->m_ps.rcPaint; | |
| 993 canvas.TileImageInt(background_, 0, 0, | |
| 994 dc_rect.right - dc_rect.left, | |
| 995 dc_rect.bottom - dc_rect.top); | |
| 996 | |
| 997 skia::DrawToNativeContext(&canvas, *dc, dirty_rect.left, dirty_rect.top, | |
| 998 NULL); | |
| 999 } else { | |
| 1000 HBRUSH white_brush = reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); | |
| 1001 dc->FillRect(&dirty_rect, white_brush); | |
| 1002 } | |
| 1003 } | |
| 1004 | |
| 1005 void RenderWidgetHostViewWin::OnNCPaint(HRGN update_region) { | |
| 1006 // Do nothing. This suppresses the resize corner that Windows would | |
| 1007 // otherwise draw for us. | |
| 1008 } | |
| 1009 | |
| 1010 LRESULT RenderWidgetHostViewWin::OnEraseBkgnd(HDC dc) { | |
| 1011 return 1; | |
| 1012 } | |
| 1013 | |
| 1014 LRESULT RenderWidgetHostViewWin::OnSetCursor(HWND window, UINT hittest_code, | |
| 1015 UINT mouse_message_id) { | |
| 1016 UpdateCursorIfOverSelf(); | |
| 1017 return 0; | |
| 1018 } | |
| 1019 | |
| 1020 void RenderWidgetHostViewWin::OnSetFocus(HWND window) { | |
| 1021 views::FocusManager::GetWidgetFocusManager()->OnWidgetFocusEvent(window, | |
| 1022 m_hWnd); | |
| 1023 if (browser_accessibility_manager_.get()) | |
| 1024 browser_accessibility_manager_->GotFocus(); | |
| 1025 if (render_widget_host_) | |
| 1026 render_widget_host_->GotFocus(); | |
| 1027 } | |
| 1028 | |
| 1029 void RenderWidgetHostViewWin::OnKillFocus(HWND window) { | |
| 1030 views::FocusManager::GetWidgetFocusManager()->OnWidgetFocusEvent(m_hWnd, | |
| 1031 window); | |
| 1032 | |
| 1033 if (render_widget_host_) | |
| 1034 render_widget_host_->Blur(); | |
| 1035 } | |
| 1036 | |
| 1037 void RenderWidgetHostViewWin::OnCaptureChanged(HWND window) { | |
| 1038 if (render_widget_host_) | |
| 1039 render_widget_host_->LostCapture(); | |
| 1040 } | |
| 1041 | |
| 1042 void RenderWidgetHostViewWin::OnCancelMode() { | |
| 1043 if (render_widget_host_) | |
| 1044 render_widget_host_->LostCapture(); | |
| 1045 | |
| 1046 if ((is_fullscreen_ || close_on_deactivate_) && | |
| 1047 shutdown_factory_.empty()) { | |
| 1048 // Dismiss popups and menus. We do this asynchronously to avoid changing | |
| 1049 // activation within this callstack, which may interfere with another window | |
| 1050 // being activated. We can synchronously hide the window, but we need to | |
| 1051 // not change activation while doing so. | |
| 1052 SetWindowPos(NULL, 0, 0, 0, 0, | |
| 1053 SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | | |
| 1054 SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER); | |
| 1055 MessageLoop::current()->PostTask(FROM_HERE, | |
| 1056 shutdown_factory_.NewRunnableMethod( | |
| 1057 &RenderWidgetHostViewWin::ShutdownHost)); | |
| 1058 } | |
| 1059 } | |
| 1060 | |
| 1061 void RenderWidgetHostViewWin::OnInputLangChange(DWORD character_set, | |
| 1062 HKL input_language_id) { | |
| 1063 // Send the given Locale ID to the ImeInput object and retrieves whether | |
| 1064 // or not the current input context has IMEs. | |
| 1065 // If the current input context has IMEs, a browser process has to send a | |
| 1066 // request to a renderer process that it needs status messages about | |
| 1067 // the focused edit control from the renderer process. | |
| 1068 // On the other hand, if the current input context does not have IMEs, the | |
| 1069 // browser process also has to send a request to the renderer process that | |
| 1070 // it does not need the status messages any longer. | |
| 1071 // To minimize the number of this notification request, we should check if | |
| 1072 // the browser process is actually retrieving the status messages (this | |
| 1073 // state is stored in ime_notification_) and send a request only if the | |
| 1074 // browser process has to update this status, its details are listed below: | |
| 1075 // * If a browser process is not retrieving the status messages, | |
| 1076 // (i.e. ime_notification_ == false), | |
| 1077 // send this request only if the input context does have IMEs, | |
| 1078 // (i.e. ime_status == true); | |
| 1079 // When it successfully sends the request, toggle its notification status, | |
| 1080 // (i.e.ime_notification_ = !ime_notification_ = true). | |
| 1081 // * If a browser process is retrieving the status messages | |
| 1082 // (i.e. ime_notification_ == true), | |
| 1083 // send this request only if the input context does not have IMEs, | |
| 1084 // (i.e. ime_status == false). | |
| 1085 // When it successfully sends the request, toggle its notification status, | |
| 1086 // (i.e.ime_notification_ = !ime_notification_ = false). | |
| 1087 // To analyze the above actions, we can optimize them into the ones | |
| 1088 // listed below: | |
| 1089 // 1 Sending a request only if ime_status_ != ime_notification_, and; | |
| 1090 // 2 Copying ime_status to ime_notification_ if it sends the request | |
| 1091 // successfully (because Action 1 shows ime_status = !ime_notification_.) | |
| 1092 bool ime_status = ime_input_.SetInputLanguage(); | |
| 1093 if (ime_status != ime_notification_) { | |
| 1094 if (render_widget_host_) { | |
| 1095 render_widget_host_->SetInputMethodActive(ime_status); | |
| 1096 ime_notification_ = ime_status; | |
| 1097 } | |
| 1098 } | |
| 1099 } | |
| 1100 | |
| 1101 void RenderWidgetHostViewWin::OnThemeChanged() { | |
| 1102 if (!render_widget_host_) | |
| 1103 return; | |
| 1104 render_widget_host_->Send(new ViewMsg_ThemeChanged( | |
| 1105 render_widget_host_->routing_id())); | |
| 1106 } | |
| 1107 | |
| 1108 LRESULT RenderWidgetHostViewWin::OnNotify(int w_param, NMHDR* header) { | |
| 1109 if (tooltip_hwnd_ == NULL) | |
| 1110 return 0; | |
| 1111 | |
| 1112 switch (header->code) { | |
| 1113 case TTN_GETDISPINFO: { | |
| 1114 NMTTDISPINFOW* tooltip_info = reinterpret_cast<NMTTDISPINFOW*>(header); | |
| 1115 tooltip_info->szText[0] = L'\0'; | |
| 1116 tooltip_info->lpszText = const_cast<wchar_t*>(tooltip_text_.c_str()); | |
| 1117 ::SendMessage( | |
| 1118 tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, kTooltipMaxWidthPixels); | |
| 1119 SetMsgHandled(TRUE); | |
| 1120 break; | |
| 1121 } | |
| 1122 case TTN_POP: | |
| 1123 tooltip_showing_ = false; | |
| 1124 SetMsgHandled(TRUE); | |
| 1125 break; | |
| 1126 case TTN_SHOW: | |
| 1127 tooltip_showing_ = true; | |
| 1128 SetMsgHandled(TRUE); | |
| 1129 break; | |
| 1130 } | |
| 1131 return 0; | |
| 1132 } | |
| 1133 | |
| 1134 LRESULT RenderWidgetHostViewWin::OnImeSetContext( | |
| 1135 UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { | |
| 1136 if (!render_widget_host_) | |
| 1137 return 0; | |
| 1138 | |
| 1139 // We need status messages about the focused input control from a | |
| 1140 // renderer process when: | |
| 1141 // * the current input context has IMEs, and; | |
| 1142 // * an application is activated. | |
| 1143 // This seems to tell we should also check if the current input context has | |
| 1144 // IMEs before sending a request, however, this WM_IME_SETCONTEXT is | |
| 1145 // fortunately sent to an application only while the input context has IMEs. | |
| 1146 // Therefore, we just start/stop status messages according to the activation | |
| 1147 // status of this application without checks. | |
| 1148 bool activated = (wparam == TRUE); | |
| 1149 if (render_widget_host_) { | |
| 1150 render_widget_host_->SetInputMethodActive(activated); | |
| 1151 ime_notification_ = activated; | |
| 1152 } | |
| 1153 | |
| 1154 if (ime_notification_) | |
| 1155 ime_input_.CreateImeWindow(m_hWnd); | |
| 1156 | |
| 1157 ime_input_.CleanupComposition(m_hWnd); | |
| 1158 return ime_input_.SetImeWindowStyle( | |
| 1159 m_hWnd, message, wparam, lparam, &handled); | |
| 1160 } | |
| 1161 | |
| 1162 LRESULT RenderWidgetHostViewWin::OnImeStartComposition( | |
| 1163 UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { | |
| 1164 if (!render_widget_host_) | |
| 1165 return 0; | |
| 1166 | |
| 1167 // Reset the composition status and create IME windows. | |
| 1168 ime_input_.CreateImeWindow(m_hWnd); | |
| 1169 ime_input_.ResetComposition(m_hWnd); | |
| 1170 // We have to prevent WTL from calling ::DefWindowProc() because the function | |
| 1171 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to | |
| 1172 // over-write the position of IME windows. | |
| 1173 handled = TRUE; | |
| 1174 return 0; | |
| 1175 } | |
| 1176 | |
| 1177 LRESULT RenderWidgetHostViewWin::OnImeComposition( | |
| 1178 UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { | |
| 1179 if (!render_widget_host_) | |
| 1180 return 0; | |
| 1181 | |
| 1182 // At first, update the position of the IME window. | |
| 1183 ime_input_.UpdateImeWindow(m_hWnd); | |
| 1184 | |
| 1185 // ui::CompositionUnderline should be identical to | |
| 1186 // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely. | |
| 1187 COMPILE_ASSERT(sizeof(ui::CompositionUnderline) == | |
| 1188 sizeof(WebKit::WebCompositionUnderline), | |
| 1189 ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff); | |
| 1190 | |
| 1191 // Retrieve the result string and its attributes of the ongoing composition | |
| 1192 // and send it to a renderer process. | |
| 1193 ui::CompositionText composition; | |
| 1194 if (ime_input_.GetResult(m_hWnd, lparam, &composition.text)) { | |
| 1195 render_widget_host_->ImeConfirmComposition(composition.text); | |
| 1196 ime_input_.ResetComposition(m_hWnd); | |
| 1197 // Fall though and try reading the composition string. | |
| 1198 // Japanese IMEs send a message containing both GCS_RESULTSTR and | |
| 1199 // GCS_COMPSTR, which means an ongoing composition has been finished | |
| 1200 // by the start of another composition. | |
| 1201 } | |
| 1202 // Retrieve the composition string and its attributes of the ongoing | |
| 1203 // composition and send it to a renderer process. | |
| 1204 if (ime_input_.GetComposition(m_hWnd, lparam, &composition)) { | |
| 1205 // TODO(suzhe): due to a bug of webkit, we can't use selection range with | |
| 1206 // composition string. See: https://bugs.webkit.org/show_bug.cgi?id=37788 | |
| 1207 composition.selection = ui::Range(composition.selection.end()); | |
| 1208 | |
| 1209 // TODO(suzhe): convert both renderer_host and renderer to use | |
| 1210 // ui::CompositionText. | |
| 1211 const std::vector<WebKit::WebCompositionUnderline>& underlines = | |
| 1212 reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( | |
| 1213 composition.underlines); | |
| 1214 render_widget_host_->ImeSetComposition( | |
| 1215 composition.text, underlines, | |
| 1216 composition.selection.start(), composition.selection.end()); | |
| 1217 } | |
| 1218 // We have to prevent WTL from calling ::DefWindowProc() because we do not | |
| 1219 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. | |
| 1220 handled = TRUE; | |
| 1221 return 0; | |
| 1222 } | |
| 1223 | |
| 1224 LRESULT RenderWidgetHostViewWin::OnImeEndComposition( | |
| 1225 UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { | |
| 1226 if (!render_widget_host_) | |
| 1227 return 0; | |
| 1228 | |
| 1229 if (ime_input_.is_composing()) { | |
| 1230 // A composition has been ended while there is an ongoing composition, | |
| 1231 // i.e. the ongoing composition has been canceled. | |
| 1232 // We need to reset the composition status both of the ImeInput object and | |
| 1233 // of the renderer process. | |
| 1234 render_widget_host_->ImeCancelComposition(); | |
| 1235 ime_input_.ResetComposition(m_hWnd); | |
| 1236 } | |
| 1237 ime_input_.DestroyImeWindow(m_hWnd); | |
| 1238 // Let WTL call ::DefWindowProc() and release its resources. | |
| 1239 handled = FALSE; | |
| 1240 return 0; | |
| 1241 } | |
| 1242 | |
| 1243 LRESULT RenderWidgetHostViewWin::OnMouseEvent(UINT message, WPARAM wparam, | |
| 1244 LPARAM lparam, BOOL& handled) { | |
| 1245 handled = TRUE; | |
| 1246 | |
| 1247 if (::IsWindow(tooltip_hwnd_)) { | |
| 1248 // Forward mouse events through to the tooltip window | |
| 1249 MSG msg; | |
| 1250 msg.hwnd = m_hWnd; | |
| 1251 msg.message = message; | |
| 1252 msg.wParam = wparam; | |
| 1253 msg.lParam = lparam; | |
| 1254 SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, NULL, | |
| 1255 reinterpret_cast<LPARAM>(&msg)); | |
| 1256 } | |
| 1257 | |
| 1258 // TODO(jcampan): I am not sure if we should forward the message to the | |
| 1259 // TabContents first in the case of popups. If we do, we would need to | |
| 1260 // convert the click from the popup window coordinates to the TabContents' | |
| 1261 // window coordinates. For now we don't forward the message in that case to | |
| 1262 // address bug #907474. | |
| 1263 // Note: GetParent() on popup windows returns the top window and not the | |
| 1264 // parent the window was created with (the parent and the owner of the popup | |
| 1265 // is the first non-child view of the view that was specified to the create | |
| 1266 // call). So the TabContents window would have to be specified to the | |
| 1267 // RenderViewHostHWND as there is no way to retrieve it from the HWND. | |
| 1268 | |
| 1269 // Don't forward if the container is a popup or fullscreen widget. | |
| 1270 if (!is_fullscreen_ && !close_on_deactivate_) { | |
| 1271 switch (message) { | |
| 1272 case WM_LBUTTONDOWN: | |
| 1273 case WM_MBUTTONDOWN: | |
| 1274 case WM_RBUTTONDOWN: | |
| 1275 // Finish the ongoing composition whenever a mouse click happens. | |
| 1276 // It matches IE's behavior. | |
| 1277 ime_input_.CleanupComposition(m_hWnd); | |
| 1278 // Fall through. | |
| 1279 case WM_MOUSEMOVE: | |
| 1280 case WM_MOUSELEAVE: { | |
| 1281 // Give the TabContents first crack at the message. It may want to | |
| 1282 // prevent forwarding to the renderer if some higher level browser | |
| 1283 // functionality is invoked. | |
| 1284 LPARAM parent_msg_lparam = lparam; | |
| 1285 if (message != WM_MOUSELEAVE) { | |
| 1286 // For the messages except WM_MOUSELEAVE, before forwarding them to | |
| 1287 // parent window, we should adjust cursor position from client | |
| 1288 // coordinates in current window to client coordinates in its parent | |
| 1289 // window. | |
| 1290 CPoint cursor_pos(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); | |
| 1291 ClientToScreen(&cursor_pos); | |
| 1292 GetParent().ScreenToClient(&cursor_pos); | |
| 1293 parent_msg_lparam = MAKELPARAM(cursor_pos.x, cursor_pos.y); | |
| 1294 } | |
| 1295 if (SendMessage(GetParent(), message, wparam, parent_msg_lparam) != 0) | |
| 1296 return 1; | |
| 1297 } | |
| 1298 } | |
| 1299 } | |
| 1300 | |
| 1301 ForwardMouseEventToRenderer(message, wparam, lparam); | |
| 1302 return 0; | |
| 1303 } | |
| 1304 | |
| 1305 LRESULT RenderWidgetHostViewWin::OnKeyEvent(UINT message, WPARAM wparam, | |
| 1306 LPARAM lparam, BOOL& handled) { | |
| 1307 handled = TRUE; | |
| 1308 | |
| 1309 // Force fullscreen windows to close on Escape. | |
| 1310 if (is_fullscreen_ && (message == WM_KEYDOWN || message == WM_KEYUP) && | |
| 1311 wparam == VK_ESCAPE) { | |
| 1312 SendMessage(WM_CANCELMODE); | |
| 1313 return 0; | |
| 1314 } | |
| 1315 | |
| 1316 // If we are a pop-up, forward tab related messages to our parent HWND, so | |
| 1317 // that we are dismissed appropriately and so that the focus advance in our | |
| 1318 // parent. | |
| 1319 // TODO(jcampan): http://b/issue?id=1192881 Could be abstracted in the | |
| 1320 // FocusManager. | |
| 1321 if (close_on_deactivate_ && | |
| 1322 (((message == WM_KEYDOWN || message == WM_KEYUP) && (wparam == VK_TAB)) || | |
| 1323 (message == WM_CHAR && wparam == L'\t'))) { | |
| 1324 DCHECK(parent_hwnd_); | |
| 1325 // First close the pop-up. | |
| 1326 SendMessage(WM_CANCELMODE); | |
| 1327 // Then move the focus by forwarding the tab key to the parent. | |
| 1328 return ::SendMessage(parent_hwnd_, message, wparam, lparam); | |
| 1329 } | |
| 1330 | |
| 1331 if (!render_widget_host_) | |
| 1332 return 0; | |
| 1333 | |
| 1334 // Bug 1845: we need to update the text direction when a user releases | |
| 1335 // either a right-shift key or a right-control key after pressing both of | |
| 1336 // them. So, we just update the text direction while a user is pressing the | |
| 1337 // keys, and we notify the text direction when a user releases either of them. | |
| 1338 // Bug 9718: http://crbug.com/9718 To investigate IE and notepad, this | |
| 1339 // shortcut is enabled only on a PC having RTL keyboard layouts installed. | |
| 1340 // We should emulate them. | |
| 1341 if (ui::ImeInput::IsRTLKeyboardLayoutInstalled()) { | |
| 1342 if (message == WM_KEYDOWN) { | |
| 1343 if (wparam == VK_SHIFT) { | |
| 1344 base::i18n::TextDirection dir; | |
| 1345 if (ui::ImeInput::IsCtrlShiftPressed(&dir)) { | |
| 1346 render_widget_host_->UpdateTextDirection( | |
| 1347 dir == base::i18n::RIGHT_TO_LEFT ? | |
| 1348 WebKit::WebTextDirectionRightToLeft : | |
| 1349 WebKit::WebTextDirectionLeftToRight); | |
| 1350 } | |
| 1351 } else if (wparam != VK_CONTROL) { | |
| 1352 // Bug 9762: http://crbug.com/9762 A user pressed a key except shift | |
| 1353 // and control keys. | |
| 1354 // When a user presses a key while he/she holds control and shift keys, | |
| 1355 // we cancel sending an IPC message in NotifyTextDirection() below and | |
| 1356 // ignore succeeding UpdateTextDirection() calls while we call | |
| 1357 // NotifyTextDirection(). | |
| 1358 // To cancel it, this call set a flag that prevents sending an IPC | |
| 1359 // message in NotifyTextDirection() only if we are going to send it. | |
| 1360 // It is harmless to call this function if we aren't going to send it. | |
| 1361 render_widget_host_->CancelUpdateTextDirection(); | |
| 1362 } | |
| 1363 } else if (message == WM_KEYUP && | |
| 1364 (wparam == VK_SHIFT || wparam == VK_CONTROL)) { | |
| 1365 // We send an IPC message only if we need to update the text direction. | |
| 1366 render_widget_host_->NotifyTextDirection(); | |
| 1367 } | |
| 1368 } | |
| 1369 | |
| 1370 // Special processing for enter key: When user hits enter in omnibox | |
| 1371 // we change focus to render host after the navigation, so repeat WM_KEYDOWNs | |
| 1372 // and WM_KEYUP are going to render host, despite being initiated in other | |
| 1373 // window. This code filters out these messages. | |
| 1374 bool ignore_keyboard_event = false; | |
| 1375 if (wparam == VK_RETURN) { | |
| 1376 if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) { | |
| 1377 if (KF_REPEAT & HIWORD(lparam)) { | |
| 1378 // this is a repeated key | |
| 1379 if (!capture_enter_key_) | |
| 1380 ignore_keyboard_event = true; | |
| 1381 } else { | |
| 1382 capture_enter_key_ = true; | |
| 1383 } | |
| 1384 } else if (message == WM_KEYUP || message == WM_SYSKEYUP) { | |
| 1385 if (!capture_enter_key_) | |
| 1386 ignore_keyboard_event = true; | |
| 1387 capture_enter_key_ = false; | |
| 1388 } else { | |
| 1389 // Ignore all other keyboard events for the enter key if not captured. | |
| 1390 if (!capture_enter_key_) | |
| 1391 ignore_keyboard_event = true; | |
| 1392 } | |
| 1393 } | |
| 1394 | |
| 1395 if (render_widget_host_ && !ignore_keyboard_event) { | |
| 1396 render_widget_host_->ForwardKeyboardEvent( | |
| 1397 NativeWebKeyboardEvent(m_hWnd, message, wparam, lparam)); | |
| 1398 } | |
| 1399 return 0; | |
| 1400 } | |
| 1401 | |
| 1402 LRESULT RenderWidgetHostViewWin::OnWheelEvent(UINT message, WPARAM wparam, | |
| 1403 LPARAM lparam, BOOL& handled) { | |
| 1404 // Forward the mouse-wheel message to the window under the mouse if it belongs | |
| 1405 // to us. | |
| 1406 if (message == WM_MOUSEWHEEL && | |
| 1407 views::RerouteMouseWheel(m_hWnd, wparam, lparam)) { | |
| 1408 handled = TRUE; | |
| 1409 return 0; | |
| 1410 } | |
| 1411 | |
| 1412 // Workaround for Thinkpad mousewheel driver. We get mouse wheel/scroll | |
| 1413 // messages even if we are not in the foreground. So here we check if | |
| 1414 // we have any owned popup windows in the foreground and dismiss them. | |
| 1415 if (m_hWnd != GetForegroundWindow()) { | |
| 1416 HWND toplevel_hwnd = ::GetAncestor(m_hWnd, GA_ROOT); | |
| 1417 EnumThreadWindows( | |
| 1418 GetCurrentThreadId(), | |
| 1419 DismissOwnedPopups, | |
| 1420 reinterpret_cast<LPARAM>(toplevel_hwnd)); | |
| 1421 } | |
| 1422 | |
| 1423 // This is a bit of a hack, but will work for now since we don't want to | |
| 1424 // pollute this object with TabContents-specific functionality... | |
| 1425 bool handled_by_TabContents = false; | |
| 1426 if (!is_fullscreen_ && GetParent()) { | |
| 1427 // Use a special reflected message to break recursion. If we send | |
| 1428 // WM_MOUSEWHEEL, the focus manager subclass of web contents will | |
| 1429 // route it back here. | |
| 1430 MSG new_message = {0}; | |
| 1431 new_message.hwnd = m_hWnd; | |
| 1432 new_message.message = message; | |
| 1433 new_message.wParam = wparam; | |
| 1434 new_message.lParam = lparam; | |
| 1435 | |
| 1436 handled_by_TabContents = | |
| 1437 !!::SendMessage(GetParent(), views::kReflectedMessage, 0, | |
| 1438 reinterpret_cast<LPARAM>(&new_message)); | |
| 1439 } | |
| 1440 | |
| 1441 if (!handled_by_TabContents && render_widget_host_) { | |
| 1442 render_widget_host_->ForwardWheelEvent( | |
| 1443 WebInputEventFactory::mouseWheelEvent(m_hWnd, message, wparam, | |
| 1444 lparam)); | |
| 1445 } | |
| 1446 handled = TRUE; | |
| 1447 return 0; | |
| 1448 } | |
| 1449 | |
| 1450 LRESULT RenderWidgetHostViewWin::OnMouseActivate(UINT message, | |
| 1451 WPARAM wparam, | |
| 1452 LPARAM lparam, | |
| 1453 BOOL& handled) { | |
| 1454 if (!render_widget_host_) | |
| 1455 return MA_NOACTIVATE; | |
| 1456 | |
| 1457 if (!IsActivatable()) | |
| 1458 return MA_NOACTIVATE; | |
| 1459 | |
| 1460 HWND focus_window = GetFocus(); | |
| 1461 if (!::IsWindow(focus_window) || !IsChild(focus_window)) { | |
| 1462 // We handle WM_MOUSEACTIVATE to set focus to the underlying plugin | |
| 1463 // child window. This is to ensure that keyboard events are received | |
| 1464 // by the plugin. The correct way to fix this would be send over | |
| 1465 // an event to the renderer which would then eventually send over | |
| 1466 // a setFocus call to the plugin widget. This would ensure that | |
| 1467 // the renderer (webkit) knows about the plugin widget receiving | |
| 1468 // focus. | |
| 1469 // TODO(iyengar) Do the right thing as per the above comment. | |
| 1470 POINT cursor_pos = {0}; | |
| 1471 ::GetCursorPos(&cursor_pos); | |
| 1472 ::ScreenToClient(m_hWnd, &cursor_pos); | |
| 1473 HWND child_window = ::RealChildWindowFromPoint(m_hWnd, cursor_pos); | |
| 1474 if (::IsWindow(child_window) && child_window != m_hWnd) { | |
| 1475 if (ui::GetClassName(child_window) == | |
| 1476 webkit::npapi::kWrapperNativeWindowClassName) | |
| 1477 child_window = ::GetWindow(child_window, GW_CHILD); | |
| 1478 | |
| 1479 ::SetFocus(child_window); | |
| 1480 return MA_NOACTIVATE; | |
| 1481 } | |
| 1482 } | |
| 1483 handled = FALSE; | |
| 1484 render_widget_host_->OnMouseActivate(); | |
| 1485 return MA_ACTIVATE; | |
| 1486 } | |
| 1487 | |
| 1488 void RenderWidgetHostViewWin::OnAccessibilityNotifications( | |
| 1489 const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { | |
| 1490 if (!browser_accessibility_manager_.get()) { | |
| 1491 browser_accessibility_manager_.reset( | |
| 1492 BrowserAccessibilityManager::CreateEmptyDocument( | |
| 1493 m_hWnd, static_cast<WebAccessibility::State>(0), this)); | |
| 1494 } | |
| 1495 browser_accessibility_manager_->OnAccessibilityNotifications(params); | |
| 1496 } | |
| 1497 | |
| 1498 void RenderWidgetHostViewWin::Observe(int type, | |
| 1499 const NotificationSource& source, | |
| 1500 const NotificationDetails& details) { | |
| 1501 DCHECK(type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED); | |
| 1502 | |
| 1503 // Get the RenderProcessHost that posted this notification, and exit | |
| 1504 // if it's not the one associated with this host view. | |
| 1505 RenderProcessHost* render_process_host = | |
| 1506 Source<RenderProcessHost>(source).ptr(); | |
| 1507 DCHECK(render_process_host); | |
| 1508 if (!render_widget_host_ || | |
| 1509 render_process_host != render_widget_host_->process()) | |
| 1510 return; | |
| 1511 | |
| 1512 // If it was our RenderProcessHost that posted the notification, | |
| 1513 // clear the BrowserAccessibilityManager, because the renderer is | |
| 1514 // dead and any accessibility information we have is now stale. | |
| 1515 browser_accessibility_manager_.reset(NULL); | |
| 1516 } | |
| 1517 | |
| 1518 static void PaintCompositorHostWindow(HWND hWnd) { | |
| 1519 PAINTSTRUCT paint; | |
| 1520 BeginPaint(hWnd, &paint); | |
| 1521 | |
| 1522 RenderWidgetHostViewWin* win = static_cast<RenderWidgetHostViewWin*>( | |
| 1523 ui::GetWindowUserData(hWnd)); | |
| 1524 // Trigger composite to rerender window. | |
| 1525 if (win) | |
| 1526 win->ScheduleComposite(); | |
| 1527 | |
| 1528 EndPaint(hWnd, &paint); | |
| 1529 } | |
| 1530 | |
| 1531 // WndProc for the compositor host window. We use this instead of Default so | |
| 1532 // we can drop WM_PAINT and WM_ERASEBKGD messages on the floor. | |
| 1533 static LRESULT CALLBACK CompositorHostWindowProc(HWND hWnd, UINT message, | |
| 1534 WPARAM wParam, LPARAM lParam) { | |
| 1535 switch (message) { | |
| 1536 case WM_ERASEBKGND: | |
| 1537 return 0; | |
| 1538 case WM_DESTROY: | |
| 1539 ui::SetWindowUserData(hWnd, NULL); | |
| 1540 return 0; | |
| 1541 case WM_PAINT: | |
| 1542 PaintCompositorHostWindow(hWnd); | |
| 1543 return 0; | |
| 1544 default: | |
| 1545 return DefWindowProc(hWnd, message, wParam, lParam); | |
| 1546 } | |
| 1547 } | |
| 1548 | |
| 1549 void RenderWidgetHostViewWin::ScheduleComposite() { | |
| 1550 if (render_widget_host_) | |
| 1551 render_widget_host_->ScheduleComposite(); | |
| 1552 } | |
| 1553 | |
| 1554 // Creates a HWND within the RenderWidgetHostView that will serve as a host | |
| 1555 // for a HWND that the GPU process will create. The host window is used | |
| 1556 // to Z-position the GPU's window relative to other plugin windows. | |
| 1557 gfx::PluginWindowHandle RenderWidgetHostViewWin::GetCompositingSurface() { | |
| 1558 // If the window has been created, don't recreate it a second time | |
| 1559 if (compositor_host_window_) | |
| 1560 return compositor_host_window_; | |
| 1561 | |
| 1562 static ATOM window_class = 0; | |
| 1563 if (!window_class) { | |
| 1564 WNDCLASSEX wcex; | |
| 1565 wcex.cbSize = sizeof(WNDCLASSEX); | |
| 1566 wcex.style = 0; | |
| 1567 wcex.lpfnWndProc = | |
| 1568 base::win::WrappedWindowProc<CompositorHostWindowProc>; | |
| 1569 wcex.cbClsExtra = 0; | |
| 1570 wcex.cbWndExtra = 0; | |
| 1571 wcex.hInstance = GetModuleHandle(NULL); | |
| 1572 wcex.hIcon = 0; | |
| 1573 wcex.hCursor = 0; | |
| 1574 wcex.hbrBackground = NULL; | |
| 1575 wcex.lpszMenuName = 0; | |
| 1576 wcex.lpszClassName = L"CompositorHostWindowClass"; | |
| 1577 wcex.hIconSm = 0; | |
| 1578 window_class = RegisterClassEx(&wcex); | |
| 1579 DCHECK(window_class); | |
| 1580 } | |
| 1581 | |
| 1582 RECT currentRect; | |
| 1583 GetClientRect(¤tRect); | |
| 1584 | |
| 1585 // Ensure window does not have zero area because D3D cannot create a zero | |
| 1586 // area swap chain. | |
| 1587 int width = std::max(1, | |
| 1588 static_cast<int>(currentRect.right - currentRect.left)); | |
| 1589 int height = std::max(1, | |
| 1590 static_cast<int>(currentRect.bottom - currentRect.top)); | |
| 1591 | |
| 1592 compositor_host_window_ = CreateWindowEx( | |
| 1593 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, | |
| 1594 MAKEINTATOM(window_class), 0, | |
| 1595 WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_DISABLED, | |
| 1596 0, 0, width, height, m_hWnd, 0, GetModuleHandle(NULL), 0); | |
| 1597 ui::CheckWindowCreated(compositor_host_window_); | |
| 1598 | |
| 1599 ui::SetWindowUserData(compositor_host_window_, this); | |
| 1600 | |
| 1601 return static_cast<gfx::PluginWindowHandle>(compositor_host_window_); | |
| 1602 } | |
| 1603 | |
| 1604 void RenderWidgetHostViewWin::ShowCompositorHostWindow(bool show) { | |
| 1605 // When we first create the compositor, we will get a show request from | |
| 1606 // the renderer before we have gotten the create request from the GPU. In this | |
| 1607 // case, simply ignore the show request. | |
| 1608 if (compositor_host_window_ == NULL) | |
| 1609 return; | |
| 1610 | |
| 1611 if (show) { | |
| 1612 ::ShowWindow(compositor_host_window_, SW_SHOW); | |
| 1613 | |
| 1614 // Get all the child windows of this view, including the compositor window. | |
| 1615 std::vector<HWND> all_child_windows; | |
| 1616 ::EnumChildWindows(m_hWnd, AddChildWindowToVector, | |
| 1617 reinterpret_cast<LPARAM>(&all_child_windows)); | |
| 1618 | |
| 1619 // Build a list of just the plugin window handles | |
| 1620 std::vector<HWND> plugin_windows; | |
| 1621 bool compositor_host_window_found = false; | |
| 1622 for (size_t i = 0; i < all_child_windows.size(); ++i) { | |
| 1623 if (all_child_windows[i] != compositor_host_window_) | |
| 1624 plugin_windows.push_back(all_child_windows[i]); | |
| 1625 else | |
| 1626 compositor_host_window_found = true; | |
| 1627 } | |
| 1628 DCHECK(compositor_host_window_found); | |
| 1629 | |
| 1630 // Set all the plugin windows to be "after" the compositor window. | |
| 1631 // When the compositor window is created, gets placed above plugins. | |
| 1632 for (size_t i = 0; i < plugin_windows.size(); ++i) { | |
| 1633 HWND next; | |
| 1634 if (i + 1 < plugin_windows.size()) | |
| 1635 next = plugin_windows[i+1]; | |
| 1636 else | |
| 1637 next = compositor_host_window_; | |
| 1638 ::SetWindowPos(plugin_windows[i], next, 0, 0, 0, 0, | |
| 1639 SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); | |
| 1640 } | |
| 1641 } else { | |
| 1642 hide_compositor_window_at_next_paint_ = true; | |
| 1643 } | |
| 1644 } | |
| 1645 | |
| 1646 void RenderWidgetHostViewWin::SetAccessibilityFocus(int acc_obj_id) { | |
| 1647 if (!render_widget_host_) | |
| 1648 return; | |
| 1649 | |
| 1650 render_widget_host_->Send(new ViewMsg_SetAccessibilityFocus( | |
| 1651 render_widget_host_->routing_id(), acc_obj_id)); | |
| 1652 } | |
| 1653 | |
| 1654 void RenderWidgetHostViewWin::AccessibilityDoDefaultAction(int acc_obj_id) { | |
| 1655 if (!render_widget_host_) | |
| 1656 return; | |
| 1657 | |
| 1658 render_widget_host_->Send(new ViewMsg_AccessibilityDoDefaultAction( | |
| 1659 render_widget_host_->routing_id(), acc_obj_id)); | |
| 1660 } | |
| 1661 | |
| 1662 IAccessible* RenderWidgetHostViewWin::GetIAccessible() { | |
| 1663 if (render_widget_host_ && !render_widget_host_->renderer_accessible()) { | |
| 1664 // Attempt to detect screen readers by sending an event with our custom id. | |
| 1665 NotifyWinEvent(EVENT_SYSTEM_ALERT, m_hWnd, kIdCustom, CHILDID_SELF); | |
| 1666 } | |
| 1667 | |
| 1668 if (!browser_accessibility_manager_.get()) { | |
| 1669 // Return busy document tree while renderer accessibility tree loads. | |
| 1670 WebAccessibility::State busy_state = | |
| 1671 static_cast<WebAccessibility::State>(1 << WebAccessibility::STATE_BUSY); | |
| 1672 browser_accessibility_manager_.reset( | |
| 1673 BrowserAccessibilityManager::CreateEmptyDocument( | |
| 1674 m_hWnd, busy_state, this)); | |
| 1675 } | |
| 1676 | |
| 1677 return browser_accessibility_manager_->GetRoot()->toBrowserAccessibilityWin(); | |
| 1678 } | |
| 1679 | |
| 1680 LRESULT RenderWidgetHostViewWin::OnGetObject(UINT message, WPARAM wparam, | |
| 1681 LPARAM lparam, BOOL& handled) { | |
| 1682 if (kIdCustom == lparam) { | |
| 1683 // An MSAA client requestes our custom id. Assume that we have detected an | |
| 1684 // active windows screen reader. | |
| 1685 BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected(); | |
| 1686 render_widget_host_->EnableRendererAccessibility(); | |
| 1687 | |
| 1688 // Return with failure. | |
| 1689 return static_cast<LRESULT>(0L); | |
| 1690 } | |
| 1691 | |
| 1692 if (lparam != OBJID_CLIENT) { | |
| 1693 handled = false; | |
| 1694 return static_cast<LRESULT>(0L); | |
| 1695 } | |
| 1696 | |
| 1697 IAccessible* iaccessible = GetIAccessible(); | |
| 1698 if (iaccessible) | |
| 1699 return LresultFromObject(IID_IAccessible, wparam, iaccessible); | |
| 1700 | |
| 1701 handled = false; | |
| 1702 return static_cast<LRESULT>(0L); | |
| 1703 } | |
| 1704 | |
| 1705 LRESULT RenderWidgetHostViewWin::OnParentNotify(UINT message, WPARAM wparam, | |
| 1706 LPARAM lparam, BOOL& handled) { | |
| 1707 handled = FALSE; | |
| 1708 | |
| 1709 if (!render_widget_host_) | |
| 1710 return 0; | |
| 1711 | |
| 1712 switch (LOWORD(wparam)) { | |
| 1713 case WM_LBUTTONDOWN: | |
| 1714 case WM_RBUTTONDOWN: | |
| 1715 case WM_MBUTTONDOWN: | |
| 1716 render_widget_host_->StartUserGesture(); | |
| 1717 break; | |
| 1718 default: | |
| 1719 break; | |
| 1720 } | |
| 1721 return 0; | |
| 1722 } | |
| 1723 | |
| 1724 void RenderWidgetHostViewWin::OnFinalMessage(HWND window) { | |
| 1725 // When the render widget host is being destroyed, it ends up calling | |
| 1726 // Destroy() which NULLs render_widget_host_. | |
| 1727 // Note: the following bug http://crbug.com/24248 seems to report that | |
| 1728 // OnFinalMessage is called with a deleted |render_widget_host_|. It is not | |
| 1729 // clear how this could happen, hence the NULLing of render_widget_host_ | |
| 1730 // above. | |
| 1731 if (!render_widget_host_ && !being_destroyed_) { | |
| 1732 // If you hit this NOTREACHED, please add a comment to report it on | |
| 1733 // http://crbug.com/24248, including what you did when it happened and if | |
| 1734 // you can repro. | |
| 1735 NOTREACHED(); | |
| 1736 } | |
| 1737 if (render_widget_host_) | |
| 1738 render_widget_host_->ViewDestroyed(); | |
| 1739 delete this; | |
| 1740 } | |
| 1741 | |
| 1742 void RenderWidgetHostViewWin::TrackMouseLeave(bool track) { | |
| 1743 if (track == track_mouse_leave_) | |
| 1744 return; | |
| 1745 track_mouse_leave_ = track; | |
| 1746 | |
| 1747 DCHECK(m_hWnd); | |
| 1748 | |
| 1749 TRACKMOUSEEVENT tme; | |
| 1750 tme.cbSize = sizeof(TRACKMOUSEEVENT); | |
| 1751 tme.dwFlags = TME_LEAVE; | |
| 1752 if (!track_mouse_leave_) | |
| 1753 tme.dwFlags |= TME_CANCEL; | |
| 1754 tme.hwndTrack = m_hWnd; | |
| 1755 | |
| 1756 TrackMouseEvent(&tme); | |
| 1757 } | |
| 1758 | |
| 1759 bool RenderWidgetHostViewWin::Send(IPC::Message* message) { | |
| 1760 if (!render_widget_host_) | |
| 1761 return false; | |
| 1762 return render_widget_host_->Send(message); | |
| 1763 } | |
| 1764 | |
| 1765 void RenderWidgetHostViewWin::EnsureTooltip() { | |
| 1766 UINT message = TTM_NEWTOOLRECT; | |
| 1767 | |
| 1768 TOOLINFO ti; | |
| 1769 ti.cbSize = sizeof(ti); | |
| 1770 ti.hwnd = m_hWnd; | |
| 1771 ti.uId = 0; | |
| 1772 if (!::IsWindow(tooltip_hwnd_)) { | |
| 1773 message = TTM_ADDTOOL; | |
| 1774 tooltip_hwnd_ = CreateWindowEx( | |
| 1775 WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), | |
| 1776 TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, m_hWnd, NULL, | |
| 1777 NULL, NULL); | |
| 1778 if (!tooltip_hwnd_) { | |
| 1779 // Tooltip creation can inexplicably fail. See bug 82913 for details. | |
| 1780 LOG_GETLASTERROR(WARNING) << | |
| 1781 "Tooltip creation failed, tooltips won't work"; | |
| 1782 return; | |
| 1783 } | |
| 1784 ti.uFlags = TTF_TRANSPARENT; | |
| 1785 ti.lpszText = LPSTR_TEXTCALLBACK; | |
| 1786 | |
| 1787 // Ensure web content tooltips are displayed for at least this amount of | |
| 1788 // time, to give users a chance to read longer messages. | |
| 1789 const int kMinimumAutopopDurationMs = 10 * 1000; | |
| 1790 int autopop_duration_ms = | |
| 1791 SendMessage(tooltip_hwnd_, TTM_GETDELAYTIME, TTDT_AUTOPOP, NULL); | |
| 1792 if (autopop_duration_ms < kMinimumAutopopDurationMs) { | |
| 1793 SendMessage(tooltip_hwnd_, TTM_SETDELAYTIME, TTDT_AUTOPOP, | |
| 1794 kMinimumAutopopDurationMs); | |
| 1795 } | |
| 1796 } | |
| 1797 | |
| 1798 CRect cr; | |
| 1799 GetClientRect(&ti.rect); | |
| 1800 SendMessage(tooltip_hwnd_, message, NULL, reinterpret_cast<LPARAM>(&ti)); | |
| 1801 } | |
| 1802 | |
| 1803 void RenderWidgetHostViewWin::ResetTooltip() { | |
| 1804 if (::IsWindow(tooltip_hwnd_)) | |
| 1805 ::DestroyWindow(tooltip_hwnd_); | |
| 1806 tooltip_hwnd_ = NULL; | |
| 1807 } | |
| 1808 | |
| 1809 void RenderWidgetHostViewWin::ForwardMouseEventToRenderer(UINT message, | |
| 1810 WPARAM wparam, | |
| 1811 LPARAM lparam) { | |
| 1812 if (!render_widget_host_) | |
| 1813 return; | |
| 1814 | |
| 1815 WebMouseEvent event( | |
| 1816 WebInputEventFactory::mouseEvent(m_hWnd, message, wparam, lparam)); | |
| 1817 | |
| 1818 // Send the event to the renderer before changing mouse capture, so that the | |
| 1819 // capturelost event arrives after mouseup. | |
| 1820 render_widget_host_->ForwardMouseEvent(event); | |
| 1821 | |
| 1822 switch (event.type) { | |
| 1823 case WebInputEvent::MouseMove: | |
| 1824 TrackMouseLeave(true); | |
| 1825 break; | |
| 1826 case WebInputEvent::MouseLeave: | |
| 1827 TrackMouseLeave(false); | |
| 1828 break; | |
| 1829 case WebInputEvent::MouseDown: | |
| 1830 SetCapture(); | |
| 1831 break; | |
| 1832 case WebInputEvent::MouseUp: | |
| 1833 if (GetCapture() == m_hWnd) | |
| 1834 ReleaseCapture(); | |
| 1835 break; | |
| 1836 } | |
| 1837 | |
| 1838 if (IsActivatable() && event.type == WebInputEvent::MouseDown) { | |
| 1839 // This is a temporary workaround for bug 765011 to get focus when the | |
| 1840 // mouse is clicked. This happens after the mouse down event is sent to | |
| 1841 // the renderer because normally Windows does a WM_SETFOCUS after | |
| 1842 // WM_LBUTTONDOWN. | |
| 1843 SetFocus(); | |
| 1844 } | |
| 1845 } | |
| 1846 | |
| 1847 void RenderWidgetHostViewWin::ShutdownHost() { | |
| 1848 shutdown_factory_.RevokeAll(); | |
| 1849 if (render_widget_host_) | |
| 1850 render_widget_host_->Shutdown(); | |
| 1851 // Do not touch any members at this point, |this| has been deleted. | |
| 1852 } | |
| 1853 | |
| 1854 void RenderWidgetHostViewWin::DoPopupOrFullscreenInit(HWND parent_hwnd, | |
| 1855 const gfx::Rect& pos, | |
| 1856 DWORD ex_style) { | |
| 1857 parent_hwnd_ = parent_hwnd; | |
| 1858 Create(parent_hwnd_, NULL, NULL, WS_POPUP, ex_style); | |
| 1859 MoveWindow(pos.x(), pos.y(), pos.width(), pos.height(), TRUE); | |
| 1860 // To show tooltip on popup window.(e.g. title in <select>) | |
| 1861 // Popups default to showing, which means |DidBecomeSelected()| isn't invoked. | |
| 1862 // Ensure the tooltip is created otherwise tooltips are never shown. | |
| 1863 EnsureTooltip(); | |
| 1864 ShowWindow(IsActivatable() ? SW_SHOW : SW_SHOWNA); | |
| 1865 } | |
| OLD | NEW |