summaryrefslogtreecommitdiffstats
path: root/remote/cdp/domains/parent/Emulation.sys.mjs
blob: 8cb01b16ac571581bd771d0fabc2bd9ec750ba07 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* 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/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.jsm",
});

const MAX_WINDOW_SIZE = 10000000;

export class Emulation extends Domain {
  destructor() {
    this.setUserAgentOverride({ userAgent: "", platform: "" });

    super.destructor();
  }

  /**
   * Overrides the values of device screen dimensions.
   *
   * Values as modified are:
   *   - window.screen.width
   *   - window.screen.height
   *   - window.innerWidth
   *   - window.innerHeight
   *   - "device-width"/"device-height"-related CSS media query results
   *
   * @param {object} options
   * @param {number} options.width
   *     Overriding width value in pixels. 0 disables the override.
   * @param {number} options.height
   *     Overriding height value in pixels. 0 disables the override.
   * @param {number} options.deviceScaleFactor
   *     Overriding device scale factor value. 0 disables the override.
   * @param {number} options.mobile [not supported]
   *     Whether to emulate a mobile device. This includes viewport meta tag,
   *     overlay scrollbars, text autosizing and more.
   * @param {number} options.screenOrientation
   *     Screen orientation override [not supported]
   */
  async setDeviceMetricsOverride(options = {}) {
    const { width, height, deviceScaleFactor } = options;

    if (
      width < 0 ||
      width > MAX_WINDOW_SIZE ||
      height < 0 ||
      height > MAX_WINDOW_SIZE
    ) {
      throw new TypeError(
        `Width and height values must be positive, not greater than ${MAX_WINDOW_SIZE}`
      );
    }

    if (typeof deviceScaleFactor != "number") {
      throw new TypeError("deviceScaleFactor: number expected");
    }

    if (deviceScaleFactor < 0) {
      throw new TypeError("deviceScaleFactor: must be positive");
    }

    const { tab } = this.session.target;
    const { linkedBrowser: browser } = tab;

    const { browsingContext } = this.session.target;
    browsingContext.overrideDPPX = deviceScaleFactor;

    // With a value of 0 the current size is used
    const { layoutViewport } = await this.session.execute(
      this.session.id,
      "Page",
      "getLayoutMetrics"
    );

    const targetWidth = width > 0 ? width : layoutViewport.clientWidth;
    const targetHeight = height > 0 ? height : layoutViewport.clientHeight;

    browser.style.setProperty("min-width", targetWidth + "px");
    browser.style.setProperty("max-width", targetWidth + "px");
    browser.style.setProperty("min-height", targetHeight + "px");
    browser.style.setProperty("max-height", targetHeight + "px");

    // Wait until the viewport has been resized
    await this.executeInChild("_awaitViewportDimensions", {
      width: targetWidth,
      height: targetHeight,
    });
  }

  /**
   * Enables touch on platforms which do not support them.
   *
   * @param {object} options
   * @param {boolean} options.enabled
   *     Whether the touch event emulation should be enabled.
   * @param {number=} options.maxTouchPoints [not yet supported]
   *     Maximum touch points supported. Defaults to one.
   */
  async setTouchEmulationEnabled(options = {}) {
    const { enabled } = options;

    if (typeof enabled != "boolean") {
      throw new TypeError(
        "Invalid parameters (enabled: boolean value expected)"
      );
    }

    const { browsingContext } = this.session.target;
    if (enabled) {
      browsingContext.touchEventsOverride = "enabled";
    } else {
      browsingContext.touchEventsOverride = "none";
    }
  }

  /**
   * Allows overriding user agent with the given string.
   *
   * @param {object} options
   * @param {string} options.userAgent
   *     User agent to use.
   * @param {string=} options.acceptLanguage [not yet supported]
   *     Browser langugage to emulate.
   * @param {string=} options.platform
   *     The platform navigator.platform should return.
   */
  async setUserAgentOverride(options = {}) {
    const { userAgent, platform } = options;

    if (typeof userAgent != "string") {
      throw new TypeError(
        "Invalid parameters (userAgent: string value expected)"
      );
    }

    if (!["undefined", "string"].includes(typeof platform)) {
      throw new TypeError("platform: string value expected");
    }

    const { browsingContext } = this.session.target;

    if (!userAgent.length) {
      browsingContext.customUserAgent = null;
    } else if (this._isValidHTTPRequestHeaderValue(userAgent)) {
      browsingContext.customUserAgent = userAgent;
    } else {
      throw new TypeError("Invalid characters found in userAgent");
    }

    if (platform?.length > 0) {
      browsingContext.customPlatform = platform;
    } else {
      browsingContext.customPlatform = null;
    }
  }

  _isValidHTTPRequestHeaderValue(value) {
    try {
      const channel = lazy.NetUtil.newChannel({
        uri: "http://localhost",
        loadUsingSystemPrincipal: true,
      });
      channel.QueryInterface(Ci.nsIHttpChannel);
      channel.setRequestHeader("X-check", value, false);
      return true;
    } catch (e) {
      return false;
    }
  }
}