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
This commit is contained in:
parent
c8a64b88e1
commit
4ab66a4fe5
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -479,7 +457,7 @@ class NetworkRequest {
|
|||
}
|
||||
|
||||
_activePageNetwork() {
|
||||
if (!this._maybeInactivePageNetwork || !this._maybeInactivePageNetwork._isActive())
|
||||
if (!this._maybeInactivePageNetwork)
|
||||
return undefined;
|
||||
return this._maybeInactivePageNetwork;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
@ -157,6 +157,8 @@ class TargetRegistry {
|
|||
target.updateUserAgent();
|
||||
if (!hasExplicitSize)
|
||||
target.updateViewportSize();
|
||||
if (browserContext.screencastOptions)
|
||||
target._startVideoRecording(browserContext.screencastOptions);
|
||||
};
|
||||
|
||||
const onTabCloseListener = event => {
|
||||
|
@ -329,6 +331,7 @@ 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]),
|
||||
|
@ -336,6 +339,12 @@ class PageTarget {
|
|||
};
|
||||
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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
|
@ -40,11 +40,9 @@ class WorkerData {
|
|||
disposeObject: (options) =>this._workerRuntime.send('disposeObject', options),
|
||||
}),
|
||||
];
|
||||
worker.channel().connect('').emit('attach');
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._worker.channel().connect('').emit('detach');
|
||||
this._workerRuntime.dispose();
|
||||
this._browserWorker.dispose();
|
||||
helper.removeListeners(this._eventListeners);
|
||||
|
@ -115,7 +113,6 @@ class PageAgent {
|
|||
this._messageManager = messageManager;
|
||||
this._browserChannel = browserChannel;
|
||||
this._browserPage = browserChannel.connect('page');
|
||||
this._browserRuntime = browserChannel.connect('runtime');
|
||||
this._frameTree = frameTree;
|
||||
this._runtime = frameTree.runtime();
|
||||
|
||||
|
@ -124,7 +121,76 @@ class PageAgent {
|
|||
this._scriptsToEvaluateOnNewDocument = new Map();
|
||||
this._isolatedWorlds = new Map();
|
||||
|
||||
const docShell = frameTree.mainFrame().docShell();
|
||||
this._docShell = docShell;
|
||||
this._initialDPPX = docShell.contentViewer.overrideDPPX;
|
||||
this._customScrollbars = null;
|
||||
this._dataTransfer = null;
|
||||
|
||||
// Dispatch frameAttached events for all initial frames
|
||||
for (const frame of this._frameTree.frames()) {
|
||||
this._onFrameAttached(frame);
|
||||
if (frame.url())
|
||||
this._onNavigationCommitted(frame);
|
||||
if (frame.pendingNavigationId())
|
||||
this._onNavigationStarted(frame);
|
||||
}
|
||||
|
||||
// Report created workers.
|
||||
for (const worker of this._frameTree.workers())
|
||||
this._onWorkerCreated(worker);
|
||||
|
||||
// Report execution contexts.
|
||||
for (const context of this._runtime.executionContexts())
|
||||
this._onExecutionContextCreated(context);
|
||||
|
||||
if (this._frameTree.isPageReady()) {
|
||||
this._browserPage.emit('pageReady', {});
|
||||
const mainFrame = this._frameTree.mainFrame();
|
||||
const domWindow = mainFrame.domWindow();
|
||||
const document = domWindow ? domWindow.document : null;
|
||||
const readyState = document ? document.readyState : null;
|
||||
// Sometimes we initialize later than the first about:blank page is opened.
|
||||
// In this case, the page might've been loaded already, and we need to issue
|
||||
// the `DOMContentLoaded` and `load` events.
|
||||
if (mainFrame.url() === 'about:blank' && readyState === 'complete')
|
||||
this._emitAllEvents(this._frameTree.mainFrame());
|
||||
}
|
||||
|
||||
this._eventListeners = [
|
||||
helper.addObserver(this._linkClicked.bind(this, false), 'juggler-link-click'),
|
||||
helper.addObserver(this._linkClicked.bind(this, true), 'juggler-link-click-sync'),
|
||||
helper.addObserver(this._onWindowOpenInNewContext.bind(this), 'juggler-window-open-in-new-context'),
|
||||
helper.addObserver(this._filePickerShown.bind(this), 'juggler-file-picker-shown'),
|
||||
helper.addEventListener(this._messageManager, 'DOMContentLoaded', this._onDOMContentLoaded.bind(this)),
|
||||
helper.addEventListener(this._messageManager, 'pageshow', this._onLoad.bind(this)),
|
||||
helper.addObserver(this._onDocumentOpenLoad.bind(this), 'juggler-document-open-loaded'),
|
||||
helper.addEventListener(this._messageManager, 'error', this._onError.bind(this)),
|
||||
helper.on(this._frameTree, 'bindingcalled', this._onBindingCalled.bind(this)),
|
||||
helper.on(this._frameTree, 'frameattached', this._onFrameAttached.bind(this)),
|
||||
helper.on(this._frameTree, 'framedetached', this._onFrameDetached.bind(this)),
|
||||
helper.on(this._frameTree, 'globalobjectcreated', this._onGlobalObjectCreated.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)),
|
||||
helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)),
|
||||
helper.on(this._frameTree, 'pageready', () => this._browserPage.emit('pageReady', {})),
|
||||
helper.on(this._frameTree, 'workercreated', this._onWorkerCreated.bind(this)),
|
||||
helper.on(this._frameTree, 'workerdestroyed', this._onWorkerDestroyed.bind(this)),
|
||||
helper.addObserver(this._onWindowOpen.bind(this), 'webNavigation-createdNavigationTarget-from-js'),
|
||||
this._runtime.events.onErrorFromWorker((domWindow, message, stack) => {
|
||||
const frame = this._frameTree.frameForDocShell(domWindow.docShell);
|
||||
if (!frame)
|
||||
return;
|
||||
this._browserPage.emit('pageUncaughtError', {
|
||||
frameId: frame.id(),
|
||||
message,
|
||||
stack,
|
||||
});
|
||||
}),
|
||||
this._runtime.events.onConsoleMessage(msg => this._browserPage.emit('runtimeConsole', msg)),
|
||||
this._runtime.events.onExecutionContextCreated(this._onExecutionContextCreated.bind(this)),
|
||||
this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)),
|
||||
browserChannel.register('page', {
|
||||
addBinding: ({ name, script }) => this._frameTree.addBinding(name, script),
|
||||
addScriptToEvaluateOnNewDocument: this._addScriptToEvaluateOnNewDocument.bind(this),
|
||||
|
@ -149,21 +215,12 @@ class PageAgent {
|
|||
setEmulatedMedia: this._setEmulatedMedia.bind(this),
|
||||
setFileInputFiles: this._setFileInputFiles.bind(this),
|
||||
setInterceptFileChooserDialog: this._setInterceptFileChooserDialog.bind(this),
|
||||
}),
|
||||
browserChannel.register('runtime', {
|
||||
evaluate: this._runtime.evaluate.bind(this._runtime),
|
||||
callFunction: this._runtime.callFunction.bind(this._runtime),
|
||||
getObjectProperties: this._runtime.getObjectProperties.bind(this._runtime),
|
||||
disposeObject: this._runtime.disposeObject.bind(this._runtime),
|
||||
}),
|
||||
];
|
||||
this._enabled = false;
|
||||
|
||||
const docShell = frameTree.mainFrame().docShell();
|
||||
this._docShell = docShell;
|
||||
this._initialDPPX = docShell.contentViewer.overrideDPPX;
|
||||
this._customScrollbars = null;
|
||||
this._dataTransfer = null;
|
||||
}
|
||||
|
||||
async _setEmulatedMedia({type, colorScheme}) {
|
||||
|
@ -206,75 +263,6 @@ class PageAgent {
|
|||
docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
|
||||
}
|
||||
|
||||
enable() {
|
||||
if (this._enabled)
|
||||
return;
|
||||
|
||||
this._enabled = true;
|
||||
// Dispatch frameAttached events for all initial frames
|
||||
for (const frame of this._frameTree.frames()) {
|
||||
this._onFrameAttached(frame);
|
||||
if (frame.url())
|
||||
this._onNavigationCommitted(frame);
|
||||
if (frame.pendingNavigationId())
|
||||
this._onNavigationStarted(frame);
|
||||
}
|
||||
|
||||
for (const worker of this._frameTree.workers())
|
||||
this._onWorkerCreated(worker);
|
||||
|
||||
this._eventListeners.push(...[
|
||||
helper.addObserver(this._linkClicked.bind(this, false), 'juggler-link-click'),
|
||||
helper.addObserver(this._linkClicked.bind(this, true), 'juggler-link-click-sync'),
|
||||
helper.addObserver(this._onWindowOpenInNewContext.bind(this), 'juggler-window-open-in-new-context'),
|
||||
helper.addObserver(this._filePickerShown.bind(this), 'juggler-file-picker-shown'),
|
||||
helper.addEventListener(this._messageManager, 'DOMContentLoaded', this._onDOMContentLoaded.bind(this)),
|
||||
helper.addEventListener(this._messageManager, 'pageshow', this._onLoad.bind(this)),
|
||||
helper.addObserver(this._onDocumentOpenLoad.bind(this), 'juggler-document-open-loaded'),
|
||||
helper.addEventListener(this._messageManager, 'error', this._onError.bind(this)),
|
||||
helper.on(this._frameTree, 'bindingcalled', this._onBindingCalled.bind(this)),
|
||||
helper.on(this._frameTree, 'frameattached', this._onFrameAttached.bind(this)),
|
||||
helper.on(this._frameTree, 'framedetached', this._onFrameDetached.bind(this)),
|
||||
helper.on(this._frameTree, 'globalobjectcreated', this._onGlobalObjectCreated.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)),
|
||||
helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)),
|
||||
helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)),
|
||||
helper.on(this._frameTree, 'pageready', () => this._browserPage.emit('pageReady', {})),
|
||||
helper.on(this._frameTree, 'workercreated', this._onWorkerCreated.bind(this)),
|
||||
helper.on(this._frameTree, 'workerdestroyed', this._onWorkerDestroyed.bind(this)),
|
||||
helper.addObserver(this._onWindowOpen.bind(this), 'webNavigation-createdNavigationTarget-from-js'),
|
||||
this._runtime.events.onErrorFromWorker((domWindow, message, stack) => {
|
||||
const frame = this._frameTree.frameForDocShell(domWindow.docShell);
|
||||
if (!frame)
|
||||
return;
|
||||
this._browserPage.emit('pageUncaughtError', {
|
||||
frameId: frame.id(),
|
||||
message,
|
||||
stack,
|
||||
});
|
||||
}),
|
||||
this._runtime.events.onConsoleMessage(msg => this._browserRuntime.emit('runtimeConsole', msg)),
|
||||
this._runtime.events.onExecutionContextCreated(this._onExecutionContextCreated.bind(this)),
|
||||
this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)),
|
||||
]);
|
||||
for (const context of this._runtime.executionContexts())
|
||||
this._onExecutionContextCreated(context);
|
||||
|
||||
if (this._frameTree.isPageReady()) {
|
||||
this._browserPage.emit('pageReady', {});
|
||||
const mainFrame = this._frameTree.mainFrame();
|
||||
const domWindow = mainFrame.domWindow();
|
||||
const document = domWindow ? domWindow.document : null;
|
||||
const readyState = document ? document.readyState : null;
|
||||
// Sometimes we initialize later than the first about:blank page is opened.
|
||||
// In this case, the page might've been loaded already, and we need to issue
|
||||
// the `DOMContentLoaded` and `load` events.
|
||||
if (mainFrame.url() === 'about:blank' && readyState === 'complete')
|
||||
this._emitAllEvents(this._frameTree.mainFrame());
|
||||
}
|
||||
}
|
||||
|
||||
_emitAllEvents(frame) {
|
||||
this._browserPage.emit('pageEventFired', {
|
||||
frameId: frame.id(),
|
||||
|
@ -287,14 +275,14 @@ class PageAgent {
|
|||
}
|
||||
|
||||
_onExecutionContextCreated(executionContext) {
|
||||
this._browserRuntime.emit('runtimeExecutionContextCreated', {
|
||||
this._browserPage.emit('runtimeExecutionContextCreated', {
|
||||
executionContextId: executionContext.id(),
|
||||
auxData: executionContext.auxData(),
|
||||
});
|
||||
}
|
||||
|
||||
_onExecutionContextDestroyed(executionContext) {
|
||||
this._browserRuntime.emit('runtimeExecutionContextDestroyed', {
|
||||
this._browserPage.emit('runtimeExecutionContextDestroyed', {
|
||||
executionContextId: executionContext.id(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,19 +35,21 @@ class RuntimeAgent {
|
|||
constructor(runtime, channel) {
|
||||
this._runtime = runtime;
|
||||
this._browserRuntime = channel.connect('runtime');
|
||||
|
||||
for (const context of this._runtime.executionContexts())
|
||||
this._onExecutionContextCreated(context);
|
||||
|
||||
this._eventListeners = [
|
||||
this._runtime.events.onConsoleMessage(msg => this._browserRuntime.emit('runtimeConsole', msg)),
|
||||
this._runtime.events.onExecutionContextCreated(this._onExecutionContextCreated.bind(this)),
|
||||
this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)),
|
||||
channel.register('runtime', {
|
||||
evaluate: this._runtime.evaluate.bind(this._runtime),
|
||||
callFunction: this._runtime.callFunction.bind(this._runtime),
|
||||
getObjectProperties: this._runtime.getObjectProperties.bind(this._runtime),
|
||||
disposeObject: this._runtime.disposeObject.bind(this._runtime),
|
||||
}),
|
||||
this._runtime.events.onConsoleMessage(msg => this._browserRuntime.emit('runtimeConsole', msg)),
|
||||
this._runtime.events.onExecutionContextCreated(this._onExecutionContextCreated.bind(this)),
|
||||
this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)),
|
||||
];
|
||||
for (const context of this._runtime.executionContexts())
|
||||
this._onExecutionContextCreated(context);
|
||||
}
|
||||
|
||||
_onExecutionContextCreated(executionContext) {
|
||||
|
@ -70,15 +72,5 @@ class RuntimeAgent {
|
|||
}
|
||||
}
|
||||
|
||||
let runtimeAgent;
|
||||
|
||||
channel.register('', {
|
||||
attach: () => {
|
||||
runtimeAgent = new RuntimeAgent(runtime, channel);
|
||||
},
|
||||
|
||||
detach: () => {
|
||||
runtimeAgent.dispose();
|
||||
},
|
||||
});
|
||||
new RuntimeAgent(runtime, channel);
|
||||
|
||||
|
|
|
@ -103,7 +103,6 @@ function initialize() {
|
|||
frameTree.addBinding(name, script);
|
||||
|
||||
pageAgent = new PageAgent(messageManager, channel, frameTree);
|
||||
pageAgent.enable();
|
||||
|
||||
channel.register('', {
|
||||
addScriptToEvaluateOnNewDocument(script) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js');
|
||||
const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
@ -60,12 +61,9 @@ class PageHandler {
|
|||
this._session = session;
|
||||
this._contentChannel = contentChannel;
|
||||
this._contentPage = contentChannel.connect('page');
|
||||
this._contentRuntime = contentChannel.connect('runtime');
|
||||
this._workers = new Map();
|
||||
|
||||
this._pageTarget = target;
|
||||
this._browser = target.linkedBrowser();
|
||||
this._dialogs = new Map();
|
||||
this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target);
|
||||
|
||||
const emitProtocolEvent = eventName => {
|
||||
|
@ -75,7 +73,23 @@ class PageHandler {
|
|||
this._reportedFrameIds = new Set();
|
||||
this._networkEventsForUnreportedFrameIds = new Map();
|
||||
|
||||
for (const dialog of this._pageTarget.dialogs())
|
||||
this._onDialogOpened(dialog);
|
||||
|
||||
if (this._pageTarget.screencastInfo())
|
||||
this._onScreencastStarted();
|
||||
|
||||
this._eventListeners = [
|
||||
helper.on(this._pageTarget, PageTarget.Events.DialogOpened, this._onDialogOpened.bind(this)),
|
||||
helper.on(this._pageTarget, PageTarget.Events.DialogClosed, this._onDialogClosed.bind(this)),
|
||||
helper.on(this._pageTarget, PageTarget.Events.Crashed, () => {
|
||||
this._session.emitEvent('Page.crashed', {});
|
||||
}),
|
||||
helper.on(this._pageTarget, PageTarget.Events.ScreencastStarted, this._onScreencastStarted.bind(this)),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.Request, this._handleNetworkEvent.bind(this, 'Network.requestWillBeSent')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.Response, this._handleNetworkEvent.bind(this, 'Network.responseReceived')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.RequestFinished, this._handleNetworkEvent.bind(this, 'Network.requestFinished')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.RequestFailed, this._handleNetworkEvent.bind(this, 'Network.requestFailed')),
|
||||
contentChannel.register('page', {
|
||||
pageBindingCalled: emitProtocolEvent('Page.bindingCalled'),
|
||||
pageDispatchMessageFromWorker: emitProtocolEvent('Page.dispatchMessageFromWorker'),
|
||||
|
@ -93,36 +107,34 @@ class PageHandler {
|
|||
pageUncaughtError: emitProtocolEvent('Page.uncaughtError'),
|
||||
pageWorkerCreated: this._onWorkerCreated.bind(this),
|
||||
pageWorkerDestroyed: this._onWorkerDestroyed.bind(this),
|
||||
}),
|
||||
contentChannel.register('runtime', {
|
||||
runtimeConsole: emitProtocolEvent('Runtime.console'),
|
||||
runtimeExecutionContextCreated: emitProtocolEvent('Runtime.executionContextCreated'),
|
||||
runtimeExecutionContextDestroyed: emitProtocolEvent('Runtime.executionContextDestroyed'),
|
||||
}),
|
||||
helper.addEventListener(this._browser, 'DOMWillOpenModalDialog', async (event) => {
|
||||
// wait for the dialog to be actually added to DOM.
|
||||
await Promise.resolve();
|
||||
this._updateModalDialogs();
|
||||
}),
|
||||
helper.addEventListener(this._browser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
|
||||
helper.on(this._pageTarget, 'crashed', () => {
|
||||
this._session.emitEvent('Page.crashed', {});
|
||||
}),
|
||||
helper.on(this._pageTarget, 'screencastStarted', () => {
|
||||
const info = this._pageTarget.screencastInfo();
|
||||
this._session.emitEvent('Page.screencastStarted', { screencastId: '' + info.videoSessionId, file: info.file });
|
||||
}),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.Request, this._handleNetworkEvent.bind(this, 'Network.requestWillBeSent')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.Response, this._handleNetworkEvent.bind(this, 'Network.responseReceived')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.RequestFinished, this._handleNetworkEvent.bind(this, 'Network.requestFinished')),
|
||||
helper.on(this._pageNetwork, PageNetwork.Events.RequestFailed, this._handleNetworkEvent.bind(this, 'Network.requestFailed')),
|
||||
this._pageNetwork.addSession(),
|
||||
];
|
||||
}
|
||||
|
||||
this._updateModalDialogs();
|
||||
const options = this._pageTarget.browserContext().screencastOptions;
|
||||
if (options)
|
||||
this._pageTarget.startVideoRecording(options);
|
||||
async dispose() {
|
||||
this._contentPage.dispose();
|
||||
helper.removeListeners(this._eventListeners);
|
||||
}
|
||||
|
||||
_onScreencastStarted() {
|
||||
const info = this._pageTarget.screencastInfo();
|
||||
this._session.emitEvent('Page.screencastStarted', { screencastId: '' + info.videoSessionId, file: info.file });
|
||||
}
|
||||
|
||||
_onDialogOpened(dialog) {
|
||||
this._session.emitEvent('Page.dialogOpened', {
|
||||
dialogId: dialog.id(),
|
||||
type: dialog.type(),
|
||||
message: dialog.message(),
|
||||
defaultValue: dialog.defaultValue(),
|
||||
});
|
||||
}
|
||||
|
||||
_onDialogClosed(dialog) {
|
||||
this._session.emitEvent('Page.dialogClosed', { dialogId: dialog.id(), });
|
||||
}
|
||||
|
||||
_onWorkerCreated({workerId, frameId, url}) {
|
||||
|
@ -169,59 +181,24 @@ class PageHandler {
|
|||
});
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
this._contentPage.dispose();
|
||||
this._contentRuntime.dispose();
|
||||
helper.removeListeners(this._eventListeners);
|
||||
|
||||
if (this._pageTarget.screencastInfo())
|
||||
await this._pageTarget.stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`));
|
||||
}
|
||||
|
||||
async ['Page.setViewportSize']({viewportSize}) {
|
||||
await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize);
|
||||
}
|
||||
|
||||
_updateModalDialogs() {
|
||||
const prompts = new Set(this._browser.tabModalPromptBox ? this._browser.tabModalPromptBox.listPrompts() : []);
|
||||
for (const dialog of this._dialogs.values()) {
|
||||
if (!prompts.has(dialog.prompt())) {
|
||||
this._dialogs.delete(dialog.id());
|
||||
this._session.emitEvent('Page.dialogClosed', {
|
||||
dialogId: dialog.id(),
|
||||
});
|
||||
} 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._session.emitEvent('Page.dialogOpened', {
|
||||
dialogId: dialog.id(),
|
||||
type: dialog.type(),
|
||||
message: dialog.message(),
|
||||
defaultValue: dialog.defaultValue(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async ['Runtime.evaluate'](options) {
|
||||
return await this._contentRuntime.send('evaluate', options);
|
||||
return await this._contentPage.send('evaluate', options);
|
||||
}
|
||||
|
||||
async ['Runtime.callFunction'](options) {
|
||||
return await this._contentRuntime.send('callFunction', options);
|
||||
return await this._contentPage.send('callFunction', options);
|
||||
}
|
||||
|
||||
async ['Runtime.getObjectProperties'](options) {
|
||||
return await this._contentRuntime.send('getObjectProperties', options);
|
||||
return await this._contentPage.send('getObjectProperties', options);
|
||||
}
|
||||
|
||||
async ['Runtime.disposeObject'](options) {
|
||||
return await this._contentRuntime.send('disposeObject', options);
|
||||
return await this._contentPage.send('disposeObject', options);
|
||||
}
|
||||
|
||||
async ['Network.getResponseBody']({requestId}) {
|
||||
|
@ -291,30 +268,18 @@ class PageHandler {
|
|||
return await this._contentPage.send('getContentQuads', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{frameId: string, url: string}} options
|
||||
*/
|
||||
async ['Page.navigate'](options) {
|
||||
return await this._contentPage.send('navigate', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{frameId: string, url: string}} options
|
||||
*/
|
||||
async ['Page.goBack'](options) {
|
||||
return await this._contentPage.send('goBack', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{frameId: string, url: string}} options
|
||||
*/
|
||||
async ['Page.goForward'](options) {
|
||||
return await this._contentPage.send('goForward', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{frameId: string, url: string}} options
|
||||
*/
|
||||
async ['Page.reload'](options) {
|
||||
return await this._contentPage.send('reload', options);
|
||||
}
|
||||
|
@ -356,7 +321,7 @@ class PageHandler {
|
|||
}
|
||||
|
||||
async ['Page.handleDialog']({dialogId, accept, promptText}) {
|
||||
const dialog = this._dialogs.get(dialogId);
|
||||
const dialog = this._pageTarget.dialog(dialogId);
|
||||
if (!dialog)
|
||||
throw new Error('Failed to find dialog with id = ' + dialogId);
|
||||
if (accept)
|
||||
|
@ -381,60 +346,5 @@ class PageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PageHandler'];
|
||||
this.PageHandler = PageHandler;
|
||||
|
|
Loading…
Reference in New Issue