feat(emulate): implement emulateMedia color scheme in FF (#71)

This commit is contained in:
Pavel Feldman 2019-11-25 15:00:04 -08:00 committed by Andrey Lushnikov
parent 860915b9da
commit 2e581f1625
8 changed files with 70 additions and 86 deletions

View File

@ -1 +1 @@
1001
1002

View File

@ -1,6 +1,6 @@
From acb1ee0450ffe7739ed96e556095fb0f1c5d60d0 Mon Sep 17 00:00:00 2001
From 533a4ce6515be3665e44ddeae30ecf5052a17191 Mon Sep 17 00:00:00 2001
From: Pavel <pavel.feldman@gmail.com>
Date: Mon, 25 Nov 2019 13:47:08 -0800
Date: Mon, 25 Nov 2019 14:56:33 -0800
Subject: [PATCH] chore: bootstrap
---
@ -23,7 +23,7 @@ Subject: [PATCH] chore: bootstrap
testing/juggler/content/ContentSession.js | 63 ++
testing/juggler/content/FrameTree.js | 232 ++++++
testing/juggler/content/NetworkMonitor.js | 62 ++
testing/juggler/content/PageAgent.js | 638 +++++++++++++++++
testing/juggler/content/PageAgent.js | 644 +++++++++++++++++
testing/juggler/content/RuntimeAgent.js | 468 ++++++++++++
testing/juggler/content/ScrollbarManager.js | 85 +++
.../juggler/content/floating-scrollbars.css | 47 ++
@ -37,7 +37,7 @@ Subject: [PATCH] chore: bootstrap
testing/juggler/protocol/NetworkHandler.js | 154 ++++
testing/juggler/protocol/PageHandler.js | 277 ++++++++
testing/juggler/protocol/PrimitiveTypes.js | 143 ++++
testing/juggler/protocol/Protocol.js | 669 ++++++++++++++++++
testing/juggler/protocol/Protocol.js | 670 ++++++++++++++++++
testing/juggler/protocol/RuntimeHandler.js | 41 ++
testing/juggler/protocol/TargetHandler.js | 75 ++
.../statusfilter/nsBrowserStatusFilter.cpp | 12 +-
@ -46,7 +46,7 @@ Subject: [PATCH] chore: bootstrap
uriloader/base/nsDocLoader.h | 5 +
uriloader/base/nsIWebProgress.idl | 7 +-
uriloader/base/nsIWebProgressListener2.idl | 23 +
42 files changed, 4579 insertions(+), 7 deletions(-)
42 files changed, 4586 insertions(+), 7 deletions(-)
create mode 100644 testing/juggler/BrowserContextManager.js
create mode 100644 testing/juggler/Helper.js
create mode 100644 testing/juggler/NetworkObserver.js
@ -1795,10 +1795,10 @@ index 000000000000..2508cce41565
+
diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js
new file mode 100644
index 000000000000..0e47a99eda47
index 000000000000..34b799b53113
--- /dev/null
+++ b/testing/juggler/content/PageAgent.js
@@ -0,0 +1,638 @@
@@ -0,0 +1,644 @@
+"use strict";
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const Ci = Components.interfaces;
@ -1857,12 +1857,18 @@ index 000000000000..0e47a99eda47
+ this._scrollbarManager.setFloatingScrollbars(isMobile);
+ }
+
+ async setEmulatedMedia({media}) {
+ async setEmulatedMedia({type, colorScheme}) {
+ const docShell = this._frameTree.mainFrame().docShell();
+ if (media)
+ docShell.contentViewer.emulateMedium(media);
+ else
+ docShell.contentViewer.stopEmulatingMedium();
+ const cv = docShell.contentViewer;
+ if (type === '')
+ cv.stopEmulatingMedium();
+ else if (type)
+ cv.emulateMedium(type);
+ switch (colorScheme) {
+ case 'light': cv.emulatePrefersColorScheme(cv.PREFERS_COLOR_SCHEME_LIGHT); break;
+ case 'dark': cv.emulatePrefersColorScheme(cv.PREFERS_COLOR_SCHEME_DARK); break;
+ case 'no-preference': cv.emulatePrefersColorScheme(cv.PREFERS_COLOR_SCHEME_NO_PREFERENCE); break;
+ }
+ }
+
+ async setUserAgent({userAgent}) {
@ -4123,10 +4129,10 @@ index 000000000000..78b6601b91d0
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js
new file mode 100644
index 000000000000..0b2044e057a4
index 000000000000..1ed27df14a1a
--- /dev/null
+++ b/testing/juggler/protocol/Protocol.js
@@ -0,0 +1,669 @@
@@ -0,0 +1,670 @@
+const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
+
+// Protocol-specific types.
@ -4617,7 +4623,8 @@ index 000000000000..0b2044e057a4
+ },
+ 'setEmulatedMedia': {
+ params: {
+ media: t.Enum(['screen', 'print', '']),
+ type: t.Optional(t.Enum(['screen', 'print', ''])),
+ colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
+ },
+ },
+ 'setCacheDisabled': {

View File

@ -1240,9 +1240,7 @@ List of all available devices is available in the source code: [DeviceDescriptor
#### page.emulateMedia(options)
- `options` <[Object]>
- `type` <?[string]> Optional. Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `features` <?[Array]<[Object]>> Optional. Given an array of media feature objects, emulates CSS media features on the page. Each media feature object must have the following properties:
- `name` <[string]> The CSS media feature name. Supported names are `'prefers-colors-scheme'` and `'prefers-reduced-motion'`.
- `value` <[string]> The value for the given CSS media feature.
- `colorScheme` <?[string]> Optional. Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- returns: <[Promise]>
```js
@ -1265,34 +1263,13 @@ await page.evaluate(() => matchMedia('print').matches));
```
```js
await page.emulateMedia({ features: [{ name: 'prefers-color-scheme', value: 'dark' }] });
await page.emulateMedia({ colorScheme: 'dark' }] });
await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
// → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
// → false
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false
await page.emulateMedia({ features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches));
// → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false
await page.emulateMedia({ features: [
{ name: 'prefers-color-scheme', value: 'dark' },
{ name: 'prefers-reduced-motion', value: 'reduce' },
] });
await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
// → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
// → false
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false
await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches));
// → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false
```
#### page.emulateTimezone(timezoneId)

View File

@ -20,7 +20,7 @@ import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import { assert, debugError, helper } from '../helper';
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption, mediaTypes, mediaColorSchemes } from '../input';
import { TimeoutSettings } from '../TimeoutSettings';
import { Browser } from './Browser';
import { BrowserContext } from './BrowserContext';
@ -78,6 +78,7 @@ export class Page extends EventEmitter {
private _fileChooserInterceptionIsDisabled = false;
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
private _disconnectPromise: Promise<Error> | undefined;
private _emulatedMediaType: string | undefined;
static async create(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise<Page> {
const page = new Page(client, target, ignoreHTTPSErrors, screenshotTaskQueue);
@ -534,15 +535,15 @@ export class Page extends EventEmitter {
await this._client.send('Page.setBypassCSP', { enabled });
}
async emulateMedia(options: { type?: string, features?: MediaFeature[] }) {
assert(options.type === 'screen' || options.type === 'print' || options.type === undefined, 'Unsupported media type: ' + options.type);
if (options.features) {
options.features.forEach(mediaFeature => {
const name = mediaFeature.name;
assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name);
});
}
await this._client.send('Emulation.setEmulatedMedia', { media: options.type, features: options.features });
async emulateMedia(options: {
type?: string,
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
const media = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type;
const features = typeof options.colorScheme === 'undefined' ? [] : [{ name: 'prefers-color-scheme', value: options.colorScheme }];
await this._client.send('Emulation.setEmulatedMedia', { media: media || '', features });
this._emulatedMediaType = options.type;
}
async emulateTimezone(timezoneId: string | null) {

View File

@ -15,7 +15,6 @@ import { Mouse, RawKeyboardImpl } from './Input';
import { createHandle, ElementHandle, JSHandle } from './JSHandle';
import { NavigationWatchdog } from './NavigationWatchdog';
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
import { ClickOptions, MultiClickOptions } from '../input';
import * as input from '../input';
const writeFileAsync = helper.promisify(fs.writeFile);
@ -151,10 +150,12 @@ export class Page extends EventEmitter {
await this._networkManager.setExtraHTTPHeaders(headers);
}
async emulateMedia(options: { type?: string, features?: MediaFeature[] }) {
assert(!options.features, 'Media feature emulation is not supported');
assert(options.type === 'screen' || options.type === 'print' || options.type === undefined, 'Unsupported media type: ' + options.type);
await this._session.send('Page.setEmulatedMedia', { media: options.type || '' });
async emulateMedia(options: {
type?: string,
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
await this._session.send('Page.setEmulatedMedia', options);
}
async exposeFunction(name: string, playwrightFunction: Function) {
@ -478,15 +479,15 @@ export class Page extends EventEmitter {
return this.mainFrame().addStyleTag(options);
}
click(selector: string, options?: ClickOptions) {
click(selector: string, options?: input.ClickOptions) {
return this.mainFrame().click(selector, options);
}
dblclick(selector: string, options?: MultiClickOptions) {
dblclick(selector: string, options?: input.MultiClickOptions) {
return this.mainFrame().dblclick(selector, options);
}
tripleclick(selector: string, options?: MultiClickOptions) {
tripleclick(selector: string, options?: input.MultiClickOptions) {
return this.mainFrame().tripleclick(selector, options);
}

View File

@ -310,3 +310,6 @@ export const fillFunction = (element: HTMLElement) => {
}
return false;
};
export const mediaTypes = new Set(['screen', 'print']);
export const mediaColorSchemes = new Set(['dark', 'light', 'no-preference']);

View File

@ -19,7 +19,7 @@ import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as mime from 'mime';
import { assert, debugError, helper, RegisteredListener } from '../helper';
import { ClickOptions, MultiClickOptions } from '../input';
import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions } from '../input';
import { TimeoutSettings } from '../TimeoutSettings';
import { Browser, BrowserContext } from './Browser';
import { TargetSession, TargetSessionEvents } from './Connection';
@ -56,6 +56,7 @@ export class Page extends EventEmitter {
private _workers = new Map<string, Worker>();
private _disconnectPromise: Promise<Error> | undefined;
private _sessionListeners: RegisteredListener[] = [];
private _emulatedMediaType: string | undefined;
static async create(session: TargetSession, target: Target, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise<Page> {
const page = new Page(session, target, screenshotTaskQueue);
@ -326,10 +327,15 @@ export class Page extends EventEmitter {
]);
}
async emulateMedia(options: { type?: string, features?: MediaFeature[] }) {
assert(!options.features, 'Media feature emulation is not supported');
assert(options.type === 'screen' || options.type === 'print' || options.type === undefined, 'Unsupported media type: ' + options.type);
await this._session.send('Page.setEmulatedMedia', { media: options.type });
async emulateMedia(options: {
type?: string | null,
colorScheme?: 'dark' | 'light' | 'no-preference' | null }) {
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
assert(!options.colorScheme, 'Media feature emulation is not supported');
const media = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type;
await this._session.send('Page.setEmulatedMedia', { media: media || '' });
this._emulatedMediaType = options.type;
}
async setViewport(viewport: Viewport) {

View File

@ -104,6 +104,9 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({});
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({ type: '' });
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false);
});
@ -114,39 +117,25 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
});
});
describe.skip(FFOX || WEBKIT)('Page.emulateMedia features', function() {
describe.skip(WEBKIT)('Page.emulateMedia colorScheme', function() {
it('should work', async({page, server}) => {
await page.emulateMedia({ features: [
{ name: 'prefers-reduced-motion', value: 'reduce' },
] });
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false);
await page.emulateMedia({ features: [
{ name: 'prefers-color-scheme', value: 'light' },
] });
await page.emulateMedia({ colorScheme: 'light' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ features: [
{ name: 'prefers-color-scheme', value: 'dark' },
] });
await page.emulateMedia({ colorScheme: 'dark' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ features: [
{ name: 'prefers-reduced-motion', value: 'reduce' },
{ name: 'prefers-color-scheme', value: 'light' },
] });
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
await page.emulateMedia({ colorScheme: 'no-preference' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(true);
});
it('should throw in case of bad argument', async({page, server}) => {
let error = null;
await page.emulateMedia({ features: [{ name: 'bad', value: '' }] }).catch(e => error = e);
expect(error.message).toBe('Unsupported media feature: bad');
await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported color scheme: bad');
});
});