Bug 1553849 - [remote] Add offline mode support to Network.emulateNetworkConditions r=whimboo,remote-protocol-reviewers,maja_zf
authorEtienne Bruines <e.bruines@q-mex.net>
Tue, 12 May 2020 11:01:10 +0000
changeset 529324 b1c859fe04b1fd7854f61f7cbe87422cbde064d5
parent 529323 7467535cbe2a16f929da85e6c2f76e44cc3b8aa5
child 529325 8e18c9fe5be50e0c7d0fb615b38ef291db498510
push id37409
push userapavel@mozilla.com
push dateWed, 13 May 2020 03:44:05 +0000
treeherdermozilla-central@59d4ec4ce296 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswhimboo, remote-protocol-reviewers, maja_zf
bugs1553849
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1553849 - [remote] Add offline mode support to Network.emulateNetworkConditions r=whimboo,remote-protocol-reviewers,maja_zf Implements the Chrome Devtools Protocol (CDP) command Network.emulateNetworkConditions partially. At the moment, only "offline" is emulated, all other arguments are ignored. Differential Revision: https://phabricator.services.mozilla.com/D73211
remote/domains/parent/Network.jsm
remote/test/browser/network/browser.ini
remote/test/browser/network/browser_emulateNetworkConditions.js
--- a/remote/domains/parent/Network.jsm
+++ b/remote/domains/parent/Network.jsm
@@ -132,16 +132,33 @@ class Network extends Domain {
           cookie.path,
           cookie.originAttributes
         );
       }
     }
   }
 
   /**
+   * Activates emulation of network conditions.
+   *
+   * @param {Object} options
+   * @param {boolean} offline
+   *     True to emulate internet disconnection.
+   */
+  emulateNetworkConditions(options = {}) {
+    const { offline } = options;
+
+    if (typeof offline != "boolean") {
+      throw new TypeError("offline: boolean value expected");
+    }
+
+    Services.io.offline = offline;
+  }
+
+  /**
    * Returns all browser cookies for the current URL.
    *
    * @param {Object} options
    * @param {Array<string>=} urls
    *     The list of URLs for which applicable cookies will be fetched.
    *     Defaults to the currently open URL.
    *
    * @return {Array<Cookie>}
--- a/remote/test/browser/network/browser.ini
+++ b/remote/test/browser/network/browser.ini
@@ -7,16 +7,17 @@ support-files =
   !/remote/test/browser/head.js
   head.js
   doc_empty.html
   doc_requestWillBeSent.html
   file_requestWillBeSent.js
   sjs-cookies.sjs
 
 [browser_deleteCookies.js]
+[browser_emulateNetworkConditions.js]
 [browser_getCookies.js]
 skip-if = (os == 'win' && os_version == '10.0' && ccov) # Bug 1605650
 [browser_requestWillBeSent.js]
 [browser_setCookie.js]
 [browser_setCookies.js]
 [browser_setCacheDisabled.js]
 skip-if = true # Bug 1610382
 [browser_setUserAgentOverride.js]
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/network/browser_emulateNetworkConditions.js
@@ -0,0 +1,248 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const pageEmptyURL =
+  "http://example.com/browser/remote/test/browser/page/doc_empty.html";
+
+/**
+ * Acts just as `add_task`, but does cleanup afterwards
+ * @param taskFn
+ */
+function add_networking_task(taskFn) {
+  add_task(async client => {
+    try {
+      await taskFn(client);
+    } finally {
+      Services.io.offline = false;
+    }
+  });
+}
+
+add_networking_task(async function offlineWithoutArguments({ client }) {
+  const { Network } = client;
+
+  let errorThrown = "";
+  try {
+    await Network.emulateNetworkConditions();
+  } catch (e) {
+    errorThrown = e.message;
+  }
+
+  ok(
+    errorThrown.match(/offline: boolean value expected/),
+    "Fails without any arguments"
+  );
+});
+
+add_networking_task(async function offlineWithEmptyArguments({ client }) {
+  const { Network } = client;
+
+  let errorThrown = "";
+  try {
+    await Network.emulateNetworkConditions({});
+  } catch (e) {
+    errorThrown = e.message;
+  }
+
+  ok(
+    errorThrown.match(/offline: boolean value expected/),
+    "Fails with only empty arguments"
+  );
+});
+
+add_networking_task(async function offlineWithInvalidArguments({ client }) {
+  const { Network } = client;
+  const testTable = [null, undefined, 1, "foo", [], {}];
+
+  for (const testCase of testTable) {
+    let errorThrown = "";
+    try {
+      await Network.emulateNetworkConditions({ offline: testCase });
+    } catch (e) {
+      errorThrown = e.message;
+    }
+
+    const testType = typeof testCase;
+
+    ok(
+      errorThrown.match(/offline: boolean value expected/),
+      `Fails with ${testType}-type argument for offline`
+    );
+  }
+});
+
+add_networking_task(async function offlineWithUnsupportedArguments({ client }) {
+  const { Network } = client;
+
+  // Random valid values for the Network.emulateNetworkConditions command, even though we don't support them yet
+  const args = {
+    offline: true,
+    latency: 500,
+    downloadThroughput: 500,
+    uploadThroughput: 500,
+    connectionType: "cellular2g",
+    someFutureArg: false,
+  };
+
+  await Network.emulateNetworkConditions(args);
+
+  ok(true, "No errors should be thrown due to non-implemented arguments");
+});
+
+add_networking_task(async function emulateOfflineWhileOnline({ client }) {
+  const { Network } = client;
+
+  // Assert we're online to begin with
+  await assertOfflineStatus(false);
+
+  // Assert for offline
+  await Network.emulateNetworkConditions({ offline: true });
+  await assertOfflineStatus(true);
+
+  // Assert we really can't navigate after setting offline
+  await assertOfflineNavigationFails();
+});
+
+add_networking_task(async function emulateOfflineWhileOffline({ client }) {
+  const { Network } = client;
+
+  // Assert we're online to begin with
+  await assertOfflineStatus(false);
+
+  // Assert for offline
+  await Network.emulateNetworkConditions({ offline: true });
+  await assertOfflineStatus(true);
+
+  // Assert for no-offline event, because we're offline - and changing to offline - so nothing changes
+  await Network.emulateNetworkConditions({ offline: true });
+  await assertOfflineStatus(true);
+
+  // Assert we still can't navigate after setting offline twice
+  await assertOfflineNavigationFails();
+});
+
+add_networking_task(async function emulateOnlineWhileOnline({ client }) {
+  const { Network } = client;
+
+  // Assert we're online to begin with
+  await assertOfflineStatus(false);
+
+  // Assert for no-offline event, because we're online - and changing to online - so nothing changes
+  await Network.emulateNetworkConditions({ offline: false });
+  await assertOfflineStatus(false);
+});
+
+add_networking_task(async function emulateOnlineWhileOffline({ client }) {
+  const { Network } = client;
+
+  // Assert we're online to begin with
+  await assertOfflineStatus(false);
+
+  // Assert for offline event, because we're online - and changing to offline
+  const offlineChanged = Promise.race([
+    BrowserTestUtils.waitForContentEvent(
+      gBrowser.selectedBrowser,
+      "online",
+      true
+    ),
+    BrowserTestUtils.waitForContentEvent(
+      gBrowser.selectedBrowser,
+      "offline",
+      true
+    ),
+  ]);
+
+  await Network.emulateNetworkConditions({ offline: true });
+
+  info("Waiting for offline event on window");
+  is(await offlineChanged, "offline", "Only the offline-event should fire");
+  await assertOfflineStatus(true);
+
+  // Assert for online event, because we're offline - and changing to online
+  const offlineChangedBack = Promise.race([
+    BrowserTestUtils.waitForContentEvent(
+      gBrowser.selectedBrowser,
+      "online",
+      true
+    ),
+    BrowserTestUtils.waitForContentEvent(
+      gBrowser.selectedBrowser,
+      "offline",
+      true
+    ),
+  ]);
+  await Network.emulateNetworkConditions({ offline: false });
+
+  info("Waiting for online event on window");
+  is(await offlineChangedBack, "online", "Only the online-event should fire");
+  await assertOfflineStatus(false);
+});
+
+/**
+ * Navigates to a page, and asserting any status code to appear
+ */
+async function assertOfflineNavigationFails() {
+  // Tests always connect to localhost, and per bug 87717, localhost is now
+  // reachable in offline mode.  To avoid this, disable any proxy.
+  const proxyPrefValue = SpecialPowers.getIntPref("network.proxy.type");
+  const diskPrefValue = SpecialPowers.getBoolPref("browser.cache.disk.enable");
+  const memoryPrefValue = SpecialPowers.getBoolPref(
+    "browser.cache.memory.enable"
+  );
+  SpecialPowers.pushPrefEnv({
+    set: [
+      ["network.proxy.type", 0],
+      ["browser.cache.disk.enable", false],
+      ["browser.cache.memory.enable", false],
+    ],
+  });
+
+  try {
+    const browser = gBrowser.selectedTab.linkedBrowser;
+    let netErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+
+    await BrowserTestUtils.loadURI(browser, pageEmptyURL);
+    await netErrorLoaded;
+
+    await SpecialPowers.spawn(browser, [], () => {
+      ok(
+        content.document.documentURI.startsWith("about:neterror?e=netOffline"),
+        "Should be showing error page"
+      );
+    });
+  } finally {
+    // Re-enable the proxy so example.com is resolved to localhost, rather than
+    // the actual example.com.
+    SpecialPowers.pushPrefEnv({
+      set: [
+        ["network.proxy.type", proxyPrefValue],
+        ["browser.cache.disk.enable", diskPrefValue],
+        ["browser.cache.memory.enable", memoryPrefValue],
+      ],
+    });
+  }
+}
+
+/**
+ * Checks on the page what the value of window.navigator.onLine is on the currently navigated page
+ *
+ * @param {boolean} offline
+ *    True if offline is expected
+ */
+function assertOfflineStatus(offline) {
+  is(
+    Services.io.offline,
+    offline,
+    "Services.io.offline should be " + (offline ? "true" : "false")
+  );
+
+  return SpecialPowers.spawn(gBrowser.selectedBrowser, [offline], offline => {
+    is(
+      content.navigator.onLine,
+      !offline,
+      "Page should be " + (offline ? "offline" : "online")
+    );
+  });
+}