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> 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 Subject: [PATCH] chore: bootstrap
--- ---
@ -23,7 +23,7 @@ Subject: [PATCH] chore: bootstrap
testing/juggler/content/ContentSession.js | 63 ++ testing/juggler/content/ContentSession.js | 63 ++
testing/juggler/content/FrameTree.js | 232 ++++++ testing/juggler/content/FrameTree.js | 232 ++++++
testing/juggler/content/NetworkMonitor.js | 62 ++ 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/RuntimeAgent.js | 468 ++++++++++++
testing/juggler/content/ScrollbarManager.js | 85 +++ testing/juggler/content/ScrollbarManager.js | 85 +++
.../juggler/content/floating-scrollbars.css | 47 ++ .../juggler/content/floating-scrollbars.css | 47 ++
@ -37,7 +37,7 @@ Subject: [PATCH] chore: bootstrap
testing/juggler/protocol/NetworkHandler.js | 154 ++++ testing/juggler/protocol/NetworkHandler.js | 154 ++++
testing/juggler/protocol/PageHandler.js | 277 ++++++++ testing/juggler/protocol/PageHandler.js | 277 ++++++++
testing/juggler/protocol/PrimitiveTypes.js | 143 ++++ 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/RuntimeHandler.js | 41 ++
testing/juggler/protocol/TargetHandler.js | 75 ++ testing/juggler/protocol/TargetHandler.js | 75 ++
.../statusfilter/nsBrowserStatusFilter.cpp | 12 +- .../statusfilter/nsBrowserStatusFilter.cpp | 12 +-
@ -46,7 +46,7 @@ Subject: [PATCH] chore: bootstrap
uriloader/base/nsDocLoader.h | 5 + uriloader/base/nsDocLoader.h | 5 +
uriloader/base/nsIWebProgress.idl | 7 +- uriloader/base/nsIWebProgress.idl | 7 +-
uriloader/base/nsIWebProgressListener2.idl | 23 + 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/BrowserContextManager.js
create mode 100644 testing/juggler/Helper.js create mode 100644 testing/juggler/Helper.js
create mode 100644 testing/juggler/NetworkObserver.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 diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js
new file mode 100644 new file mode 100644
index 000000000000..0e47a99eda47 index 000000000000..34b799b53113
--- /dev/null --- /dev/null
+++ b/testing/juggler/content/PageAgent.js +++ b/testing/juggler/content/PageAgent.js
@@ -0,0 +1,638 @@ @@ -0,0 +1,644 @@
+"use strict"; +"use strict";
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const Ci = Components.interfaces; +const Ci = Components.interfaces;
@ -1857,12 +1857,18 @@ index 000000000000..0e47a99eda47
+ this._scrollbarManager.setFloatingScrollbars(isMobile); + this._scrollbarManager.setFloatingScrollbars(isMobile);
+ } + }
+ +
+ async setEmulatedMedia({media}) { + async setEmulatedMedia({type, colorScheme}) {
+ const docShell = this._frameTree.mainFrame().docShell(); + const docShell = this._frameTree.mainFrame().docShell();
+ if (media) + const cv = docShell.contentViewer;
+ docShell.contentViewer.emulateMedium(media); + if (type === '')
+ else + cv.stopEmulatingMedium();
+ docShell.contentViewer.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}) { + async setUserAgent({userAgent}) {
@ -4123,10 +4129,10 @@ index 000000000000..78b6601b91d0
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme']; +this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js
new file mode 100644 new file mode 100644
index 000000000000..0b2044e057a4 index 000000000000..1ed27df14a1a
--- /dev/null --- /dev/null
+++ b/testing/juggler/protocol/Protocol.js +++ 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'); +const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
+ +
+// Protocol-specific types. +// Protocol-specific types.
@ -4617,7 +4623,8 @@ index 000000000000..0b2044e057a4
+ }, + },
+ 'setEmulatedMedia': { + 'setEmulatedMedia': {
+ params: { + params: {
+ media: t.Enum(['screen', 'print', '']), + type: t.Optional(t.Enum(['screen', 'print', ''])),
+ colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
+ }, + },
+ }, + },
+ 'setCacheDisabled': { + 'setCacheDisabled': {

View File

@ -1240,9 +1240,7 @@ List of all available devices is available in the source code: [DeviceDescriptor
#### page.emulateMedia(options) #### page.emulateMedia(options)
- `options` <[Object]> - `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. - `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: - `colorScheme` <?[string]> Optional. Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- `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.
- returns: <[Promise]> - returns: <[Promise]>
```js ```js
@ -1265,34 +1263,13 @@ await page.evaluate(() => matchMedia('print').matches));
``` ```
```js ```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)); await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
// → true // → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)); await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
// → false // → false
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false // → 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) #### page.emulateTimezone(timezoneId)

View File

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

View File

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

View File

@ -310,3 +310,6 @@ export const fillFunction = (element: HTMLElement) => {
} }
return false; 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 fs from 'fs';
import * as mime from 'mime'; import * as mime from 'mime';
import { assert, debugError, helper, RegisteredListener } from '../helper'; import { assert, debugError, helper, RegisteredListener } from '../helper';
import { ClickOptions, MultiClickOptions } from '../input'; import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions } from '../input';
import { TimeoutSettings } from '../TimeoutSettings'; import { TimeoutSettings } from '../TimeoutSettings';
import { Browser, BrowserContext } from './Browser'; import { Browser, BrowserContext } from './Browser';
import { TargetSession, TargetSessionEvents } from './Connection'; import { TargetSession, TargetSessionEvents } from './Connection';
@ -56,6 +56,7 @@ export class Page extends EventEmitter {
private _workers = new Map<string, Worker>(); private _workers = new Map<string, Worker>();
private _disconnectPromise: Promise<Error> | undefined; private _disconnectPromise: Promise<Error> | undefined;
private _sessionListeners: RegisteredListener[] = []; private _sessionListeners: RegisteredListener[] = [];
private _emulatedMediaType: string | undefined;
static async create(session: TargetSession, target: Target, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise<Page> { static async create(session: TargetSession, target: Target, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise<Page> {
const page = new Page(session, target, screenshotTaskQueue); const page = new Page(session, target, screenshotTaskQueue);
@ -326,10 +327,15 @@ export class Page extends EventEmitter {
]); ]);
} }
async emulateMedia(options: { type?: string, features?: MediaFeature[] }) { async emulateMedia(options: {
assert(!options.features, 'Media feature emulation is not supported'); type?: string | null,
assert(options.type === 'screen' || options.type === 'print' || options.type === undefined, 'Unsupported media type: ' + options.type); colorScheme?: 'dark' | 'light' | 'no-preference' | null }) {
await this._session.send('Page.setEmulatedMedia', { media: options.type }); 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) { 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('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({}); 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('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); 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}) => { it('should work', async({page, server}) => {
await page.emulateMedia({ features: [ await page.emulateMedia({ colorScheme: 'light' });
{ 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' },
] });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); 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: 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: no-preference)').matches)).toBe(false);
await page.emulateMedia({ features: [ await page.emulateMedia({ colorScheme: 'dark' });
{ name: 'prefers-color-scheme', value: 'dark' },
] });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); 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: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false); expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ features: [ await page.emulateMedia({ colorScheme: 'no-preference' });
{ 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);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); 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}) => { it('should throw in case of bad argument', async({page, server}) => {
let error = null; let error = null;
await page.emulateMedia({ features: [{ name: 'bad', value: '' }] }).catch(e => error = e); await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported media feature: bad'); expect(error.message).toBe('Unsupported color scheme: bad');
}); });
}); });