summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/emulation.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/emulation.js')
-rw-r--r--devtools/server/actors/emulation.js241
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;