Window Placement: Implement Screens and ScreenAdvanced interfaces

This CL makes the following changes for a planned API redesign:
docs.google.com/document/d/1lgCentReLlym6j9kBS_Qo3r2HHZ6Ov7cSGjSWyi8cYs

1) Add Screens interface to vend multi-screen info for windowing.
- RuntimeEnabled and permission-gated; designed like window.screen.

2) Add ScreenAdvanced interface, to extend Screen with extra info.
- Make Screen impl non-final; override functions in ScreenAdvanced.

3) Add WindowScreens supplement, to host Window.getScreens().
- Rename the previous API access point to getScreensDeprecated().

4) Add ScreenInfo::is_primary & is_internal to expose in ScreenAdvanced.
- Add ScreenInfo::display_id for internal logic, it is not web-exposed.

5) Propagate multi-screen VisualProperties from browser to renderers:
- Replace the singular ScreenInfo member with a new ScreenInfos struct.
- Encapsulates multi-screen information and a current screen id.
- Add [mutable_]current() for easy/legacy access to the current screen.
- Validate mojo struct traits in [de]serialization; add tests.

6) Update WidgetBase, ChromeClient, tests, and more code accordingly.
- Update RenderWidgetHostViewMac's cached display::Display on changes.
- Add CoreInitializer plumbing to fire Screens.change in modules/.

FOLLOWUP: Refine Screens::ScreenInfosChanged implementation.
FOLLOWUP: Propagate multi-screen info to RemoteFrames.
FOLLOWUP: Update/add tests; remove old API and plumbing.
FOLLOWUP: Add WindowScreens PermissionObserver & set_disconnect_handler?
FOLLOWUP: Use WebContentsImpl's NativeView in RWHI::GetScreenInfo?

Bug: 897300, 1116528, 1138596, 1169312, 1116528, 1179876, 1179945
Test: New API roughly WAI w/ --enable-blink-features=WindowPlacement
Change-Id: I1d67cfabda62796274992e3e650d5209dd7bb857
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2669359
Commit-Queue: Michael Wasserman <msw@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Victor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#858379}
diff --git a/screen_enumeration/getScreens.tentative.https.window.js b/screen_enumeration/getScreens.tentative.https.window.js
index acd38bb..a4d6ec0 100644
--- a/screen_enumeration/getScreens.tentative.https.window.js
+++ b/screen_enumeration/getScreens.tentative.https.window.js
@@ -9,8 +9,10 @@
 
 promise_test(async t => {
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
-  const screens = await self.getScreens();
+  const screensInterface = await self.getScreens();
+  const screens = screensInterface.screens;
   assert_greater_than(screens.length, 0);
+  assert_true(screens.includes(screensInterface.currentScreen));
 
   assert_equals(typeof screens[0].availWidth, 'number');
   assert_equals(typeof screens[0].availHeight, 'number');
@@ -25,16 +27,18 @@
   assert_equals(typeof screens[0].top, 'number');
   assert_equals(typeof screens[0].orientation, 'object');
 
-  assert_equals(typeof screens[0].primary, 'boolean');
-  assert_equals(typeof screens[0].internal, 'boolean');
-  assert_equals(typeof screens[0].scaleFactor, 'number');
+  assert_equals(typeof screens[0].isExtended, 'boolean');
+  assert_equals(typeof screens[0].isPrimary, 'boolean');
+  assert_equals(typeof screens[0].isInternal, 'boolean');
+  assert_equals(typeof screens[0].devicePixelRatio, 'number');
   assert_equals(typeof screens[0].id, 'string');
-  assert_equals(typeof screens[0].touchSupport, 'boolean');
+  assert_equals(typeof screens[0].pointerTypes, 'object');
+  assert_equals(typeof screens[0].label, 'string');
 }, 'getScreens() returns at least 1 Screen with permission granted');
 
 promise_test(async t => {
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
-  assert_greater_than((await self.getScreens()).length, 0);
+  assert_greater_than((await self.getScreens()).screens.length, 0);
   await test_driver.set_permission({name: 'window-placement'}, 'denied');
   await promise_rejects_dom(t, 'NotAllowedError', self.getScreens());
 }, 'getScreens() rejects the promise with permission denied');
