tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_Capabilities.js (22074B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { AppInfo } = ChromeUtils.importESModule(
      8  "chrome://remote/content/shared/AppInfo.sys.mjs"
      9 );
     10 const { error } = ChromeUtils.importESModule(
     11  "chrome://remote/content/shared/webdriver/Errors.sys.mjs"
     12 );
     13 const {
     14  Capabilities,
     15  mergeCapabilities,
     16  PageLoadStrategy,
     17  processCapabilities,
     18  ProxyConfiguration,
     19  Timeouts,
     20  validateCapabilities,
     21 } = ChromeUtils.importESModule(
     22  "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs"
     23 );
     24 
     25 add_task(function test_Timeouts_ctor() {
     26  let ts = new Timeouts();
     27  equal(ts.implicit, 0);
     28  equal(ts.pageLoad, 300000);
     29  equal(ts.script, 30000);
     30 });
     31 
     32 add_task(function test_Timeouts_toString() {
     33  equal(new Timeouts().toString(), "[object Timeouts]");
     34 });
     35 
     36 add_task(function test_Timeouts_toJSON() {
     37  let ts = new Timeouts();
     38  deepEqual(ts.toJSON(), { implicit: 0, pageLoad: 300000, script: 30000 });
     39 });
     40 
     41 add_task(function test_Timeouts_fromJSON() {
     42  let json = {
     43    implicit: 0,
     44    pageLoad: 2.0,
     45    script: Number.MAX_SAFE_INTEGER,
     46  };
     47  let ts = Timeouts.fromJSON(json);
     48  equal(ts.implicit, json.implicit);
     49  equal(ts.pageLoad, json.pageLoad);
     50  equal(ts.script, json.script);
     51 });
     52 
     53 add_task(function test_Timeouts_fromJSON_unrecognized_field() {
     54  let json = {
     55    sessionId: "foobar",
     56  };
     57  try {
     58    Timeouts.fromJSON(json);
     59  } catch (e) {
     60    equal(e.name, error.InvalidArgumentError.name);
     61    equal(e.message, "Unrecognized timeout: sessionId");
     62  }
     63 });
     64 
     65 add_task(function test_Timeouts_fromJSON_invalid_types() {
     66  for (let value of [null, [], {}, false, "10", 2.5]) {
     67    Assert.throws(
     68      () => Timeouts.fromJSON({ implicit: value }),
     69      /InvalidArgumentError/
     70    );
     71  }
     72 });
     73 
     74 add_task(function test_Timeouts_fromJSON_bounds() {
     75  for (let value of [-1, Number.MAX_SAFE_INTEGER + 1]) {
     76    Assert.throws(
     77      () => Timeouts.fromJSON({ script: value }),
     78      /InvalidArgumentError/
     79    );
     80  }
     81 });
     82 
     83 add_task(function test_PageLoadStrategy() {
     84  equal(PageLoadStrategy.None, "none");
     85  equal(PageLoadStrategy.Eager, "eager");
     86  equal(PageLoadStrategy.Normal, "normal");
     87 });
     88 
     89 add_task(function test_Proxy_ctor() {
     90  let p = new ProxyConfiguration();
     91  let props = [
     92    "proxyType",
     93    "httpProxy",
     94    "sslProxy",
     95    "socksProxy",
     96    "socksVersion",
     97    "proxyAutoconfigUrl",
     98  ];
     99  for (let prop of props) {
    100    ok(prop in p, `${prop} in ${JSON.stringify(props)}`);
    101    equal(p[prop], null);
    102  }
    103 });
    104 
    105 add_task(function test_Proxy_init() {
    106  let p = new ProxyConfiguration();
    107 
    108  // no changed made, and 5 (system) is default
    109  equal(p.init(), false);
    110  equal(Services.prefs.getIntPref("network.proxy.type"), 5);
    111 
    112  // pac
    113  p.proxyType = "pac";
    114  p.proxyAutoconfigUrl = "http://localhost:1234";
    115  ok(p.init());
    116 
    117  equal(Services.prefs.getIntPref("network.proxy.type"), 2);
    118  equal(
    119    Services.prefs.getStringPref("network.proxy.autoconfig_url"),
    120    "http://localhost:1234"
    121  );
    122 
    123  // direct
    124  p = new ProxyConfiguration();
    125  p.proxyType = "direct";
    126  ok(p.init());
    127  equal(Services.prefs.getIntPref("network.proxy.type"), 0);
    128 
    129  // autodetect
    130  p = new ProxyConfiguration();
    131  p.proxyType = "autodetect";
    132  ok(p.init());
    133  equal(Services.prefs.getIntPref("network.proxy.type"), 4);
    134 
    135  // system
    136  p = new ProxyConfiguration();
    137  p.proxyType = "system";
    138  ok(p.init());
    139  equal(Services.prefs.getIntPref("network.proxy.type"), 5);
    140 
    141  // manual
    142  for (let proxy of ["http", "ssl", "socks"]) {
    143    p = new ProxyConfiguration();
    144    p.proxyType = "manual";
    145    p.noProxy = ["foo", "bar"];
    146    p[`${proxy}Proxy`] = "foo";
    147    p[`${proxy}ProxyPort`] = 42;
    148    if (proxy === "socks") {
    149      p[`${proxy}Version`] = 4;
    150    }
    151 
    152    ok(p.init());
    153    equal(Services.prefs.getIntPref("network.proxy.type"), 1);
    154    equal(
    155      Services.prefs.getStringPref("network.proxy.no_proxies_on"),
    156      "foo, bar"
    157    );
    158    equal(Services.prefs.getStringPref(`network.proxy.${proxy}`), "foo");
    159    equal(Services.prefs.getIntPref(`network.proxy.${proxy}_port`), 42);
    160    if (proxy === "socks") {
    161      equal(Services.prefs.getIntPref(`network.proxy.${proxy}_version`), 4);
    162    }
    163  }
    164 
    165  // empty no proxy should reset default exclustions
    166  p = new ProxyConfiguration();
    167  p.proxyType = "manual";
    168  p.noProxy = [];
    169  ok(p.init());
    170  equal(Services.prefs.getStringPref("network.proxy.no_proxies_on"), "");
    171 });
    172 
    173 add_task(function test_Proxy_toString() {
    174  equal(new ProxyConfiguration().toString(), "[object Proxy]");
    175 });
    176 
    177 add_task(function test_Proxy_toJSON() {
    178  let p = new ProxyConfiguration();
    179  deepEqual(p.toJSON(), {});
    180 
    181  // autoconfig url
    182  p = new ProxyConfiguration();
    183  p.proxyType = "pac";
    184  p.proxyAutoconfigUrl = "foo";
    185  deepEqual(p.toJSON(), { proxyType: "pac", proxyAutoconfigUrl: "foo" });
    186 
    187  // manual proxy
    188  p = new ProxyConfiguration();
    189  p.proxyType = "manual";
    190  deepEqual(p.toJSON(), { proxyType: "manual" });
    191 
    192  for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
    193    let expected = { proxyType: "manual" };
    194 
    195    p = new ProxyConfiguration();
    196    p.proxyType = "manual";
    197 
    198    if (proxy == "socksProxy") {
    199      p.socksVersion = 5;
    200      expected.socksVersion = 5;
    201    }
    202 
    203    // without port
    204    p[proxy] = "foo";
    205    expected[proxy] = "foo";
    206    deepEqual(p.toJSON(), expected);
    207 
    208    // with port
    209    p[proxy] = "foo";
    210    p[`${proxy}Port`] = 0;
    211    expected[proxy] = "foo:0";
    212    deepEqual(p.toJSON(), expected);
    213 
    214    p[`${proxy}Port`] = 42;
    215    expected[proxy] = "foo:42";
    216    deepEqual(p.toJSON(), expected);
    217 
    218    // add brackets for IPv6 address as proxy hostname
    219    p[proxy] = "2001:db8::1";
    220    p[`${proxy}Port`] = 42;
    221    expected[proxy] = "foo:42";
    222    expected[proxy] = "[2001:db8::1]:42";
    223    deepEqual(p.toJSON(), expected);
    224  }
    225 
    226  // noProxy: add brackets for IPv6 address
    227  p = new ProxyConfiguration();
    228  p.proxyType = "manual";
    229  p.noProxy = ["2001:db8::1"];
    230  let expected = { proxyType: "manual", noProxy: "[2001:db8::1]" };
    231  deepEqual(p.toJSON(), expected);
    232 });
    233 
    234 add_task(function test_Proxy_fromJSON() {
    235  let p = new ProxyConfiguration();
    236  deepEqual(p, ProxyConfiguration.fromJSON(undefined));
    237  deepEqual(p, ProxyConfiguration.fromJSON(null));
    238 
    239  for (let typ of [true, 42, "foo", []]) {
    240    Assert.throws(
    241      () => ProxyConfiguration.fromJSON(typ),
    242      /InvalidArgumentError/
    243    );
    244  }
    245 
    246  // must contain a valid proxyType
    247  Assert.throws(() => ProxyConfiguration.fromJSON({}), /InvalidArgumentError/);
    248  Assert.throws(
    249    () => ProxyConfiguration.fromJSON({ proxyType: "foo" }),
    250    /InvalidArgumentError/
    251  );
    252 
    253  // autoconfig url
    254  for (let url of [true, 42, [], {}]) {
    255    Assert.throws(
    256      () =>
    257        ProxyConfiguration.fromJSON({
    258          proxyType: "pac",
    259          proxyAutoconfigUrl: url,
    260        }),
    261      /InvalidArgumentError/
    262    );
    263  }
    264 
    265  p = new ProxyConfiguration();
    266  p.proxyType = "pac";
    267  p.proxyAutoconfigUrl = "foo";
    268  deepEqual(
    269    p,
    270    ProxyConfiguration.fromJSON({ proxyType: "pac", proxyAutoconfigUrl: "foo" })
    271  );
    272 
    273  // manual proxy
    274  p = new ProxyConfiguration();
    275  p.proxyType = "manual";
    276  deepEqual(p, ProxyConfiguration.fromJSON({ proxyType: "manual" }));
    277 
    278  for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
    279    let manual = { proxyType: "manual" };
    280 
    281    // invalid hosts
    282    for (let host of [
    283      true,
    284      42,
    285      [],
    286      {},
    287      null,
    288      "http://foo",
    289      "foo:-1",
    290      "foo:65536",
    291      "foo/test",
    292      "foo#42",
    293      "foo?foo=bar",
    294      "2001:db8::1",
    295    ]) {
    296      manual[proxy] = host;
    297      Assert.throws(
    298        () => ProxyConfiguration.fromJSON(manual),
    299        /InvalidArgumentError/
    300      );
    301    }
    302 
    303    p = new ProxyConfiguration();
    304    p.proxyType = "manual";
    305    if (proxy == "socksProxy") {
    306      manual.socksVersion = 5;
    307      p.socksVersion = 5;
    308    }
    309 
    310    let host_map = {
    311      "foo:1": { hostname: "foo", port: 1 },
    312      "foo:21": { hostname: "foo", port: 21 },
    313      "foo:80": { hostname: "foo", port: 80 },
    314      "foo:443": { hostname: "foo", port: 443 },
    315      "foo:65535": { hostname: "foo", port: 65535 },
    316      "127.0.0.1:42": { hostname: "127.0.0.1", port: 42 },
    317      "[2001:db8::1]:42": { hostname: "2001:db8::1", port: "42" },
    318    };
    319 
    320    // valid proxy hosts with port
    321    for (let host in host_map) {
    322      manual[proxy] = host;
    323 
    324      p[`${proxy}`] = host_map[host].hostname;
    325      p[`${proxy}Port`] = host_map[host].port;
    326 
    327      deepEqual(p, ProxyConfiguration.fromJSON(manual));
    328    }
    329 
    330    // Without a port the default port of the scheme is used
    331    for (let host of ["foo", "foo:"]) {
    332      manual[proxy] = host;
    333 
    334      // For socks no default port is available
    335      p[proxy] = `foo`;
    336      if (proxy === "socksProxy") {
    337        p[`${proxy}Port`] = null;
    338      } else {
    339        let default_ports = { httpProxy: 80, sslProxy: 443 };
    340 
    341        p[`${proxy}Port`] = default_ports[proxy];
    342      }
    343 
    344      deepEqual(p, ProxyConfiguration.fromJSON(manual));
    345    }
    346  }
    347 
    348  // missing required socks version
    349  Assert.throws(
    350    () =>
    351      ProxyConfiguration.fromJSON({
    352        proxyType: "manual",
    353        socksProxy: "foo:1234",
    354      }),
    355    /InvalidArgumentError/
    356  );
    357 
    358  // missing required socks proxy
    359  Assert.throws(
    360    () => ProxyConfiguration.fromJSON({ proxyType: "manual", socksVersion: 4 }),
    361    /InvalidArgumentError/
    362  );
    363 
    364  // noProxy: invalid settings
    365  for (let noProxy of [true, 42, {}, null, "foo", [true], [42], [{}], [null]]) {
    366    Assert.throws(
    367      () => ProxyConfiguration.fromJSON({ proxyType: "manual", noProxy }),
    368      /InvalidArgumentError/
    369    );
    370  }
    371 
    372  // noProxy: valid settings
    373  p = new ProxyConfiguration();
    374  p.proxyType = "manual";
    375  for (let noProxy of [[], ["foo"], ["foo", "bar"], ["127.0.0.1"]]) {
    376    let manual = { proxyType: "manual", noProxy };
    377    p.noProxy = noProxy;
    378    deepEqual(p, ProxyConfiguration.fromJSON(manual));
    379  }
    380 
    381  // noProxy: IPv6 needs brackets removed
    382  p = new ProxyConfiguration();
    383  p.proxyType = "manual";
    384  p.noProxy = ["2001:db8::1"];
    385  let manual = { proxyType: "manual", noProxy: ["[2001:db8::1]"] };
    386  deepEqual(p, ProxyConfiguration.fromJSON(manual));
    387 });
    388 
    389 add_task(function test_Capabilities_ctor_http_default() {
    390  const caps = new Capabilities();
    391 
    392  equal(true, caps.get("moz:webdriverClick"));
    393 });
    394 
    395 add_task(function test_Capabilities_ctor_http() {
    396  const caps = new Capabilities(false);
    397 
    398  ok(caps.has("browserName"));
    399  ok(caps.has("browserVersion"));
    400  ok(caps.has("platformName"));
    401  ok(["linux", "mac", "windows", "android"].includes(caps.get("platformName")));
    402  equal(PageLoadStrategy.Normal, caps.get("pageLoadStrategy"));
    403  equal(false, caps.get("acceptInsecureCerts"));
    404  ok(caps.get("timeouts") instanceof Timeouts);
    405  ok(caps.get("proxy") instanceof ProxyConfiguration);
    406  equal(caps.get("setWindowRect"), !AppInfo.isAndroid);
    407  equal(caps.get("strictFileInteractability"), false);
    408  equal(caps.get("webSocketUrl"), null);
    409 
    410  equal(false, caps.get("moz:accessibilityChecks"));
    411  ok(caps.has("moz:buildID"));
    412  ok(caps.has("moz:platformVersion"));
    413  ok(caps.has("moz:processID"));
    414  ok(caps.has("moz:profile"));
    415  equal(true, caps.get("moz:webdriverClick"));
    416 });
    417 
    418 add_task(function test_Capabilities_ctor_bidi() {
    419  const caps = new Capabilities(true);
    420 
    421  ok(caps.has("browserName"));
    422  ok(caps.has("browserVersion"));
    423  ok(caps.has("platformName"));
    424  ok(["linux", "mac", "windows", "android"].includes(caps.get("platformName")));
    425  equal(undefined, caps.get("pageLoadStrategy"));
    426  equal(false, caps.get("acceptInsecureCerts"));
    427  ok(!caps.has("timeouts"));
    428  ok(caps.get("proxy") instanceof ProxyConfiguration);
    429  ok(caps.has("setWindowRect"));
    430  ok(!caps.has("strictFileInteractability"));
    431  ok(!caps.has("webSocketUrl"));
    432 
    433  ok(!caps.has("moz:accessibilityChecks"));
    434  ok(caps.has("moz:buildID"));
    435  ok(caps.has("moz:platformVersion"));
    436  ok(caps.has("moz:processID"));
    437  ok(caps.has("moz:profile"));
    438  ok(!caps.has("moz:webdriverClick"));
    439 });
    440 
    441 add_task(function test_Capabilities_toString() {
    442  equal("[object Capabilities]", new Capabilities().toString());
    443 });
    444 
    445 add_task(function test_Capabilities_toJSON() {
    446  let caps = new Capabilities();
    447  let json = caps.toJSON();
    448 
    449  equal(caps.get("browserName"), json.browserName);
    450  equal(caps.get("browserVersion"), json.browserVersion);
    451  equal(caps.get("platformName"), json.platformName);
    452  equal(caps.get("pageLoadStrategy"), json.pageLoadStrategy);
    453  equal(caps.get("acceptInsecureCerts"), json.acceptInsecureCerts);
    454  deepEqual(caps.get("proxy").toJSON(), json.proxy);
    455  deepEqual(caps.get("timeouts").toJSON(), json.timeouts);
    456  equal(caps.get("setWindowRect"), json.setWindowRect);
    457  equal(caps.get("strictFileInteractability"), json.strictFileInteractability);
    458  equal(caps.get("webSocketUrl"), json.webSocketUrl);
    459 
    460  equal(caps.get("moz:accessibilityChecks"), json["moz:accessibilityChecks"]);
    461  equal(caps.get("moz:buildID"), json["moz:buildID"]);
    462  equal(caps.get("moz:platformVersion"), json["moz:platformVersion"]);
    463  equal(caps.get("moz:processID"), json["moz:processID"]);
    464  equal(caps.get("moz:profile"), json["moz:profile"]);
    465  equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
    466 });
    467 
    468 add_task(function test_Capabilities_fromJSON_http() {
    469  const { fromJSON } = Capabilities;
    470 
    471  // plain
    472  for (const type of [{}, null, undefined]) {
    473    ok(fromJSON(type, false).has("browserName"));
    474  }
    475 
    476  let caps;
    477 
    478  // Capabilities supported by HTTP and BiDi
    479  caps = fromJSON({ acceptInsecureCerts: true }, false);
    480  equal(true, caps.get("acceptInsecureCerts"));
    481 
    482  let proxyConfig = { proxyType: "manual" };
    483  caps = fromJSON({ proxy: proxyConfig }, false);
    484  equal("manual", caps.get("proxy").proxyType);
    485 
    486  // WebDriver HTTP-only capabilities
    487  for (let strategy of Object.values(PageLoadStrategy)) {
    488    caps = fromJSON({ pageLoadStrategy: strategy }, false);
    489    equal(strategy, caps.get("pageLoadStrategy"));
    490  }
    491 
    492  let timeoutsConfig = { implicit: 123 };
    493  caps = fromJSON({ timeouts: timeoutsConfig }, false);
    494  equal(123, caps.get("timeouts").implicit);
    495 
    496  caps = fromJSON({ strictFileInteractability: true }, false);
    497  equal(true, caps.get("strictFileInteractability"));
    498 
    499  caps = fromJSON({ webSocketUrl: true }, false);
    500  equal(true, caps.get("webSocketUrl"));
    501 
    502  // Mozilla specific capabilities
    503  caps = fromJSON({ "moz:accessibilityChecks": true }, false);
    504  equal(true, caps.get("moz:accessibilityChecks"));
    505 
    506  caps = fromJSON({ "moz:webdriverClick": true }, false);
    507  equal(true, caps.get("moz:webdriverClick"));
    508 
    509  // Extension capabilities
    510  caps = fromJSON({ "webauthn:virtualAuthenticators": true }, false);
    511  equal(true, caps.get("webauthn:virtualAuthenticators"));
    512  Assert.throws(
    513    () => fromJSON({ "webauthn:virtualAuthenticators": "foo" }, false),
    514    /InvalidArgumentError/
    515  );
    516 
    517  caps = fromJSON({ "webauthn:extension:uvm": true }, false);
    518  equal(true, caps.get("webauthn:extension:uvm"));
    519  Assert.throws(
    520    () => fromJSON({ "webauthn:extension:uvm": "foo" }, false),
    521    /InvalidArgumentError/
    522  );
    523 
    524  caps = fromJSON({ "webauthn:extension:prf": true }, false);
    525  equal(true, caps.get("webauthn:extension:prf"));
    526  Assert.throws(
    527    () => fromJSON({ "webauthn:extension:prf": "foo" }, false),
    528    /InvalidArgumentError/
    529  );
    530 
    531  caps = fromJSON({ "webauthn:extension:largeBlob": true }, false);
    532  equal(true, caps.get("webauthn:extension:largeBlob"));
    533  Assert.throws(
    534    () => fromJSON({ "webauthn:extension:largeBlob": "foo" }, false),
    535    /InvalidArgumentError/
    536  );
    537 
    538  caps = fromJSON({ "webauthn:extension:credBlob": true }, false);
    539  equal(true, caps.get("webauthn:extension:credBlob"));
    540  Assert.throws(
    541    () => fromJSON({ "webauthn:extension:credBlob": "foo" }, false),
    542    /InvalidArgumentError/
    543  );
    544 });
    545 
    546 add_task(function test_Capabilities_fromJSON_bidi() {
    547  const { fromJSON } = Capabilities;
    548 
    549  // plain
    550  for (const type of [{}, null, undefined]) {
    551    ok(fromJSON(type, true).has("browserName"));
    552  }
    553 
    554  let caps;
    555 
    556  // Capabilities supported by HTTP and BiDi
    557  caps = fromJSON({ acceptInsecureCerts: true }, true);
    558  equal(true, caps.get("acceptInsecureCerts"));
    559 
    560  let proxyConfig = { proxyType: "manual" };
    561  caps = fromJSON({ proxy: proxyConfig }, true);
    562  equal("manual", caps.get("proxy").proxyType);
    563 
    564  // HTTP capabilities are ignored for BiDi-only sessions
    565  for (let strategy of Object.values(PageLoadStrategy)) {
    566    caps = fromJSON({ pageLoadStrategy: strategy }, true);
    567    ok(!caps.has("pageLoadStrategy"));
    568  }
    569 
    570  let timeoutsConfig = { implicit: 123 };
    571  caps = fromJSON({ timeouts: timeoutsConfig }, true);
    572  ok(!caps.has("timeouts"));
    573 
    574  caps = fromJSON({ strictFileInteractability: true }, true);
    575  ok(!caps.has("strictFileInteractability"));
    576 
    577  caps = fromJSON({ webSocketUrl: true }, true);
    578  ok(!caps.has("webSocketUrl"));
    579 
    580  // Mozilla specific capabilities
    581  caps = fromJSON({ "moz:accessibilityChecks": true }, true);
    582  ok(!caps.has("moz:accessibilityChecks"));
    583 
    584  caps = fromJSON({ "moz:webdriverClick": true }, true);
    585  equal(undefined, caps.get("moz:webdriverClick"));
    586 
    587  // Extension capabilities
    588  caps = fromJSON({ "webauthn:virtualAuthenticators": true }, true);
    589  ok(!caps.has("webauthn:virtualAuthenticators"));
    590 
    591  caps = fromJSON({ "webauthn:extension:uvm": true }, true);
    592  ok(!caps.has("webauthn:extension:uvm"));
    593 
    594  caps = fromJSON({ "webauthn:extension:prf": true }, true);
    595  ok(!caps.has("webauthn:extension:prf"));
    596 
    597  caps = fromJSON({ "webauthn:extension:largeBlob": true }, true);
    598  ok(!caps.has("webauthn:extension:largeBlob"));
    599 
    600  caps = fromJSON({ "webauthn:extension:credBlob": true }, true);
    601  ok(!caps.has("webauthn:extension:credBlob"));
    602 });
    603 
    604 add_task(function test_mergeCapabilities() {
    605  // Shadowed values.
    606  Assert.throws(
    607    () =>
    608      mergeCapabilities(
    609        { acceptInsecureCerts: true },
    610        { acceptInsecureCerts: false }
    611      ),
    612    /InvalidArgumentError/
    613  );
    614 
    615  deepEqual(
    616    { acceptInsecureCerts: true },
    617    mergeCapabilities({ acceptInsecureCerts: true }, undefined)
    618  );
    619  deepEqual(
    620    { acceptInsecureCerts: true, browserName: "Firefox" },
    621    mergeCapabilities({ acceptInsecureCerts: true }, { browserName: "Firefox" })
    622  );
    623 });
    624 
    625 add_task(function test_validateCapabilities_invalid() {
    626  const invalidCapabilities = [
    627    true,
    628    42,
    629    "foo",
    630    [],
    631    { acceptInsecureCerts: "foo" },
    632    { browserName: true },
    633    { browserVersion: true },
    634    { platformName: true },
    635    { pageLoadStrategy: "foo" },
    636    { proxy: false },
    637    { strictFileInteractability: "foo" },
    638    { timeouts: false },
    639    { unhandledPromptBehavior: false },
    640    { webSocketUrl: false },
    641    { webSocketUrl: "foo" },
    642    { "moz:firefoxOptions": "foo" },
    643    { "moz:accessibilityChecks": "foo" },
    644    { "moz:webdriverClick": "foo" },
    645    { "moz:webdriverClick": 1 },
    646    { "moz:someRandomString": {} },
    647  ];
    648  for (const capabilities of invalidCapabilities) {
    649    Assert.throws(
    650      () => validateCapabilities(capabilities),
    651      /InvalidArgumentError/
    652    );
    653  }
    654 });
    655 
    656 add_task(function test_validateCapabilities_valid() {
    657  // Ignore null value.
    658  deepEqual({}, validateCapabilities({ test: null }));
    659 
    660  const validCapabilities = [
    661    { acceptInsecureCerts: true },
    662    { browserName: "firefox" },
    663    { browserVersion: "12" },
    664    { platformName: "linux" },
    665    { pageLoadStrategy: "eager" },
    666    { proxy: { proxyType: "manual", httpProxy: "test.com" } },
    667    { strictFileInteractability: true },
    668    { timeouts: { pageLoad: 500 } },
    669    { unhandledPromptBehavior: "accept" },
    670    { webSocketUrl: true },
    671    { "moz:firefoxOptions": {} },
    672    { "moz:accessibilityChecks": true },
    673    { "moz:webdriverClick": true },
    674    { "test:extension": "foo" },
    675  ];
    676  for (const validCapability of validCapabilities) {
    677    deepEqual(validCapability, validateCapabilities(validCapability));
    678  }
    679 });
    680 
    681 add_task(function test_processCapabilities() {
    682  for (const invalidValue of [
    683    { capabilities: null },
    684    { capabilities: undefined },
    685    { capabilities: "foo" },
    686    { capabilities: true },
    687    { capabilities: [] },
    688    { capabilities: { alwaysMatch: null } },
    689    { capabilities: { alwaysMatch: "foo" } },
    690    { capabilities: { alwaysMatch: true } },
    691    { capabilities: { alwaysMatch: [] } },
    692    { capabilities: { firstMatch: null } },
    693    { capabilities: { firstMatch: "foo" } },
    694    { capabilities: { firstMatch: true } },
    695    { capabilities: { firstMatch: {} } },
    696    { capabilities: { firstMatch: [] } },
    697  ]) {
    698    Assert.throws(
    699      () => processCapabilities(invalidValue),
    700      /InvalidArgumentError/
    701    );
    702  }
    703 
    704  deepEqual(
    705    { acceptInsecureCerts: true },
    706    processCapabilities({
    707      capabilities: { alwaysMatch: { acceptInsecureCerts: true } },
    708    })
    709  );
    710  deepEqual(
    711    { browserName: "Firefox" },
    712    processCapabilities({
    713      capabilities: { firstMatch: [{ browserName: "Firefox" }] },
    714    })
    715  );
    716  deepEqual(
    717    { acceptInsecureCerts: true, browserName: "Firefox" },
    718    processCapabilities({
    719      capabilities: {
    720        alwaysMatch: { acceptInsecureCerts: true },
    721        firstMatch: [{ browserName: "Firefox" }],
    722      },
    723    })
    724  );
    725 });
    726 
    727 // use Proxy.toJSON to test marshal
    728 add_task(function test_marshal() {
    729  let proxy = new ProxyConfiguration();
    730 
    731  // drop empty fields
    732  deepEqual({}, proxy.toJSON());
    733  proxy.proxyType = "manual";
    734  deepEqual({ proxyType: "manual" }, proxy.toJSON());
    735  proxy.proxyType = null;
    736  deepEqual({}, proxy.toJSON());
    737  proxy.proxyType = undefined;
    738  deepEqual({}, proxy.toJSON());
    739 
    740  // iterate over object literals
    741  proxy.proxyType = { foo: "bar" };
    742  deepEqual({ proxyType: { foo: "bar" } }, proxy.toJSON());
    743 
    744  // iterate over complex object that implement toJSON
    745  proxy.proxyType = new ProxyConfiguration();
    746  deepEqual({}, proxy.toJSON());
    747  proxy.proxyType.proxyType = "manual";
    748  deepEqual({ proxyType: { proxyType: "manual" } }, proxy.toJSON());
    749 
    750  // drop objects with no entries
    751  proxy.proxyType = { foo: {} };
    752  deepEqual({}, proxy.toJSON());
    753  proxy.proxyType = { foo: new ProxyConfiguration() };
    754  deepEqual({}, proxy.toJSON());
    755 });