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 { ...@@ -19,12 +19,12 @@ namespace content {
namespace { 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"( constexpr char kGetScreensScript[] = R"(
(async () => { (async () => {
const screens = await self.getScreens(); const screens = await self.getScreens();
let result = []; let result = [];
for (s of screens) { for (let s of screens) {
result.push({ availHeight: s.availHeight, result.push({ availHeight: s.availHeight,
availLeft: s.availLeft, availLeft: s.availLeft,
availTop: s.availTop, availTop: s.availTop,
...@@ -46,6 +46,11 @@ constexpr char kGetScreensScript[] = R"( ...@@ -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 // Returns a list of dictionary values from native screen information, intended
// for comparison with the result of kGetScreensScript. // for comparison with the result of kGetScreensScript.
base::ListValue GetExpectedScreens() { base::ListValue GetExpectedScreens() {
...@@ -107,6 +112,14 @@ IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, GetScreensBasic) { ...@@ -107,6 +112,14 @@ IN_PROC_BROWSER_TEST_F(ScreenEnumerationTest, GetScreensBasic) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value)); 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. // Tests screen enumeration functionality with a fake Screen object.
class FakeScreenEnumerationTest : public ScreenEnumerationTest { class FakeScreenEnumerationTest : public ScreenEnumerationTest {
public: public:
...@@ -153,7 +166,7 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) { ...@@ -153,7 +166,7 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) {
ASSERT_EQ(true, EvalJs(test_shell()->web_contents(), "'getScreens' in self")); ASSERT_EQ(true, EvalJs(test_shell()->web_contents(), "'getScreens' in self"));
screen()->display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)}, 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)}, screen()->display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
display::DisplayList::Type::NOT_PRIMARY); display::DisplayList::Type::NOT_PRIMARY);
...@@ -161,6 +174,27 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) { ...@@ -161,6 +174,27 @@ IN_PROC_BROWSER_TEST_F(FakeScreenEnumerationTest, MAYBE_GetScreensFaked) {
EXPECT_EQ(GetExpectedScreens(), base::Value::AsListValue(result.value)); 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): Windows crashes static casting to ScreenWin.
// TODO(crbug.com/1042990): Android requires a GetDisplayNearestView overload. // TODO(crbug.com/1042990): Android requires a GetDisplayNearestView overload.
#if defined(OS_ANDROID) || defined(OS_WIN) #if defined(OS_ANDROID) || defined(OS_WIN)
......
...@@ -28,9 +28,8 @@ void ScreenEnumerationImpl::Bind( ...@@ -28,9 +28,8 @@ void ScreenEnumerationImpl::Bind(
void ScreenEnumerationImpl::GetDisplays(GetDisplaysCallback callback) { void ScreenEnumerationImpl::GetDisplays(GetDisplaysCallback callback) {
// Ensure the callback is run if this object is prematurely destroyed. // Ensure the callback is run if this object is prematurely destroyed.
auto scoped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun( auto scoped_callback =
std::move(callback), std::vector<display::Display>(), mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), nullptr);
display::kInvalidDisplayId, display::kInvalidDisplayId, false);
auto* permission_controller = PermissionControllerImpl::FromBrowserContext( auto* permission_controller = PermissionControllerImpl::FromBrowserContext(
render_frame_host_->GetProcess()->GetBrowserContext()); render_frame_host_->GetProcess()->GetBrowserContext());
...@@ -46,18 +45,26 @@ void ScreenEnumerationImpl::GetDisplaysWithPermissionStatus( ...@@ -46,18 +45,26 @@ void ScreenEnumerationImpl::GetDisplaysWithPermissionStatus(
GetDisplaysCallback callback, GetDisplaysCallback callback,
blink::mojom::PermissionStatus permission_status) { blink::mojom::PermissionStatus permission_status) {
if (permission_status != blink::mojom::PermissionStatus::GRANTED) { if (permission_status != blink::mojom::PermissionStatus::GRANTED) {
std::move(callback).Run({}, display::kInvalidDisplayId, std::move(callback).Run(nullptr);
display::kInvalidDisplayId, false);
return; return;
} }
display::Screen* screen = display::Screen::GetScreen(); display::Screen* screen = display::Screen::GetScreen();
const std::vector<display::Display> displays = screen->GetAllDisplays(); auto result = blink::mojom::Displays::New();
const int64_t internal_id = display::Display::HasInternalDisplay() result->displays = screen->GetAllDisplays();
? display::Display::InternalDisplayId() result->internal_id = display::Display::HasInternalDisplay()
: display::kInvalidDisplayId; ? display::Display::InternalDisplayId()
const int64_t primary_id = screen->GetPrimaryDisplay().id(); : display::kInvalidDisplayId;
std::move(callback).Run(std::move(displays), internal_id, primary_id, true); 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 } // namespace content
\ No newline at end of file
...@@ -28,6 +28,7 @@ class ScreenEnumerationImpl : public blink::mojom::ScreenEnumeration { ...@@ -28,6 +28,7 @@ class ScreenEnumerationImpl : public blink::mojom::ScreenEnumeration {
// blink::mojom::ScreenEnumeration: // blink::mojom::ScreenEnumeration:
void GetDisplays(GetDisplaysCallback callback) override; void GetDisplays(GetDisplaysCallback callback) override;
void HasMultipleDisplays(HasMultipleDisplaysCallback callback) override;
private: private:
// Called with the result of the permission request in GetDisplays(). // Called with the result of the permission request in GetDisplays().
......
...@@ -6,14 +6,27 @@ module blink.mojom; ...@@ -6,14 +6,27 @@ module blink.mojom;
import "ui/display/mojom/display.mojom"; import "ui/display/mojom/display.mojom";
// This interface is implemented by the browser process to pass screen data to // A struct containing information about the set of connected displays.
// window and worker processes. 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 { interface ScreenEnumeration {
// If success is false, other returned values are meaningless. Otherwise, // Returns information about the connected displays, or null if an error
// |displays| is the list of connected display devices; |internal_id| and // occurred (e.g. display information is unavailable or access is denied).
// |primary_id| are respectively the ids of the internal and primary displays. GetDisplays() => (Displays? result);
GetDisplays() => (array<display.mojom.Display> displays,
int64 internal_id, // Returns information about the presence of multiple displays.
int64 primary_id, HasMultipleDisplays() => (MultipleDisplays result);
bool success);
}; };
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "third_party/blink/public/mojom/screen_enumeration/screen_enumeration.mojom-blink.h" #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.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.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/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/screen.h" #include "third_party/blink/renderer/core/frame/screen.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
...@@ -22,32 +23,54 @@ namespace blink { ...@@ -22,32 +23,54 @@ namespace blink {
namespace { namespace {
void DidGetDisplays( void DidGetDisplays(ScriptPromiseResolver* resolver,
ScriptPromiseResolver* resolver, mojo::Remote<mojom::blink::ScreenEnumeration>,
mojo::Remote<mojom::blink::ScreenEnumeration>, mojom::blink::DisplaysPtr result) {
WTF::Vector<display::mojom::blink::DisplayPtr> backend_displays,
int64_t internal_id,
int64_t primary_id,
bool success) {
ScriptState* script_state = resolver->GetScriptState(); ScriptState* script_state = resolver->GetScriptState();
if (!script_state->ContextIsValid()) if (!script_state->ContextIsValid())
return; 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; HeapVector<Member<Screen>> screens;
screens.ReserveInitialCapacity(backend_displays.size()); screens.ReserveInitialCapacity(result->displays.size());
for (display::mojom::blink::DisplayPtr& backend_display : backend_displays) { for (display::mojom::blink::DisplayPtr& display : result->displays) {
const bool internal = backend_display->id == internal_id; const bool internal = display->id == result->internal_id;
const bool primary = backend_display->id == primary_id; const bool primary = display->id == result->primary_id;
// TODO(http://crbug.com/994889): Implement temporary, generated per-origin // TODO(http://crbug.com/994889): Implement temporary, generated per-origin
// unique device IDs that reset when cookies are deleted. See related: // unique device IDs that reset when cookies are deleted. See related:
// web_bluetooth_device_id.h, unguessable_token.h, and uuid.h // web_bluetooth_device_id.h, unguessable_token.h, and uuid.h
const String id = String::NumberToStringECMAScript(screens.size()); const String id = String::NumberToStringECMAScript(screens.size());
screens.emplace_back(MakeGarbageCollected<Screen>( screens.emplace_back(MakeGarbageCollected<Screen>(std::move(display),
std::move(backend_display), internal, primary, id)); internal, primary, id));
} }
resolver->Resolve(std::move(screens)); 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 } // namespace
// static // static
...@@ -55,22 +78,46 @@ ScriptPromise GlobalScreenEnumeration::getScreens( ...@@ -55,22 +78,46 @@ ScriptPromise GlobalScreenEnumeration::getScreens(
ScriptState* script_state, ScriptState* script_state,
LocalDOMWindow&, LocalDOMWindow&,
ExceptionState& exception_state) { 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. // TODO(msw): Cache the backend connection.
mojo::Remote<mojom::blink::ScreenEnumeration> backend; mojo::Remote<mojom::blink::ScreenEnumeration> backend;
ExecutionContext::From(script_state) ExecutionContext::From(script_state)
->GetBrowserInterfaceBroker() ->GetBrowserInterfaceBroker()
.GetInterface(backend.BindNewPipeAndPassReceiver()); .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, exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"ScreenEnumeration backend unavailable"); "The execution context is not valid.");
return ScriptPromise(); 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* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
auto* raw_backend = backend.get(); auto* raw_backend = backend.get();
raw_backend->GetDisplays( raw_backend->HasMultipleDisplays(WTF::Bind(
WTF::Bind(&DidGetDisplays, WrapPersistent(resolver), std::move(backend))); &DidHasMultipleDisplays, WrapPersistent(resolver), std::move(backend)));
return resolver->Promise(); return resolver->Promise();
} }
......
...@@ -22,9 +22,14 @@ class GlobalScreenEnumeration { ...@@ -22,9 +22,14 @@ class GlobalScreenEnumeration {
public: public:
// Resolves to the list of |Screen| objects in the device's screen space. // Resolves to the list of |Screen| objects in the device's screen space.
static ScriptPromise getScreens(ScriptState*, static ScriptPromise getScreens(ScriptState* script_state,
LocalDOMWindow&, 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) DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(screenschange, kScreenschange)
}; };
......
...@@ -10,5 +10,7 @@ ...@@ -10,5 +10,7 @@
] partial interface Window { ] partial interface Window {
[CallWith=ScriptState, RaisesException] Promise<sequence<Screen>> getScreens(); [CallWith=ScriptState, RaisesException] Promise<sequence<Screen>> getScreens();
[CallWith=ScriptState, RaisesException] Promise<boolean> isMultiScreen();
attribute EventHandler onscreenschange; attribute EventHandler onscreenschange;
}; };
...@@ -44,11 +44,22 @@ var ScreenEnumerationTest = (() => { ...@@ -44,11 +44,22 @@ var ScreenEnumerationTest = (() => {
} }
async getDisplays() { 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({ return Promise.resolve({
displays: this.displays_, result: this.displays_.length > 1
internalId: this.internalId_, ? blink.mojom.MultipleDisplays.kTrue
primaryId: this.primaryId_, : blink.mojom.MultipleDisplays.kFalse,
success: this.success_,
}); });
} }
} }
......
...@@ -19,6 +19,7 @@ The `ScreenEnumerationTest` interface is defined as: ...@@ -19,6 +19,7 @@ The `ScreenEnumerationTest` interface is defined as:
addDisplay(display); // Push display to the display vector. addDisplay(display); // Push display to the display vector.
removeDisplay(id); // Remove display from the display vector. removeDisplay(id); // Remove display from the display vector.
async getDisplays(); // Interceptor of getDisplays (screen_enumeration.mojom). async getDisplays(); // Interceptor of getDisplays (screen_enumeration.mojom).
async hasMultipleDisplays(); // Interceptor of hasMultipleDisplays (screen_enumeration.mojom).
}; };
``` ```
......
// META: global=window // META: global=window
// META: script=/resources/testdriver.js // META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js // META: script=/resources/testdriver-vendor.js
"use strict"; 'use strict';
promise_test(async testCase => { promise_test(async t => {
assert_equals(typeof self.getScreens, "function"); assert_equals(typeof self.getScreens, 'function');
}, "self.getScreens is present"); }, 'getScreens() is present');
promise_test(async testCase => { promise_test(async t => {
await test_driver.set_permission({name: "window-placement"}, "granted"); await test_driver.set_permission({name: 'window-placement'}, 'granted');
const screens = await self.getScreens(); const screens = await self.getScreens();
assert_greater_than(screens.length, 0); assert_greater_than(screens.length, 0);
assert_equals(typeof screens[0].availWidth, "number"); assert_equals(typeof screens[0].availWidth, 'number');
assert_equals(typeof screens[0].availHeight, "number"); assert_equals(typeof screens[0].availHeight, 'number');
assert_equals(typeof screens[0].width, "number"); assert_equals(typeof screens[0].width, 'number');
assert_equals(typeof screens[0].height, "number"); assert_equals(typeof screens[0].height, 'number');
assert_equals(typeof screens[0].colorDepth, "number"); assert_equals(typeof screens[0].colorDepth, 'number');
assert_equals(typeof screens[0].pixelDepth, "number"); assert_equals(typeof screens[0].pixelDepth, 'number');
assert_equals(typeof screens[0].availLeft, "number"); assert_equals(typeof screens[0].availLeft, 'number');
assert_equals(typeof screens[0].availTop, "number"); assert_equals(typeof screens[0].availTop, 'number');
assert_equals(typeof screens[0].left, "number"); assert_equals(typeof screens[0].left, 'number');
assert_equals(typeof screens[0].top, "number"); assert_equals(typeof screens[0].top, 'number');
assert_equals(typeof screens[0].orientation, "object"); assert_equals(typeof screens[0].orientation, 'object');
assert_equals(typeof screens[0].primary, "boolean"); assert_equals(typeof screens[0].primary, 'boolean');
assert_equals(typeof screens[0].internal, "boolean"); assert_equals(typeof screens[0].internal, 'boolean');
assert_equals(typeof screens[0].scaleFactor, "number"); assert_equals(typeof screens[0].scaleFactor, 'number');
assert_equals(typeof screens[0].id, "string"); assert_equals(typeof screens[0].id, 'string');
assert_equals(typeof screens[0].touchSupport, "boolean"); assert_equals(typeof screens[0].touchSupport, 'boolean');
}, "self.getScreens returns at least 1 Screen with permission granted"); }, 'getScreens() returns at least 1 Screen with permission granted');
promise_test(async testCase => { 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'); await test_driver.set_permission({name: 'window-placement'}, 'denied');
const screens = await self.getScreens(); await promise_rejects_dom(t, 'NotAllowedError', self.getScreens());
assert_equals(screens.length, 0); }, 'getScreens() rejects the promise with permission denied');
}, 'self.getScreens returns no Screen objects 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> <!DOCTYPE html>
<meta charset="utf-8"> <meta charset='utf-8'>
<title>Screen Enumeration: getScreens() tentative</title> <title>Screen Enumeration: getScreens() tentative</title>
<!-- TODO: update link to W3C whenever specifications are ready --> <!-- TODO: update link to W3C whenever specifications are ready -->
<link rel="help" href="https://github.com/webscreens/screen-enumeration"/> <link rel='help' href='https://github.com/webscreens/screen-enumeration'/>
<script src="/resources/testharness.js"></script> <script src='/resources/testharness.js'></script>
<script src="/resources/testharnessreport.js"></script> <script src='/resources/testharnessreport.js'></script>
<script src="resources/screenenumeration-helpers.js"></script> <script src='resources/screenenumeration-helpers.js'></script>
<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) => { screen_enumeration_test(async (t, mockScreenEnum) => {
let display1 = makeDisplay(10, let display1 = makeDisplay(10,
{x: 0, y: 10, width: 1200, height: 800}, {x: 0, y: 0, width: 800, height: 600},
{x: 20, y: 30, width: 1000, height: 600}, {x: 0, y: 0, width: 800, height: 550},
1.0); 1.0);
mockScreenEnum.addDisplay(display1); mockScreenEnum.addDisplay(display1);
...@@ -21,43 +39,31 @@ screen_enumeration_test(async (t, mockScreenEnum) => { ...@@ -21,43 +39,31 @@ screen_enumeration_test(async (t, mockScreenEnum) => {
mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id); mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id);
mockScreenEnum.setSuccess(true); 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(); const screens = await self.getScreens();
assert_equals(screens.length, 1); assert_equals(screens.length, 1);
check_screen_matches_display(screens[0], display1);
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);
assert_equals(screens[0].primary, true); assert_equals(screens[0].primary, true);
assert_equals(screens[0].internal, true); assert_equals(screens[0].internal, true);
assert_equals(screens[0].scaleFactor, 1.0); assert_equals(screens[0].id, '0');
assert_equals(screens[0].id, "0"); }, 'getScreens() supports a single mocked screen');
}, "getScreens() returns a single mocked screen");
screen_enumeration_test(async (t, mockScreenEnum) => { screen_enumeration_test(async (t, mockScreenEnum) => {
let display1 = makeDisplay(10, let display1 = makeDisplay(10,
{x: 0, y: 10, width: 1200, height: 800}, {x: 0, y: 0, width: 800, height: 600},
{x: 20, y: 30, width: 1000, height: 600}, {x: 0, y: 0, width: 800, height: 550},
1.0); 1.0);
let display2 = makeDisplay(11, let display2 = makeDisplay(11,
{x: 0, y: 10, width: 1200, height: 800}, {x: 800, y: 0, width: 800, height: 600},
{x: 20, y: 30, width: 1000, height: 600}, {x: 800, y: 0, width: 800, height: 550},
1.0); 2.0);
let display3 = makeDisplay(12, let display3 = makeDisplay(12,
{x: 0, y: 10, width: 1200, height: 800}, {x: 0, y: 600, width: 1200, height: 800},
{x: 20, y: 30, width: 1000, height: 600}, {x: 50, y: 50, width: 1150, height: 750},
1.0); 1.5);
mockScreenEnum.addDisplay(display1); mockScreenEnum.addDisplay(display1);
mockScreenEnum.addDisplay(display2); mockScreenEnum.addDisplay(display2);
...@@ -66,29 +72,42 @@ screen_enumeration_test(async (t, mockScreenEnum) => { ...@@ -66,29 +72,42 @@ screen_enumeration_test(async (t, mockScreenEnum) => {
mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id); mockScreenEnum.setInternalId(mockScreenEnum.displays_[0].id);
mockScreenEnum.setSuccess(true); 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(); let screens = await self.getScreens();
assert_equals(screens.length, 3); assert_equals(screens.length, 3);
assert_equals(screens[0].id, "0"); check_screen_matches_display(screens[0], display1);
assert_equals(screens[1].id, "1"); assert_equals(screens[0].primary, true);
assert_equals(screens[2].id, "2"); 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); mockScreenEnum.removeDisplay(display2.id);
screens = await self.getScreens(); screens = await self.getScreens();
assert_equals(screens.length, 2); assert_equals(screens.length, 2);
assert_equals(screens[0].id, "0"); check_screen_matches_display(screens[0], display1);
assert_equals(screens[1].id, "1"); assert_equals(screens[0].id, '0');
check_screen_matches_display(screens[1], display3);
assert_equals(screens[1].id, '1');
mockScreenEnum.removeDisplay(display1.id); mockScreenEnum.removeDisplay(display1.id);
screens = await self.getScreens(); screens = await self.getScreens();
assert_equals(screens.length, 1); assert_equals(screens.length, 1);
assert_equals(screens[0].id, "0"); check_screen_matches_display(screens[0], display3);
}, "getScreens() supports multiple mocked screens"); 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> </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 ...@@ -11932,6 +11932,7 @@ interface webkitURL
method getScreens method getScreens
method getSelection method getSelection
method getWindowSegments method getWindowSegments
method isMultiScreen
method matchMedia method matchMedia
method moveBy method moveBy
method moveTo 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