@@ -42,7 +46,7 @@
 promise_test(async t => {
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
   let iframe = document.body.appendChild(document.createElement('iframe'));
-  assert_greater_than((await iframe.contentWindow.getScreens()).length, 0);
+  assert_greater_than((await iframe.contentWindow.getScreens()).screens.length, 0);
 
   let iframeGetScreens;
   let constructor;
@@ -62,3 +66,16 @@
   assert_equals(iframe.contentWindow, null);
   await promise_rejects_dom(t, 'InvalidStateError', constructor, iframeGetScreens());
 }, "getScreens() resolves for attached iframe; rejects for detached iframe");
+
+promise_test(async t => {
+  await test_driver.set_permission({name: 'window-placement'}, 'granted');
+  let iframe = document.body.appendChild(document.createElement('iframe'));
+  const screensInterface = await iframe.contentWindow.getScreens();
+  assert_greater_than(screensInterface.screens.length, 0);
+  assert_equals(screensInterface.currentScreen, screensInterface.screens[0]);
+  iframe.remove();
+  await t.step_wait(() => !iframe.contentWindow, "execution context invalid");
+  assert_equals(iframe.contentWindow, null);
+  assert_equals(screensInterface.screens.length, 0);
+  assert_equals(screensInterface.currentScreen, null);
+}, "Cached Screens interface from detached iframe doesn't crash, behaves okay");
diff --git a/screen_enumeration/getScreens.values.https.html b/screen_enumeration/getScreens.values.https.html
index 93b27b4..b29ff5a 100644
--- a/screen_enumeration/getScreens.values.https.html
+++ b/screen_enumeration/getScreens.values.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset='utf-8'>
-<title>Window Placement: getScreens() tentative</title>
+<title>Window Placement: getScreensDeprecated() tentative</title>
 <!-- TODO: update link to W3C whenever specifications are ready -->
 <link rel='help' href='https://github.com/webscreens/window-placement'/>
 <script src='/resources/testharness.js'></script>
@@ -25,8 +25,8 @@
 screen_enumeration_test(async (t, mockScreenEnum) => {
   mockScreenEnum.setSuccess(true);
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
-  assert_equals((await self.getScreens()).length, 0);
-}, 'getScreens() supports an empty set of mocked screens');
+  assert_equals((await self.getScreensDeprecated()).length, 0);
+}, 'getScreensDeprecated() supports an empty set of mocked screens');
 
 screen_enumeration_test(async (t, mockScreenEnum) => {
   let display1 = makeDisplay(10,
@@ -41,13 +41,13 @@
 
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
 
-  const screens = await self.getScreens();
+  const screens = await self.getScreensDeprecated();
   assert_equals(screens.length, 1);
   check_screen_matches_display(screens[0], display1);
   assert_equals(screens[0].primary, true);
   assert_equals(screens[0].internal, true);
   assert_equals(screens[0].id, '0');
-}, 'getScreens() supports a single mocked screen');
+}, 'getScreensDeprecated() supports a single mocked screen');
 
 screen_enumeration_test(async (t, mockScreenEnum) => {
   let display1 = makeDisplay(10,
@@ -74,7 +74,7 @@
 
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
 
-  let screens = await self.getScreens();
+  let screens = await self.getScreensDeprecated();
   assert_equals(screens.length, 3);
   check_screen_matches_display(screens[0], display1);
   assert_equals(screens[0].primary, true);
@@ -90,7 +90,7 @@
   assert_equals(screens[2].id, '2');
 
   mockScreenEnum.removeDisplay(display2.id);
-  screens = await self.getScreens();
+  screens = await self.getScreensDeprecated();
   assert_equals(screens.length, 2);
   check_screen_matches_display(screens[0], display1);
   assert_equals(screens[0].id, '0');
@@ -98,16 +98,16 @@
   assert_equals(screens[1].id, '1');
 
   mockScreenEnum.removeDisplay(display1.id);
-  screens = await self.getScreens();
+  screens = await self.getScreensDeprecated();
   assert_equals(screens.length, 1);
   check_screen_matches_display(screens[0], display3);
   assert_equals(screens[0].id, '0');
-}, 'getScreens() supports multiple mocked screens');
+}, 'getScreensDeprecated() supports multiple mocked screens');
 
 screen_enumeration_test(async (t, mockScreenEnum) => {
   mockScreenEnum.setSuccess(false);
   await test_driver.set_permission({name: 'window-placement'}, 'granted');
-  promise_rejects_dom(t, 'NotAllowedError', self.getScreens());
-}, 'getScreens() rejects when the mock success value is set to false');
+  promise_rejects_dom(t, 'NotAllowedError', self.getScreensDeprecated());
+}, 'getScreensDeprecated() rejects when the mock success value is set false');
 
 </script>