0

Reland: Window Placement: Support multi-screen info from Mac shim

The original CL (uploaded as PS1) broke compile on iOS:
  https://chromium-review.googlesource.com/c/chromium/src/+/2851592
PS3 fixes compile by using a more canonical EXPECT_DCHECK* test macro.

Pluralize display observation/plumbing/caching for Mac renderers:
- Observe multi-display changes from RWHNSViewBridge (shim).
- Plumb multi-display structs: RWHVCocoa (shim) -> RWHVMac (browser).
- Cache multi-display state in RWHVMac & BrowserCompositorMac.

Enable Mac multi-screen APIs via RenderWidgetHostImpl::GetScreenInfos:
- Get RWHI's multi-screen info from RWHV*, like legacy singular info.
- Add RWHVBase::GetDisplays(), to return live info from display::Screen.
- Override GetDisplays() in RWHVMac, to return cached remote shim info.

Avoid incorrect usage of process-local screen info on RWHVMac:
- Remove improper OnSynchronizedDisplayPropertiesChanged override.
- Start/Stop local Screen observation in MigrateNSViewBridge.

Add DisplayList mojo struct & traits; add current id; update API/impl.

Make RWHVMac better conform with RWHVBase screen info patterns:
- Make RWHVBase::UpdateScreenInfo virtual, override in RWHVMac.
- Inline HasDisplayPropertyChanged in RWHVBase::UpdateScreenInfo.
- Inline BrowserCompositorMac::GetRendererScreenInfo in RWHVMac.

Add some explanatory comments and TODOs with associated bugs.

Bug: 1194700, 1169291, 1169312, 1204273, 1207996
Change-Id: I1a304f0df347b5b611608bc5412d81b70abe5999
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2893191
Commit-Queue: Mike Wasserman <msw@chromium.org>
Auto-Submit: Mike Wasserman <msw@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#883300}
This commit is contained in:
Mike Wasserman
2021-05-16 00:46:26 +00:00
committed by Chromium LUCI CQ
parent 8133aef8d3
commit 11554ff99d
36 changed files with 693 additions and 242 deletions

@ -663,7 +663,6 @@ class ExperimentalFullscreenControllerInteractiveTest
// where the window server's async handling of the fullscreen window state may
// transition the window into fullscreen on the actual (non-mocked) display
// bounds before or after the window bounds checks, yielding flaky results.
// TODO(crbug.com/1194700): Disabled on Mac because of GetScreenInfos staleness.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_FullscreenOnSecondDisplay DISABLED_FullscreenOnSecondDisplay
#else
@ -748,7 +747,6 @@ IN_PROC_BROWSER_TEST_F(ExperimentalFullscreenControllerInteractiveTest,
// transition the window into fullscreen on the actual (non-mocked) display
// bounds before or after the window bounds checks, yielding flaky results.
// TODO(msw): Parameterize the maximized state and combine with the test above.
// TODO(crbug.com/1194700): Disabled on Mac because of GetScreenInfos staleness.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_FullscreenOnSecondDisplayMaximized \
DISABLED_FullscreenOnSecondDisplayMaximized
@ -838,7 +836,6 @@ IN_PROC_BROWSER_TEST_F(ExperimentalFullscreenControllerInteractiveTest,
// where the window server's async handling of the fullscreen window state may
// transition the window into fullscreen on the actual (non-mocked) display
// bounds before or after the window bounds checks, yielding flaky results.
// TODO(crbug.com/1194700): Disabled on Mac because of GetScreenInfos staleness.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_FullscreenChangeDisplays DISABLED_FullscreenChangeDisplays
#else

@ -35,7 +35,6 @@ class WindowPlacementTest : public InProcessBrowserTest {
// TODO(crbug.com/1183791): Disabled on non-ChromeOS because of races with
// SetScreenInstance and observers not being notified.
// TODO(crbug.com/1194700): Disabled on Mac because of GetScreenInfos staleness.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_OnScreensChangeEvent DISABLED_OnScreensChangeEvent
#else

@ -80,8 +80,9 @@ class RenderWidgetHostNSViewBridge : public mojom::RenderWidgetHostNSView,
bool IsPopup() const { return !!popup_window_; }
// display::DisplayObserver implementation.
void OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) override;
void OnDisplayAdded(const display::Display&) override;
void OnDisplayRemoved(const display::Display&) override;
void OnDisplayMetricsChanged(const display::Display&, uint32_t) override;
// The NSView used for input and display.
base::scoped_nsobject<RenderWidgetHostViewCocoa> cocoa_view_;

@ -215,9 +215,17 @@ void RenderWidgetHostNSViewBridge::SetShowingContextMenu(bool showing) {
[cocoa_view_ setShowingContextMenu:showing];
}
void RenderWidgetHostNSViewBridge::OnDisplayAdded(const display::Display&) {
[cocoa_view_ updateScreenProperties];
}
void RenderWidgetHostNSViewBridge::OnDisplayRemoved(const display::Display&) {
[cocoa_view_ updateScreenProperties];
}
void RenderWidgetHostNSViewBridge::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
const display::Display&,
uint32_t) {
// Note that -updateScreenProperties is also be called by the notification
// NSWindowDidChangeBackingPropertiesNotification (some of these calls
// will be redundant).

@ -1391,11 +1391,13 @@ void ExtractUnderlines(NSAttributedString* string,
if (!enclosingWindow)
return;
display::Screen* screen = display::Screen::GetScreen();
// TODO(ccameron): This will call [enclosingWindow screen], which may return
// nil. Do that call here to avoid sending bogus display info to the host.
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestView(self);
_host->OnDisplayChanged(display);
display::DisplayList display_list(screen->GetAllDisplays(),
screen->GetPrimaryDisplay().id(),
screen->GetDisplayNearestView(self).id());
_host->OnDisplaysChanged(display_list);
}
// This will be called when the NSView's NSWindow moves from one NSScreen to

@ -14,11 +14,10 @@
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/common/surfaces/scoped_surface_id_allocator.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "third_party/blink/public/common/widget/screen_info.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer_observer.h"
#include "ui/display/display.h"
#include "ui/display/display_list.h"
#include "ui/gfx/ca_layer_params.h"
namespace ui {
@ -53,7 +52,7 @@ class CONTENT_EXPORT BrowserCompositorMac : public DelegatedFrameHostClient,
ui::AcceleratedWidgetMacNSView* accelerated_widget_mac_ns_view,
BrowserCompositorMacClient* client,
bool render_widget_host_is_hidden,
const display::Display& initial_display,
const display::DisplayList& initial_display_list,
const viz::FrameSinkId& frame_sink_id);
~BrowserCompositorMac() override;
@ -78,7 +77,7 @@ class CONTENT_EXPORT BrowserCompositorMac : public DelegatedFrameHostClient,
// true if any properties that need to be communicated to the
// RenderWidgetHostImpl have changed.
bool UpdateSurfaceFromNSView(const gfx::Size& new_size_dip,
const display::Display& new_display);
const display::DisplayList& new_display_list);
// Update the renderer's SurfaceId to reflect |new_size_in_pixels| in
// anticipation of the NSView resizing during auto-resize.
@ -110,7 +109,7 @@ class CONTENT_EXPORT BrowserCompositorMac : public DelegatedFrameHostClient,
}
const gfx::Size& GetRendererSize() const { return dfh_size_dip_; }
void GetRendererScreenInfo(blink::ScreenInfo* screen_info) const;
const display::DisplayList& display_list() const { return display_list_; }
viz::ScopedSurfaceIdAllocator GetScopedRendererSurfaceIdAllocator(
base::OnceCallback<void()> allocation_task);
const viz::LocalSurfaceId& GetRendererLocalSurfaceId();
@ -189,7 +188,10 @@ class CONTENT_EXPORT BrowserCompositorMac : public DelegatedFrameHostClient,
viz::ParentLocalSurfaceIdAllocator dfh_local_surface_id_allocator_;
gfx::Size dfh_size_pixels_;
gfx::Size dfh_size_dip_;
display::Display dfh_display_;
// Cached info about the displays relevant to the RenderWidgetHostView.
// TODO(crbug.com/1169312): Consolidate this cache with that of RWHVBase.
display::DisplayList display_list_;
// This is used to cache the saved frame state to be used for tab switching
// metric. In tab switch in MacOS, DelegatedFrameHost::WasShown is called once

