emulation.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const { Ci } = require("chrome");
  6. const protocol = require("devtools/shared/protocol");
  7. const { emulationSpec } = require("devtools/shared/specs/emulation");
  8. const { SimulatorCore } = require("devtools/shared/touch/simulator-core");
  9. /**
  10. * This actor overrides various browser features to simulate different environments to
  11. * test how pages perform under various conditions.
  12. *
  13. * The design below, which saves the previous value of each property before setting, is
  14. * needed because it's possible to have multiple copies of this actor for a single page.
  15. * When some instance of this actor changes a property, we want it to be able to restore
  16. * that property to the way it was found before the change.
  17. *
  18. * A subtle aspect of the code below is that all get* methods must return non-undefined
  19. * values, so that the absence of a previous value can be distinguished from the value for
  20. * "no override" for each of the properties.
  21. */
  22. let EmulationActor = protocol.ActorClassWithSpec(emulationSpec, {
  23. initialize(conn, tabActor) {
  24. protocol.Actor.prototype.initialize.call(this, conn);
  25. this.tabActor = tabActor;
  26. this.docShell = tabActor.docShell;
  27. this.simulatorCore = new SimulatorCore(tabActor.chromeEventHandler);
  28. },
  29. disconnect() {
  30. this.destroy();
  31. },
  32. destroy() {
  33. this.clearDPPXOverride();
  34. this.clearNetworkThrottling();
  35. this.clearTouchEventsOverride();
  36. this.clearUserAgentOverride();
  37. this.tabActor = null;
  38. this.docShell = null;
  39. this.simulatorCore = null;
  40. protocol.Actor.prototype.destroy.call(this);
  41. },
  42. /**
  43. * Retrieve the console actor for this tab. This allows us to expose network throttling
  44. * as part of emulation settings, even though it's internally connected to the network
  45. * monitor, which for historical reasons is part of the console actor.
  46. */
  47. get _consoleActor() {
  48. if (this.tabActor.exited) {
  49. return null;
  50. }
  51. let form = this.tabActor.form();
  52. return this.conn._getOrCreateActor(form.consoleActor);
  53. },
  54. /* DPPX override */
  55. _previousDPPXOverride: undefined,
  56. setDPPXOverride(dppx) {
  57. if (this.getDPPXOverride() === dppx) {
  58. return false;
  59. }
  60. if (this._previousDPPXOverride === undefined) {
  61. this._previousDPPXOverride = this.getDPPXOverride();
  62. }
  63. this.docShell.contentViewer.overrideDPPX = dppx;
  64. return true;
  65. },
  66. getDPPXOverride() {
  67. return this.docShell.contentViewer.overrideDPPX;
  68. },
  69. clearDPPXOverride() {
  70. if (this._previousDPPXOverride !== undefined) {
  71. return this.setDPPXOverride(this._previousDPPXOverride);
  72. }
  73. return false;
  74. },
  75. /* Network Throttling */
  76. _previousNetworkThrottling: undefined,
  77. /**
  78. * Transform the RDP format into the internal format and then set network throttling.
  79. */
  80. setNetworkThrottling({ downloadThroughput, uploadThroughput, latency }) {
  81. let throttleData = {
  82. roundTripTimeMean: latency,
  83. roundTripTimeMax: latency,
  84. downloadBPSMean: downloadThroughput,
  85. downloadBPSMax: downloadThroughput,
  86. uploadBPSMean: uploadThroughput,
  87. uploadBPSMax: uploadThroughput,
  88. };
  89. return this._setNetworkThrottling(throttleData);
  90. },
  91. _setNetworkThrottling(throttleData) {
  92. let current = this._getNetworkThrottling();
  93. // Check if they are both objects or both null
  94. let match = throttleData == current;
  95. // If both objects, check all entries
  96. if (match && current && throttleData) {
  97. match = Object.entries(current).every(([ k, v ]) => {
  98. return throttleData[k] === v;
  99. });
  100. }
  101. if (match) {
  102. return false;
  103. }
  104. if (this._previousNetworkThrottling === undefined) {
  105. this._previousNetworkThrottling = current;
  106. }
  107. let consoleActor = this._consoleActor;
  108. if (!consoleActor) {
  109. return false;
  110. }
  111. consoleActor.onStartListeners({
  112. listeners: [ "NetworkActivity" ],
  113. });
  114. consoleActor.onSetPreferences({
  115. preferences: {
  116. "NetworkMonitor.throttleData": throttleData,
  117. }
  118. });
  119. return true;
  120. },
  121. /**
  122. * Get network throttling and then transform the internal format into the RDP format.
  123. */
  124. getNetworkThrottling() {
  125. let throttleData = this._getNetworkThrottling();
  126. if (!throttleData) {
  127. return null;
  128. }
  129. let { downloadBPSMax, uploadBPSMax, roundTripTimeMax } = throttleData;
  130. return {
  131. downloadThroughput: downloadBPSMax,
  132. uploadThroughput: uploadBPSMax,
  133. latency: roundTripTimeMax,
  134. };
  135. },
  136. _getNetworkThrottling() {
  137. let consoleActor = this._consoleActor;
  138. if (!consoleActor) {
  139. return null;
  140. }
  141. let prefs = consoleActor.onGetPreferences({
  142. preferences: [ "NetworkMonitor.throttleData" ],
  143. });
  144. return prefs.preferences["NetworkMonitor.throttleData"] || null;
  145. },
  146. clearNetworkThrottling() {
  147. if (this._previousNetworkThrottling !== undefined) {
  148. return this._setNetworkThrottling(this._previousNetworkThrottling);
  149. }
  150. return false;
  151. },
  152. /* Touch events override */
  153. _previousTouchEventsOverride: undefined,
  154. setTouchEventsOverride(flag) {
  155. if (this.getTouchEventsOverride() == flag) {
  156. return false;
  157. }
  158. if (this._previousTouchEventsOverride === undefined) {
  159. this._previousTouchEventsOverride = this.getTouchEventsOverride();
  160. }
  161. // Start or stop the touch simulator depending on the override flag
  162. if (flag == Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED) {
  163. this.simulatorCore.start();
  164. } else {
  165. this.simulatorCore.stop();
  166. }
  167. this.docShell.touchEventsOverride = flag;
  168. return true;
  169. },
  170. getTouchEventsOverride() {
  171. return this.docShell.touchEventsOverride;
  172. },
  173. clearTouchEventsOverride() {
  174. if (this._previousTouchEventsOverride !== undefined) {
  175. return this.setTouchEventsOverride(this._previousTouchEventsOverride);
  176. }
  177. return false;
  178. },
  179. /* User agent override */
  180. _previousUserAgentOverride: undefined,
  181. setUserAgentOverride(userAgent) {
  182. if (this.getUserAgentOverride() == userAgent) {
  183. return false;
  184. }
  185. if (this._previousUserAgentOverride === undefined) {
  186. this._previousUserAgentOverride = this.getUserAgentOverride();
  187. }
  188. this.docShell.customUserAgent = userAgent;
  189. return true;
  190. },
  191. getUserAgentOverride() {
  192. return this.docShell.customUserAgent;
  193. },
  194. clearUserAgentOverride() {
  195. if (this._previousUserAgentOverride !== undefined) {
  196. return this.setUserAgentOverride(this._previousUserAgentOverride);
  197. }
  198. return false;
  199. },
  200. });
  201. exports.EmulationActor = EmulationActor;