Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit 7581e55

Browse files
Introducing tabs.windowSize()
This lets a script set the window size (width, height). This a easy to implement alternative for the mobile emulation that we had in v1 (the api for this only present in XUL, not in WebExt). However the downside is that we are setting the window size instead of the viewport size. And we can not set all sizes because of OS limitations (minimum window size, actual screen resolution, etc)
1 parent 13f5205 commit 7581e55

File tree

7 files changed

+211
-4
lines changed

7 files changed

+211
-4
lines changed

runner-modules/tabs/lib/background/ScriptWindow.js

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,109 @@
22
const EventEmitter = require('events').EventEmitter;
33

44
const log = require('../../../../lib/logger')({hostname: 'background', MODULE: 'tabs/background/ScriptWindow'});
5+
const {BLANK_HTML} = require('./urls');
6+
const WaitForEvent = require('../../../../lib/WaitForEvent');
57

68
class ScriptWindow extends EventEmitter {
7-
constructor(browserWindows, browserTabs) {
9+
constructor({browserWindows, browserTabs, browserWebNavigation}) {
810
super();
11+
this._attached = false;
912
this.browserWindows = browserWindows;
1013
this.browserTabs = browserTabs;
14+
this.browserWebNavigation = browserWebNavigation;
1115
this.openPromise = null;
1216
this.firstTabCreation = true;
1317
this.closed = false;
18+
this._navigationCompletedWait = new WaitForEvent(); // key is the browserTabId
19+
this._sizeMinusViewport = Object.freeze({width: 0, height: 0});
20+
this.handleWebNavigationCompleted = this.handleWebNavigationCompleted.bind(this);
1421
Object.seal(this);
1522
}
1623

24+
attach() {
25+
this.browserWebNavigation.onCompleted.addListener(this.handleWebNavigationCompleted);
26+
this._attached = true;
27+
}
28+
29+
detach() {
30+
this._attached = false;
31+
this.browserWebNavigation.onCompleted.removeListener(this.handleWebNavigationCompleted);
32+
}
33+
34+
handleWebNavigationCompleted({tabId: browserTabId, frameId, url}) {
35+
try {
36+
log.debug({browserTabId, frameId, url}, 'browser.webNavigation.onCompleted');
37+
38+
if (frameId || url === 'about:blank') { // frameId === 0 is top; otherwise it is an iframe
39+
return;
40+
}
41+
42+
this._navigationCompletedWait.resolve(browserTabId);
43+
}
44+
catch (err) {
45+
log.error({err}, 'Error in browser.webNavigation.onCompleted');
46+
}
47+
}
48+
1749
async open() {
1850
if (this.closed || this.openPromise) {
1951
throw Error('Invalid state');
2052
}
2153

54+
log.debug({}, 'Creating new window...');
55+
2256
this.firstTabCreation = true;
2357
this.openPromise = this.browserWindows.create({
24-
// focused: true,
2558
incognito: true,
2659
state: 'maximized',
2760
url: 'about:blank',
2861
}).then(window => window.id);
2962

3063
const browserWindowId = await this.openPromise;
64+
const {tabs: windowTabs} = await this.browserWindows.get(browserWindowId, {populate: true});
65+
const firstTabId = windowTabs[0].id;
66+
await this._gatherBrowserWindowDetails(browserWindowId, firstTabId);
67+
68+
log.debug({browserWindowId, firstTabId, sizeMinusViewport: this.sizeMinusViewport}, 'Created a new window');
69+
3170
this.emit('windowCreated', {browserWindowId});
3271
}
3372

73+
/**
74+
* Navigate to an extension page and gather some statistics about the environment, such as the position of the viewport.
75+
* @param {string} browserWindowId
76+
* @param {string} firstTabId
77+
* @private
78+
*/
79+
async _gatherBrowserWindowDetails(browserWindowId, firstTabId) {
80+
// navigate to blank.html and wait for the load event
81+
await this._navigationCompletedWait.wait(firstTabId, async () => {
82+
await this.browserTabs.update(firstTabId, {url: BLANK_HTML});
83+
});
84+
85+
let sizeMinusViewport = [0, 0];
86+
try {
87+
[sizeMinusViewport] = await this.browserTabs.executeScript(firstTabId, {
88+
code: '([window.outerWidth - window.innerWidth, window.outerHeight - window.innerHeight])',
89+
});
90+
}
91+
catch (err) {
92+
log.debug({browserWindowId, firstTabId, err}, 'Unable to determine the window dimension');
93+
}
94+
95+
this._sizeMinusViewport = Object.freeze({
96+
width: Number(sizeMinusViewport[0]),
97+
height: Number(sizeMinusViewport[1]),
98+
});
99+
}
100+
101+
/**
102+
* @return {{width: number, height: number}}
103+
*/
104+
get sizeMinusViewport() {
105+
return this._sizeMinusViewport;
106+
}
107+
34108
get isOpen() {
35109
return Boolean(this.openPromise);
36110
}
@@ -47,7 +121,6 @@ class ScriptWindow extends EventEmitter {
47121
const browserWindowId = await this.getBrowserWindowId();
48122
return await this.browserWindows.get(browserWindowId, {
49123
populate: true,
50-
windowTypes: ['normal'],
51124
});
52125
}
53126

@@ -94,6 +167,20 @@ class ScriptWindow extends EventEmitter {
94167
const {id: browserWindowId} = await this.getBrowserWindow();
95168
return browserWindowId === browserTab.windowId;
96169
}
170+
171+
async setWindowSize({width, height}) {
172+
const {id: browserWindowId} = await this.getBrowserWindow();
173+
await this.browserWindows.update(browserWindowId, {
174+
width: Number(width),
175+
height: Number(height),
176+
});
177+
178+
const result = await this.browserWindows.get(browserWindowId);
179+
return Object.freeze({
180+
width: result.width,
181+
height: result.height,
182+
});
183+
}
97184
}
98185

99186

runner-modules/tabs/lib/background/TabManager.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class TabManager extends EventEmitter {
1919
this.browserTabs = browserTabs;
2020
this.browserWebNavigation = browserWebNavigation;
2121
// all tabs opened by the script end up in a single window:
22-
this.scriptWindow = new ScriptWindow(browserWindows, browserTabs);
22+
this.scriptWindow = new ScriptWindow({browserWindows, browserTabs, browserWebNavigation});
2323
this.myTabs = new TabTracker();
2424
this.tabContentRPC = new TabContentRPC({
2525
browserRuntime,
@@ -42,6 +42,7 @@ class TabManager extends EventEmitter {
4242

4343
attach() {
4444
this.tabContentRPC.attach();
45+
this.scriptWindow.attach();
4546
this.browserTabs.onCreated.addListener(this.handleTabCreated);
4647
this.browserWebNavigation.onBeforeNavigate.addListener(this.handleWebNavigationOnBeforeNavigate);
4748
this.browserWebNavigation.onCommitted.addListener(this.handleWebNavigationOnCommitted);
@@ -52,6 +53,7 @@ class TabManager extends EventEmitter {
5253
detach() {
5354
this._attached = false;
5455
this.tabContentRPC.detach();
56+
this.scriptWindow.detach();
5557
this.browserTabs.onCreated.removeListener(this.handleTabCreated);
5658
this.browserWebNavigation.onBeforeNavigate.removeListener(this.handleWebNavigationOnBeforeNavigate);
5759
this.browserWebNavigation.onCommitted.removeListener(this.handleWebNavigationOnCommitted);
@@ -267,6 +269,13 @@ class TabManager extends EventEmitter {
267269
return this.scriptWindow.getBrowserWindowId();
268270
}
269271

272+
/**
273+
* @return {{width: number, height: number}}
274+
*/
275+
get windowSizeMinusViewport() {
276+
return this.scriptWindow.sizeMinusViewport;
277+
}
278+
270279
async closeScriptWindow() {
271280
if (!this._attached) {
272281
throw illegalStateError('TabManager.closeScriptWindow: Not initialized yet or in the progress of cleaning up');
@@ -292,6 +301,10 @@ class TabManager extends EventEmitter {
292301

293302
await this.scriptWindow.close();
294303
}
304+
305+
async setWindowSize(options) {
306+
return await this.scriptWindow.setWindowSize(options);
307+
}
295308
}
296309

297310
module.exports = TabManager;

runner-modules/tabs/lib/background/tabsMethods.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@ module.exports = (tabManager) => {
1212
return tab.id;
1313
};
1414

15+
const setViewportSize = async ({width: viewportWidth, height: viewportHeight}) => {
16+
if (typeof viewportWidth !== 'number' || !Number.isSafeInteger(viewportWidth) || viewportWidth < 1 || viewportWidth > 7680) {
17+
throw illegalArgumentError('tabs.viewportSize(): invalid argument `width`');
18+
}
19+
20+
if (typeof viewportHeight !== 'number' || !Number.isSafeInteger(viewportHeight) || viewportHeight < 1 || viewportHeight > 4320) {
21+
throw illegalArgumentError('tabs.viewPortSize(): invalid argument `height`');
22+
}
23+
24+
const {windowSizeMinusViewport} = tabManager;
25+
const width = viewportWidth + windowSizeMinusViewport.width;
26+
const height = viewportHeight + windowSizeMinusViewport.height;
27+
const {width: resultWidth, height: resultHeight} = await tabManager.setWindowSize({width, height});
28+
29+
if (width !== resultWidth || height !== resultHeight) {
30+
throw illegalArgumentError(
31+
`tabs.viewportSize: Failed to set the viewport size to ${viewportWidth}x${viewportHeight}. ` +
32+
`After resizing the window to ${width}x${height}, the actual size is ${resultWidth}x${resultHeight}. ` +
33+
'The given size is probably too small, or too large for the screen.'
34+
);
35+
}
36+
};
37+
1538
const navigateTab = async ({id, url}) => {
1639
if (typeof id !== 'string' || !tabManager.hasTab(id)) {
1740
throw illegalArgumentError('tabs.navigate(): invalid argument `id`');
@@ -114,6 +137,7 @@ module.exports = (tabManager) => {
114137

115138
return new Map([
116139
['tabs.create', createTab],
140+
['tabs.setViewportSize', setViewportSize],
117141
['tabs.navigate', navigateTab],
118142
['tabs.run', run],
119143
['tabs.wait', wait],
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
const BLANK_HTML = browser.extension.getURL('runner-modules/tabs/lib/content/blank.html');
4+
5+
module.exports = {BLANK_HTML};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Openrunner</title>
6+
</head>
7+
<body>
8+
</body>
9+
</html>

runner-modules/tabs/lib/script-env/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ openRunnerRegisterRunnerModule('tabs', async ({script}) => {
1818
});
1919
};
2020

21+
const viewportSize = async ({width, height}) => {
22+
return extendStack(async () => {
23+
await script.rpcCall('tabs.setViewportSize', {width, height});
24+
});
25+
};
26+
2127
return {
2228
create,
29+
viewportSize,
2330
};
2431
});

test/integration/tabs.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,4 +581,66 @@ describe('integration/tabs', {timeout: 60000, slow: 10000}, () => {
581581
eq(scriptStackFrames[0].runnerScriptContext, 'main');
582582
});
583583
});
584+
585+
specify('Changing the window size', async () => {
586+
/* eslint-disable no-undef */
587+
const result = await runScriptFromFunction(async () => {
588+
'Openrunner-Script: v1';
589+
const tabs = await include('tabs');
590+
const assert = await include('assert');
591+
const tab = await tabs.create();
592+
await tab.navigate(injected.url, {timeout: '2s'});
593+
594+
{
595+
await tabs.viewportSize({width: 400, height: 500});
596+
const result = await tab.run(() => {
597+
return {
598+
innerHeight: window.innerHeight,
599+
outerHeight: window.outerHeight,
600+
innerWidth: window.innerWidth,
601+
outerWidth: window.outerWidth,
602+
};
603+
});
604+
assert.strictEqual(result.innerWidth, 400);
605+
assert.strictEqual(result.innerHeight, 500);
606+
assert.isAtLeast(result.outerWidth, 400);
607+
assert.isAtLeast(result.outerHeight, 500);
608+
}
609+
610+
await assert.isRejected(
611+
tabs.viewportSize({width: 1, height: 2}),
612+
Error,
613+
/tabs.viewportSize.*failed.*set.*viewport.*size.*1x2/i
614+
);
615+
616+
await assert.isRejected(
617+
tabs.viewportSize({width: 10000, height: 500}),
618+
Error,
619+
/tabs.viewportSize.*invalid.*width/i
620+
);
621+
622+
// make sure that we can recover from the prior errors
623+
{
624+
await tabs.viewportSize({width: 700, height: 400});
625+
const result = await tab.run(() => {
626+
return {
627+
innerHeight: window.innerHeight,
628+
outerHeight: window.outerHeight,
629+
innerWidth: window.innerWidth,
630+
outerWidth: window.outerWidth,
631+
};
632+
});
633+
assert.strictEqual(result.innerWidth, 700);
634+
assert.strictEqual(result.innerHeight, 400);
635+
assert.isAtLeast(result.outerWidth, 700);
636+
assert.isAtLeast(result.outerHeight, 400);
637+
}
638+
639+
}, {url: `http://localhost:${testServerPort()}/static/static.html`});
640+
/* eslint-enable no-undef */
641+
642+
if (result.error) {
643+
throw result.error;
644+
}
645+
});
584646
});

0 commit comments

Comments
 (0)