Bug 1380186 test http redirects to moz-ext protocol, r=kmag
authorShane Caraveo <scaraveo@mozilla.com>
Thu, 27 Jul 2017 13:34:54 -0700
changeset 422616 73f48b4bd6018f6f4c6dea264299861c9bc9a664
parent 422615 4d7f8040175114453ed4a1927fd6a0bf376f21d3
child 422617 21996e01ca43e5505f0d7c2c8a3b0f742da429c6
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1380186
milestone56.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 1380186 test http redirects to moz-ext protocol, r=kmag MozReview-Commit-ID: Kg8ELe3tV2z
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -57,25 +57,25 @@ function frameScript() {
   Components.utils.import("resource://gre/modules/Services.jsm");
 
   Services.obs.notifyObservers(this, "tab-content-frameloader-created");
 }
 
 const FRAME_SCRIPT = `data:text/javascript,(${encodeURI(frameScript)}).call(this)`;
 
 let kungFuDeathGrip = new Set();
-function promiseBrowserLoaded(browser, url) {
+function promiseBrowserLoaded(browser, url, redirectUrl) {
   return new Promise(resolve => {
     const listener = {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIWebProgressListener]),
 
       onStateChange(webProgress, request, stateFlags, statusCode) {
         let requestUrl = request.URI ? request.URI.spec : webProgress.DOMWindow.location.href;
-
-        if (webProgress.isTopLevel && requestUrl === url &&
+        if (webProgress.isTopLevel &&
+            (requestUrl === url || requestUrl === redirectUrl) &&
             (stateFlags & Ci.nsIWebProgressListener.STATE_STOP)) {
           resolve();
           kungFuDeathGrip.delete(listener);
           browser.removeProgressListener(listener);
         }
       },
     };
 
@@ -126,21 +126,21 @@ class ContentPage {
 
     await awaitFrameLoader;
     browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
 
     this.browser = browser;
     return browser;
   }
 
-  async loadURL(url) {
+  async loadURL(url, redirectUrl = undefined) {
     await this.browserReady;
 
     this.browser.loadURI(url);
-    return promiseBrowserLoaded(this.browser, url);
+    return promiseBrowserLoaded(this.browser, url, redirectUrl);
   }
 
   async close() {
     await this.browserReady;
 
     this.browser = null;
 
     this.windowlessBrowser.close();
@@ -671,16 +671,16 @@ var ExtensionTestUtils = {
   get remoteContentScripts() {
     return REMOTE_CONTENT_SCRIPTS;
   },
 
   set remoteContentScripts(val) {
     REMOTE_CONTENT_SCRIPTS = !!val;
   },
 
-  loadContentPage(url, remote = undefined) {
+  loadContentPage(url, remote = undefined, redirectUrl = undefined) {
     let contentPage = new ContentPage(remote);
 
-    return contentPage.loadURL(url).then(() => {
+    return contentPage.loadURL(url, redirectUrl).then(() => {
       return contentPage;
     });
   },
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
@@ -0,0 +1,247 @@
+"use strict";
+
+// Tests whether we can redirect to a moz-extension: url.
+XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
+                                  "resource://testing-common/TestUtils.jsm");
+const XMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest");
+
+// nsIWebRequestListener is a nsIThreadRetargetableStreamListener that handles
+// forwarding of nsIRequestObserver for JS consumers.  It does nothing more
+// than that.
+let WebRequestListener = Components.Constructor("@mozilla.org/webextensions/webRequestListener;1",
+                                                "nsIWebRequestListener", "init");
+
+const server = createHttpServer();
+const gServerUrl = `http://localhost:${server.identity.primaryPort}`;
+
+server.registerPathHandler("/redirect", (request, response) => {
+  let params = new URLSearchParams(request.queryString);
+  response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+  response.setHeader("Location", params.get("redirect_uri"));
+});
+
+server.registerPathHandler("/dummy", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.write("ok");
+});
+
+function onStopListener(channel) {
+  return new Promise(resolve => {
+    new WebRequestListener({
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+                                             Ci.nsIStreamListener]),
+      getFinalURI(request) {
+        let {loadInfo} = request;
+        return (loadInfo && loadInfo.resultPrincipalURI) || request.originalURI;
+      },
+      onStartRequest(request, context) {
+      },
+      onStopRequest(request, context, statusCode) {
+        let URI = this.getFinalURI(request.QueryInterface(Ci.nsIChannel));
+        resolve(URI && URI.spec);
+      },
+    }, channel);
+  });
+}
+
+async function onModifyListener(originUrl, redirectToUrl) {
+  return TestUtils.topicObserved("http-on-modify-request", (subject, data) => {
+    let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    return channel.URI && channel.URI.spec == originUrl;
+  }).then(([subject, data]) => {
+    let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    if (redirectToUrl) {
+      channel.redirectTo(Services.io.newURI(redirectToUrl));
+    }
+    return channel;
+  });
+}
+
+function getExtension(accessible = false, background = undefined) {
+  let manifest = {
+    "permissions": [
+      "webRequest",
+      "webRequestBlocking",
+      "<all_urls>",
+    ],
+  };
+  if (accessible) {
+    manifest.web_accessible_resources = ["finished.html"];
+  }
+  if (!background) {
+    background = () => {
+      // send the extensions public uri to the test.
+      let exturi = browser.extension.getURL("finished.html");
+      browser.test.sendMessage("redirectURI", exturi);
+    };
+  }
+  return ExtensionTestUtils.loadExtension({
+    manifest,
+    files: {
+      "finished.html": `
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <h1>redirected!</h1>
+          </body>
+        </html>
+      `.trim(),
+    },
+    background,
+  });
+}
+
+async function redirection_test(url, channelRedirectUrl) {
+  // setup our observer
+  let watcher = onModifyListener(url, channelRedirectUrl).then(channel => {
+    return onStopListener(channel);
+  });
+  let xhr = new XMLHttpRequest();
+  xhr.open("GET", url);
+  xhr.send();
+  return watcher;
+}
+
+// This test verifies failure without web_accessible_resources.
+add_task(async function test_redirect_to_non_accessible_resource() {
+  let extension = getExtension();
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
+  let result = await redirection_test(url);
+  equal(result, url, `expected no redirect`);
+  await extension.unload();
+});
+
+// This test makes a request against a server that redirects with a 302.
+add_task(async function test_302_redirect_to_extension() {
+  let extension = getExtension(true);
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
+  let result = await redirection_test(url);
+  equal(result, redirectUrl, "redirect request is finished");
+  await extension.unload();
+});
+
+// This test uses channel.redirectTo during http-on-modify to redirect to the
+// moz-extension url.
+add_task(async function test_channel_redirect_to_extension() {
+  let extension = getExtension(true);
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/dummy?r=${Math.random()}`;
+  let result = await redirection_test(url, redirectUrl);
+  equal(result, redirectUrl, "redirect request is finished");
+  await extension.unload();
+});
+
+// This test verifies failure without web_accessible_resources.
+add_task(async function test_content_redirect_to_non_accessible_resource() {
+  let extension = getExtension();
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
+  let watcher = onModifyListener(url).then(channel => {
+    return onStopListener(channel);
+  });
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, "about:blank");
+  equal(contentPage.browser.documentURI.spec, "about:blank", `expected no redirect`);
+  equal(await watcher, url, "expected no redirect");
+  await contentPage.close();
+  await extension.unload();
+});
+
+// This test makes a request against a server that redirects with a 302.
+add_task(async function test_content_302_redirect_to_extension() {
+  let extension = getExtension(true);
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
+  await contentPage.close();
+  await extension.unload();
+});
+
+// This test uses channel.redirectTo during http-on-modify to redirect to the
+// moz-extension url.
+add_task(async function test_content_channel_redirect_to_extension() {
+  let extension = getExtension(true);
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let url = `${gServerUrl}/dummy?r=${Math.random()}`;
+  onModifyListener(url, redirectUrl);
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
+  await contentPage.close();
+  await extension.unload();
+});
+
+// This test makes a request against a server and tests webrequest.  Currently
+// disabled due to NS_BINDING_ABORTED happening.
+add_task(async function test_extension_302_redirect() {
+  let extension = getExtension(true, () => {
+    let myuri = browser.extension.getURL("*");
+    let exturi = browser.extension.getURL("finished.html");
+    browser.webRequest.onBeforeRedirect.addListener(details => {
+      browser.test.assertEq(details.redirectUrl, exturi, "redirect matches");
+    }, {urls: ["<all_urls>", myuri]});
+    browser.webRequest.onCompleted.addListener(details => {
+      browser.test.assertEq(details.url, exturi, "expected url received");
+      browser.test.notifyPass("requestCompleted");
+    }, {urls: ["<all_urls>", myuri]});
+    browser.webRequest.onErrorOccurred.addListener(details => {
+      browser.test.log(`onErrorOccurred ${JSON.stringify(details)}`);
+      browser.test.notifyFail("requestCompleted");
+    }, {urls: ["<all_urls>", myuri]});
+    // send the extensions public uri to the test.
+    browser.test.sendMessage("redirectURI", exturi);
+  });
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let completed = extension.awaitFinish("requestCompleted");
+  let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  equal(contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect`);
+  await completed;
+  await contentPage.close();
+  await extension.unload();
+}).skip();
+
+// This test makes a request and uses onBeforeRequet to redirect to moz-ext.
+// Currently disabled due to NS_BINDING_ABORTED happening.
+add_task(async function test_extension_redirect() {
+  let extension = getExtension(true, () => {
+    let myuri = browser.extension.getURL("*");
+    let exturi = browser.extension.getURL("finished.html");
+    browser.webRequest.onBeforeRequest.addListener(details => {
+      return {redirectUrl: exturi};
+    }, {urls: ["<all_urls>", myuri]}, ["blocking"]);
+    browser.webRequest.onBeforeRedirect.addListener(details => {
+      browser.test.assertEq(details.redirectUrl, exturi, "redirect matches");
+    }, {urls: ["<all_urls>", myuri]});
+    browser.webRequest.onCompleted.addListener(details => {
+      browser.test.assertEq(details.url, exturi, "expected url received");
+      browser.test.notifyPass("requestCompleted");
+    }, {urls: ["<all_urls>", myuri]});
+    browser.webRequest.onErrorOccurred.addListener(details => {
+      browser.test.log(`onErrorOccurred ${JSON.stringify(details)}`);
+      browser.test.notifyFail("requestCompleted");
+    }, {urls: ["<all_urls>", myuri]});
+    // send the extensions public uri to the test.
+    browser.test.sendMessage("redirectURI", exturi);
+  });
+  await extension.startup();
+  let redirectUrl = await extension.awaitMessage("redirectURI");
+  let completed = extension.awaitFinish("requestCompleted");
+  let url = `${gServerUrl}/dummy?r=${Math.random()}`;
+  let contentPage = await ExtensionTestUtils.loadContentPage(url, undefined, redirectUrl);
+  equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`);
+  await completed;
+  await contentPage.close();
+  await extension.unload();
+}).skip();
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -37,16 +37,17 @@ skip-if = os == "android" # checking for
 [test_ext_localStorage.js]
 [test_ext_management.js]
 [test_ext_management_uninstall_self.js]
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_privacy.js]
 [test_ext_privacy_disable.js]
 [test_ext_privacy_update.js]
+[test_ext_redirects.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
 [test_ext_runtime_onInstalled_and_onStartup.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]