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