Skip to content

Commit

Permalink
browser(firefox): follow-up with assorted simplifications (#4066)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aslushnikov authored Oct 6, 2020
1 parent c8a64b8 commit 4ab66a4
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 273 deletions.
4 changes: 2 additions & 2 deletions browser_patches/firefox/BUILD_NUMBER
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
1182
Changed: lushnikov@chromium.org Mon Oct 5 23:55:54 PDT 2020
1183
Changed: lushnikov@chromium.org Tue Oct 6 01:20:41 PDT 2020
26 changes: 2 additions & 24 deletions browser_patches/firefox/juggler/NetworkObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,36 +50,14 @@ class PageNetwork {
constructor(target) {
EventEmitter.decorate(this);
this._target = target;
this._sessionCount = 0;
this._extraHTTPHeaders = null;
this._responseStorage = null;
this._responseStorage = new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10);
this._requestInterceptionEnabled = false;
// This is requestId => NetworkRequest map, only contains requests that are
// awaiting interception action (abort, resume, fulfill) over the protocol.
this._interceptedRequests = new Map();
}

addSession() {
if (this._sessionCount === 0)
this._responseStorage = new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10);
++this._sessionCount;
return () => this._stopTracking();
}

_stopTracking() {
--this._sessionCount;
if (this._sessionCount === 0) {
this._extraHTTPHeaders = null;
this._responseStorage = null;
this._requestInterceptionEnabled = false;
this._interceptedRequests.clear();
}
}

_isActive() {
return this._sessionCount > 0;
}

setExtraHTTPHeaders(headers) {
this._extraHTTPHeaders = headers;
}
Expand Down Expand Up @@ -479,7 +457,7 @@ class NetworkRequest {
}

_activePageNetwork() {
if (!this._maybeInactivePageNetwork || !this._maybeInactivePageNetwork._isActive())
if (!this._maybeInactivePageNetwork)
return undefined;
return this._maybeInactivePageNetwork;
}
Expand Down
12 changes: 5 additions & 7 deletions browser_patches/firefox/juggler/SimpleChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,11 @@ class SimpleChannel {
throw new Error('ERROR: double-register for namespace ' + namespace);
this._handlers.set(namespace, handler);
// Try to re-deliver all pending messages.
Promise.resolve().then(() => {
const bufferedRequests = this._bufferedRequests;
this._bufferedRequests = [];
for (const data of bufferedRequests) {
this._onMessage(data);
}
});
const bufferedRequests = this._bufferedRequests;
this._bufferedRequests = [];
for (const data of bufferedRequests) {
this._onMessage(data);
}
return () => this.unregister(namespace);
}

Expand Down
114 changes: 108 additions & 6 deletions browser_patches/firefox/juggler/TargetRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class TargetRegistry {
const target = this._browserToTarget.get(browser);
if (!target)
return;
target.emit('crashed');
target.emit(PageTarget.Events.Crashed);
target.dispose();
}
}, 'oop-frameloader-crashed');
Expand Down Expand Up @@ -157,6 +157,8 @@ class TargetRegistry {
target.updateUserAgent();
if (!hasExplicitSize)
target.updateViewportSize();
if (browserContext.screencastOptions)
target._startVideoRecording(browserContext.screencastOptions);
};

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

const navigationListener = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
};
this._eventListeners = [
helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
helper.addEventListener(this._linkedBrowser, 'DOMWillOpenModalDialog', async (event) => {
// wait for the dialog to be actually added to DOM.
await Promise.resolve();
this._updateModalDialogs();
}),
helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
];

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

dialog(dialogId) {
return this._dialogs.get(dialogId);
}

dialogs() {
return [...this._dialogs.values()];
}

async windowReady() {
await waitForWindowReady(this._window);
}
Expand All @@ -362,6 +379,25 @@ class PageTarget {
this._linkedBrowser.browsingContext.customUserAgent = this._browserContext.defaultUserAgent;
}

_updateModalDialogs() {
const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);
for (const dialog of this._dialogs.values()) {
if (!prompts.has(dialog.prompt())) {
this._dialogs.delete(dialog.id());
this.emit(PageTarget.Events.DialogClosed, dialog);
} else {
prompts.delete(dialog.prompt());
}
}
for (const prompt of prompts) {
const dialog = Dialog.createIfSupported(prompt);
if (!dialog)
continue;
this._dialogs.set(dialog.id(), dialog);
this.emit(PageTarget.Events.DialogOpened, dialog);
}
}

async updateViewportSize() {
// Viewport size is defined by three arguments:
// 1. default size. Could be explicit if set as part of `window.open` call, e.g.
Expand Down Expand Up @@ -433,7 +469,7 @@ class PageTarget {
return await this._channel.connect('').send('hasFailedToOverrideTimezone').catch(e => true);
}

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

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

dispose() {
this._disposed = true;
if (this._screencastInfo)
this._stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`));
this._browserContext.pages.delete(this);
this._registry._browserToTarget.delete(this._linkedBrowser);
this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext);
Expand All @@ -487,6 +525,13 @@ class PageTarget {
}
}

PageTarget.Events = {
ScreencastStarted: Symbol('PageTarget.ScreencastStarted'),
Crashed: Symbol('PageTarget.Crashed'),
DialogOpened: Symbol('PageTarget.DialogOpened'),
DialogClosed: Symbol('PageTarget.DialogClosed'),
};

class BrowserContext {
constructor(registry, browserContextId, removeOnDetach) {
this._registry = registry;
Expand Down Expand Up @@ -702,11 +747,67 @@ class BrowserContext {
return;
const promises = [];
for (const page of this.pages)
promises.push(page.startVideoRecording(options));
promises.push(page._startVideoRecording(options));
await Promise.all(promises);
}
}

class Dialog {
static createIfSupported(prompt) {
const type = prompt.args.promptType;
switch (type) {
case 'alert':
case 'prompt':
case 'confirm':
return new Dialog(prompt, type);
case 'confirmEx':
return new Dialog(prompt, 'beforeunload');
default:
return null;
};
}

constructor(prompt, type) {
this._id = helper.generateId();
this._type = type;
this._prompt = prompt;
}

id() {
return this._id;
}

message() {
return this._prompt.ui.infoBody.textContent;
}

type() {
return this._type;
}

prompt() {
return this._prompt;
}

dismiss() {
if (this._prompt.ui.button1)
this._prompt.ui.button1.click();
else
this._prompt.ui.button0.click();
}

defaultValue() {
return this._prompt.ui.loginTextbox.value;
}

accept(promptValue) {
if (typeof promptValue === 'string' && this._type === 'prompt')
this._prompt.ui.loginTextbox.value = promptValue;
this._prompt.ui.button0.click();
}
}


function dirPath(path) {
return path.substring(0, path.lastIndexOf('/') + 1);
}
Expand Down Expand Up @@ -755,5 +856,6 @@ TargetRegistry.Events = {
DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),
};

var EXPORTED_SYMBOLS = ['TargetRegistry'];
var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget'];
this.TargetRegistry = TargetRegistry;
this.PageTarget = PageTarget;
Loading

0 comments on commit 4ab66a4

Please sign in to comment.