Bug 1446517 - move some webRequest auth tests to xpcshell, r?kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Tue, 20 Mar 2018 14:21:51 -0500
changeset 770111 beed9fdf2b86268c7499fc08af300b7333af5fa2
parent 770080 3d4f4a6bb6ba36a649f7f9b856b7b42c2127e133
push id103327
push usermixedpuppy@gmail.com
push dateTue, 20 Mar 2018 19:22:16 +0000
reviewerskmag
bugs1446517
milestone61.0a1
Bug 1446517 - move some webRequest auth tests to xpcshell, r?kmag MozReview-Commit-ID: 5A4oDnRh2lS
toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
@@ -54,188 +54,16 @@ function getAuthHandler(result, blocking
   return ExtensionTestUtils.loadExtension({
     manifest: {
       permissions,
     },
     background: `(${background})(${JSON.stringify(result)})`,
   });
 }
 
-add_task(async function test_webRequest_auth() {
-  // Make use of head_webrequest to ensure event sequence.
-  let events = {
-    "onBeforeRequest":     [{urls: ["*://mochi.test/*"]}, ["blocking"]],
-    "onBeforeSendHeaders": [{urls: ["*://mochi.test/*"]}, ["blocking", "requestHeaders"]],
-    "onSendHeaders":       [{urls: ["*://mochi.test/*"]}, ["requestHeaders"]],
-    "onBeforeRedirect":    [{urls: ["*://mochi.test/*"]}],
-    "onHeadersReceived":   [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onAuthRequired":      [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onResponseStarted":   [{urls: ["*://mochi.test/*"]}],
-    "onCompleted":         [{urls: ["*://mochi.test/*"]}, ["responseHeaders"]],
-    "onErrorOccurred":     [{urls: ["*://mochi.test/*"]}],
-  };
-
-  let extension = makeExtension(events);
-  await extension.startup();
-  let authInfo = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let expect = {
-    "authenticate.sjs": {
-      type: "xmlhttprequest",
-      // we expect these additional events after onAuthRequired
-      optional_events: ["onBeforeRequest", "onHeadersReceived"],
-      authInfo,
-    },
-  };
-  // expecting origin == undefined
-  extension.sendMessage("set-expected", {expect, origin: location.href});
-  await extension.awaitMessage("continue");
-
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authInfo.username}&pass=${authInfo.password}`);
-
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
-
-// This test is the same as above, however we shouldn't receive onAuthRequired
-// since those credentials are now cached (thus optional_events is not set).
-add_task(async function test_webRequest_cached_credentials() {
-  // Make use of head_webrequest to ensure event sequence.
-  let events = {
-    "onBeforeRequest":     [{urls: ["*://mochi.test/*"]}, ["blocking"]],
-    "onBeforeSendHeaders": [{urls: ["*://mochi.test/*"]}, ["blocking", "requestHeaders"]],
-    "onSendHeaders":       [{urls: ["*://mochi.test/*"]}, ["requestHeaders"]],
-    "onBeforeRedirect":    [{urls: ["*://mochi.test/*"]}],
-    "onHeadersReceived":   [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onAuthRequired":      [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onResponseStarted":   [{urls: ["*://mochi.test/*"]}],
-    "onCompleted":         [{urls: ["*://mochi.test/*"]}, ["responseHeaders"]],
-    "onErrorOccurred":     [{urls: ["*://mochi.test/*"]}],
-  };
-
-  let extension = makeExtension(events);
-  await extension.startup();
-  let authInfo = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let expect = {
-    "authenticate.sjs": {
-      type: "xmlhttprequest",
-      events: ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders", "onHeadersReceived", "onResponseStarted", "onCompleted"],
-    },
-  };
-  // expecting origin == undefined
-  extension.sendMessage("set-expected", {expect, origin: location.href});
-  await extension.awaitMessage("continue");
-
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authInfo.username}&pass=${authInfo.password}`);
-
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
-
-add_task(async function test_webRequest_cached_credentials2() {
-  let authCredentials = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let ex1 = getAuthHandler();
-  await ex1.startup();
-
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await ex1.awaitMessage("onCompleted");
-  await ex1.unload();
-});
-
-add_task(async function test_webRequest_window() {
-  let authCredentials = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let ex1 = getAuthHandler();
-  await ex1.startup();
-
-  let win = window.open(`${baseUrl}?realm=test_webRequest_window&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await ex1.awaitMessage("onCompleted");
-  await ex1.unload();
-  win.close();
-});
-
-add_task(async function test_webRequest_auth_cancelled() {
-  let authCredentials = {
-    username: "testuser_canceled",
-    password: "testpass_canceled",
-  };
-  let ex1 = getAuthHandler({authCredentials});
-  await ex1.startup();
-  let ex2 = getAuthHandler({cancel: true});
-  await ex2.startup();
-
-  await Assert.rejects(testXHR(`${baseUrl}?realm=test_webRequest_auth_cancelled&user=${authCredentials.username}&pass=${authCredentials.password}`), "caught rejected xhr");
-
-  await Promise.all([
-    ex1.awaitMessage("onAuthRequired"),
-    ex2.awaitMessage("onAuthRequired"),
-    ex1.awaitMessage("onErrorOccurred"),
-    ex2.awaitMessage("onErrorOccurred"),
-  ]);
-  await ex1.unload();
-  await ex2.unload();
-});
-
-add_task(async function test_webRequest_auth_nonblocking() {
-  // The first listener handles the auth request, the second listener
-  // is a non-blocking listener and cannot respond but will get the call.
-  let authCredentials = {
-    username: "foobar",
-    password: "testpass",
-  };
-  let handlingExt = getAuthHandler({authCredentials});
-  await handlingExt.startup();
-  let extension = getAuthHandler({}, false);
-  await extension.startup();
-
-  await testXHR(`${baseUrl}?realm=webRequest_auth_nonblocking&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await Promise.all([
-    extension.awaitMessage("onAuthRequired"),
-    extension.awaitMessage("onCompleted"),
-    handlingExt.awaitMessage("onAuthRequired"),
-    handlingExt.awaitMessage("onCompleted"),
-  ]);
-  await extension.unload();
-  await handlingExt.unload();
-});
-
-
-add_task(async function test_webRequest_auth_blocking_noreturn() {
-  // The first listener is blocking but doesn't return anything.  The second
-  // listener cancels the request.
-  let ext = getAuthHandler();
-  await ext.startup();
-  let canceler = getAuthHandler({cancel: true});
-  await canceler.startup();
-
-  await Assert.rejects(testXHR(`${baseUrl}?realm=auth_blocking_noreturn&user=auth_blocking_noreturn&pass=auth_blocking_noreturn`), "caught rejected xhr");
-
-  await Promise.all([
-    ext.awaitMessage("onAuthRequired"),
-    ext.awaitMessage("onErrorOccurred"),
-    canceler.awaitMessage("onAuthRequired"),
-    canceler.awaitMessage("onErrorOccurred"),
-  ]);
-  await ext.unload();
-  await canceler.unload();
-});
-
 add_task(async function test_webRequest_auth_nonblocking_forwardAuthProvider() {
   // The chrome script sets up a default auth handler on the channel, the
   // extension does not return anything in the authRequred call.  We should
   // get the call in the extension first, then in the chrome code where we
   // cancel the request to avoid dealing with the prompt dialog here.  The test
   // is to ensure that WebRequest calls the previous notificationCallbacks
   // if the authorization is not handled by the onAuthRequired handler.
 
@@ -307,24 +135,24 @@ add_task(async function test_webRequest_
       if (!(channel instanceof Ci.nsIHttpChannel && channel.URI.host === "mochi.test")) {
         return;
       }
       Services.obs.removeObserver(observer, "http-on-modify-request");
       channel.notificationCallbacks = {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor,
                                                Ci.nsIAuthPrompt2]),
         getInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
-        promptAuth(channel, level, authInfo) {
+        promptAuth(request, level, authInfo) {
           throw Cr.NS_ERROR_NO_INTERFACE;
         },
-        asyncPromptAuth(channel, callback, context, level, authInfo) {
+        asyncPromptAuth(request, callback, context, level, authInfo) {
           // We just cancel here, we're only ensuring that non-webrequest
           // notificationcallbacks get called if webrequest doesn't handle it.
           Promise.resolve().then(() => {
-            channel.cancel(Cr.NS_BINDING_ABORTED);
+            request.cancel(Cr.NS_BINDING_ABORTED);
             sendAsyncMessage("callback-complete");
           });
         },
       };
     };
     Services.obs.addObserver(observer, "http-on-modify-request");
     sendAsyncMessage("chrome-ready");
   });
@@ -339,88 +167,15 @@ add_task(async function test_webRequest_
   await callbackComplete;
   await handlingExt.awaitMessage("onAuthRequired");
   // We expect onErrorOccurred because the "default" authprompt above cancelled
   // the auth request to avoid a dialog.
   await handlingExt.awaitMessage("onErrorOccurred");
   await handlingExt.unload();
   chromeScript.destroy();
 });
-
-add_task(async function test_webRequest_duelingAuth() {
-  let exNone = getAuthHandler();
-  await exNone.startup();
-  let authCredentials = {
-    username: "testuser_da1",
-    password: "testpass_da1",
-  };
-  let ex1 = getAuthHandler({authCredentials});
-  await ex1.startup();
-  let exEmpty = getAuthHandler({});
-  await exEmpty.startup();
-  let ex2 = getAuthHandler({authCredentials: {
-    username: "testuser_da2",
-    password: "testpass_da2",
-  }});
-  await ex2.startup();
-
-  // XHR should succeed since the first credentials win, and they are correct.
-  await testXHR(`${baseUrl}?realm=test_webRequest_duelingAuth&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await Promise.all([
-    exNone.awaitMessage("onAuthRequired"),
-    exNone.awaitMessage("onCompleted"),
-    exEmpty.awaitMessage("onAuthRequired"),
-    exEmpty.awaitMessage("onCompleted"),
-    ex1.awaitMessage("onAuthRequired"),
-    ex1.awaitMessage("onCompleted"),
-    ex2.awaitMessage("onAuthRequired"),
-    ex2.awaitMessage("onCompleted"),
-  ]);
-  await Promise.all([
-    exNone.unload(),
-    exEmpty.unload(),
-    ex1.unload(),
-    ex2.unload(),
-  ]);
-});
-
-add_task(async function test_webRequest_auth_proxy() {
-  function background() {
-    let proxyOk = false;
-    browser.webRequest.onAuthRequired.addListener((details) => {
-      browser.test.succeed(`handlingExt onAuthRequired called with ${details.requestId} ${details.url}`);
-      if (details.isProxy) {
-        browser.test.succeed("providing proxy authorization");
-        proxyOk = true;
-        return {authCredentials: {username: "puser", password: "ppass"}};
-      }
-      browser.test.assertTrue(proxyOk, "providing www authorization after proxy auth");
-      browser.test.sendMessage("done");
-      return {authCredentials: {username: "auser", password: "apass"}};
-    }, {urls: ["*://mochi.test/*"]}, ["blocking"]);
-  }
-
-  let handlingExt = ExtensionTestUtils.loadExtension({
-    manifest: {
-      permissions: [
-        "webRequest",
-        "webRequestBlocking",
-        "*://mochi.test/*",
-      ],
-    },
-    background,
-  });
-
-  await handlingExt.startup();
-
-  await testXHR(`${baseUrl}?realm=auth_proxy&user=auser&pass=apass&proxy_user=puser&proxy_pass=ppass`);
-
-  await handlingExt.awaitMessage("done");
-  await handlingExt.unload();
-});
 </script>
 </head>
 <body>
 <div id="test">Authorization Test</div>
 
 </body>
 </html>
copy from toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
copy to toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
@@ -1,426 +1,384 @@
-<!DOCTYPE HTML>
-
-<html>
-<head>
-<meta charset="utf-8">
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head_webrequest.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-<script>
 "use strict";
 
-// This file defines content scripts.
-/* eslint-env mozilla/frame-script */
+Cu.importGlobalProperties(["URL"]);
+
+const HOSTS = new Set([
+  "example.com",
+]);
+
+const server = createHttpServer({hosts: HOSTS});
 
-let baseUrl = "http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/authenticate.sjs";
-function testXHR(url) {
-  return new Promise((resolve, reject) => {
-    let xhr = new XMLHttpRequest();
-    xhr.open("GET", url);
-    xhr.onload = resolve;
-    xhr.onabort = reject;
-    xhr.onerror = reject;
-    xhr.send();
-  });
-}
+const BASE_URL = "http://example.com";
+
+// Save seen realms for cache checking.
+let realms = new Set([]);
+
+server.registerPathHandler("/authenticate.sjs", (request, response) => {
+  let url = new URL(`${BASE_URL}${request.path}?${request.queryString}`);
+  let realm = url.searchParams.get("realm") || "mochitest";
+  let proxy_realm = url.searchParams.get("proxy_realm");
 
-function getAuthHandler(result, blocking = true) {
-  function background(result) {
-    browser.webRequest.onAuthRequired.addListener((details) => {
-      browser.test.succeed(`authHandler.onAuthRequired called with ${details.requestId} ${details.url} result ${JSON.stringify(result)}`);
-      browser.test.sendMessage("onAuthRequired");
-      return result;
-    }, {urls: ["*://mochi.test/*"]}, ["blocking"]);
-    browser.webRequest.onCompleted.addListener((details) => {
-      browser.test.succeed(`authHandler.onCompleted called with ${details.requestId} ${details.url}`);
-      browser.test.sendMessage("onCompleted");
-    }, {urls: ["*://mochi.test/*"]});
-    browser.webRequest.onErrorOccurred.addListener((details) => {
-      browser.test.succeed(`authHandler.onErrorOccurred called with ${details.requestId} ${details.url}`);
-      browser.test.sendMessage("onErrorOccurred");
-    }, {urls: ["*://mochi.test/*"]});
+  function checkAuthorization(authorization) {
+    let expected_user = url.searchParams.get("user");
+    if (!expected_user) {
+      return true;
+    }
+    let expected_pass = url.searchParams.get("pass");
+    let actual_user, actual_pass;
+    let authHeader = request.getHeader("Authorization");
+    let match = /Basic (.+)/.exec(authHeader);
+    if (match.length != 2) {
+      throw new Error("Couldn't parse auth header: " + authHeader);
+    }
+    let userpass = atob(match[1]); // no atob() :-(
+    match = /(.*):(.*)/.exec(userpass);
+    if (match.length != 3) {
+      throw new Error("Couldn't decode auth header: " + userpass);
+    }
+    actual_user = match[1];
+    actual_pass = match[2];
+    return expected_user === actual_user && expected_pass === actual_pass;
   }
 
-  let permissions = [
-    "webRequest",
-    "*://mochi.test/*",
-  ];
-  if (blocking) {
-    permissions.push("webRequestBlocking");
+  response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
+  if (proxy_realm && !request.hasHeader("Proxy-Authorization")) {
+    // We're not testing anything that requires checking the proxy auth user/password.
+    response.setStatusLine("1.0", 407, "Proxy authentication required");
+    response.setHeader("Proxy-Authenticate", `basic realm="${proxy_realm}"`, true);
+    response.write("proxy auth required");
+  } else if (!(realms.has(realm) && request.hasHeader("Authorization") && checkAuthorization())) {
+    realms.add(realm);
+    response.setStatusLine(request.httpVersion, 401, "Authentication required");
+    response.setHeader("WWW-Authenticate", `basic realm="${realm}"`, true);
+    response.write("auth required");
+  } else {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.write("ok, got authorization");
   }
+});
+
+function getExtension(bgConfig) {
+  function background(config) {
+    let path = config.path;
+    browser.webRequest.onBeforeRequest.addListener((details) => {
+      browser.test.log(`onBeforeRequest called with ${details.requestId} ${details.url}`);
+      browser.test.sendMessage("onBeforeRequest");
+      return config.onBeforeRequest.hasOwnProperty("result") && config.onBeforeRequest.result;
+    }, {urls: [path]}, config.onBeforeRequest.hasOwnProperty("extra") ? config.onBeforeRequest.extra : []);
+    browser.webRequest.onAuthRequired.addListener((details) => {
+      browser.test.log(`onAuthRequired called with ${details.requestId} ${details.url}`);
+      browser.test.assertEq(config.realm, details.realm, "providing www authorization");
+      browser.test.sendMessage("onAuthRequired");
+      return config.onAuthRequired.hasOwnProperty("result") && config.onAuthRequired.result;
+    }, {urls: [path]}, config.onAuthRequired.hasOwnProperty("extra") ? config.onAuthRequired.extra : []);
+    browser.webRequest.onCompleted.addListener((details) => {
+      browser.test.log(`onCompleted called with ${details.requestId} ${details.url}`);
+      browser.test.sendMessage("onCompleted");
+    }, {urls: [path]});
+    browser.webRequest.onErrorOccurred.addListener((details) => {
+      browser.test.log(`onErrorOccurred called with ${JSON.stringify(details)}`);
+      browser.test.sendMessage("onErrorOccurred");
+    }, {urls: [path]});
+  }
+
   return ExtensionTestUtils.loadExtension({
     manifest: {
-      permissions,
+      permissions: [
+        "webRequest",
+        "webRequestBlocking",
+        bgConfig.path,
+      ],
     },
-    background: `(${background})(${JSON.stringify(result)})`,
+    background: `(${background})(${JSON.stringify(bgConfig)})`,
   });
 }
 
 add_task(async function test_webRequest_auth() {
-  // Make use of head_webrequest to ensure event sequence.
-  let events = {
-    "onBeforeRequest":     [{urls: ["*://mochi.test/*"]}, ["blocking"]],
-    "onBeforeSendHeaders": [{urls: ["*://mochi.test/*"]}, ["blocking", "requestHeaders"]],
-    "onSendHeaders":       [{urls: ["*://mochi.test/*"]}, ["requestHeaders"]],
-    "onBeforeRedirect":    [{urls: ["*://mochi.test/*"]}],
-    "onHeadersReceived":   [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onAuthRequired":      [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onResponseStarted":   [{urls: ["*://mochi.test/*"]}],
-    "onCompleted":         [{urls: ["*://mochi.test/*"]}, ["responseHeaders"]],
-    "onErrorOccurred":     [{urls: ["*://mochi.test/*"]}],
+  let config = {
+    path: `${BASE_URL}/*`,
+    realm: `webRequest_auth${Math.random()}`,
+    onBeforeRequest: {
+      extra: ["blocking"],
+    },
+    onAuthRequired: {
+      extra: ["blocking"],
+      result: {
+        authCredentials: {
+          username: "testuser",
+          password: "testpass",
+        },
+      },
+    },
   };
 
-  let extension = makeExtension(events);
+  let extension = getExtension(config);
   await extension.startup();
-  let authInfo = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let expect = {
-    "authenticate.sjs": {
-      type: "xmlhttprequest",
-      // we expect these additional events after onAuthRequired
-      optional_events: ["onBeforeRequest", "onHeadersReceived"],
-      authInfo,
-    },
-  };
-  // expecting origin == undefined
-  extension.sendMessage("set-expected", {expect, origin: location.href});
-  await extension.awaitMessage("continue");
 
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authInfo.username}&pass=${authInfo.password}`);
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
+  await Promise.all([
+    extension.awaitMessage("onBeforeRequest"),
+    extension.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        extension.awaitMessage("onBeforeRequest"),
+        extension.awaitMessage("onCompleted"),
+      ]);
+    }),
+  ]);
+  await contentPage.close();
 
-  await extension.awaitMessage("done");
+  // Second time around to test cached credentials
+  contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
+  await Promise.all([
+    extension.awaitMessage("onBeforeRequest"),
+    extension.awaitMessage("onCompleted"),
+  ]);
+
+  await contentPage.close();
   await extension.unload();
 });
 
-// This test is the same as above, however we shouldn't receive onAuthRequired
-// since those credentials are now cached (thus optional_events is not set).
-add_task(async function test_webRequest_cached_credentials() {
-  // Make use of head_webrequest to ensure event sequence.
-  let events = {
-    "onBeforeRequest":     [{urls: ["*://mochi.test/*"]}, ["blocking"]],
-    "onBeforeSendHeaders": [{urls: ["*://mochi.test/*"]}, ["blocking", "requestHeaders"]],
-    "onSendHeaders":       [{urls: ["*://mochi.test/*"]}, ["requestHeaders"]],
-    "onBeforeRedirect":    [{urls: ["*://mochi.test/*"]}],
-    "onHeadersReceived":   [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onAuthRequired":      [{urls: ["*://mochi.test/*"]}, ["blocking", "responseHeaders"]],
-    "onResponseStarted":   [{urls: ["*://mochi.test/*"]}],
-    "onCompleted":         [{urls: ["*://mochi.test/*"]}, ["responseHeaders"]],
-    "onErrorOccurred":     [{urls: ["*://mochi.test/*"]}],
-  };
-
-  let extension = makeExtension(events);
-  await extension.startup();
-  let authInfo = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let expect = {
-    "authenticate.sjs": {
-      type: "xmlhttprequest",
-      events: ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders", "onHeadersReceived", "onResponseStarted", "onCompleted"],
+add_task(async function test_webRequest_auth_cancelled() {
+  // Test that any auth listener can cancel.
+  let config = {
+    path: `${BASE_URL}/*`,
+    realm: `webRequest_auth${Math.random()}`,
+    onBeforeRequest: {
+      extra: ["blocking"],
+    },
+    onAuthRequired: {
+      extra: ["blocking"],
+      result: {
+        authCredentials: {
+          username: "testuser",
+          password: "testpass",
+        },
+      },
     },
   };
-  // expecting origin == undefined
-  extension.sendMessage("set-expected", {expect, origin: location.href});
-  await extension.awaitMessage("continue");
 
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authInfo.username}&pass=${authInfo.password}`);
-
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
-
-add_task(async function test_webRequest_cached_credentials2() {
-  let authCredentials = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let ex1 = getAuthHandler();
+  let ex1 = getExtension(config);
+  config.onAuthRequired.result = {cancel: true};
+  let ex2 = getExtension(config);
   await ex1.startup();
-
-  await testXHR(`${baseUrl}?realm=webRequest_auth&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await ex1.awaitMessage("onCompleted");
-  await ex1.unload();
-});
-
-add_task(async function test_webRequest_window() {
-  let authCredentials = {
-    username: "testuser",
-    password: "testpass",
-  };
-  let ex1 = getAuthHandler();
-  await ex1.startup();
-
-  let win = window.open(`${baseUrl}?realm=test_webRequest_window&user=${authCredentials.username}&pass=${authCredentials.password}`);
-
-  await ex1.awaitMessage("onCompleted");
-  await ex1.unload();
-  win.close();
-});
-
-add_task(async function test_webRequest_auth_cancelled() {
-  let authCredentials = {
-    username: "testuser_canceled",
-    password: "testpass_canceled",
-  };
-  let ex1 = getAuthHandler({authCredentials});
-  await ex1.startup();
-  let ex2 = getAuthHandler({cancel: true});
   await ex2.startup();
 
-  await Assert.rejects(testXHR(`${baseUrl}?realm=test_webRequest_auth_cancelled&user=${authCredentials.username}&pass=${authCredentials.password}`), "caught rejected xhr");
-
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
   await Promise.all([
+    ex1.awaitMessage("onBeforeRequest"),
     ex1.awaitMessage("onAuthRequired"),
+    ex1.awaitMessage("onErrorOccurred"),
+    ex2.awaitMessage("onBeforeRequest"),
     ex2.awaitMessage("onAuthRequired"),
-    ex1.awaitMessage("onErrorOccurred"),
     ex2.awaitMessage("onErrorOccurred"),
   ]);
+
+  await contentPage.close();
   await ex1.unload();
   await ex2.unload();
 });
 
+
 add_task(async function test_webRequest_auth_nonblocking() {
-  // The first listener handles the auth request, the second listener
-  // is a non-blocking listener and cannot respond but will get the call.
-  let authCredentials = {
-    username: "foobar",
-    password: "testpass",
+  let config = {
+    path: `${BASE_URL}/*`,
+    realm: `webRequest_auth${Math.random()}`,
+    onBeforeRequest: {
+      extra: ["blocking"],
+    },
+    onAuthRequired: {
+      extra: ["blocking"],
+      result: {
+        authCredentials: {
+          username: "testuser",
+          password: "testpass",
+        },
+      },
+    },
   };
-  let handlingExt = getAuthHandler({authCredentials});
-  await handlingExt.startup();
-  let extension = getAuthHandler({}, false);
-  await extension.startup();
 
-  await testXHR(`${baseUrl}?realm=webRequest_auth_nonblocking&user=${authCredentials.username}&pass=${authCredentials.password}`);
+  let ex1 = getExtension(config);
+  // non-blocking ext tries to cancel but cannot.
+  delete config.onBeforeRequest.extra;
+  delete config.onAuthRequired.extra;
+  config.onAuthRequired.result = {cancel: true};
+  let ex2 = getExtension(config);
+  await ex1.startup();
+  await ex2.startup();
 
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
   await Promise.all([
-    extension.awaitMessage("onAuthRequired"),
-    extension.awaitMessage("onCompleted"),
-    handlingExt.awaitMessage("onAuthRequired"),
-    handlingExt.awaitMessage("onCompleted"),
+    ex1.awaitMessage("onBeforeRequest"),
+    ex1.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        ex1.awaitMessage("onBeforeRequest"),
+        ex1.awaitMessage("onCompleted"),
+      ]);
+    }),
+    ex2.awaitMessage("onBeforeRequest"),
+    ex2.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        ex2.awaitMessage("onBeforeRequest"),
+        ex2.awaitMessage("onCompleted"),
+      ]);
+    }),
   ]);
-  await extension.unload();
-  await handlingExt.unload();
+
+  await contentPage.close();
+  Services.obs.notifyObservers(null, "net:clear-active-logins");
+  await ex1.unload();
+  await ex2.unload();
 });
 
-
 add_task(async function test_webRequest_auth_blocking_noreturn() {
   // The first listener is blocking but doesn't return anything.  The second
   // listener cancels the request.
-  let ext = getAuthHandler();
-  await ext.startup();
-  let canceler = getAuthHandler({cancel: true});
-  await canceler.startup();
-
-  await Assert.rejects(testXHR(`${baseUrl}?realm=auth_blocking_noreturn&user=auth_blocking_noreturn&pass=auth_blocking_noreturn`), "caught rejected xhr");
-
-  await Promise.all([
-    ext.awaitMessage("onAuthRequired"),
-    ext.awaitMessage("onErrorOccurred"),
-    canceler.awaitMessage("onAuthRequired"),
-    canceler.awaitMessage("onErrorOccurred"),
-  ]);
-  await ext.unload();
-  await canceler.unload();
-});
-
-add_task(async function test_webRequest_auth_nonblocking_forwardAuthProvider() {
-  // The chrome script sets up a default auth handler on the channel, the
-  // extension does not return anything in the authRequred call.  We should
-  // get the call in the extension first, then in the chrome code where we
-  // cancel the request to avoid dealing with the prompt dialog here.  The test
-  // is to ensure that WebRequest calls the previous notificationCallbacks
-  // if the authorization is not handled by the onAuthRequired handler.
-
-  let chromeScript = SpecialPowers.loadChromeScript(() => {
-    ChromeUtils.import("resource://gre/modules/Services.jsm");
-    ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-    let observer = channel => {
-      if (!(channel instanceof Ci.nsIHttpChannel && channel.URI.host === "mochi.test")) {
-        return;
-      }
-      Services.obs.removeObserver(observer, "http-on-modify-request");
-      channel.notificationCallbacks = {
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor,
-                                               Ci.nsIAuthPromptProvider,
-                                               Ci.nsIAuthPrompt2]),
-        getInterface: XPCOMUtils.generateQI([Ci.nsIAuthPromptProvider,
-                                             Ci.nsIAuthPrompt2]),
-        promptAuth(channel, level, authInfo) {
-          throw Cr.NS_ERROR_NO_INTERFACE;
-        },
-        getAuthPrompt(reason, iid) {
-          return this;
-        },
-        asyncPromptAuth(channel, callback, context, level, authInfo) {
-          // We just cancel here, we're only ensuring that non-webrequest
-          // notificationcallbacks get called if webrequest doesn't handle it.
-          Promise.resolve().then(() => {
-            callback.onAuthCancelled(context, false);
-            channel.cancel(Cr.NS_BINDING_ABORTED);
-            sendAsyncMessage("callback-complete");
-          });
-        },
-      };
-    };
-    Services.obs.addObserver(observer, "http-on-modify-request");
-    sendAsyncMessage("chrome-ready");
-  });
-  await chromeScript.promiseOneMessage("chrome-ready");
-  let callbackComplete = chromeScript.promiseOneMessage("callback-complete");
+  let config = {
+    path: `${BASE_URL}/*`,
+    realm: `webRequest_auth${Math.random()}`,
+    onBeforeRequest: {
+      extra: ["blocking"],
+    },
+    onAuthRequired: {
+      extra: ["blocking"],
+    },
+  };
 
-  let handlingExt = getAuthHandler();
-  await handlingExt.startup();
-
-  await Assert.rejects(testXHR(`${baseUrl}?realm=auth_nonblocking_forwardAuth&user=auth_nonblocking_forwardAuth&pass=auth_nonblocking_forwardAuth`), "caught rejected xhr");
-
-  await callbackComplete;
-  await handlingExt.awaitMessage("onAuthRequired");
-  // We expect onErrorOccurred because the "default" authprompt above cancelled
-  // the auth request to avoid a dialog.
-  await handlingExt.awaitMessage("onErrorOccurred");
-  await handlingExt.unload();
-  chromeScript.destroy();
-});
-
-add_task(async function test_webRequest_auth_nonblocking_forwardAuthPrompt2() {
-  // The chrome script sets up a default auth handler on the channel, the
-  // extension does not return anything in the authRequred call.  We should
-  // get the call in the extension first, then in the chrome code where we
-  // cancel the request to avoid dealing with the prompt dialog here.  The test
-  // is to ensure that WebRequest calls the previous notificationCallbacks
-  // if the authorization is not handled by the onAuthRequired handler.
-
-  let chromeScript = SpecialPowers.loadChromeScript(() => {
-    ChromeUtils.import("resource://gre/modules/Services.jsm");
-    ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+  let ex1 = getExtension(config);
+  config.onAuthRequired.result = {cancel: true};
+  let ex2 = getExtension(config);
+  await ex1.startup();
+  await ex2.startup();
 
-    let observer = channel => {
-      if (!(channel instanceof Ci.nsIHttpChannel && channel.URI.host === "mochi.test")) {
-        return;
-      }
-      Services.obs.removeObserver(observer, "http-on-modify-request");
-      channel.notificationCallbacks = {
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor,
-                                               Ci.nsIAuthPrompt2]),
-        getInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
-        promptAuth(channel, level, authInfo) {
-          throw Cr.NS_ERROR_NO_INTERFACE;
-        },
-        asyncPromptAuth(channel, callback, context, level, authInfo) {
-          // We just cancel here, we're only ensuring that non-webrequest
-          // notificationcallbacks get called if webrequest doesn't handle it.
-          Promise.resolve().then(() => {
-            channel.cancel(Cr.NS_BINDING_ABORTED);
-            sendAsyncMessage("callback-complete");
-          });
-        },
-      };
-    };
-    Services.obs.addObserver(observer, "http-on-modify-request");
-    sendAsyncMessage("chrome-ready");
-  });
-  await chromeScript.promiseOneMessage("chrome-ready");
-  let callbackComplete = chromeScript.promiseOneMessage("callback-complete");
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
+  await Promise.all([
+    ex1.awaitMessage("onBeforeRequest"),
+    ex1.awaitMessage("onAuthRequired"),
+    ex1.awaitMessage("onErrorOccurred"),
+    ex2.awaitMessage("onBeforeRequest"),
+    ex2.awaitMessage("onAuthRequired"),
+    ex2.awaitMessage("onErrorOccurred"),
+  ]);
 
-  let handlingExt = getAuthHandler();
-  await handlingExt.startup();
-
-  await Assert.rejects(testXHR(`${baseUrl}?realm=auth_nonblocking_forwardAuthPromptProvider&user=auth_nonblocking_forwardAuth&pass=auth_nonblocking_forwardAuth`), "caught rejected xhr");
-
-  await callbackComplete;
-  await handlingExt.awaitMessage("onAuthRequired");
-  // We expect onErrorOccurred because the "default" authprompt above cancelled
-  // the auth request to avoid a dialog.
-  await handlingExt.awaitMessage("onErrorOccurred");
-  await handlingExt.unload();
-  chromeScript.destroy();
+  await contentPage.close();
+  await ex1.unload();
+  await ex2.unload();
 });
 
 add_task(async function test_webRequest_duelingAuth() {
-  let exNone = getAuthHandler();
-  await exNone.startup();
-  let authCredentials = {
-    username: "testuser_da1",
-    password: "testpass_da1",
+  let config = {
+    path: `${BASE_URL}/*`,
+    realm: `webRequest_auth${Math.random()}`,
+    onBeforeRequest: {
+      extra: ["blocking"],
+    },
+    onAuthRequired: {
+      extra: ["blocking"],
+    },
   };
-  let ex1 = getAuthHandler({authCredentials});
+  let exNone = getExtension(config);
+  await exNone.startup();
+
+  let authCredentials = {
+    username: `testuser_da1${Math.random()}`,
+    password: `testpass_da1${Math.random()}`,
+  };
+  config.onAuthRequired.result = {authCredentials};
+  let ex1 = getExtension(config);
   await ex1.startup();
-  let exEmpty = getAuthHandler({});
+
+  config.onAuthRequired.result = {};
+  let exEmpty = getExtension(config);
   await exEmpty.startup();
-  let ex2 = getAuthHandler({authCredentials: {
-    username: "testuser_da2",
-    password: "testpass_da2",
-  }});
+
+  config.onAuthRequired.result = {
+    authCredentials: {
+      username: `testuser_da2${Math.random()}`,
+      password: `testpass_da2${Math.random()}`,
+    },
+  };
+  let ex2 = getExtension(config);
   await ex2.startup();
 
-  // XHR should succeed since the first credentials win, and they are correct.
-  await testXHR(`${baseUrl}?realm=test_webRequest_duelingAuth&user=${authCredentials.username}&pass=${authCredentials.password}`);
+
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}&user=${authCredentials.username}&pass=${authCredentials.password}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
+  await Promise.all([
+    exNone.awaitMessage("onBeforeRequest"),
+    exNone.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        exNone.awaitMessage("onBeforeRequest"),
+        exNone.awaitMessage("onCompleted"),
+      ]);
+    }),
+    exEmpty.awaitMessage("onBeforeRequest"),
+    exEmpty.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        exEmpty.awaitMessage("onBeforeRequest"),
+        exEmpty.awaitMessage("onCompleted"),
+      ]);
+    }),
+    ex1.awaitMessage("onBeforeRequest"),
+    ex1.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        ex1.awaitMessage("onBeforeRequest"),
+        ex1.awaitMessage("onCompleted"),
+      ]);
+    }),
+    ex2.awaitMessage("onBeforeRequest"),
+    ex2.awaitMessage("onAuthRequired").then(() => {
+      return Promise.all([
+        ex2.awaitMessage("onBeforeRequest"),
+        ex2.awaitMessage("onCompleted"),
+      ]);
+    }),
+  ]);
 
   await Promise.all([
-    exNone.awaitMessage("onAuthRequired"),
-    exNone.awaitMessage("onCompleted"),
-    exEmpty.awaitMessage("onAuthRequired"),
-    exEmpty.awaitMessage("onCompleted"),
-    ex1.awaitMessage("onAuthRequired"),
-    ex1.awaitMessage("onCompleted"),
-    ex2.awaitMessage("onAuthRequired"),
-    ex2.awaitMessage("onCompleted"),
-  ]);
-  await Promise.all([
+    await contentPage.close(),
     exNone.unload(),
     exEmpty.unload(),
     ex1.unload(),
     ex2.unload(),
   ]);
 });
 
 add_task(async function test_webRequest_auth_proxy() {
-  function background() {
+  function background(permissionPath) {
     let proxyOk = false;
     browser.webRequest.onAuthRequired.addListener((details) => {
-      browser.test.succeed(`handlingExt onAuthRequired called with ${details.requestId} ${details.url}`);
+      browser.test.log(`handlingExt onAuthRequired called with ${details.requestId} ${details.url}`);
       if (details.isProxy) {
         browser.test.succeed("providing proxy authorization");
         proxyOk = true;
         return {authCredentials: {username: "puser", password: "ppass"}};
       }
       browser.test.assertTrue(proxyOk, "providing www authorization after proxy auth");
       browser.test.sendMessage("done");
       return {authCredentials: {username: "auser", password: "apass"}};
-    }, {urls: ["*://mochi.test/*"]}, ["blocking"]);
+    }, {urls: [permissionPath]}, ["blocking"]);
   }
 
   let handlingExt = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "webRequest",
         "webRequestBlocking",
-        "*://mochi.test/*",
+        `${BASE_URL}/*`,
       ],
     },
-    background,
+    background: `(${background})("${BASE_URL}/*")`,
   });
 
   await handlingExt.startup();
 
-  await testXHR(`${baseUrl}?realm=auth_proxy&user=auser&pass=apass&proxy_user=puser&proxy_pass=ppass`);
+  let requestUrl = `${BASE_URL}/authenticate.sjs?realm=webRequest_auth${Math.random()}&proxy_realm=proxy_auth${Math.random()}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
 
   await handlingExt.awaitMessage("done");
+  await contentPage.close();
   await handlingExt.unload();
 });
-</script>
-</head>
-<body>
-<div id="test">Authorization Test</div>
-
-</body>
-</html>
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -99,16 +99,17 @@ skip-if = os == "android"
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_tab_teardown.js]
 skip-if = os == 'android' # Bug 1258975 on android.
 [test_ext_trustworthy_origin.js]
 [test_ext_topSites.js]
 skip-if = os == "android"
 [test_ext_unload_frame.js]
 skip-if = true # Too frequent intermittent failures
+[test_ext_webRequest_auth.js]
 [test_ext_webRequest_filterResponseData.js]
 [test_ext_webRequest_permission.js]
 [test_ext_webRequest_responseBody.js]
 [test_ext_webRequest_set_cookie.js]
 [test_ext_webRequest_suspend.js]
 [test_ext_webRequest_webSocket.js]
 [test_ext_xhr_capabilities.js]
 [test_native_manifests.js]