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