Bug 1426562 - Don't crash on `Response` constructor in WebExtensions. r=baku
authorPerry Jiang <perry@mozilla.com>
Mon, 13 May 2019 17:03:58 +0000
changeset 532450 dac784d695b31fcffe11a840c2c08e287b467ee6
parent 532449 d1985ca4a9e2cc6d94366eea82150788f185305b
child 532451 fbcf6cd744b163f7073d8a6a00d89412b4bba9dc
push id11268
push usercsabou@mozilla.com
push dateTue, 14 May 2019 15:24:22 +0000
treeherdermozilla-beta@5fb7fcd568d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1426562
milestone68.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 1426562 - Don't crash on `Response` constructor in WebExtensions. r=baku Differential Revision: https://phabricator.services.mozilla.com/D29365
dom/fetch/Response.cpp
dom/fetch/moz.build
dom/fetch/tests/mochitest.ini
dom/fetch/tests/test_ext_response_constructor.html
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -162,16 +162,21 @@ already_AddRefed<Response> Response::Red
 
 /*static*/
 already_AddRefed<Response> Response::Constructor(
     const GlobalObject& aGlobal,
     const Optional<Nullable<fetch::ResponseBodyInit>>& aBody,
     const ResponseInit& aInit, ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   if (aInit.mStatus < 200 || aInit.mStatus > 599) {
     aRv.ThrowRangeError<MSG_INVALID_RESPONSE_STATUSCODE_ERROR>();
     return nullptr;
   }
 
   // Check if the status text contains illegal characters
   nsACString::const_iterator start, end;
   aInit.mStatusText.BeginReading(start);
@@ -204,20 +209,32 @@ already_AddRefed<Response> Response::Con
 
       principalInfo.reset(new mozilla::ipc::PrincipalInfo());
       nsresult rv =
           PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
         return nullptr;
       }
-    } else {
+
+      internalResponse->InitChannelInfo(info);
+    } else if (nsContentUtils::IsSystemPrincipal(global->PrincipalOrNull())) {
       info.InitFromChromeGlobal(global);
+
+      internalResponse->InitChannelInfo(info);
     }
-    internalResponse->InitChannelInfo(info);
+
+    /**
+     * The channel info is left uninitialized if neither the above `if` nor
+     * `else if` statements are executed; this could be because we're in a
+     * WebExtensions content script, where the global (i.e. `global`) is a
+     * wrapper, and the principal is an expanded principal. In this case,
+     * as far as I can tell, there's no way to get the security info, but we'd
+     * like the `Response` to be successfully constructed.
+     */
   } else {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     internalResponse->InitChannelInfo(worker->GetChannelInfo());
     principalInfo =
         MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
   }
 
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -55,12 +55,13 @@ LOCAL_INCLUDES += [
     '/netwerk/base',
     # For nsDataHandler.h
     '/netwerk/protocol/data',
     # For HttpBaseChannel.h
     '/netwerk/protocol/http',
 ]
 
 BROWSER_CHROME_MANIFESTS += [ 'tests/browser.ini' ]
+MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
 
 FINAL_LIBRARY = 'xul'
 
 include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644
--- /dev/null
+++ b/dom/fetch/tests/mochitest.ini
@@ -0,0 +1,1 @@
+[test_ext_response_constructor.html]
new file mode 100644
--- /dev/null
+++ b/dom/fetch/tests/test_ext_response_constructor.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test `Response` constructor in a WebExtension</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+  <script>
+    add_task(async function testResponseConstructor() {
+      function contentScript() {
+        new Response();
+        browser.test.notifyPass("done");
+      }
+
+      const extension = ExtensionTestUtils.loadExtension({
+        manifest: {
+          content_scripts: [
+            {
+              matches: ["<all_urls>"],
+              js: ["content_script.js"],
+            },
+          ],
+        },
+
+        files: {
+          "content_script.js": contentScript,
+        },
+      });
+
+      await extension.startup();
+
+      const win = window.open("https://example.com");
+      await extension.awaitFinish("done");
+      win.close();
+
+      await extension.unload();
+    });
+  </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>