@ -17,7 +17,6 @@
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/renderer_host/display_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/context_factory.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
@ -25,7 +24,6 @@
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/layout.h"
#include "ui/compositor/recyclable_compositor_mac.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/size_conversions.h"
@ -52,11 +50,11 @@ BrowserCompositorMac::BrowserCompositorMac(
ui::AcceleratedWidgetMacNSView* accelerated_widget_mac_ns_view,
BrowserCompositorMacClient* client,
bool render_widget_host_is_hidden,
const display::Display& initial_display,
const display::DisplayList& initial_display_list,
const viz::FrameSinkId& frame_sink_id)
: client_(client),
accelerated_widget_mac_ns_view_(accelerated_widget_mac_ns_view),
dfh_display_(initial_display),
display_list_(initial_display_list),
weak_factory_(this) {
g_browser_compositors.Get().insert(this);
@ -118,22 +116,36 @@ void BrowserCompositorMac::SetBackgroundColor(SkColor background_color) {
bool BrowserCompositorMac::UpdateSurfaceFromNSView(
const gfx::Size& new_size_dip,
const display::Display& new_display) {
if (new_size_dip == dfh_size_dip_ && new_display == dfh_display_)
return false;
const display::DisplayList& new_display_list) {
if (new_size_dip == dfh_size_dip_) {
if (new_display_list == display_list_)
return false;
if (*new_display_list.GetCurrentDisplayIterator() ==
*display_list_.GetCurrentDisplayIterator()) {
// Another display changed; no SurfaceId updates are needed here, but
// returning true instructs the caller to notify its RenderWidgetHostImpl.
// That will synchronize visual properties thoughout the frame tree,
// updating cached screen info and events exposed by web platform APIs.
display_list_ = new_display_list;
return true;
}
}
bool is_resize = !dfh_size_dip_.IsEmpty() && new_size_dip != dfh_size_dip_;
bool needs_new_surface_id =
new_size_dip != dfh_size_dip_ ||
new_display.device_scale_factor() != dfh_display_.device_scale_factor();
new_display_list.GetCurrentDisplayIterator()->device_scale_factor() !=
display_list_.GetCurrentDisplayIterator()->device_scale_factor();
dfh_display_ = new_display;
display_list_ = new_display_list;
dfh_size_dip_ = new_size_dip;
const display::Display& display = *display_list_.GetCurrentDisplayIterator();
// The device scale factor is always an integer, so the result here is also
// an integer.
dfh_size_pixels_ = gfx::ToRoundedSize(gfx::ConvertSizeToPixels(
dfh_size_dip_, dfh_display_.device_scale_factor()));
dfh_size_pixels_ = gfx::ToRoundedSize(
gfx::ConvertSizeToPixels(dfh_size_dip_, display.device_scale_factor()));
root_layer_->SetBounds(gfx::Rect(dfh_size_dip_));
if (needs_new_surface_id) {
@ -145,8 +157,8 @@ bool BrowserCompositorMac::UpdateSurfaceFromNSView(
if (recyclable_compositor_) {
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
dfh_display_.device_scale_factor(),
dfh_display_.color_spaces());
display.device_scale_factor(),
display.color_spaces());
}
return true;
@ -159,16 +171,19 @@ void BrowserCompositorMac::UpdateSurfaceFromChild(
const viz::LocalSurfaceId& child_local_surface_id) {
if (dfh_local_surface_id_allocator_.UpdateFromChild(child_local_surface_id)) {
if (auto_resize_enabled) {
dfh_display_.set_device_scale_factor(new_device_scale_factor);
// TODO(crbug.com/1169312): Update RWHVMac's cached screen info similarly?
display::Display display = *display_list_.GetCurrentDisplayIterator();
display.set_device_scale_factor(new_device_scale_factor);
display_list_.UpdateDisplay(display);
// TODO(danakj): We should avoid lossy conversions to integer DIPs.
dfh_size_dip_ = gfx::ToFlooredSize(gfx::ConvertSizeToDips(
new_size_in_pixels, dfh_display_.device_scale_factor()));
new_size_in_pixels, display.device_scale_factor()));
dfh_size_pixels_ = new_size_in_pixels;
root_layer_->SetBounds(gfx::Rect(dfh_size_dip_));
if (recyclable_compositor_) {
recyclable_compositor_->UpdateSurface(
dfh_size_pixels_, dfh_display_.device_scale_factor(),
dfh_display_.color_spaces());
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
display.device_scale_factor(),
display.color_spaces());
}
}
delegated_frame_host_->EmbedSurface(
@ -270,9 +285,11 @@ void BrowserCompositorMac::TransitionToState(State new_state) {
recyclable_compositor_ =
ui::RecyclableCompositorMacFactory::Get()->CreateCompositor(
content::GetContextFactory());
const display::Display& display =
*display_list_.GetCurrentDisplayIterator();
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
dfh_display_.device_scale_factor(),
dfh_display_.color_spaces());
display.device_scale_factor(),
display.color_spaces());
recyclable_compositor_->compositor()->SetRootLayer(root_layer_.get());
recyclable_compositor_->compositor()->SetBackgroundColor(background_color_);
recyclable_compositor_->widget()->SetNSView(
@ -330,7 +347,7 @@ void BrowserCompositorMac::OnFrameTokenChanged(
}
float BrowserCompositorMac::GetDeviceScaleFactor() const {
return dfh_display_.device_scale_factor();
return display_list_.GetCurrentDisplayIterator()->device_scale_factor();
}
void BrowserCompositorMac::InvalidateLocalSurfaceIdOnEviction() {
@ -382,14 +399,12 @@ void BrowserCompositorMac::SetParentUiLayer(ui::Layer* new_parent_ui_layer) {
}
bool BrowserCompositorMac::ForceNewSurfaceForTesting() {
display::Display new_display(dfh_display_);
new_display.set_device_scale_factor(new_display.device_scale_factor() * 2.0f);
return UpdateSurfaceFromNSView(dfh_size_dip_, new_display);
}
void BrowserCompositorMac::GetRendererScreenInfo(
blink::ScreenInfo* screen_info) const {
DisplayUtil::DisplayToScreenInfo(screen_info, dfh_display_);
display::DisplayList new_display_list(display_list_);
display::Display display = *new_display_list.GetCurrentDisplayIterator();
// TODO(crbug.com/1169312): Update RWHVMac's cached screen info similarly?
display.set_device_scale_factor(display.device_scale_factor() * 2.0f);
new_display_list.UpdateDisplay(display);
return UpdateSurfaceFromNSView(dfh_size_dip_, new_display_list);
}
viz::ScopedSurfaceIdAllocator

@ -48,6 +48,8 @@ void DisplayUtil::DisplayToScreenInfo(blink::ScreenInfo* screen_info,
screen_info->orientation_type = GetOrientationTypeForDesktop(display);
#endif
// TODO(crbug.com/1194700 and crbug.com/1182855): Use cross-process screen
// info caches, not local-process info, for child frames and Mac's shim.
auto* screen = display::Screen::GetScreen();
// Some tests are run with no Screen initialized.
screen_info->is_extended = screen && screen->GetNumDisplays() > 1;

@ -926,7 +926,7 @@ IN_PROC_BROWSER_TEST_P(
EXPECT_EQ(ui::TouchSelectionController::INACTIVE,
rwhva->selection_controller()->active_status());
EXPECT_FALSE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning());
EXPECT_EQ(2.f, rwhva->current_device_scale_factor());
EXPECT_EQ(2.f, rwhva->GetCurrentDeviceScaleFactor());
EXPECT_EQ(gfx::RectF(),
rwhva->selection_controller()->GetVisibleRectBetweenBounds());

@ -2031,14 +2031,6 @@ blink::ScreenInfos RenderWidgetHostImpl::GetScreenInfos() {
blink::ScreenInfo current_screen_info;
GetScreenInfo(&current_screen_info);
// TODO(enne): RenderWidgetHostViewMac caches the ScreenInfo and chooses
// not to change it during resizes. This means that the RWHV::GetScreenInfo
// returned might be stale wrt GetAllDisplays() below. Fix this.
// For now, just return the legacy screen info for mac.
#if defined(OS_MAC)
return blink::ScreenInfos(current_screen_info);
#else
// If this widget has not been connected to a view yet (or has been
// disconnected), the display code may be using a fake primary display.
// In these cases, temporarily return the legacy screen info until
@ -2056,12 +2048,10 @@ blink::ScreenInfos RenderWidgetHostImpl::GetScreenInfos() {
return blink::ScreenInfos(current_screen_info);
}
display::Screen* screen = display::Screen::GetScreen();
if (!screen) {
return blink::ScreenInfos(current_screen_info);
}
const std::vector<display::Display>& displays = screen->GetAllDisplays();
// Get displays from RenderWidgetHostView, not directly from display::Screen.
// This helps maintain consistency with the legacy singular GetScreenInfo().
// For example, Mac may cache display info from a remote process NSWindow.
const std::vector<display::Display>& displays = view_->GetDisplays();
// Just return the legacy singular ScreenInfo, if its id is invalid or if the
// display::Screen is not initialized; each of which occurs in various tests.
@ -2073,9 +2063,8 @@ blink::ScreenInfos RenderWidgetHostImpl::GetScreenInfos() {
return blink::ScreenInfos(current_screen_info);
}
// If we get here, we are asserting that the current display as reported
// by the RenderWidgetHostView is inside of GetAllDisplays().
// Build multi-screen info from the displays returned by RenderWidgetHostView,
// ensure its legacy singular screen info struct is included in this set.
blink::ScreenInfos result;
bool current_display_added = false;
for (const auto& display : displays) {
@ -2122,7 +2111,6 @@ blink::ScreenInfos RenderWidgetHostImpl::GetScreenInfos() {
// Fall back to legacy screen info, if we are in a bad state.
return blink::ScreenInfos(current_screen_info);
#endif
}
void RenderWidgetHostImpl::GetSnapshotFromBrowser(

@ -1940,7 +1940,7 @@ void RenderWidgetHostInputEventRouter::SetCursor(const WebCursor& cursor) {
return;
last_device_scale_factor_ =
last_mouse_move_root_view_->current_device_scale_factor();
last_mouse_move_root_view_->GetCurrentDeviceScaleFactor();
if (auto* cursor_manager = last_mouse_move_root_view_->GetCursorManager()) {
for (auto it : owner_map_) {
if (it.second)

@ -501,40 +501,73 @@ void RenderWidgetHostViewBase::ProcessAckedTouchEvent(
NOTREACHED();
}
const std::vector<display::Display>& RenderWidgetHostViewBase::GetDisplays()
const {
// Get the latest info directly from display::Screen, like GetScreenInfo().
// TODO(crbug.com/1169312): Unify display info caching and change detection.
if (auto* screen = display::Screen::GetScreen())
return screen->GetAllDisplays();
static const base::NoDestructor<std::vector<display::Display>> kEmptyDisplays;
return *kEmptyDisplays;
}
void RenderWidgetHostViewBase::UpdateScreenInfo(gfx::NativeView view) {
if (host() && host()->delegate())
host()->delegate()->SendScreenRects();
// TODO(crbug.com/1169312): Unify display info caching and change detection.
display::Display::Rotation old_display_rotation = current_display_.rotation();
if (HasDisplayPropertyChanged(view) && host()) {
OnSynchronizedDisplayPropertiesChanged(old_display_rotation !=
current_display_.rotation());
host()->NotifyScreenInfoChanged();
bool has_display_property_changed = true;
bool has_rotation_changed = false;
auto* screen = display::Screen::GetScreen();
display::DisplayList new_display_list(
screen->GetAllDisplays(), screen->GetPrimaryDisplay().id(),
screen->GetDisplayNearestView(view).id());
if (!display_list_.displays().empty()) {
const display::Display& current_display =
*display_list_.GetCurrentDisplayIterator();
const display::Display& new_display =
*new_display_list.GetCurrentDisplayIterator();
// Proposed multi-screen APIs expose the current display's status as the
// primary display, and whether it is one of several extended displays, so
// those changes should also be surfaced via RenderWidgetHostImpl.
const bool current_display_is_primary =
display_list_.primary_id() == current_display.id();
const bool current_display_is_extended =
display_list_.displays().size() > 1;
const bool new_display_is_primary =
new_display_list.primary_id() == new_display.id();
const bool new_display_is_extended = new_display_list.displays().size() > 1;
has_display_property_changed =
current_display.id() != new_display.id() ||
current_display.bounds() != new_display.bounds() ||
current_display.work_area() != new_display.work_area() ||
current_display.device_scale_factor() !=
new_display.device_scale_factor() ||
current_display.rotation() != new_display.rotation() ||
current_display.color_spaces() != new_display.color_spaces() ||
current_display.IsInternal() != new_display.IsInternal() ||
current_display_is_primary != new_display_is_primary ||
current_display_is_extended != new_display_is_extended;
has_rotation_changed = current_display.rotation() != new_display.rotation();
}
if (has_display_property_changed) {
display_list_ = new_display_list;
// Notify the associated RenderWidgetHostImpl when screen info has changed.
// That will synchronize visual properties needed for frame tree rendering
// and for web platform APIs that expose screen and window info and events.
if (host()) {
OnSynchronizedDisplayPropertiesChanged(has_rotation_changed);
host()->NotifyScreenInfoChanged();
}
}
}
bool RenderWidgetHostViewBase::HasDisplayPropertyChanged(gfx::NativeView view) {
auto* screen = display::Screen::GetScreen();
auto display = screen->GetDisplayNearestView(view);
bool display_is_extended = screen->GetNumDisplays() > 1;
bool display_is_primary = screen->GetPrimaryDisplay().id() == display.id();
if (current_display_.id() == display.id() &&
current_display_.bounds() == display.bounds() &&
current_display_.work_area() == display.work_area() &&
current_display_.device_scale_factor() == display.device_scale_factor() &&
current_display_.rotation() == display.rotation() &&
current_display_.color_spaces() == display.color_spaces() &&
current_display_.IsInternal() == display.IsInternal() &&
current_display_is_extended_ == display_is_extended &&
current_display_is_primary_ == display_is_primary) {
return false;
}
current_display_ = display;
current_display_is_extended_ = display_is_extended;
current_display_is_primary_ = display_is_primary;
return true;
float RenderWidgetHostViewBase::GetCurrentDeviceScaleFactor() const {
if (display_list_.displays().empty())
return display::Display().device_scale_factor();
return display_list_.GetCurrentDisplayIterator()->device_scale_factor();
}
void RenderWidgetHostViewBase::DidUnregisterFromTextInputManager(

@ -41,6 +41,7 @@
#include "ui/base/ime/text_input_mode.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/display/display.h"
#include "ui/display/display_list.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
@ -101,10 +102,6 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
RenderWidgetHostViewBase(const RenderWidgetHostViewBase&) = delete;
RenderWidgetHostViewBase& operator=(const RenderWidgetHostViewBase&) = delete;
float current_device_scale_factor() const {
return current_display_.device_scale_factor();
}
// Returns the focused RenderWidgetHost inside this |view|'s RWH.
RenderWidgetHostImpl* GetFocusedWidget() const;
@ -174,11 +171,14 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
virtual void SendInitialPropertiesIfNeeded() {}
// Called when screen information or native widget bounds change.
void UpdateScreenInfo(gfx::NativeView view);
// Get display info known to this view; must be consistent with GetScreenInfo.
virtual const std::vector<display::Display>& GetDisplays() const;
// Updates cached screen information and returns whether it has changed.
bool HasDisplayPropertyChanged(gfx::NativeView view);
// Called when screen information or native widget bounds change.
virtual void UpdateScreenInfo(gfx::NativeView view);
// Get the device scale factor of the associated display.
float GetCurrentDeviceScaleFactor() const;
// Called by the TextInputManager to notify the view about being removed from
// the list of registered views, i.e., TextInputManager is no longer tracking
@ -574,10 +574,6 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
virtual bool HasFallbackSurface() const;
void set_current_device_scale_factor(float scale) {
current_display_.set_device_scale_factor(scale);
}
// The model object. Access is protected to allow access to
// RenderWidgetHostViewChildFrame.
RenderWidgetHostImpl* host_;
@ -585,6 +581,9 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
// Whether this view is a frame or a popup.
WidgetType widget_type_ = WidgetType::kFrame;
// Cached information about the renderer's display environment.
display::DisplayList display_list_;
// Indicates whether keyboard lock is active for this view.
bool keyboard_locked_ = false;
@ -650,11 +649,6 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
return view_stopped_flinging_for_test_;
}
// Cached information about the renderer's display environment.
display::Display current_display_;
bool current_display_is_extended_ = false;
bool current_display_is_primary_ = false;
base::ObserverList<RenderWidgetHostViewBaseObserver>::Unchecked observers_;
absl::optional<blink::WebGestureEvent> pending_touchpad_pinch_begin_;

@ -47,22 +47,22 @@ namespace content {
// static
RenderWidgetHostViewChildFrame* RenderWidgetHostViewChildFrame::Create(
RenderWidgetHost* widget,
const blink::ScreenInfo& screen_info) {
const blink::ScreenInfo& parent_screen_info) {
RenderWidgetHostViewChildFrame* view =
new RenderWidgetHostViewChildFrame(widget, screen_info);
new RenderWidgetHostViewChildFrame(widget, parent_screen_info);
view->Init();
return view;
}
RenderWidgetHostViewChildFrame::RenderWidgetHostViewChildFrame(
RenderWidgetHost* widget_host,
const blink::ScreenInfo& screen_info)
const blink::ScreenInfo& parent_screen_info)
: RenderWidgetHostViewBase(widget_host),
frame_sink_id_(
base::checked_cast<uint32_t>(widget_host->GetProcess()->GetID()),
base::checked_cast<uint32_t>(widget_host->GetRoutingID())),
frame_connector_(nullptr),
screen_info_(screen_info) {
parent_screen_info_(parent_screen_info) {
GetHostFrameSinkManager()->RegisterFrameSinkId(
frame_sink_id_, this, viz::ReportFirstSurfaceActivation::kNo);
GetHostFrameSinkManager()->SetFrameSinkDebugLabel(
@ -131,8 +131,17 @@ void RenderWidgetHostViewChildFrame::SetFrameConnector(
SetParentFrameSinkId(parent_view->GetFrameSinkId());
}
set_current_device_scale_factor(
// Initialize a display struct as needed, to cache the scale factor.
// TODO(crbug.com/1182855): Use the parent_view's entire display::DisplayList.
if (display_list_.displays().empty()) {
display_list_ = display::DisplayList(
{display::Display(display::kDefaultDisplayId)},
display::kDefaultDisplayId, display::kDefaultDisplayId);
}
display::Display current_display = *display_list_.GetCurrentDisplayIterator();
current_display.set_device_scale_factor(
frame_connector_->screen_info().device_scale_factor);
display_list_.UpdateDisplay(current_display);
auto* root_view = frame_connector_->GetRootRenderWidgetHostView();
if (root_view) {
@ -840,7 +849,7 @@ void RenderWidgetHostViewChildFrame::
const cc::RenderFrameMetadata& metadata =
host()->render_frame_metadata_provider()->LastRenderFrameMetadata();
selection_controller_client_->UpdateSelectionBoundsIfNeeded(
metadata.selection, current_device_scale_factor());
metadata.selection, GetCurrentDeviceScaleFactor());
}
}
@ -908,9 +917,11 @@ RenderWidgetHostViewChildFrame::FilterInputEvent(
void RenderWidgetHostViewChildFrame::GetScreenInfo(
blink::ScreenInfo* screen_info) {
// TODO(crbug.com/1182855): Propagate screen infos from the parent on changes
// and on connection init; avoid lazily updating the local cache like this.
if (frame_connector_)
screen_info_ = frame_connector_->screen_info();
*screen_info = screen_info_;
parent_screen_info_ = frame_connector_->screen_info();
*screen_info = parent_screen_info_;
}
void RenderWidgetHostViewChildFrame::EnableAutoResize(

@ -59,9 +59,10 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
public RenderFrameMetadataProvider::Observer,
public viz::HostFrameSinkClient {
public:
// TODO(crbug.com/1182855): Pass multi-screen info from the parent.
static RenderWidgetHostViewChildFrame* Create(
RenderWidgetHost* widget,
const blink::ScreenInfo& screen_info);
const blink::ScreenInfo& parent_screen_info);
void SetFrameConnector(CrossProcessFrameConnector* frame_connector);
@ -214,8 +215,9 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewChildFrameTest,
ForwardsBeginFrameAcks);
explicit RenderWidgetHostViewChildFrame(RenderWidgetHost* widget,
const blink::ScreenInfo& screen_info);
// TODO(crbug.com/1182855): Pass multi-screen info from the parent.
RenderWidgetHostViewChildFrame(RenderWidgetHost* widget,
const blink::ScreenInfo& parent_screen_info);
void Init();
// Sets |parent_frame_sink_id_| and registers frame sink hierarchy. If the
@ -308,7 +310,7 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
// created, to be used before this view is connected to its FrameDelegate.
// This is kept up to date anytime GetScreenInfo() is called and we have
// a FrameDelegate.
blink::ScreenInfo screen_info_;
blink::ScreenInfo parent_screen_info_;
base::WeakPtrFactory<RenderWidgetHostViewChildFrame> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewChildFrame);

@ -959,7 +959,7 @@ bool RenderWidgetHostViewEventHandler::MatchesSynthesizedMovePosition(
const blink::WebMouseEvent& event) {
if (event.GetType() == blink::WebInputEvent::Type::kMouseMove &&
synthetic_move_position_.has_value()) {
if (IsFractionalScaleFactor(host_view_->current_device_scale_factor())) {
if (IsFractionalScaleFactor(host_view_->GetCurrentDeviceScaleFactor())) {
// For fractional scale factors, the conversion from pixels to dip and
// vice versa could result in off by 1 or 2 errors which hurts us because
// the artificial move to center event cause the cursor to bounce around

@ -148,10 +148,11 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
gfx::NativeViewAccessible AccessibilityGetNativeViewAccessibleForWindow()
override;
absl::optional<SkColor> GetBackgroundColor() override;
void OnSynchronizedDisplayPropertiesChanged(bool rotation) override;
void TransformPointToRootSurface(gfx::PointF* point) override;
gfx::Rect GetBoundsInRootWindow() override;
const std::vector<display::Display>& GetDisplays() const override;
void UpdateScreenInfo(gfx::NativeView view) override;
viz::ScopedSurfaceIdAllocator DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) override;
void DidNavigate() override;
@ -296,11 +297,6 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
.max_time_between_phase_ended_and_momentum_phase_began();
}
// Update the size, scale factor, color profile, vsync parameters, and any
// other properties of the NSView or its NSScreen. Propagate these to the
// RenderWidgetHostImpl as well.
void UpdateNSViewAndDisplayProperties();
// RenderWidgetHostNSViewHostHelper implementation.
id GetRootBrowserAccessibilityElement() override;
id GetFocusedBrowserAccessibilityElement() override;
@ -334,7 +330,7 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
bool attached_to_window) override;
void OnWindowFrameInScreenChanged(
const gfx::Rect& window_frame_in_screen_dip) override;
void OnDisplayChanged(const display::Display& display) override;
void OnDisplaysChanged(const display::DisplayList& display_list) override;
void BeginKeyboardEvent() override;
void EndKeyboardEvent() override;
void ForwardKeyboardEventWithCommands(
@ -566,9 +562,6 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
// (where the origin is the upper-left corner of Screen::GetPrimaryDisplay).
gfx::Rect window_frame_in_screen_dip_;
// Cached copy of the display information pushed to us from the NSView.
display::Display display_;
// Whether or not the NSView's NSWindow is the key window.
bool is_window_key_ = false;

@ -31,6 +31,7 @@
#import "content/browser/accessibility/browser_accessibility_mac.h"
#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/display_util.h"
#include "content/browser/renderer_host/input/motion_event_web.h"
#import "content/browser/renderer_host/input/synthetic_gesture_target_mac.h"
#include "content/browser/renderer_host/input/web_input_event_builders_mac.h"
@ -187,13 +188,15 @@ RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
// Guess that the initial screen we will be on is the screen of the current
// window (since that's the best guess that we have, and is usually right).
// https://crbug.com/357443
display_ =
display::Screen::GetScreen()->GetDisplayNearestWindow([NSApp keyWindow]);
display::Screen* screen = display::Screen::GetScreen();
display_list_ = display::DisplayList(
screen->GetAllDisplays(), screen->GetPrimaryDisplay().id(),
screen->GetDisplayNearestWindow([NSApp keyWindow]).id());
viz::FrameSinkId frame_sink_id = host()->GetFrameSinkId();
browser_compositor_ = std::make_unique<BrowserCompositorMac>(
this, this, host()->is_hidden(), display_, frame_sink_id);
this, this, host()->is_hidden(), display_list_, frame_sink_id);
DCHECK(![GetInProcessNSView() window]);
host()->SetView(this);
@ -254,6 +257,10 @@ void RenderWidgetHostViewMac::MigrateNSViewBridge(
// If no host is specified, then use the locally hosted NSView.
if (!remote_cocoa_application) {
ns_view_ = in_process_ns_view_bridge_.get();
// Observe local Screen info, to correspond with the locally hosted NSView.
// TODO(crbug.com/1204273): Maybe recreate `in_process_ns_view_bridge_`?
display::Screen::GetScreen()->RemoveObserver(
in_process_ns_view_bridge_.get());
return;
}
@ -276,6 +283,12 @@ void RenderWidgetHostViewMac::MigrateNSViewBridge(
ns_view_ = remote_ns_view_.get();
// End local display::Screen observation via `in_process_ns_view_bridge_`;
// the remote NSWindow's display::Screen information will be sent by Mojo.
// TODO(crbug.com/1204273): Maybe just destroy `in_process_ns_view_bridge_`?
display::Screen::GetScreen()->RemoveObserver(
in_process_ns_view_bridge_.get());
// Popup windows will specify an invalid |parent_ns_view_id|, because popups
// have their own NSWindows (of which they are the content NSView).
if (parent_ns_view_id != remote_cocoa::kInvalidNSViewId)
@ -397,45 +410,21 @@ RenderWidgetHostImpl* RenderWidgetHostViewMac::GetWidgetForIme() {
return GetActiveWidget();
}
void RenderWidgetHostViewMac::UpdateNSViewAndDisplayProperties() {
display_link_ = ui::DisplayLinkMac::GetForDisplay(display_.id());
if (!display_link_) {
// Note that on some headless systems, the display link will fail to be
// created, so this should not be a fatal error.
LOG(ERROR) << "Failed to create display link.";
}
// During auto-resize it is the responsibility of the caller to ensure that
// the NSView and RenderWidgetHostImpl are kept in sync.
if (host()->auto_resize_enabled())
return;
if (host()->delegate())
host()->delegate()->SendScreenRects();
else
host()->SendScreenRects();
// RenderWidgetHostImpl will query BrowserCompositorMac for the dimensions
// to send to the renderer, so it is required that BrowserCompositorMac be
// updated first. Only notify RenderWidgetHostImpl of the update if any
// properties it will query have changed.
if (browser_compositor_->UpdateSurfaceFromNSView(
view_bounds_in_window_dip_.size(), display_)) {
host()->NotifyScreenInfoChanged();
}
}
void RenderWidgetHostViewMac::GetScreenInfo(blink::ScreenInfo* screen_info) {
browser_compositor_->GetRendererScreenInfo(screen_info);
}
void RenderWidgetHostViewMac::OnSynchronizedDisplayPropertiesChanged(
bool rotation) {
// Update cached screen information when the current display changes.
const auto& display =
display::Screen::GetScreen()->GetDisplayNearestWindow([NSApp keyWindow]);
if (display != display_)
OnDisplayChanged(display);
const display::DisplayList& displays = browser_compositor_->display_list();
DisplayUtil::DisplayToScreenInfo(screen_info,
*displays.GetCurrentDisplayIterator());
// Recalculate some ScreenInfo properties from the cached screen info, which
// may originate from a remote process that hosts the associated NSWindow.
// DisplayToScreenInfo derives some properties from the latest display::Screen
// info observed directly in this process, which may be intermittently
// out-of-sync with remote info. Also, BrowserCompositorMac and
// RenderWidgetHostViewMac do not update their cached screen info during
// auto-resize.
// TODO(crbug.com/1194700): Pass cached remote process screen info to
// DisplayToScreenInfo; it should not use local process info internally.
screen_info->is_extended = displays.displays().size() > 1;
screen_info->is_primary = screen_info->display_id == displays.primary_id();
}
void RenderWidgetHostViewMac::Show() {
@ -744,6 +733,50 @@ void RenderWidgetHostViewMac::UpdateTooltip(
SetTooltipText(tooltip_text);
}
const std::vector<display::Display>& RenderWidgetHostViewMac::GetDisplays()
const {
// Return cached screen info, which may originate from a remote process that
// hosts the associated NSWindow. The latest display::Screen info observed
// directly in this process may be intermittently out-of-sync with that info.
// Also, BrowserCompositorMac and RenderWidgetHostViewMac do not update their
// cached screen info during auto-resize.
return browser_compositor_->display_list().displays();
}
void RenderWidgetHostViewMac::UpdateScreenInfo(gfx::NativeView view) {
// Update the size, scale factor, color profile, vsync parameters, and any
// other properties of the NSView or pertinent NSScreens. Propagate these to
// the RenderWidgetHostImpl as well.
display_link_ = ui::DisplayLinkMac::GetForDisplay(display_list_.current_id());
if (!display_link_) {
// Note that on some headless systems, the display link will fail to be
// created, so this should not be a fatal error.
LOG(ERROR) << "Failed to create display link.";
}
// During auto-resize it is the responsibility of the caller to ensure that
// the NSView and RenderWidgetHostImpl are kept in sync.
if (host()->auto_resize_enabled())
return;
if (host()->delegate())
host()->delegate()->SendScreenRects();
else
host()->SendScreenRects();
// TODO(crbug.com/1169312): Unify display info caching and change detection.
// Notify the associated RenderWidgetHostImpl when screen info has changed.
// That will synchronize visual properties needed for frame tree rendering
// and for web platform APIs that expose screen and window info and events.
// RenderWidgetHostImpl will query BrowserCompositorMac for the dimensions
// to send to the renderer, so BrowserCompositorMac must be updated first.
if (browser_compositor_->UpdateSurfaceFromNSView(
view_bounds_in_window_dip_.size(), display_list_)) {
host()->NotifyScreenInfoChanged();
}
}
viz::ScopedSurfaceIdAllocator
RenderWidgetHostViewMac::DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) {
@ -868,10 +901,13 @@ void RenderWidgetHostViewMac::CopyFromSurface(
->GetDelegatedFrameHost()
->GetWeakPtr();
}
// TODO(crbug.com/1169321): Resolve potential differences between display info
// caches in RenderWidgetHostViewMac and BrowserCompositorMac.
RenderWidgetHostViewBase::CopyMainAndPopupFromSurface(
host()->GetWeakPtr(),
browser_compositor_->GetDelegatedFrameHost()->GetWeakPtr(), popup_host,
popup_frame_host, src_subrect, dst_size, display_.device_scale_factor(),
popup_frame_host, src_subrect, dst_size,
display_list_.GetCurrentDisplayIterator()->device_scale_factor(),
std::move(callback));
}
@ -1510,7 +1546,7 @@ void RenderWidgetHostViewMac::OnBoundsInWindowChanged(
}
if (view_size_changed)
UpdateNSViewAndDisplayProperties();
UpdateScreenInfo(GetNativeView());
}
void RenderWidgetHostViewMac::OnWindowFrameInScreenChanged(
@ -1525,11 +1561,16 @@ void RenderWidgetHostViewMac::OnWindowFrameInScreenChanged(
host()->SendScreenRects();
}
void RenderWidgetHostViewMac::OnDisplayChanged(
const display::Display& display) {
display_ = display;
// TODO(crbug.com/1169291): Unify per-platform DisplayObserver instances.
UpdateNSViewAndDisplayProperties();
void RenderWidgetHostViewMac::OnDisplaysChanged(
const display::DisplayList& display_list) {
// Cache this screen info, which may originate from a remote process that
// hosts the associated NSWindow. The latest display::Screen info observed
// directly in this process may be intermittently out-of-sync with that info.
// Also, BrowserCompositorMac and RenderWidgetHostViewMac do not update their
// cached screen info during auto-resize.
// TODO(crbug.com/1169291): Unify screen info plumbing, caching, etc.
display_list_ = display_list;
UpdateScreenInfo(GetNativeView());
}
void RenderWidgetHostViewMac::BeginKeyboardEvent() {

@ -120,6 +120,8 @@ IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, DISABLED_GetScreensBasic) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value));
}
// TODO(crbug.com/1205676): Remove this test in favor of IsExtendedBasic.
// window.isMultiScreen() is deprecated in favor of screen.isExtended.
IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, IsMultiScreenBasic) {
ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "empty.html")));
ASSERT_EQ(true, EvalJs(shell(), "'isMultiScreen' in self"));
@ -191,6 +193,8 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value));
}
// TODO(crbug.com/1205676): Remove this test in favor of IsExtendedFaked.
// window.isMultiScreen() is deprecated in favor of screen.isExtended.
// TODO(crbug.com/1042990): Windows crashes static casting to ScreenWin.
// TODO(crbug.com/1042990): Android requires a GetDisplayNearestView overload.
#if defined(OS_ANDROID) || defined(OS_WIN)
@ -233,50 +237,51 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_IsExtendedFaked) {
// TODO(crbug.com/1042990): Windows crashes static casting to ScreenWin.
// TODO(crbug.com/1042990): Android requires a GetDisplayNearestView overload.
#if defined(OS_ANDROID) || defined(OS_WIN)
#define MAYBE_OnScreensChangeNoPermission DISABLED_OnScreensChangeNoPermission
#define MAYBE_ScreenOnchangeNoPermission DISABLED_ScreenOnchangeNoPermission
#else
#define MAYBE_OnScreensChangeNoPermission OnScreensChangeNoPermission
#define MAYBE_ScreenOnchangeNoPermission ScreenOnchangeNoPermission
#endif
// Sites with no permission only get an event if isMultiScreen() changes.
// Sites with no permission only get an event if screen.isExtended changes.
// TODO(crbug.com/1119974): Need content_browsertests permission controls.
IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest,
MAYBE_OnScreensChangeNoPermission) {
MAYBE_ScreenOnchangeNoPermission) {
ASSERT_TRUE(NavigateToURL(test_shell(), GetTestUrl(nullptr, "empty.html")));
ASSERT_EQ(true, EvalJs(test_shell(), "'onscreenschange' in self"));
constexpr char kSetOnScreensChange[] = R"(
onscreenschange = function() { ++document.title; };
ASSERT_EQ(true, EvalJs(test_shell(), "'onchange' in screen"));
constexpr char kSetScreenOnchange[] = R"(
window.screen.onchange = function() { ++document.title; };
document.title = 0;
)";
EXPECT_EQ(0, EvalJs(test_shell(), kSetOnScreensChange));
EXPECT_EQ(0, EvalJs(test_shell(), kSetScreenOnchange));
EXPECT_EQ(false, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("0", EvalJs(test_shell(), "document.title"));
// isMultiScreen() changes from false to true here, so an event is sent.
EXPECT_EQ(false, EvalJs(test_shell(), kIsMultiScreenScript));
// screen.isExtended changes from false to true here, so an event is sent.
EXPECT_EQ(false, EvalJs(test_shell(), "screen.isExtended"));
screen()->display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
display::DisplayList::Type::NOT_PRIMARY);
EXPECT_EQ(true, EvalJs(test_shell(), kIsMultiScreenScript));
EXPECT_EQ(true, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("1", EvalJs(test_shell(), "document.title"));
// isMultiScreen() remains unchanged, so no event is sent.
// screen.isExtended remains unchanged, so no event is sent.
screen()->display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
display::DisplayList::Type::NOT_PRIMARY);
EXPECT_EQ(true, EvalJs(test_shell(), kIsMultiScreenScript));
EXPECT_EQ(true, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("1", EvalJs(test_shell(), "document.title"));
// isMultiScreen() remains unchanged, so no event is sent.
// screen.isExtended remains unchanged, so no event is sent.
EXPECT_NE(0u, screen()->display_list().UpdateDisplay(
{2, gfx::Rect(902, 100, 801, 802)}));
EXPECT_EQ(true, EvalJs(test_shell(), kIsMultiScreenScript));
EXPECT_EQ(true, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("1", EvalJs(test_shell(), "document.title"));
// isMultiScreen() remains unchanged, so no event is sent.
// screen.isExtended remains unchanged, so no event is sent.
screen()->display_list().RemoveDisplay(2);
EXPECT_EQ(true, EvalJs(test_shell(), kIsMultiScreenScript));
EXPECT_EQ(true, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("1", EvalJs(test_shell(), "document.title"));
// isMultiScreen() changes from true to false here, so an event is sent.
// screen.isExtended changes from true to false here, so an event is sent.
screen()->display_list().RemoveDisplay(1);
EXPECT_EQ(false, EvalJs(test_shell(), kIsMultiScreenScript));
EXPECT_EQ(false, EvalJs(test_shell(), "screen.isExtended"));
EXPECT_EQ("2", EvalJs(test_shell(), "document.title"));
}

@ -1355,12 +1355,20 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) {
OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::OnScreensChange",
"is_multi_screen_changed", is_multi_screen_changed);
// Allow fullscreen requests shortly after user-generated screens changes.
// TODO(crbug.com/1169291): Mac should not activate this on local process
// display::Screen signals, but via RenderWidgetHostViewMac screen updates.
transient_allow_fullscreen_.Activate();
// Mac display info may originate from a remote process hosting the NSWindow;
// this local process display::Screen signal should not trigger updates.
// TODO(crbug.com/1169291): Unify screen info plumbing, caching, etc.
#if !defined(OS_MAC)
// Send |is_multi_screen_changed| events to all visible frames, but limit
// other events to frames with the Window Placement permission. This obviates
// the most pressing need for sites to poll isMultiScreen(), which is exposed
// without explicit permission, while also protecting privacy.
// TODO(crbug.com/1109989): Postpone events; refine utility/privacy balance.
// TODO(crbug.com/1205676): Remove this deprecated window.screenschange code.
for (FrameTreeNode* node : frame_tree_.Nodes()) {
RenderFrameHostImpl* rfh = node->current_frame_host();
if ((is_multi_screen_changed &&
@ -1378,6 +1386,7 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) {
GetRenderViewHost()->GetWidget()->GetView()) {
view->UpdateScreenInfo(view->GetNativeView());
}
#endif // !OS_MAC
}
void WebContentsImpl::OnScreenOrientationChange() {

@ -10,7 +10,7 @@ import "third_party/blink/public/mojom/webshare/share_error.mojom";
import "ui/base/mojom/attributed_string.mojom";
import "ui/base/cursor/mojom/cursor.mojom";
import "ui/base/ime/mojom/ime_types.mojom";
import "ui/display/mojom/display.mojom";
import "ui/display/mojom/display_list.mojom";
import "ui/events/mojom/event.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom";
import "ui/gfx/mojom/ca_layer_params.mojom";
@ -139,8 +139,8 @@ interface RenderWidgetHostNSViewHost {
OnWindowFrameInScreenChanged(
gfx.mojom.Rect window_frame_in_screen_dip);
// Indicate changes to the NSView's NSScreen's properties.
OnDisplayChanged(display.mojom.Display display);
// Indicate changes to the NSScreen hosting the NSView, or other NSScreens.
OnDisplaysChanged(display.mojom.DisplayList display_list);
// Indicate the begin and end block of a keyboard event. The beginning of this
// block will record the active RenderWidgetHost, and will forward all

@ -27,9 +27,8 @@ ScreenInfo& ScreenInfos::mutable_current() {
}
const ScreenInfo& ScreenInfos::current() const {
const auto& current_screen_info = base::ranges::find_if(
screen_infos,
[&](const ScreenInfo& s) { return s.display_id == current_display_id; });
const auto& current_screen_info = base::ranges::find(
screen_infos, current_display_id, &ScreenInfo::display_id);
CHECK(current_screen_info != screen_infos.end());
return *current_screen_info;
}

@ -8,6 +8,7 @@
namespace mojo {
// static
bool StructTraits<blink::mojom::ScreenInfosDataView, blink::ScreenInfos>::Read(
blink::mojom::ScreenInfosDataView data,
blink::ScreenInfos* out) {

@ -38,6 +38,8 @@
namespace blink {
// This structure roughly parallels display::Display. It may be desirable to
// deprecate derived counterparts of ui/display types; see crbug.com/1208469.
struct BLINK_COMMON_EXPORT ScreenInfo {
// Device scale factor. Specifies the ratio between physical and logical
// pixels.

@ -12,6 +12,8 @@ namespace blink {
// Information about a set of screens that are relevant to a particular widget.
// This includes an id for the screen currently showing the widget.
// This structure roughly parallels display::DisplayList. It may be desirable to
// deprecate derived counterparts of ui/display types; see crbug.com/1208469.
struct BLINK_COMMON_EXPORT ScreenInfos {
ScreenInfos();
explicit ScreenInfos(const ScreenInfo& screen_info);

@ -9,7 +9,10 @@ import "ui/gfx/geometry/mojom/geometry.mojom";
import "ui/gfx/mojom/display_color_spaces.mojom";
// Information about the screen on which a WidgetBase is being displayed. This
// is the content counterpart to blink::ScreenInfo.
// is the content counterpart to blink::ScreenInfo, and it roughly parallels
// display.mojom.Display. It may be desirable to deprecate derived counterparts
// of ui/display types, but doing so is complicated by widespread use and legacy
// quirks around blink::ScreenInfo.
struct ScreenInfo {
// Device scale factor. Specifies the ratio between physical and logical
// pixels.

@ -8,6 +8,9 @@ import "third_party/blink/public/mojom/widget/screen_info.mojom";
// Information about a set of screens that are relevant to a particular widget.
// This includes an id for the screen currently showing the widget.
// This structure roughly parallels display.mojom.DisplayList. It may be
// desirable to deprecate derived counterparts of ui/display types, but doing so
// is complicated by widespread use and legacy quirks around blink::ScreenInfo.
struct ScreenInfos {
// The array of ScreenInfo objects for the set of relevant screens.
array<ScreenInfo> screen_infos;

@ -200,6 +200,7 @@ test("display_unittests") {
"manager/display_manager_utilities_unittest.cc",
"manager/json_converter_unittest.cc",
"manager/managed_display_info_unittest.cc",
"mojom/display_list_mojom_traits_unittest.cc",
"mojom/display_mojom_traits_unittest.cc",
"screen_unittest.cc",
"unified_desktop_utils_unittests.cc",
@ -238,6 +239,7 @@ test("display_unittests") {
"//build:chromeos_buildflags",
"//cc/base",
"//mojo/core/test:run_all_unittests",
"//mojo/public/cpp/test_support:test_utils",
"//testing/gmock",
"//testing/gtest",
"//ui/display/fake",

@ -4,7 +4,9 @@
#include "ui/display/display_list.h"
#include "base/containers/flat_set.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "ui/display/display_observer.h"
namespace display {
@ -13,6 +15,43 @@ DisplayList::DisplayList() = default;
DisplayList::~DisplayList() = default;
DisplayList::DisplayList(const Displays& displays,
int64_t primary_id,
int64_t current_id)
: displays_(displays), primary_id_(primary_id), current_id_(current_id) {
#if defined(OS_FUCHSIA)
// TODO(crbug.com/1207996): Resolve ScenicScreen's lack of primary display.
if (!displays_.empty() && primary_id_ == kInvalidDisplayId)
primary_id_ = displays_[0].id();
#endif // OS_FUCHSIA
DCHECK(observers_.empty());
DCHECK(IsValid());
}
DisplayList::DisplayList(const DisplayList& other)
: displays_(other.displays_),
primary_id_(other.primary_id_),
current_id_(other.current_id_) {
DCHECK(other.observers_.empty());
DCHECK(observers_.empty());
DCHECK(IsValid());
}
DisplayList& DisplayList::operator=(const DisplayList& other) {
displays_ = other.displays_;
primary_id_ = other.primary_id_;
current_id_ = other.current_id_;
DCHECK(other.observers_.empty());
DCHECK(observers_.empty());
DCHECK(IsValid());
return *this;
}
bool DisplayList::operator==(const DisplayList& other) const {
return displays_ == other.displays_ && primary_id_ == other.primary_id_ &&
current_id_ == other.current_id_;
}
void DisplayList::AddObserver(DisplayObserver* observer) {
observers_.AddObserver(observer);
}
@ -23,18 +62,17 @@ void DisplayList::RemoveObserver(DisplayObserver* observer) {
DisplayList::Displays::const_iterator DisplayList::FindDisplayById(
int64_t id) const {
for (auto iter = displays_.begin(); iter != displays_.end(); ++iter) {
if (iter->id() == id)
return iter;
}
return displays_.end();
return base::ranges::find(displays_, id, &Display::id);
}
DisplayList::Displays::const_iterator DisplayList::GetPrimaryDisplayIterator()
const {
return primary_display_index_ == -1
? displays_.end()
: displays_.begin() + primary_display_index_;
return FindDisplayById(primary_id_);
}
DisplayList::Displays::const_iterator DisplayList::GetCurrentDisplayIterator()
const {
return FindDisplayById(current_id_);
}
void DisplayList::AddOrUpdateDisplay(const Display& display, Type type) {
@ -54,10 +92,10 @@ uint32_t DisplayList::UpdateDisplay(const Display& display, Type type) {
Display* local_display = &(*iter);
uint32_t changed_values = 0;
if (type == Type::PRIMARY &&
static_cast<int>(iter - displays_.begin()) !=
static_cast<int>(GetPrimaryDisplayIterator() - displays_.begin())) {
primary_display_index_ = static_cast<int>(iter - displays_.begin());
// TODO(crbug.com/1207996): Guard against removal of the primary designation.
// DCHECK(type == Type::PRIMARY || local_display->id() != primary_id_);
if (type == Type::PRIMARY && local_display->id() != primary_id_) {
primary_id_ = local_display->id();
// ash::DisplayManager only notifies for the Display gaining primary, not
// the one losing it.
changed_values |= DisplayObserver::DISPLAY_METRIC_PRIMARY;
@ -95,51 +133,77 @@ uint32_t DisplayList::UpdateDisplay(const Display& display, Type type) {
}
for (DisplayObserver& observer : observers_)
observer.OnDisplayMetricsChanged(*local_display, changed_values);
DCHECK(IsValid());
return changed_values;
}
void DisplayList::AddDisplay(const Display& display, Type type) {
DCHECK(displays_.end() == FindDisplayByIdInternal(display.id()));
DCHECK(displays_.end() == FindDisplayById(display.id()));
displays_.push_back(display);
if (type == Type::PRIMARY)
primary_display_index_ = static_cast<int>(displays_.size()) - 1;
// The first display added should always be designated as the primary display.
// Displays added later can take on the primary designation as appropriate.
if (type == Type::PRIMARY || displays_.size() == 1)
primary_id_ = display.id();
for (DisplayObserver& observer : observers_)
observer.OnDisplayAdded(display);
DCHECK(IsValid());
}
void DisplayList::RemoveDisplay(int64_t id) {
auto iter = FindDisplayByIdInternal(id);
DCHECK(displays_.end() != iter);
if (primary_display_index_ == static_cast<int>(iter - displays_.begin())) {
if (id == primary_id_) {
// The primary display can only be removed if it is the last display.
// Users must choose a new primary before removing an old primary display.
DCHECK_EQ(1u, displays_.size());
primary_display_index_ = -1;
} else if (primary_display_index_ >
static_cast<int>(iter - displays_.begin())) {
primary_display_index_--;
primary_id_ = kInvalidDisplayId;
}
if (id == current_id_) {
// The current display can only be removed if it is the last display.
// Users must choose a new current before removing an old current display.
DCHECK_EQ(1u, displays_.size());
current_id_ = kInvalidDisplayId;
}
const Display display = *iter;
displays_.erase(iter);
for (DisplayObserver& observer : observers_)
observer.OnDisplayRemoved(display);
DCHECK(IsValid());
}
bool DisplayList::IsValid() const {
// The primary and current ids must be invalid when `displays_` is empty.
if (displays_.empty())
return primary_id_ == kInvalidDisplayId && current_id_ == kInvalidDisplayId;
// Ensure ids are unique.
base::flat_set<int64_t> display_ids;
display_ids.reserve(displays_.size());
for (const auto& display : displays_) {
if (!display_ids.insert(display.id()).second)
return false;
}
// The primary id must correspond to a `displays_` entry.
if (GetPrimaryDisplayIterator() == displays_.end())
return false;
// The current id may be invalid, or must correspond to a `displays_` entry.
if (current_id_ != kInvalidDisplayId &&
GetCurrentDisplayIterator() == displays_.end()) {
return false;
}
return true;
}
DisplayList::Type DisplayList::GetTypeByDisplayId(int64_t display_id) const {
if (primary_display_index_ == -1)
return Type::NOT_PRIMARY;
return (displays_[primary_display_index_].id() == display_id
? Type::PRIMARY
: Type::NOT_PRIMARY);
return display_id == primary_id_ ? Type::PRIMARY : Type::NOT_PRIMARY;
}
DisplayList::Displays::iterator DisplayList::FindDisplayByIdInternal(
int64_t id) {
for (auto iter = displays_.begin(); iter != displays_.end(); ++iter) {
if (iter->id() == id)
return iter;
}
return displays_.end();
return base::ranges::find(displays_, id, &Display::id);
}
} // namespace display

@ -7,7 +7,6 @@
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/observer_list.h"
@ -16,8 +15,6 @@
namespace display {
class Display;
class DisplayList;
class DisplayObserver;
// Maintains an ordered list of Displays as well as operations to add, remove
@ -35,14 +32,24 @@ class DISPLAY_EXPORT DisplayList {
DisplayList();
~DisplayList();
// WARNING: The copy constructor and assignment operator do not copy nor move
// observers; also, the comparison operator does not compare observers.
DisplayList(const Displays& displays, int64_t primary_id, int64_t current_id);
DisplayList(const DisplayList& other);
DisplayList& operator=(const DisplayList& other);
bool operator==(const DisplayList& other) const;
void AddObserver(DisplayObserver* observer);
void RemoveObserver(DisplayObserver* observer);
const Displays& displays() const { return displays_; }
int64_t primary_id() const { return primary_id_; }
int64_t current_id() const { return current_id_; }
Displays::const_iterator FindDisplayById(int64_t id) const;
Displays::const_iterator GetPrimaryDisplayIterator() const;
Displays::const_iterator GetCurrentDisplayIterator() const;
void AddOrUpdateDisplay(const Display& display, Type type);
@ -62,6 +69,9 @@ class DISPLAY_EXPORT DisplayList {
// Removes the Display with the specified id.
void RemoveDisplay(int64_t id);
// Checks expectations around DisplayList validity.
bool IsValid() const;
base::ObserverList<DisplayObserver>* observers() { return &observers_; }
private:
@ -69,11 +79,19 @@ class DISPLAY_EXPORT DisplayList {
Displays::iterator FindDisplayByIdInternal(int64_t id);
// The list of displays tracked by the display::Screen or other client.
std::vector<Display> displays_;
int primary_display_index_ = -1;
// The id of the primary Display in `displays_` for the display::Screen.
int64_t primary_id_ = kInvalidDisplayId;
// The id of the current Display in `displays_`, for some client's context.
// This is used when DisplayList needs to track which display a client is on,
// typically for a cached DisplayList owned by the client window itself. This
// should be kInvalidDisplayId for the DisplayList owned by display::Screen,
// which represents the system-wide state and needs to work for all clients.
// This member is included in this structure to maintain consistency with some
// list of cached displays, perhaps that's not ideal. See crbug.com/1207996.
int64_t current_id_ = kInvalidDisplayId;
base::ObserverList<DisplayObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(DisplayList);
};
} // namespace display

@ -8,6 +8,7 @@
#include <vector>
#include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display.h"
#include "ui/display/display_observer.h"
@ -107,5 +108,64 @@ TEST(DisplayListTest, AddUpdateRemove) {
EXPECT_EQ(3, display_list.GetPrimaryDisplayIterator()->id());
}
TEST(DisplayListTest, EmptyIsValid) {
DisplayList display_list;
EXPECT_TRUE(display_list.IsValid());
}
TEST(DisplayListTest, FirstDisplayAddedIsForcedToBePrimary) {
DisplayList display_list;
display_list.AddDisplay(Display(1), DisplayList::Type::NOT_PRIMARY);
EXPECT_EQ(1, display_list.primary_id());
}
TEST(DisplayListTest, SinglePrimaryDisplayNoCurrentIdIsValid) {
DisplayList display_list({Display(1)}, /*primary_id=*/1,
/*current_id=*/kInvalidDisplayId);
EXPECT_TRUE(display_list.IsValid());
}
TEST(DisplayListTest, SinglePrimaryDisplayWithCurrentIdIsValid) {
DisplayList display_list({Display(1)}, /*primary_id=*/1, /*current_id=*/1);
EXPECT_TRUE(display_list.IsValid());
}
TEST(DisplayListTest, PrimaryMustBeInvalidWhenEmpty) {
// `primary_id` must be kInvalidDisplayId if `displays` is empty.
EXPECT_DCHECK_DEATH(EXPECT_FALSE(
DisplayList({}, /*primary_id=*/1, /*current_id=*/kInvalidDisplayId)
.IsValid()));
}
TEST(DisplayListTest, CurrentMustBeInvalidWhenEmpty) {
// `primary_id` must be kInvalidDisplayId if `displays` is empty.
EXPECT_DCHECK_DEATH(
EXPECT_FALSE(DisplayList({}, /*primary_id=*/kInvalidDisplayId,
/*current_id=*/1)
.IsValid()));
}
TEST(DisplayListTest, PrimaryIdMustBePresent) {
// `primary_id` must match an element of `displays`.
EXPECT_DCHECK_DEATH(EXPECT_FALSE(DisplayList({Display(1)}, /*primary_id=*/2,
/*current_id=*/1)
.IsValid()));
}
TEST(DisplayListTest, CurrentIdMustBePresent) {
// `current_id` must match an element of `displays`.
EXPECT_DCHECK_DEATH(EXPECT_FALSE(DisplayList({Display(1)}, /*primary_id=*/1,
/*current_id=*/2)
.IsValid()));
}
TEST(DisplayListTest, DisplaysIdsMustBeUnique) {
// `displays` must use unique id values.
EXPECT_DCHECK_DEATH(EXPECT_FALSE(DisplayList({Display(1), Display(1)},
/*primary_id=*/1,
/*current_id=*/1)
.IsValid()));
}
} // namespace
} // namespace display

@ -12,6 +12,7 @@ mojom("mojom") {
"display_configuration_params.mojom",
"display_constants.mojom",
"display_layout.mojom",
"display_list.mojom",
"display_mode.mojom",
"display_snapshot.mojom",
"gamma_ramp_rgb_entry.mojom",
@ -151,6 +152,17 @@ mojom("mojom") {
traits_public_deps = [ "//ui/display" ]
traits_deps = [ "//ui/gfx/geometry" ]
},
{
types = [
{
mojom = "display.mojom.DisplayList"
cpp = "::display::DisplayList"
},
]
traits_sources = [ "display_list_mojom_traits.cc" ]
traits_headers = [ "display_list_mojom_traits.h" ]
traits_public_deps = [ ":shared_mojom_traits" ]
},
]
cpp_typemaps += shared_cpp_typemaps

@ -0,0 +1,20 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module display.mojom;
import "ui/display/mojom/display.mojom";
// A vector of Displays and indices of notable entries.
// Corresponds to display::DisplayList and roughly parallels blink::ScreenInfos.
// WARNING: This does not convey observers held by display::DisplayList.
struct DisplayList {
// The array of Display objects for the set of relevant displays.
array<Display> displays;
// The id of the primary Display in `displays` for the aggregate Screen.
int64 primary_id;
// The id of the current Display in `displays` for a context (e.g. a window).
int64 current_id;
};

@ -0,0 +1,21 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/display/mojom/display_list_mojom_traits.h"
#include "ui/display/mojom/display.mojom.h"
namespace mojo {
// static
bool StructTraits<display::mojom::DisplayListDataView, display::DisplayList>::
Read(display::mojom::DisplayListDataView data, display::DisplayList* out) {
std::vector<display::Display> displays;
if (!data.ReadDisplays(&displays))
return false;
*out = display::DisplayList(displays, data.primary_id(), data.current_id());
return out->IsValid();
}
} // namespace mojo

@ -0,0 +1,41 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_DISPLAY_MOJOM_DISPLAY_LIST_MOJOM_TRAITS_H_
#define UI_DISPLAY_MOJOM_DISPLAY_LIST_MOJOM_TRAITS_H_
#include "ui/display/display_list.h"
#include "ui/display/mojom/display_list.mojom-shared.h"
namespace display {
class Display;
} // namespace display
namespace mojo {
template <>
struct StructTraits<display::mojom::DisplayListDataView, display::DisplayList> {
static const std::vector<display::Display>& displays(
const display::DisplayList& r) {
DCHECK(r.IsValid());
return r.displays();
}
static int64_t primary_id(const display::DisplayList& r) {
DCHECK(r.IsValid());
return r.primary_id();
}
static int64_t current_id(const display::DisplayList& r) {
DCHECK(r.IsValid());
return r.current_id();
}
static bool Read(display::mojom::DisplayListDataView r,
display::DisplayList* out);
};
} // namespace mojo
#endif // UI_DISPLAY_MOJOM_DISPLAY_LIST_MOJOM_TRAITS_H_

@ -0,0 +1,101 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/display/mojom/display_list_mojom_traits.h"
#include "base/test/gtest_util.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display_list.h"
#include "ui/display/mojom/display_list.mojom.h"
namespace {
using mojo::test::SerializeAndDeserialize;
TEST(StructTraitsTest, DisplayListRoundtripEmpty) {
display::DisplayList i, o;
ASSERT_TRUE(SerializeAndDeserialize<display::mojom::DisplayList>(i, o));
EXPECT_EQ(i, o);
}
TEST(StructTraitsTest, DisplayListRoundtripValid) {
display::DisplayList i, o;
i = display::DisplayList({display::Display(1)}, /*primary_id=*/1,
/*current_id=*/1);
EXPECT_EQ(i.primary_id(), i.displays()[0].id());
EXPECT_EQ(i.current_id(), i.displays()[0].id());
ASSERT_TRUE(SerializeAndDeserialize<display::mojom::DisplayList>(i, o));
EXPECT_EQ(i, o);
}
TEST(StructTraitsTest, DisplayListPrimaryMustBeInvalidWhenEmpty) {
// `primary_id` must be kInvalidDisplayId if `displays` is empty.
display::mojom::DisplayListPtr i = display::mojom::DisplayList::New();
display::DisplayList o;
i->displays = {};
i->primary_id = 1;
i->current_id = display::kInvalidDisplayId;
EXPECT_DCHECK_DEATH({
SerializeAndDeserialize<display::mojom::DisplayList>(i, o);
EXPECT_FALSE(o.IsValid());
});
}
TEST(StructTraitsTest, DisplayListCurrentMustBeInvalidWhenEmpty) {
// `current_id` must be kInvalidDisplayId if `displays` is empty.
display::mojom::DisplayListPtr i = display::mojom::DisplayList::New();
display::DisplayList o;
i->displays = {};
i->primary_id = display::kInvalidDisplayId;
i->current_id = 1;
EXPECT_DCHECK_DEATH({
SerializeAndDeserialize<display::mojom::DisplayList>(i, o);
EXPECT_FALSE(o.IsValid());
});
}
TEST(StructTraitsTest, DisplayListPrimaryIdMustBePresent) {
// `primary_id` must match an element of `displays`.
display::mojom::DisplayListPtr i = display::mojom::DisplayList::New();
display::DisplayList o;
i->displays = {display::Display(1)};
i->primary_id = 2;
i->current_id = display::kInvalidDisplayId;
EXPECT_NE(i->primary_id, i->displays[0].id());
EXPECT_DCHECK_DEATH({
SerializeAndDeserialize<display::mojom::DisplayList>(i, o);
EXPECT_FALSE(o.IsValid());
});
}
TEST(StructTraitsTest, DisplayListCurrentIdMustBePresent) {
// `current_id` must match an element of `displays`.
display::mojom::DisplayListPtr i = display::mojom::DisplayList::New();
display::DisplayList o;
i->displays = {display::Display(1)};
i->primary_id = 1;
i->current_id = 2;
EXPECT_NE(i->current_id, i->displays[0].id());
EXPECT_DCHECK_DEATH({
SerializeAndDeserialize<display::mojom::DisplayList>(i, o);
EXPECT_FALSE(o.IsValid());
});
}
TEST(StructTraitsTest, DisplayListDisplaysIdsMustBeUnique) {
// `displays` must use unique id values.
display::mojom::DisplayListPtr i = display::mojom::DisplayList::New();
display::DisplayList o;
i->displays = {display::Display(1), display::Display(1)};
i->primary_id = 1;
i->current_id = 1;
EXPECT_EQ(i->displays[0].id(), i->displays[1].id());
EXPECT_DCHECK_DEATH({
SerializeAndDeserialize<display::mojom::DisplayList>(i, o);
EXPECT_FALSE(o.IsValid());
});
}
} // namespace