Commit b8a4405e authored by Mike Wasserman's avatar Mike Wasserman Committed by Commit Bot

Screen Enumeration: Add Window.isMultiScreen() function

This single bit exposes whether the device has multiple screens.
It informs the value of permission prompts to request more information.
This CL adds the IDL, IPC, implementation, and testing.

Add a mojo struct for GetDisplays results; reject on failure.

Test with the DevTools console (and multiple screens) with:
chrome://flags#enable-experimental-web-platform-features

Bug: 1098861, 1080710
Test: window.isMultiScreen() works as expected
Change-Id: I5d5d86716f1910bfc4ddc5dc70faef252b831a31
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2278529Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Commit-Queue: Michael Wasserman <msw@chromium.org>
Auto-Submit: Michael Wasserman <msw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789356}
parent ae0f783a
......@@ -19,12 +19,12 @@ namespace content {
namespace {
// Used to get info about the screens in a list of dictionary values.
// Used to get async getScreens() info in a list of dictionary values.
constexpr char kGetScreensScript[] = R"(
(async () => {
const screens = await self.getScreens();
let result = [];
for (s of screens) {
for (let s of screens) {
result.push({ availHeight: s.availHeight,
availLeft: s.availLeft,
availTop: s.availTop,
......@@ -46,6 +46,11 @@ constexpr char kGetScreensScript[] = R"(
})();
)";
// Used to get the async result of isMultiScreen().
constexpr char kIsMultiScreenScript[] = R"(
(async () => { return await self.isMultiScreen(); })();
)";
// Returns a list of dictionary values from native screen information, intended
// for comparison with the result of kGetScreensScript.
base::ListValue GetExpectedScreens() {
......@@ -107,6 +112,14 @@ IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, GetScreensBasic) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value));
}
IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, IsMultiScreenBasic) {
ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "empty.html")));
ASSERT_EQ(true, EvalJs(shell()->web_contents(), "'isMultiScreen' in self"));
auto result = EvalJs(shell()->web_contents(), kIsMultiScreenScript);
EXPECT_EQ(display::Screen::GetScreen()->GetNumDisplays() > 1,
result.ExtractBool());
}
// Tests screen enumeration functionality with a fake Screen object.
class FakeScreenEnumerationTest : public ScreenEnumerationTest {
public:
......@@ -153,7 +166,7 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) {
ASSERT_EQ(true, EvalJs(test_shell()->web_contents(), "'getScreens' in self"));
screen()->display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
display::DisplayList::Type::PRIMARY);
display::DisplayList::Type::NOT_PRIMARY);
screen()->display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
display::DisplayList::Type::NOT_PRIMARY);
......@@ -161,6 +174,27 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value));
}
// 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_IsMultiScreenFaked DISABLED_IsMultiScreenFaked
#else
#define MAYBE_IsMultiScreenFaked IsMultiScreenFaked
#endif
IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_IsMultiScreenFaked) {
ASSERT_TRUE(NavigateToURL(test_shell(), GetTestUrl(nullptr, "empty.html")));
ASSERT_EQ(true,
EvalJs(test_shell()->web_contents(), "'isMultiScreen' in self"));
EXPECT_EQ(false, EvalJs(test_shell()->web_contents(), kIsMultiScreenScript));
screen()->display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
display::DisplayList::Type::NOT_PRIMARY);
EXPECT_EQ(true, EvalJs(test_shell()->web_contents(), kIsMultiScreenScript));
screen()->display_list().RemoveDisplay(1);
EXPECT_EQ(false, EvalJs(test_shell()->web_contents(), kIsMultiScreenScript));
}
// 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)
......
......@@ -28,9 +28,8 @@ void ScreenEnumerationImpl::Bind(
void ScreenEnumerationImpl::GetDisplays(GetDisplaysCallback callback) {
// Ensure the callback is run if this object is prematurely destroyed.
auto scoped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<display::Display>(),
display::kInvalidDisplayId, display::kInvalidDisplayId, false);
auto scoped_callback =
mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), nullptr);
auto* permission_controller = PermissionControllerImpl::FromBrowserContext(
render_frame_host_->GetProcess()->GetBrowserContext());
......@@ -46,18 +45,26 @@ void ScreenEnumerationImpl::GetDisplaysWithPermissionStatus(
GetDisplaysCallback callback,
blink::mojom::PermissionStatus permission_status) {
if (permission_status != blink::mojom::PermissionStatus::GRANTED) {
std::move(callback).Run({}, display::kInvalidDisplayId,
display::kInvalidDisplayId, false);
std::move(callback).Run(nullptr);
return;
}
display::Screen* screen = display::Screen::GetScreen();
const std::vector<display::Display> displays = screen->GetAllDisplays();
const int64_t internal_id = display::Display::HasInternalDisplay()
? display::Display::InternalDisplayId()
: display::kInvalidDisplayId;
const int64_t primary_id = screen->GetPrimaryDisplay().id();
std::move(callback).Run(std::move(displays), internal_id, primary_id, true);
auto result = blink::mojom::Displays::New();
result->displays = screen->GetAllDisplays();
result->internal_id = display::Display::HasInternalDisplay()
? display::Display::InternalDisplayId()
: display::kInvalidDisplayId;
result->primary_id = screen->GetPrimaryDisplay().id();
std::move(callback).Run(std::move(result));
}
void ScreenEnumerationImpl::HasMultipleDisplays(
HasMultipleDisplaysCallback callback) {
auto result = display::Screen::GetScreen()->GetNumDisplays() > 1
? blink::mojom::MultipleDisplays::kTrue
: blink::mojom::MultipleDisplays::kFalse;
std::move(callback).Run(result);
}
} // namespace content
\ No newline at end of file
......@@ -28,6 +28,7 @@ class ScreenEnumerationImpl : public blink::mojom::ScreenEnumeration {
// blink::mojom::ScreenEnumeration:
void GetDisplays(GetDisplaysCallback callback) override;
void HasMultipleDisplays(HasMultipleDisplaysCallback callback) override;
private:
// Called with the result of the permission request in GetDisplays().
......
......@@ -6,14 +6,27 @@ module blink.mojom;
import "ui/display/mojom/display.mojom";
// This interface is implemented by the browser process to pass screen data to
// window and worker processes.
// A struct containing information about the set of connected displays.
struct Displays {
array<display.mojom.Display> displays; // The list of connected displays.
int64 internal_id; // The internal display id or kInvalidDisplayId if none.
int64 primary_id; // The primary display id or kInvalidDisplayId if none.
};
// An enum representing the presence of multiple displays, or an error state.
enum MultipleDisplays {
kFalse, // 0 or 1 displays are connected.
kTrue, // 2 or more displays are connected.
kError, // The display count is unavailable or access is denied.
};
// An interface enabling renderers to request information about screens
// connected to the device from the browser process.
interface ScreenEnumeration {
// If success is false, other returned values are meaningless. Otherwise,
// |displays| is the list of connected display devices; |internal_id| and
// |primary_id| are respectively the ids of the internal and primary displays.
GetDisplays() => (array<display.mojom.Display> displays,
int64 internal_id,
int64 primary_id,
bool success);
// Returns information about the connected displays, or null if an error
// occurred (e.g. display information is unavailable or access is denied).
GetDisplays() => (Displays? result);
// Returns information about the presence of multiple displays.
HasMultipleDisplays() => (MultipleDisplays result);
};
......@@ -9,6 +9,7 @@
#include "third_party/blink/public/mojom/screen_enumeration/screen_enumeration.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/screen.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
......@@ -22,32 +23,54 @@ namespace blink {
namespace {
void DidGetDisplays(
ScriptPromiseResolver* resolver,
mojo::Remote<mojom::blink::ScreenEnumeration>,
WTF::Vector<display::mojom::blink::DisplayPtr> backend_displays,
int64_t internal_id,
int64_t primary_id,
bool success) {
void DidGetDisplays(ScriptPromiseResolver* resolver,
mojo::Remote<mojom::blink::ScreenEnumeration>,
mojom::blink::DisplaysPtr result) {
ScriptState* script_state = resolver->GetScriptState();
if (!script_state->ContextIsValid())
return;
ScriptState::Scope scope(script_state);
if (result.is_null()) {
resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError,
"Screen information is unavailable or access is not allowed."));
return;
}
HeapVector<Member<Screen>> screens;
screens.ReserveInitialCapacity(backend_displays.size());
for (display::mojom::blink::DisplayPtr& backend_display : backend_displays) {
const bool internal = backend_display->id == internal_id;
const bool primary = backend_display->id == primary_id;
screens.ReserveInitialCapacity(result->displays.size());
for (display::mojom::blink::DisplayPtr& display : result->displays) {
const bool internal = display->id == result->internal_id;
const bool primary = display->id == result->primary_id;
// TODO(http://crbug.com/994889): Implement temporary, generated per-origin
// unique device IDs that reset when cookies are deleted. See related:
// web_bluetooth_device_id.h, unguessable_token.h, and uuid.h
const String id = String::NumberToStringECMAScript(screens.size());
screens.emplace_back(MakeGarbageCollected<Screen>(
std::move(backend_display), internal, primary, id));
screens.emplace_back(MakeGarbageCollected<Screen>(std::move(display),
internal, primary, id));
}
resolver->Resolve(std::move(screens));
}
void DidHasMultipleDisplays(ScriptPromiseResolver* resolver,
mojo::Remote<mojom::blink::ScreenEnumeration>,
mojom::blink::MultipleDisplays result) {
ScriptState* script_state = resolver->GetScriptState();
if (!script_state->ContextIsValid())
return;
ScriptState::Scope scope(script_state);
if (result == mojom::blink::MultipleDisplays::kError) {
resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError,
"Screen information is unavailable or access is not allowed."));
return;
}
resolver->Resolve(result == mojom::blink::MultipleDisplays::kTrue);
}
} // namespace
// static
......@@ -55,22 +78,46 @@ ScriptPromise GlobalScreenEnumeration::getScreens(
ScriptState* script_state,
LocalDOMWindow&,
ExceptionState& exception_state) {
if (!script_state->ContextIsValid()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The execution context is not valid.");
return ScriptPromise();
}
// TODO(msw): Cache the backend connection.
mojo::Remote<mojom::blink::ScreenEnumeration> backend;
ExecutionContext::From(script_state)
->GetBrowserInterfaceBroker()
.GetInterface(backend.BindNewPipeAndPassReceiver());
if (!backend) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
auto* raw_backend = backend.get();
raw_backend->GetDisplays(
WTF::Bind(&DidGetDisplays, WrapPersistent(resolver), std::move(backend)));
return resolver->Promise();
}
// static
ScriptPromise GlobalScreenEnumeration::isMultiScreen(
ScriptState* script_state,
LocalDOMWindow&,
ExceptionState& exception_state) {
if (!script_state->ContextIsValid()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"ScreenEnumeration backend unavailable");
"The execution context is not valid.");
return ScriptPromise();
}
// TODO(msw): Cache the backend connection.
mojo::Remote<mojom::blink::ScreenEnumeration> backend;
ExecutionContext::From(script_state)
->GetBrowserInterfaceBroker()
.GetInterface(backend.BindNewPipeAndPassReceiver());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
auto* raw_backend = backend.get();
raw_backend->GetDisplays(
WTF::Bind(&DidGetDisplays, WrapPersistent(resolver), std::move(backend)));
raw_backend->HasMultipleDisplays(WTF::Bind(
&DidHasMultipleDisplays, WrapPersistent(resolver), std::move(backend)));
return resolver->Promise();
}
......
......@@ -22,9 +22,14 @@ class GlobalScreenEnumeration {
public:
// Resolves to the list of |Screen| objects in the device's screen space.
static ScriptPromise getScreens(ScriptState*,
static ScriptPromise getScreens(ScriptState* script_state,
LocalDOMWindow&,
ExceptionState&);
ExceptionState& exception_state);
// Resolves to true if the number of available screens is greater than one.
static ScriptPromise isMultiScreen(ScriptState* script_state,
LocalDOMWindow&,
ExceptionState& exception_state);
DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(screenschange, kScreenschange)
};
......
......@@ -10,5 +10,7 @@
] partial interface Window {
[CallWith=ScriptState, RaisesException] Promise<sequence<Screen>> getScreens();
[CallWith=ScriptState, RaisesException] Promise<boolean> isMultiScreen();
attribute EventHandler onscreenschange;
};
......@@ -44,11 +44,22 @@ var ScreenEnumerationTest = (() => {
}
async getDisplays() {
if (!this.success_)
return Promise.resolve({ result: undefined, });
let value = new blink.mojom.Displays();
value.displays = this.displays_;
value.internalId = this.internalId_;
value.primaryId = this.primaryId_;
return Promise.resolve({ result: value, });
}
async hasMultipleDisplays() {
if (!this.success_)
return Promise.resolve({ result: blink.mojom.MultipleDisplays.kError });
return Promise.resolve({
displays: this.displays_,
internalId: this.internalId_,
primaryId: this.primaryId_,
success: this.success_,
result: this.displays_.length > 1
? blink.mojom.MultipleDisplays.kTrue
: blink.mojom.MultipleDisplays.kFalse,
});
}
}
......
......@@ -19,6 +19,7 @@ The `ScreenEnumerationTest` interface is defined as:
addDisplay(display); // Push display to the display vector.
removeDisplay(id); // Remove display from the display vector.
async getDisplays(); // Interceptor of getDisplays (screen_enumeration.mojom).
async hasMultipleDisplays(); // Interceptor of hasMultipleDisplays (screen_enumeration.mojom).
};
```
......
// META: global=window
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
"use strict";
'use strict';
promise_test(async testCase => {
assert_equals(typeof self.getScreens, "function");
}, "self.getScreens is present");
promise_test(async t => {
assert_equals(typeof self.getScreens, 'function');
}, 'getScreens() is present');
promise_test(async testCase => {
await test_driver.set_permission({name: "window-placement"}, "granted");
promise_test(async t => {
await test_driver.set_permission({name: 'window-placement'}, 'granted');
const screens = await self.getScreens();
assert_greater_than(screens.length, 0);
assert_equals(typeof screens[0].availWidth, "number");
assert_equals(typeof screens[0].availHeight, "number");
assert_equals(typeof screens[0].width, "number");
assert_equals(typeof screens[0].height, "number");
assert_equals(typeof screens[0].colorDepth, "number");
assert_equals(typeof screens[0].pixelDepth, "number");
assert_equals(typeof screens[0].availLeft, "number");
assert_equals(typeof screens[0].availTop, "number");
assert_equals(typeof screens[0].left, "number");
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].id, "string");
assert_equals(typeof screens[0].touchSupport, "boolean");
}, "self.getScreens returns at least 1 Screen with permission granted");
promise_test(async testCase => {
assert_equals(typeof screens[0].availWidth, 'number');
assert_equals(typeof screens[0].availHeight, 'number');
assert_equals(typeof screens[0].width, 'number');
assert_equals(typeof screens[0].height, 'number');
assert_equals(typeof screens[0].colorDepth, 'number');
assert_equals(typeof screens[0].pixelDepth, 'number');
assert_equals(typeof screens[0].availLeft, 'number');
assert_equals(typeof screens[0].availTop, 'number');
assert_equals(typeof screens[0].left, 'number');
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].id, 'string');
assert_equals(typeof screens[0].touchSupport, 'boolean');
}, '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);
await test_driver.set_permission({name: 'window-placement'}, 'denied');
const screens = await self.getScreens();
assert_equals(screens.length, 0);
}, 'self.getScreens returns no Screen objects with permission denied');
await promise_rejects_dom(t, 'NotAllowedError', self.getScreens());
}, 'getScreens() rejects the promise with permission denied');
async_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);
iframe.contentWindow.onunload = t.step_func(async () => {
// TODO(crbug.com/1106132): This should reject or resolve; not hang.
// assert_greater_than((await iframe.contentWindow.getScreens()).length, 0);
let iframeGetScreens = iframe.contentWindow.getScreens;
let constructor = iframe.contentWindow.DOMException;
assert_not_equals(iframeGetScreens, undefined);
assert_not_equals(constructor, undefined);
await t.step_wait(() => !iframe.contentWindow, "execution context invalid");
assert_equals(iframe.contentWindow, null);
await promise_rejects_dom(t, 'InvalidStateError', constructor, iframeGetScreens());
t.done();
});
document.body.removeChild(iframe);
}, "getScreens() resolves for attached iframe; rejects for detached iframe");
<!DOCTYPE html>
<meta charset="utf-8">
<meta charset='utf-8'>
<title>Screen Enumeration: getScreens() tentative</title>
<!-- TODO: update link to W3C whenever specifications are ready -->
<link rel="help" href="https://github.com/webscreens/screen-enumeration"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/screenenumeration-helpers.js"></script>
<link rel='help' href='https://github.com/webscreens/screen-enumeration'/>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src='resources/screenenumeration-helpers.js'></script>
<script>
"use strict";
'use strict';
function check_screen_matches_display(screen, display) {
assert_equals(screen.left, display.bounds.x);
assert_equals(screen.top, display.bounds.y);
assert_equals(screen.width, display.bounds.width);
assert_equals(screen.height, display.bounds.height);
assert_equals(screen.availLeft, display.workArea.x);
assert_equals(screen.availTop, display.workArea.y);
assert_equals(screen.availWidth, display.workArea.width);
assert_equals(screen.availHeight, display.workArea.height);
assert_equals(screen.scaleFactor, display.deviceScaleFactor);
}
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');
screen_enumeration_test(async (t, mockScreenEnum) => {
let display1 = makeDisplay(10,
{x: 0, y: 10, width: 1200, height: 800},
{x: 20, y: 30, width: 1000, height: 600},
{x: 0, y: 0, width: 800, height: 600},
{x: 0, y: 0, width: 800, height: 550},
1.0);
mockScreenEnum.addDisplay(display1);
......@@ -21,43 +39,31 @@ screen_enumeration_test(async (t, mockScreenEnum) => {
mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id);
mockScreenEnum.setSuccess(true);
// Grant window-placement permissions for testdriver.
await test_driver.set_permission({name: "window-placement"}, "granted");
await test_driver.set_permission({name: 'window-placement'}, 'granted');
// This returns the mocked displays via MockScreenEnumeration implementation.
const screens = await self.getScreens();
assert_equals(screens.length, 1);
assert_equals(screens[0].left, 0);
assert_equals(screens[0].top, 10);
assert_equals(screens[0].width, 1200);
assert_equals(screens[0].height, 800);
assert_equals(screens[0].availLeft, 20);
assert_equals(screens[0].availTop, 30);
assert_equals(screens[0].availWidth, 1000);
assert_equals(screens[0].availHeight, 600);
check_screen_matches_display(screens[0], display1);
assert_equals(screens[0].primary, true);
assert_equals(screens[0].internal, true);
assert_equals(screens[0].scaleFactor, 1.0);
assert_equals(screens[0].id, "0");
}, "getScreens() returns a single mocked screen");
assert_equals(screens[0].id, '0');
}, 'getScreens() supports a single mocked screen');
screen_enumeration_test(async (t, mockScreenEnum) => {
let display1 = makeDisplay(10,
{x: 0, y: 10, width: 1200, height: 800},
{x: 20, y: 30, width: 1000, height: 600},
{x: 0, y: 0, width: 800, height: 600},
{x: 0, y: 0, width: 800, height: 550},
1.0);
let display2 = makeDisplay(11,
{x: 0, y: 10, width: 1200, height: 800},
{x: 20, y: 30, width: 1000, height: 600},
1.0);
{x: 800, y: 0, width: 800, height: 600},
{x: 800, y: 0, width: 800, height: 550},
2.0);
let display3 = makeDisplay(12,
{x: 0, y: 10, width: 1200, height: 800},
{x: 20, y: 30, width: 1000, height: 600},
1.0);
{x: 0, y: 600, width: 1200, height: 800},
{x: 50, y: 50, width: 1150, height: 750},
1.5);
mockScreenEnum.addDisplay(display1);
mockScreenEnum.addDisplay(display2);
......@@ -66,29 +72,42 @@ screen_enumeration_test(async (t, mockScreenEnum) => {
mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id);
mockScreenEnum.setSuccess(true);
// Grant window-placement permissions for testdriver.
await test_driver.set_permission({name: "window-placement"}, "granted");
await test_driver.set_permission({name: 'window-placement'}, 'granted');
// This returns the mocked displays via MockScreenEnumeration implementation.
let screens = await self.getScreens();
assert_equals(screens.length, 3);
assert_equals(screens[0].id, "0");
assert_equals(screens[1].id, "1");
assert_equals(screens[2].id, "2");
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');
check_screen_matches_display(screens[1], display2);
assert_equals(screens[1].primary, false);
assert_equals(screens[1].internal, false);
assert_equals(screens[1].id, '1');
check_screen_matches_display(screens[2], display3);
assert_equals(screens[2].primary, false);
assert_equals(screens[2].internal, false);
assert_equals(screens[2].id, '2');
mockScreenEnum.removeDisplay(display2.id);
screens = await self.getScreens();
assert_equals(screens.length, 2);
assert_equals(screens[0].id, "0");
assert_equals(screens[1].id, "1");
check_screen_matches_display(screens[0], display1);
assert_equals(screens[0].id, '0');
check_screen_matches_display(screens[1], display3);
assert_equals(screens[1].id, '1');
mockScreenEnum.removeDisplay(display1.id);
screens = await self.getScreens();
assert_equals(screens.length, 1);
assert_equals(screens[0].id, "0");
}, "getScreens() supports multiple mocked screens");
check_screen_matches_display(screens[0], display3);
assert_equals(screens[0].id, '0');
}, 'getScreens() 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');
</script>
// META: global=window
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
'use strict';
promise_test(async t => {
assert_equals(typeof self.isMultiScreen, 'function');
}, 'isMultiScreen() is present');
promise_test(async t => {
await test_driver.set_permission({name: 'window-placement'}, 'granted');
assert_equals(typeof await self.isMultiScreen(), 'boolean');
}, 'isMultiScreen() returns a boolean value with permission granted');
promise_test(async t => {
await test_driver.set_permission({name: 'window-placement'}, 'denied');
assert_equals(typeof await self.isMultiScreen(), 'boolean');
}, 'isMultiScreen() returns a boolean value with permission denied');
async_test(async t => {
let iframe = document.body.appendChild(document.createElement('iframe'));
assert_equals(typeof await iframe.contentWindow.isMultiScreen(), 'boolean');
iframe.contentWindow.onunload = t.step_func(async () => {
// TODO(crbug.com/1106132): This should reject or resolve; not hang.
// assert_equals(typeof await iframe.contentWindow.isMultiScreen(), 'boolean');
let iframeIsMultiScreen = iframe.contentWindow.isMultiScreen;
let constructor = iframe.contentWindow.DOMException;
assert_not_equals(iframeIsMultiScreen, undefined);
assert_not_equals(constructor, undefined);
await t.step_wait(() => !iframe.contentWindow, "execution context invalid");
assert_equals(iframe.contentWindow, null);
await promise_rejects_dom(t, 'InvalidStateError', constructor, iframeIsMultiScreen());
t.done();
});
document.body.removeChild(iframe);
}, "isMultiScreen() resolves for attached iframe; rejects for detached iframe");
<!DOCTYPE html>
<meta charset='utf-8'>
<title>Screen Enumeration: isMultiScreen() tentative</title>
<!-- TODO: update link to W3C whenever specifications are ready -->
<link rel='help' href='https://github.com/webscreens/screen-enumeration'/>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src='resources/screenenumeration-helpers.js'></script>
<script>
'use strict';
screen_enumeration_test(async (t, mockScreenEnum) => {
let display1 = makeDisplay(10,
{x: 0, y: 0, width: 800, height: 600},
{x: 0, y: 0, width: 800, height: 550},
1.0);
let display2 = makeDisplay(11,
{x: 800, y: 0, width: 800, height: 600},
{x: 800, y: 0, width: 800, height: 550},
2.0);
mockScreenEnum.setSuccess(true);
assert_equals(await self.isMultiScreen(), false);
mockScreenEnum.addDisplay(display1);
assert_equals(await self.isMultiScreen(), false);
mockScreenEnum.addDisplay(display2);
assert_equals(await self.isMultiScreen(), true);
mockScreenEnum.removeDisplay(display2.id);
assert_equals(await self.isMultiScreen(), false);
}, 'isMultiScreen() works as expected with mocked screens');
screen_enumeration_test(async (t, mockScreenEnum) => {
mockScreenEnum.setSuccess(false);
promise_rejects_dom(t, 'NotAllowedError', self.isMultiScreen());
}, 'isMultiScreen() rejects when the mock success value is set to false');
</script>
......@@ -11932,6 +11932,7 @@ interface webkitURL
method getScreens
method getSelection
method getWindowSegments
method isMultiScreen
method matchMedia
method moveBy
method moveTo
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment