Skip to content

Commit 4ab66a4

Browse files
authored
browser(firefox): follow-up with assorted simplifications (#4066)
This patch: - moves `SimpleChannel` to synchronously dispatch buffered commands instead of a `await Promise.resolve()` hack - moves dialog & screencast handling from `PageHandler` to `TargetManager`. This leaves `PageHandler` to be concerned solely about protocol. - removes `attach` and `detach` methods for worker channels: since channels are buffering messages until the namespace registers, there's no chance to loose any events. - slightly simplifies `PageNetwork` class: it's lifetime is now identical to the lifetime of the associated `PageTarget`, so a lot can be simplified later on. References #3995
1 parent c8a64b8 commit 4ab66a4

File tree

8 files changed

+240
-273
lines changed

8 files changed

+240
-273
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-
1182
2-
Changed: [email protected] Mon Oct 5 23:55:54 PDT 2020
1+
1183
2+
Changed: [email protected] Tue Oct 6 01:20:41 PDT 2020

browser_patches/firefox/juggler/NetworkObserver.js

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,36 +50,14 @@ class PageNetwork {
5050
constructor(target) {
5151
EventEmitter.decorate(this);
5252
this._target = target;
53-
this._sessionCount = 0;
5453
this._extraHTTPHeaders = null;
55-
this._responseStorage = null;
54+
this._responseStorage = new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10);
5655
this._requestInterceptionEnabled = false;
5756
// This is requestId => NetworkRequest map, only contains requests that are
5857
// awaiting interception action (abort, resume, fulfill) over the protocol.
5958
this._interceptedRequests = new Map();
6059
}
6160

62-
addSession() {
63-
if (this._sessionCount === 0)
64-
this._responseStorage = new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10);
65-
++this._sessionCount;
66-
return () => this._stopTracking();
67-
}
68-
69-
_stopTracking() {
70-
--this._sessionCount;
71-
if (this._sessionCount === 0) {
72-
this._extraHTTPHeaders = null;
73-
this._responseStorage = null;
74-
this._requestInterceptionEnabled = false;
75-
this._interceptedRequests.clear();
76-
}
77-
}
78-
79-
_isActive() {
80-
return this._sessionCount > 0;
81-
}
82-
8361
setExtraHTTPHeaders(headers) {
8462
this._extraHTTPHeaders = headers;
8563
}
@@ -479,7 +457,7 @@ class NetworkRequest {
479457
}
480458

481459
_activePageNetwork() {
482-
if (!this._maybeInactivePageNetwork || !this._maybeInactivePageNetwork._isActive())
460+
if (!this._maybeInactivePageNetwork)
483461
return undefined;
484462
return this._maybeInactivePageNetwork;
485463
}

browser_patches/firefox/juggler/SimpleChannel.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,11 @@ class SimpleChannel {
7272
throw new Error('ERROR: double-register for namespace ' + namespace);
7373
this._handlers.set(namespace, handler);
7474
// Try to re-deliver all pending messages.
75-
Promise.resolve().then(() => {
76-
const bufferedRequests = this._bufferedRequests;
77-
this._bufferedRequests = [];
78-
for (const data of bufferedRequests) {
79-
this._onMessage(data);
80-
}
81-
});
75+
const bufferedRequests = this._bufferedRequests;
76+
this._bufferedRequests = [];
77+
for (const data of bufferedRequests) {
78+
this._onMessage(data);
79+
}
8280
return () => this.unregister(namespace);
8381
}
8482

browser_patches/firefox/juggler/TargetRegistry.js

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class TargetRegistry {
117117
const target = this._browserToTarget.get(browser);
118118
if (!target)
119119
return;
120-
target.emit('crashed');
120+
target.emit(PageTarget.Events.Crashed);
121121
target.dispose();
122122
}
123123
}, 'oop-frameloader-crashed');
@@ -157,6 +157,8 @@ class TargetRegistry {
157157
target.updateUserAgent();
158158
if (!hasExplicitSize)
159159
target.updateViewportSize();
160+
if (browserContext.screencastOptions)
161+
target._startVideoRecording(browserContext.screencastOptions);
160162
};
161163

162164
const onTabCloseListener = event => {
@@ -329,13 +331,20 @@ class PageTarget {
329331
this._openerId = opener ? opener.id() : undefined;
330332
this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager);
331333
this._screencastInfo = undefined;
334+
this._dialogs = new Map();
332335

333336
const navigationListener = {
334337
QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
335338
onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
336339
};
337340
this._eventListeners = [
338341
helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
342+
helper.addEventListener(this._linkedBrowser, 'DOMWillOpenModalDialog', async (event) => {
343+
// wait for the dialog to be actually added to DOM.
344+
await Promise.resolve();
345+
this._updateModalDialogs();
346+
}),
347+
helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
339348
];
340349

341350
this._disposed = false;
@@ -346,6 +355,14 @@ class PageTarget {
346355
this._registry.emit(TargetRegistry.Events.TargetCreated, this);
347356
}
348357

358+
dialog(dialogId) {
359+
return this._dialogs.get(dialogId);
360+
}
361+
362+
dialogs() {
363+
return [...this._dialogs.values()];
364+
}
365+
349366
async windowReady() {
350367
await waitForWindowReady(this._window);
351368
}
@@ -362,6 +379,25 @@ class PageTarget {
362379
this._linkedBrowser.browsingContext.customUserAgent = this._browserContext.defaultUserAgent;
363380
}
364381

382+
_updateModalDialogs() {
383+
const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);
384+
for (const dialog of this._dialogs.values()) {
385+
if (!prompts.has(dialog.prompt())) {
386+
this._dialogs.delete(dialog.id());
387+
this.emit(PageTarget.Events.DialogClosed, dialog);
388+
} else {
389+
prompts.delete(dialog.prompt());
390+
}
391+
}
392+
for (const prompt of prompts) {
393+
const dialog = Dialog.createIfSupported(prompt);
394+
if (!dialog)
395+
continue;
396+
this._dialogs.set(dialog.id(), dialog);
397+
this.emit(PageTarget.Events.DialogOpened, dialog);
398+
}
399+
}
400+
365401
async updateViewportSize() {
366402
// Viewport size is defined by three arguments:
367403
// 1. default size. Could be explicit if set as part of `window.open` call, e.g.
@@ -433,7 +469,7 @@ class PageTarget {
433469
return await this._channel.connect('').send('hasFailedToOverrideTimezone').catch(e => true);
434470
}
435471

436-
async startVideoRecording({width, height, scale, dir}) {
472+
async _startVideoRecording({width, height, scale, dir}) {
437473
// On Mac the window may not yet be visible when TargetCreated and its
438474
// NSWindow.windowNumber may be -1, so we wait until the window is known
439475
// to be initialized and visible.
@@ -451,10 +487,10 @@ class PageTarget {
451487
const devicePixelRatio = this._window.devicePixelRatio;
452488
const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, scale || 0, devicePixelRatio * rect.top);
453489
this._screencastInfo = { videoSessionId, file };
454-
this.emit('screencastStarted');
490+
this.emit(PageTarget.Events.ScreencastStarted);
455491
}
456492

457-
async stopVideoRecording() {
493+
async _stopVideoRecording() {
458494
if (!this._screencastInfo)
459495
throw new Error('No video recording in progress');
460496
const screencastInfo = this._screencastInfo;
@@ -479,6 +515,8 @@ class PageTarget {
479515

480516
dispose() {
481517
this._disposed = true;
518+
if (this._screencastInfo)
519+
this._stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`));
482520
this._browserContext.pages.delete(this);
483521
this._registry._browserToTarget.delete(this._linkedBrowser);
484522
this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext);
@@ -487,6 +525,13 @@ class PageTarget {
487525
}
488526
}
489527

528+
PageTarget.Events = {
529+
ScreencastStarted: Symbol('PageTarget.ScreencastStarted'),
530+
Crashed: Symbol('PageTarget.Crashed'),
531+
DialogOpened: Symbol('PageTarget.DialogOpened'),
532+
DialogClosed: Symbol('PageTarget.DialogClosed'),
533+
};
534+
490535
class BrowserContext {
491536
constructor(registry, browserContextId, removeOnDetach) {
492537
this._registry = registry;
@@ -702,11 +747,67 @@ class BrowserContext {
702747
return;
703748
const promises = [];
704749
for (const page of this.pages)
705-
promises.push(page.startVideoRecording(options));
750+
promises.push(page._startVideoRecording(options));
706751
await Promise.all(promises);
707752
}
708753
}
709754

755+
class Dialog {
756+
static createIfSupported(prompt) {
757+
const type = prompt.args.promptType;
758+
switch (type) {
759+
case 'alert':
760+
case 'prompt':
761+
case 'confirm':
762+
return new Dialog(prompt, type);
763+
case 'confirmEx':
764+
return new Dialog(prompt, 'beforeunload');
765+
default:
766+
return null;
767+
};
768+
}
769+
770+
constructor(prompt, type) {
771+
this._id = helper.generateId();
772+
this._type = type;
773+
this._prompt = prompt;
774+
}
775+
776+
id() {
777+
return this._id;
778+
}
779+
780+
message() {
781+
return this._prompt.ui.infoBody.textContent;
782+
}
783+
784+
type() {
785+
return this._type;
786+
}
787+
788+
prompt() {
789+
return this._prompt;
790+
}
791+
792+
dismiss() {
793+
if (this._prompt.ui.button1)
794+
this._prompt.ui.button1.click();
795+
else
796+
this._prompt.ui.button0.click();
797+
}
798+
799+
defaultValue() {
800+
return this._prompt.ui.loginTextbox.value;
801+
}
802+
803+
accept(promptValue) {
804+
if (typeof promptValue === 'string' && this._type === 'prompt')
805+
this._prompt.ui.loginTextbox.value = promptValue;
806+
this._prompt.ui.button0.click();
807+
}
808+
}
809+
810+
710811
function dirPath(path) {
711812
return path.substring(0, path.lastIndexOf('/') + 1);
712813
}
@@ -755,5 +856,6 @@ TargetRegistry.Events = {
755856
DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),
756857
};
757858

758-
var EXPORTED_SYMBOLS = ['TargetRegistry'];
859+
var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget'];
759860
this.TargetRegistry = TargetRegistry;
861+
this.PageTarget = PageTarget;

0 commit comments

Comments
 (0)