diff options
Diffstat (limited to 'devtools/server/actors/emulation.js')
-rw-r--r-- | devtools/server/actors/emulation.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/devtools/server/actors/emulation.js b/devtools/server/actors/emulation.js new file mode 100644 index 000000000..b69183305 --- /dev/null +++ b/devtools/server/actors/emulation.js @@ -0,0 +1,241 @@ +/* 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 { Ci } = require("chrome"); +const protocol = require("devtools/shared/protocol"); +const { emulationSpec } = require("devtools/shared/specs/emulation"); +const { SimulatorCore } = require("devtools/shared/touch/simulator-core"); + +/** + * This actor overrides various browser features to simulate different environments to + * test how pages perform under various conditions. + * + * The design below, which saves the previous value of each property before setting, is + * needed because it's possible to have multiple copies of this actor for a single page. + * When some instance of this actor changes a property, we want it to be able to restore + * that property to the way it was found before the change. + * + * A subtle aspect of the code below is that all get* methods must return non-undefined + * values, so that the absence of a previous value can be distinguished from the value for + * "no override" for each of the properties. + */ +let EmulationActor = protocol.ActorClassWithSpec(emulationSpec, { + + initialize(conn, tabActor) { + protocol.Actor.prototype.initialize.call(this, conn); + this.tabActor = tabActor; + this.docShell = tabActor.docShell; + this.simulatorCore = new SimulatorCore(tabActor.chromeEventHandler); + }, + + disconnect() { + this.destroy(); + }, + + destroy() { + this.clearDPPXOverride(); + this.clearNetworkThrottling(); + this.clearTouchEventsOverride(); + this.clearUserAgentOverride(); + this.tabActor = null; + this.docShell = null; + this.simulatorCore = null; + protocol.Actor.prototype.destroy.call(this); + }, + + /** + * Retrieve the console actor for this tab. This allows us to expose network throttling + * as part of emulation settings, even though it's internally connected to the network + * monitor, which for historical reasons is part of the console actor. + */ + get _consoleActor() { + if (this.tabActor.exited) { + return null; + } + let form = this.tabActor.form(); + return this.conn._getOrCreateActor(form.consoleActor); + }, + + /* DPPX override */ + + _previousDPPXOverride: undefined, + + setDPPXOverride(dppx) { + if (this.getDPPXOverride() === dppx) { + return false; + } + + if (this._previousDPPXOverride === undefined) { + this._previousDPPXOverride = this.getDPPXOverride(); + } + + this.docShell.contentViewer.overrideDPPX = dppx; + + return true; + }, + + getDPPXOverride() { + return this.docShell.contentViewer.overrideDPPX; + }, + + clearDPPXOverride() { + if (this._previousDPPXOverride !== undefined) { + return this.setDPPXOverride(this._previousDPPXOverride); + } + + return false; + }, + + /* Network Throttling */ + + _previousNetworkThrottling: undefined, + + /** + * Transform the RDP format into the internal format and then set network throttling. + */ + setNetworkThrottling({ downloadThroughput, uploadThroughput, latency }) { + let throttleData = { + roundTripTimeMean: latency, + roundTripTimeMax: latency, + downloadBPSMean: downloadThroughput, + downloadBPSMax: downloadThroughput, + uploadBPSMean: uploadThroughput, + uploadBPSMax: uploadThroughput, + }; + return this._setNetworkThrottling(throttleData); + }, + + _setNetworkThrottling(throttleData) { + let current = this._getNetworkThrottling(); + // Check if they are both objects or both null + let match = throttleData == current; + // If both objects, check all entries + if (match && current && throttleData) { + match = Object.entries(current).every(([ k, v ]) => { + return throttleData[k] === v; + }); + } + if (match) { + return false; + } + + if (this._previousNetworkThrottling === undefined) { + this._previousNetworkThrottling = current; + } + + let consoleActor = this._consoleActor; + if (!consoleActor) { + return false; + } + consoleActor.onStartListeners({ + listeners: [ "NetworkActivity" ], + }); + consoleActor.onSetPreferences({ + preferences: { + "NetworkMonitor.throttleData": throttleData, + } + }); + return true; + }, + + /** + * Get network throttling and then transform the internal format into the RDP format. + */ + getNetworkThrottling() { + let throttleData = this._getNetworkThrottling(); + if (!throttleData) { + return null; + } + let { downloadBPSMax, uploadBPSMax, roundTripTimeMax } = throttleData; + return { + downloadThroughput: downloadBPSMax, + uploadThroughput: uploadBPSMax, + latency: roundTripTimeMax, + }; + }, + + _getNetworkThrottling() { + let consoleActor = this._consoleActor; + if (!consoleActor) { + return null; + } + let prefs = consoleActor.onGetPreferences({ + preferences: [ "NetworkMonitor.throttleData" ], + }); + return prefs.preferences["NetworkMonitor.throttleData"] || null; + }, + + clearNetworkThrottling() { + if (this._previousNetworkThrottling !== undefined) { + return this._setNetworkThrottling(this._previousNetworkThrottling); + } + + return false; + }, + + /* Touch events override */ + + _previousTouchEventsOverride: undefined, + + setTouchEventsOverride(flag) { + if (this.getTouchEventsOverride() == flag) { + return false; + } + if (this._previousTouchEventsOverride === undefined) { + this._previousTouchEventsOverride = this.getTouchEventsOverride(); + } + + // Start or stop the touch simulator depending on the override flag + if (flag == Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED) { + this.simulatorCore.start(); + } else { + this.simulatorCore.stop(); + } + + this.docShell.touchEventsOverride = flag; + return true; + }, + + getTouchEventsOverride() { + return this.docShell.touchEventsOverride; + }, + + clearTouchEventsOverride() { + if (this._previousTouchEventsOverride !== undefined) { + return this.setTouchEventsOverride(this._previousTouchEventsOverride); + } + return false; + }, + + /* User agent override */ + + _previousUserAgentOverride: undefined, + + setUserAgentOverride(userAgent) { + if (this.getUserAgentOverride() == userAgent) { + return false; + } + if (this._previousUserAgentOverride === undefined) { + this._previousUserAgentOverride = this.getUserAgentOverride(); + } + this.docShell.customUserAgent = userAgent; + return true; + }, + + getUserAgentOverride() { + return this.docShell.customUserAgent; + }, + + clearUserAgentOverride() { + if (this._previousUserAgentOverride !== undefined) { + return this.setUserAgentOverride(this._previousUserAgentOverride); + } + return false; + }, + +}); + +exports.EmulationActor = EmulationActor; |