Skip to content

Commit 24bc0e3

Browse files
authored
browser(firefox): remove the hack around setting viewport size (#4010)
Juggler code had a bug where we subscribed to window and tab events, but did not iterate collections of current windows and tabs. As a result, we were sometimes failing to set viewport size for the initial window, and implemented an artificial promise to workaround the problem. This patch: - starts calling `onOpenWindow` and `onOpenTabListener` callbacks for *all* windows and tabs - current and future, eliminating the race condition. This worked too well and we started overriding window sizes that were set by users with `window.open(url, 'width=300;height=400')` (we have a test for this). To fix this, we now plumb `CHROME_WITH_SIZE` flag from appWindow and override viewport iff this flag is not set. After this patch, we will use the `onTabOpened` event to move user agent emulation to the browser-side. References #3995
1 parent a20c0e0 commit 24bc0e3

File tree

4 files changed

+86
-52
lines changed

4 files changed

+86
-52
lines changed

browser_patches/firefox/BUILD_NUMBER

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
1174
2-
Changed: [email protected] Tue Sep 29 02:02:37 MDT 2020
1+
1175
2+
Changed: [email protected] Wed Sep 30 00:44:40 MDT 2020

browser_patches/firefox/juggler/Helper.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ class Helper {
2222
return () => receiver.removeEventListener(eventName, handler);
2323
}
2424

25+
awaitEvent(receiver, eventName) {
26+
return new Promise(resolve => {
27+
receiver.addEventListener(eventName, function listener() {
28+
receiver.removeEventListener(eventName, listener);
29+
resolve();
30+
});
31+
});
32+
}
33+
2534
on(receiver, eventName, handler) {
2635
// The toolkit/modules/EventEmitter.jsm dispatches event name as a first argument.
2736
// Fire event listeners without it for convenience.

browser_patches/firefox/juggler/TargetRegistry.js

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,12 @@ class TargetRegistry {
190190
},
191191
});
192192

