/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; 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; const Cu = Components.utils; const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; const helper = new Helper(); class WorkerHandler { constructor(session, contentChannel, workerId) { this._session = session; this._contentWorker = contentChannel.connect(workerId); this._workerId = workerId; const emitWrappedProtocolEvent = eventName => { return params => { this._session.emitEvent('Page.dispatchMessageFromWorker', { workerId, message: JSON.stringify({method: eventName, params}), }); } } this._eventListeners = [ contentChannel.register(workerId, { runtimeConsole: emitWrappedProtocolEvent('Runtime.console'), runtimeExecutionContextCreated: emitWrappedProtocolEvent('Runtime.executionContextCreated'), runtimeExecutionContextDestroyed: emitWrappedProtocolEvent('Runtime.executionContextDestroyed'), }), ]; } async sendMessage(message) { const [domain, method] = message.method.split('.'); if (domain !== 'Runtime') throw new Error('ERROR: can only dispatch to Runtime domain inside worker'); const result = await this._contentWorker.send(method, message.params); this._session.emitEvent('Page.dispatchMessageFromWorker', { workerId: this._workerId, message: JSON.stringify({result, id: message.id}), }); } dispose() { this._contentWorker.dispose(); helper.removeListeners(this._eventListeners); } } class PageHandler { constructor(target, session, contentChannel) { this._session = session; this._contentChannel = contentChannel; this._contentPage = contentChannel.connect('page'); this._workers = new Map(); this._pageTarget = target; this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target); const emitProtocolEvent = eventName => { return (...args) => this._session.emitEvent(eventName, ...args); } 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'), pageEventFired: emitProtocolEvent('Page.eventFired'), pageFileChooserOpened: emitProtocolEvent('Page.fileChooserOpened'), pageFrameAttached: this._onFrameAttached.bind(this), pageFrameDetached: emitProtocolEvent('Page.frameDetached'), pageLinkClicked: emitProtocolEvent('Page.linkClicked'), pageWillOpenNewWindowAsynchronously: emitProtocolEvent('Page.willOpenNewWindowAsynchronously'), pageNavigationAborted: emitProtocolEvent('Page.navigationAborted'), pageNavigationCommitted: emitProtocolEvent('Page.navigationCommitted'), pageNavigationStarted: emitProtocolEvent('Page.navigationStarted'), pageReady: emitProtocolEvent('Page.ready'), pageSameDocumentNavigation: emitProtocolEvent('Page.sameDocumentNavigation'), pageUncaughtError: emitProtocolEvent('Page.uncaughtError'), pageWorkerCreated: this._onWorkerCreated.bind(this), pageWorkerDestroyed: this._onWorkerDestroyed.bind(this), runtimeConsole: emitProtocolEvent('Runtime.console'), runtimeExecutionContextCreated: emitProtocolEvent('Runtime.executionContextCreated'), runtimeExecutionContextDestroyed: emitProtocolEvent('Runtime.executionContextDestroyed'), webSocketCreated: emitProtocolEvent('Page.webSocketCreated'), webSocketOpened: emitProtocolEvent('Page.webSocketOpened'), webSocketClosed: emitProtocolEvent('Page.webSocketClosed'), webSocketFrameReceived: emitProtocolEvent('Page.webSocketFrameReceived'), webSocketFrameSent: emitProtocolEvent('Page.webSocketFrameSent'), }), ]; } 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}) { const worker = new WorkerHandler(this._session, this._contentChannel, workerId); this._workers.set(workerId, worker); this._session.emitEvent('Page.workerCreated', {workerId, frameId, url}); } _onWorkerDestroyed({workerId}) { const worker = this._workers.get(workerId); if (!worker) return; this._workers.delete(workerId); worker.dispose(); this._session.emitEvent('Page.workerDestroyed', {workerId}); } _handleNetworkEvent(protocolEventName, eventDetails, frameId) { if (!this._reportedFrameIds.has(frameId)) { let events = this._networkEventsForUnreportedFrameIds.get(frameId); if (!events) { events = []; this._networkEventsForUnreportedFrameIds.set(frameId, events); } events.push({eventName: protocolEventName, eventDetails}); } else { this._session.emitEvent(protocolEventName, eventDetails); } } _onFrameAttached({frameId, parentFrameId}) { this._session.emitEvent('Page.frameAttached', {frameId, parentFrameId}); this._reportedFrameIds.add(frameId); const events = this._networkEventsForUnreportedFrameIds.get(frameId) || []; this._networkEventsForUnreportedFrameIds.delete(frameId); for (const {eventName, eventDetails} of events) this._session.emitEvent(eventName, eventDetails); } async ['Page.close']({runBeforeUnload}) { // Postpone target close to deliver response in session. Services.tm.dispatchToMainThread(() => { this._pageTarget.close(runBeforeUnload); }); } async ['Page.setViewportSize']({viewportSize}) { await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize); } async ['Runtime.evaluate'](options) { return await this._contentPage.send('evaluate', options); } async ['Runtime.callFunction'](options) { return await this._contentPage.send('callFunction', options); } async ['Runtime.getObjectProperties'](options) { return await this._contentPage.send('getObjectProperties', options); } async ['Runtime.disposeObject'](options) { return await this._contentPage.send('disposeObject', options); } async ['Network.getResponseBody']({requestId}) { return this._pageNetwork.getResponseBody(requestId); } async ['Network.setExtraHTTPHeaders']({headers}) { this._pageNetwork.setExtraHTTPHeaders(headers); } async ['Network.setRequestInterception']({enabled}) { if (enabled) this._pageNetwork.enableRequestInterception(); else this._pageNetwork.disableRequestInterception(); } async ['Network.resumeInterceptedRequest']({requestId, method, headers, postData}) { this._pageNetwork.resumeInterceptedRequest(requestId, method, headers, postData); } async ['Network.abortInterceptedRequest']({requestId, errorCode}) { this._pageNetwork.abortInterceptedRequest(requestId, errorCode); } async ['Network.fulfillInterceptedRequest']({requestId, status, statusText, headers, base64body}) { this._pageNetwork.fulfillInterceptedRequest(requestId, status, statusText, headers, base64body); } async ['Accessibility.getFullAXTree'](params) { return await this._contentPage.send('getFullAXTree', params); } async ['Page.setFileInputFiles'](options) { return await this._contentPage.send('setFileInputFiles', options); } async ['Page.setEmulatedMedia'](options) { return await this._contentPage.send('setEmulatedMedia', options); } async ['Page.bringToFront'](options) { this._pageTarget._window.focus(); } async ['Page.setCacheDisabled'](options) { return await this._contentPage.send('setCacheDisabled', options); } async ['Page.addBinding'](options) { return await this._contentPage.send('addBinding', options); } async ['Page.adoptNode'](options) { return await this._contentPage.send('adoptNode', options); } async ['Page.screenshot'](options) { return await this._contentPage.send('screenshot', options); } async ['Page.getBoundingBox'](options) { return await this._contentPage.send('getBoundingBox', options); } async ['Page.getContentQuads'](options) { return await this._contentPage.send('getContentQuads', options); } async ['Page.navigate'](options) { return await this._contentPage.send('navigate', options); } async ['Page.goBack'](options) { return await this._contentPage.send('goBack', options); } async ['Page.goForward'](options) { return await this._contentPage.send('goForward', options); } async ['Page.reload'](options) { return await this._contentPage.send('reload', options); } async ['Page.describeNode'](options) { return await this._contentPage.send('describeNode', options); } async ['Page.scrollIntoViewIfNeeded'](options) { return await this._contentPage.send('scrollIntoViewIfNeeded', options); } async ['Page.addScriptToEvaluateOnNewDocument'](options) { return await this._contentPage.send('addScriptToEvaluateOnNewDocument', options); } async ['Page.removeScriptToEvaluateOnNewDocument'](options) { return await this._contentPage.send('removeScriptToEvaluateOnNewDocument', options); } async ['Page.dispatchKeyEvent'](options) { return await this._contentPage.send('dispatchKeyEvent', options); } async ['Page.dispatchTouchEvent'](options) { return await this._contentPage.send('dispatchTouchEvent', options); } async ['Page.dispatchTapEvent'](options) { return await this._contentPage.send('dispatchTapEvent', options); } async ['Page.dispatchMouseEvent'](options) { return await this._contentPage.send('dispatchMouseEvent', options); } async ['Page.insertText'](options) { return await this._contentPage.send('insertText', options); } async ['Page.crash'](options) { return await this._contentPage.send('crash', options); } async ['Page.handleDialog']({dialogId, accept, promptText}) { const dialog = this._pageTarget.dialog(dialogId); if (!dialog) throw new Error('Failed to find dialog with id = ' + dialogId); if (accept) dialog.accept(promptText); else dialog.dismiss(); } async ['Page.setInterceptFileChooserDialog'](options) { return await this._contentPage.send('setInterceptFileChooserDialog', options); } async ['Page.sendMessageToWorker']({workerId, message}) { const worker = this._workers.get(workerId); if (!worker) throw new Error('ERROR: cannot find worker with id ' + workerId); return await worker.sendMessage(JSON.parse(message)); } async ['Page.stopVideoRecording']() { await this._pageTarget.stopVideoRecording(); } } var EXPORTED_SYMBOLS = ['PageHandler']; this.PageHandler = PageHandler;