193-
const onTabOpenListener = (window, event) => {
193+
const onTabOpenListener = (appWindow, window, event) => {
194194
const tab = event.target;
195195
const userContextId = tab.userContextId;
196196
const browserContext = this._userContextIdToBrowserContext.get(userContextId);
197-
if (browserContext && browserContext.defaultViewportSize)
197+
const hasExplicitSize = (appWindow.chromeFlags & Ci.nsIWebBrowserChrome.JUGGLER_WINDOW_EXPLICIT_SIZE) !== 0;
198+
if (!hasExplicitSize && browserContext && browserContext.defaultViewportSize)
198199
setViewportSizeForBrowser(browserContext.defaultViewportSize, tab.linkedBrowser, window);
199200
};
200201

@@ -206,36 +207,46 @@ class TargetRegistry {
206207
target.dispose();
207208
};
208209

209-
Services.wm.addListener({
210-
onOpenWindow: async window => {
211-
const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
212-
if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
213-
return;
214-
if (domWindow.document.readyState !== 'uninitialized')
215-
throw new Error('DOMWindow should not be loaded yet');
216-
await new Promise(fulfill => {
217-
domWindow.addEventListener('DOMContentLoaded', function listener() {
218-
domWindow.removeEventListener('DOMContentLoaded', listener);
219-
fulfill();
220-
});
221-
});
222-
if (!domWindow.gBrowser)
223-
return;
224-
domWindow.gBrowser.tabContainer.addEventListener('TabOpen', event => onTabOpenListener(domWindow, event));
225-
domWindow.gBrowser.tabContainer.addEventListener('TabClose', onTabCloseListener);
226-
},
227-
onCloseWindow: window => {
228-
const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
229-
if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
230-
return;
231-
if (!domWindow.gBrowser)
232-
return;
233-
domWindow.gBrowser.tabContainer.removeEventListener('TabOpen', onTabOpenListener);
234-
domWindow.gBrowser.tabContainer.removeEventListener('TabClose', onTabCloseListener);
235-
for (const tab of domWindow.gBrowser.tabs)
236-
onTabCloseListener({ target: tab });
237-
},
238-
});
210+
const domWindowTabListeners = new Map();
211+
212+
const onOpenWindow = async (appWindow) => {
213+
if (!(appWindow instanceof Ci.nsIAppWindow))
214+
return;
215+
const domWindow = appWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
216+
if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
217+
return;
218+
if (domWindow.document.readyState !== 'uninitialized')
219+
throw new Error('DOMWindow should not be loaded yet');
220+
await helper.awaitEvent(domWindow, 'DOMContentLoaded');
221+
222+
if (!domWindow.gBrowser)
223+
return;
224+
const tabContainer = domWindow.gBrowser.tabContainer;
225+
domWindowTabListeners.set(domWindow, [
226+
helper.addEventListener(tabContainer, 'TabOpen', event => onTabOpenListener(appWindow, domWindow, event)),
227+
helper.addEventListener(tabContainer, 'TabClose', onTabCloseListener),
228+
]);
229+
for (const tab of domWindow.gBrowser.tabs)
230+
onTabOpenListener(appWindow, domWindow, { target: tab });
231+
};
232+
233+
const onCloseWindow = window => {
234+
const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
235+
if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
236+
return;
237+
if (!domWindow.gBrowser)
238+
return;
239+
240+
const listeners = domWindowTabListeners.get(domWindow) || [];
241+
domWindowTabListeners.delete(domWindow);
242+
helper.removeListeners(listeners);
243+
for (const tab of domWindow.gBrowser.tabs)
244+
onTabCloseListener({ target: tab });
245+
};
246+
247+
Services.wm.addListener({ onOpenWindow, onCloseWindow });
248+
for (const win of Services.wm.getEnumerator(null))
249+
onOpenWindow(win);
239250

240251
const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService);
241252
extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this));
@@ -308,7 +319,7 @@ class TargetRegistry {
308319
const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);
309320
await waitForWindowReady(window);
310321
if (window.gBrowser.browsers.length !== 1)
311-
throw new Error(`Unexpcted number of tabs in the new window: ${window.gBrowser.browsers.length}`);
322+
throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`);
312323
const browser = window.gBrowser.browsers[0];
313324
const target = this._browserToTarget.get(browser) || await new Promise(fulfill => {
314325
const listener = helper.on(this, TargetRegistry.Events.TargetCreated, ({target}) => {
@@ -318,8 +329,6 @@ class TargetRegistry {
318329
}
319330
});
320331
});
321-
if (browserContext && browserContext.defaultViewportSize)
322-
setViewportSizeForBrowser(browserContext.defaultViewportSize, browser, window);
323332
browser.focus();
324333
if (browserContext.settings.timezoneId) {
325334
if (await target.hasFailedToOverrideTimezone())
@@ -373,7 +382,6 @@ class PageTarget {
373382

374383
this._disposed = false;
375384
browserContext.pages.add(this);
376-
browserContext._firstPageCallback();
377385
this._registry._browserToTarget.set(this._linkedBrowser, this);
378386
this._registry._browserBrowsingContextToTarget.set(this._linkedBrowser.browsingContext, this);
379387
}
@@ -501,7 +509,6 @@ class BrowserContext {
501509
this.bindings = [];
502510
this.settings = {};
503511
this.pages = new Set();
504-
this._firstPagePromise = new Promise(f => this._firstPageCallback = f);
505512
}
506513

507514
async destroy() {
@@ -545,11 +552,6 @@ class BrowserContext {
545552

546553
async setDefaultViewport(viewport) {
547554
this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
548-
if (!this.userContextId) {
549-
// First page in the default context comes before onTabOpenListener
550-
// so we don't set default viewport. Wait for it here and ensure the viewport.
551-
await this._firstPagePromise;
552-
}
553555
const promises = Array.from(this.pages).map(async page => {
554556
// Resize to new default, unless the page has a custom viewport.
555557
if (!page._viewportSize)
@@ -707,14 +709,8 @@ async function waitForWindowReady(window) {
707709
}, "browser-delayed-startup-finished");
708710
}));
709711
}
710-
if (window.document.readyState !== 'complete') {
711-
await new Promise(fulfill => {
712-
window.addEventListener('load', function listener() {
713-
window.removeEventListener('load', listener);
714-
fulfill();
715-
});
716-
});
717-
}
712+
if (window.document.readyState !== 'complete')
713+
await helper.awaitEvent(window, 'load');
718714
}
719715

720716
function setViewportSizeForBrowser(viewportSize, browser, window) {

browser_patches/firefox/patches/bootstrap.diff

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,20 @@ index 66df8509044600a0d71eb36bb838f96087a53ef1..e4558874434e3aa57bc26344f0ca89b3
15471547
? "https://firefox.settings.services.mozilla.com/v1"
15481548
: gServerURL;
15491549
},
1550+
diff --git a/toolkit/components/browser/nsIWebBrowserChrome.idl b/toolkit/components/browser/nsIWebBrowserChrome.idl
1551+
index 1e9bea1655af731fc003f8d0cab3ad4d2ad29f5d..5081c0e1ee0c41c6a79bd2ed358a57442e3baa6b 100644
1552+
--- a/toolkit/components/browser/nsIWebBrowserChrome.idl
1553+
+++ b/toolkit/components/browser/nsIWebBrowserChrome.idl
1554+
@@ -70,6 +70,9 @@ interface nsIWebBrowserChrome : nsISupports
1555+
// Whether this window should use out-of-process cross-origin subframes.
1556+
const unsigned long CHROME_FISSION_WINDOW = 0x00200000;
1557+
1558+
+ // Whether this window has "width" or "height" defined in features
1559+
+ const unsigned long JUGGLER_WINDOW_EXPLICIT_SIZE = 0x00400000;
1560+
+
1561+
// Prevents new window animations on MacOS and Windows. Currently
1562+
// ignored for Linux.
1563+
const unsigned long CHROME_SUPPRESS_ANIMATION = 0x01000000;
15501564
diff --git a/toolkit/components/startup/nsAppStartup.cpp b/toolkit/components/startup/nsAppStartup.cpp
15511565
index 4eebb0e55ab3622e8f1f55655ef096d919e0ecb1..fb0e29281393f9c68d14a6549d3a9988569b0366 100644
15521566
--- a/toolkit/components/startup/nsAppStartup.cpp
@@ -1583,6 +1597,21 @@ index 318037b12e9ea7b8bad92498950ac48ff936fb3c..44db941025a5253da38572600cd0fc57
15831597
int32_t aCurSelfProgress,
15841598
int32_t aMaxSelfProgress,
15851599
int32_t aCurTotalProgress,
1600+
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
1601+
index 876b1ae05bbc2bdf725b749687b1c86e63b1f225..be0aee2bcc5cf0a0576d953b31e85d1d84bfd245 100644
1602+
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
1603+
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
1604+
@@ -1796,6 +1796,10 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent(
1605+
uint32_t chromeFlags = CalculateChromeFlagsHelper(
1606+
nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, aSizeSpec);
1607+
1608+
+ if (aFeatures.Exists("width") || aFeatures.Exists("height")) {
1609+
+ chromeFlags |= nsIWebBrowserChrome::JUGGLER_WINDOW_EXPLICIT_SIZE;
1610+
+ }
1611+
+
1612+
return EnsureFlagsSafeForContent(chromeFlags);
1613+
}
1614+
15861615
diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm
15871616
index 32a9ac1478a20ecfcf5c5fa1cefe8468b122b895..3aaa78c4558019c9d1d76b3c4204beae45f7b70c 100644
15881617
--- a/toolkit/mozapps/update/UpdateService.jsm

0 commit comments

Comments
 (0)