Bug 1331730 - Log CORS messages from the content process r=bz,mayhemer
authorKate McKinley <kmckinley@mozilla.com>
Wed, 15 Feb 2017 12:40:41 +0900
changeset 420801 9417de5e94b495c8f3225c652d1c2854a33865c1
parent 420800 713ec2a7dc6ca812cd93aacdc6c2ec690db21486
child 420802 527790e669cf9c28f4cf5fbb893b9fc70f449938
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, mayhemer
bugs1331730
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 1331730 - Log CORS messages from the content process r=bz,mayhemer In e10s, a channel created by parent does not have a reliable reference to the inner window ID that initiated the request. Without that, the channel must request that the content process log and blocked messages to the web console. This patch creates a new ipdl interface to pass the message from the parent to the child process. The nsCORSListenerProxy also needs to keep a reference to the nsIHttpChannel that created it so it can find its way back to the child. Additionally, the HttpChannelParent needs to be propagated when creating a new channel for CORS. MozReview-Commit-ID: 8CUhlVCTWxt
dom/security/test/cors/browser.ini
dom/security/test/cors/browser_CORS-console-warnings.js
dom/security/test/cors/file_cors_logging_test.html
dom/security/test/cors/file_cors_logging_test.html.css
dom/security/test/cors/head.js
dom/security/test/moz.build
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/NullHttpChannel.cpp
netwerk/protocol/http/PHttpChannel.ipdl
netwerk/protocol/http/nsCORSListenerProxy.cpp
netwerk/protocol/http/nsCORSListenerProxy.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsIHttpChannel.idl
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+  file_CrossSiteXHR_server.sjs
+  file_CrossSiteXHR_inner.html
+  file_cors_logging_test.html
+  head.js
+
+[browser_CORS-console-warnings.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/browser_CORS-console-warnings.js
@@ -0,0 +1,93 @@
+/*
+ * Description of the test:
+ *   Ensure that CORS warnings are printed to the web console.
+ *
+ *   This test uses the same tests as the plain mochitest, but needs access to
+ *   the console.
+ */
+'use strict';
+
+function console_observer(subject, topic, data) {
+  var message = subject.wrappedJSObject.arguments[0];
+  ok(false, message);
+};
+
+var webconsole = null;
+var messages_seen = 0;
+var expected_messages = 50;
+
+function on_new_message(event, new_messages) {
+  for (let message of new_messages) {
+    let elem = message.node;
+    let text = elem.textContent;
+    if (text.match('Cross-Origin Request Blocked:')) {
+      ok(true, "message is: " + text);
+      messages_seen++;
+    }
+  }
+}
+
+function do_cleanup() {
+  if (webconsole) {
+    webconsole.ui.off("new-messages", on_new_message);
+  }
+  yield unsetCookiePref();
+}
+
+/**
+ * Set e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are set.
+ */
+function setCookiePref() {
+  return new Promise(resolve =>
+    // accept all cookies so that the CORS requests will send the right cookies
+    SpecialPowers.pushPrefEnv({
+      set: [
+        ["network.cookie.cookieBehavior", 0],
+      ]
+    }, resolve));
+}
+
+/**
+ * Unset e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are unset.
+ */
+function unsetCookiePref() {
+  return new Promise(resolve => {
+    SpecialPowers.popPrefEnv(resolve);
+  });
+}
+
+//jscs:disable
+add_task(function*() {
+  //jscs:enable
+  // A longer timeout is necessary for this test than the plain mochitests
+  // due to opening a new tab with the web console.
+  requestLongerTimeout(4);
+  registerCleanupFunction(do_cleanup);
+  yield setCookiePref();
+
+  let test_uri = "http://mochi.test:8888/browser/dom/security/test/cors/file_cors_logging_test.html";
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  let toolbox = yield openToolboxForTab(tab, "webconsole");
+  ok(toolbox, "Got toolbox");
+  let hud = toolbox.getCurrentPanel().hud;
+  ok(hud, "Got hud");
+
+  if (!webconsole) {
+    registerCleanupFunction(do_cleanup);
+    hud.ui.on("new-messages", on_new_message);
+    webconsole = hud;
+  }
+
+  BrowserTestUtils.loadURI(gBrowser, test_uri);
+
+  yield BrowserTestUtils.waitForLocationChange(gBrowser, test_uri+"#finished");
+
+  // Different OS combinations
+  ok(messages_seen > 0, "Saw " + messages_seen + " messages.");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/file_cors_logging_test.html
@@ -0,0 +1,1311 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+  <title>Test for Cross Site XMLHttpRequest</title>
+</head>
+<body onload="initTest()">
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+const runPreflightTests = 1;
+const runCookieTests = 1;
+const runRedirectTests = 1;
+
+var gen;
+
+function initTest() {
+  window.addEventListener("message", function(e) {
+    gen.next(e.data);
+  });
+
+  gen = runTest();
+
+  gen.next()
+}
+
+function initTestCallback() {
+}
+
+function* runTest() {
+  var loader = document.getElementById('loader');
+  var loaderWindow = loader.contentWindow;
+  loader.onload = function () { gen.next() };
+
+  // Test preflight-less requests
+  basePath = "/browser/dom/security/test/cors/file_CrossSiteXHR_server.sjs?"
+  baseURL = "http://mochi.test:8888" + basePath;
+
+  // Test preflighted requests
+  loader.src = "http://example.org/browser/dom/security/test/cors/file_CrossSiteXHR_inner.html";
+  origin = "http://example.org";
+  yield undefined;
+
+  tests =     [// Plain request
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+               },
+
+               // undefined username
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: undefined
+               },
+
+               // undefined username and password
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: undefined,
+                 password: undefined
+               },
+
+               // nonempty username
+               { pass: 0,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 username: "user",
+               },
+
+               // nonempty password
+               // XXXbz this passes for now, because we ignore passwords
+               // without usernames in most cases.
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 password: "password",
+               },
+
+               // Default allowed headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // Custom headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "X-My-Header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header": "secondValue" },
+                 allowHeaders: "x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my%-header": "myValue" },
+                 allowHeaders: "x-my%-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "" },
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "y-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header y-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, y-my-header z",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, y-my-he(ader",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "myheader": "" },
+                 allowMethods: "myheader",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "User-Agent": "myValue" },
+                 allowHeaders: "User-Agent",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "User-Agent": "myValue" },
+               },
+
+               // Multiple custom headers
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header, second-header, third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header,second-header,third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header ,second-header ,third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue",
+                            "third-header": "thirdValue" },
+                 allowHeaders: "x-my-header , second-header , third-header",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue" },
+                 allowHeaders: ",  x-my-header, , ,, second-header, ,   ",
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "second-header": "secondValue" },
+                 allowHeaders: "x-my-header, second-header, unused-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "myValue",
+                            "y-my-header": "secondValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "",
+                            "y-my-header": "" },
+                 allowHeaders: "x-my-header",
+               },
+
+               // HEAD requests
+               { pass: 1,
+                 method: "HEAD",
+                 noAllowPreflight: 1,
+               },
+
+               // HEAD with safe headers
+               { pass: 1,
+                 method: "HEAD",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // HEAD with custom headers
+               { pass: 1,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "y-my-header",
+               },
+               { pass: 0,
+                 method: "HEAD",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header y-my-header",
+               },
+
+               // POST tests
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+               },
+               { pass: 1,
+                 method: "POST",
+                 noAllowPreflight: 1,
+               },
+
+               // POST with standard headers
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "multipart/form-data" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "application/x-www-form-urlencoded" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+               },
+               { pass: 0,
+                 method: "POST",
+                 headers: { "Content-Type": "foo/bar" },
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain",
+                            "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar, text/plain" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+                 noAllowPreflight: 1,
+               },
+
+               // POST with custom headers
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Accept": "foo/bar",
+                            "Accept-Language": "sv-SE",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 headers: { "Content-Type": "text/plain",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, content-type",
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+                 noAllowPreflight: 1,
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar",
+                            "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "x-my-header": "myValue" },
+                 allowHeaders: "x-my-header, $_%",
+               },
+
+               // Other methods
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowHeaders: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST, PUT, DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST, DELETE, PUT",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE, POST, PUT",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST ,PUT ,DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST,PUT,DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "POST , PUT , DELETE",
+               },
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "  ,,  PUT ,,  ,    , DELETE  ,  ,",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETEZ",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE PUT",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE, PUT Z",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "DELETE, PU(T",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PUT Z, DELETE",
+               },
+               { pass: 0,
+                 method: "DELETE",
+                 allowMethods: "PU(T, DELETE",
+               },
+               { pass: 0,
+                 method: "MYMETHOD",
+                 allowMethods: "myMethod",
+               },
+               { pass: 0,
+                 method: "PUT",
+                 allowMethods: "put",
+               },
+
+               // Progress events
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 uploadProgress: "progress",
+               },
+               { pass: 0,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "text/plain" },
+                 uploadProgress: "progress",
+                 noAllowPreflight: 1,
+               },
+
+               // Status messages
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 404,
+                 statusMessage: "nothin' here",
+               },
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 401,
+                 statusMessage: "no can do",
+               },
+               { pass: 1,
+                 method: "POST",
+                 body: "hi there",
+                 headers: { "Content-Type": "foo/bar" },
+                 allowHeaders: "content-type",
+                 status: 500,
+                 statusMessage: "server boo",
+               },
+               { pass: 1,
+                 method: "GET",
+                 noAllowPreflight: 1,
+                 status: 200,
+                 statusMessage: "Yes!!",
+               },
+               { pass: 0,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 400
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 200
+               },
+               { pass: 1,
+                 method: "GET",
+                 headers: { "x-my-header": "header value" },
+                 allowHeaders: "x-my-header",
+                 preflightStatus: 204
+               },
+
+               // exposed headers
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header",
+                 expectedResponseHeaders: ["x-my-header"],
+               },
+               { pass: 0,
+                 method: "GET",
+                 origin: "http://invalid",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header y",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "y x-my-header",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header, y-my-header z",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header" },
+                 exposeHeaders: "x-my-header, y-my-hea(er",
+                 expectedResponseHeaders: [],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "x-my-header": "x header",
+                                    "y-my-header": "y header" },
+                 exposeHeaders: "  ,  ,,y-my-header,z-my-header,  ",
+                 expectedResponseHeaders: ["y-my-header"],
+               },
+               { pass: 1,
+                 method: "GET",
+                 responseHeaders: { "Cache-Control": "cacheControl header",
+                                    "Content-Language": "contentLanguage header",
+                                    "Expires":"expires header",
+                                    "Last-Modified":"lastModified header",
+                                    "Pragma":"pragma header",
+                                    "Unexpected":"unexpected header" },
+                 expectedResponseHeaders: ["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"],
+               },
+               // Check that sending a body in the OPTIONS response works
+               { pass: 1,
+                 method: "DELETE",
+                 allowMethods: "DELETE",
+                 preflightBody: "I'm a preflight response body",
+               },
+               ];
+
+  if (!runPreflightTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    var req = {
+      url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+      method: test.method,
+      headers: test.headers,
+      uploadProgress: test.uploadProgress,
+      body: test.body,
+      responseHeaders: test.responseHeaders,
+    };
+
+    if (test.pass) {
+       req.url += "&origin=" + escape(origin) +
+                  "&requestMethod=" + test.method;
+    }
+
+    if ("username" in test) {
+      req.username = test.username;
+    }
+
+    if ("password" in test) {
+      req.password = test.password;
+    }
+
+    if (test.noAllowPreflight)
+      req.url += "&noAllowPreflight";
+
+    if (test.pass && "headers" in test) {
+      function isUnsafeHeader(name) {
+        lName = name.toLowerCase();
+        return lName != "accept" &&
+               lName != "accept-language" &&
+               (lName != "content-type" ||
+                ["text/plain",
+                 "multipart/form-data",
+                 "application/x-www-form-urlencoded"]
+                   .indexOf(test.headers[name].toLowerCase()) == -1);
+      }
+      req.url += "&headers=" + escape(test.headers.toSource());
+      reqHeaders =
+        escape(Object.keys(test.headers)
+               .filter(isUnsafeHeader)
+               .map(s => s.toLowerCase())
+               .sort()
+               .join(","));
+      req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
+    }
+    if ("allowHeaders" in test)
+      req.url += "&allowHeaders=" + escape(test.allowHeaders);
+    if ("allowMethods" in test)
+      req.url += "&allowMethods=" + escape(test.allowMethods);
+    if (test.body)
+      req.url += "&body=" + escape(test.body);
+    if (test.status) {
+      req.url += "&status=" + test.status;
+      req.url += "&statusMessage=" + escape(test.statusMessage);
+    }
+    if (test.preflightStatus)
+      req.url += "&preflightStatus=" + test.preflightStatus;
+    if (test.responseHeaders)
+      req.url += "&responseHeaders=" + escape(test.responseHeaders.toSource());
+    if (test.exposeHeaders)
+      req.url += "&exposeHeaders=" + escape(test.exposeHeaders);
+    if (test.preflightBody)
+      req.url += "&preflightBody=" + escape(test.preflightBody);
+
+    loaderWindow.postMessage(req.toSource(), origin);
+    res = eval(yield);
+  }
+
+  // Test cookie behavior
+  tests = [{ pass: 1,
+             method: "GET",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 0,
+             method: "GET",
+             withCred: 1,
+             allowCred: 0,
+           },
+           { pass: 0,
+             method: "GET",
+             withCred: 1,
+             allowCred: 1,
+             origin: "*",
+           },
+           { pass: 1,
+             method: "GET",
+             withCred: 0,
+             allowCred: 1,
+             origin: "*",
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             noCookie: 1,
+             withCred: 0,
+             allowCred: 1,
+           },
+           { pass: 0,
+             method: "GET",
+             noCookie: 1,
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=2",
+             withCred: 0,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=1",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             setCookie: "a=2",
+             withCred: 1,
+             allowCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             cookie: "a=2",
+             withCred: 1,
+             allowCred: 1,
+           },
+           ];
+
+  if (!runCookieTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    req = {
+      url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+      method: test.method,
+      headers: test.headers,
+      withCred: test.withCred,
+    };
+
+    if (test.allowCred)
+      req.url += "&allowCred";
+
+    if (test.setCookie)
+      req.url += "&setCookie=" + escape(test.setCookie);
+    if (test.cookie)
+      req.url += "&cookie=" + escape(test.cookie);
+    if (test.noCookie)
+      req.url += "&noCookie";
+
+    if ("allowHeaders" in test)
+      req.url += "&allowHeaders=" + escape(test.allowHeaders);
+    if ("allowMethods" in test)
+      req.url += "&allowMethods=" + escape(test.allowMethods);
+
+    loaderWindow.postMessage(req.toSource(), origin);
+
+    res = eval(yield);
+  }
+
+  // Make sure to clear cookies to avoid affecting other tests
+  document.cookie = "a=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT"
+
+  // Test redirects
+
+  tests = [{ pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://example.org",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: "*"
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "x"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin
+                    },
+                    { server: "http://test2.example.org:8000",
+                      allowOrigin: origin
+                    },
+                    { server: "http://sub2.xn--lt-uia.example.org",
+                      allowOrigin: "*"
+                    },
+                    { server: "http://sub1.test1.example.org",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain" },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      noAllowPreflight: 1,
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowMethods: "DELETE",
+                      noAllowPreflight: 1,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                    },
+                    { server: "http://sub1.test1.example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+           { pass: 1,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain" },
+             hops: [{ server: "http://example.org",
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://example.org",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    ],
+           },
+
+           // test redirects with different credentials settings
+           {
+             // Initialize by setting a cookies for same- and cross- origins.
+             pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      setCookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowCred: 1,
+                      setCookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      noCookie: 1,
+                    },
+                    ],
+             withCred: 0,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      allowCred: 1,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           // expected fail because allow-credentials CORS header is not set
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: origin,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           { pass: 1,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: '*',
+                      noCookie: 1,
+                    },
+                    ],
+             withCred: 0,
+           },
+           { pass: 0,
+             method: "GET",
+             hops: [{ server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: origin,
+                      cookie: escape("a=1"),
+                    },
+                    { server: "http://example.com",
+                      allowOrigin: '*',
+                      allowCred: 1,
+                      cookie: escape("a=2"),
+                    },
+                    ],
+             withCred: 1,
+           },
+           ];
+
+  if (!runRedirectTests) {
+    tests = [];
+  }
+
+  for (test of tests) {
+    req = {
+      url: test.hops[0].server + basePath + "hop=1&hops=" +
+           escape(test.hops.toSource()),
+      method: test.method,
+      headers: test.headers,
+      body: test.body,
+      withCred: test.withCred,
+    };
+
+    if (test.pass) {
+      if (test.body)
+        req.url += "&body=" + escape(test.body);
+    }
+
+    loaderWindow.postMessage(req.toSource(), origin);
+
+    res = eval(yield);
+  }
+
+  document.location.href += "#finished";
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/dom/security/test/cors/head.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+function scopedCuImport(path) {
+  const scope = {};
+  Cu.import(path, scope);
+  return scope;
+}
+const {loader, require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
+const {TargetFactory} = require("devtools/client/framework/target");
+const {Utils: WebConsoleUtils} =
+  require("devtools/client/webconsole/utils");
+let { gDevTools } = require("devtools/client/framework/devtools");
+loader.lazyGetter(this, "HUDService", () => require("devtools/client/webconsole/webconsole"));
+loader.lazyGetter(this, "HUDService", () => require("devtools/client/webconsole/hudservice"));
+let promise = require("promise");
+
+/**
+ * Open the toolbox in a given tab.
+ * @param {XULNode} tab The tab the toolbox should be opened in.
+ * @param {String} toolId Optional. The ID of the tool to be selected.
+ * @param {String} hostType Optional. The type of toolbox host to be used.
+ * @return {Promise} Resolves with the toolbox, when it has been opened.
+ */
+var openToolboxForTab = Task.async(function* (tab, toolId, hostType) {
+  info("Opening the toolbox");
+
+  let toolbox;
+  let target = TargetFactory.forTab(tab);
+  yield target.makeRemote();
+
+  // Check if the toolbox is already loaded.
+  toolbox = gDevTools.getToolbox(target);
+  if (toolbox) {
+    if (!toolId || (toolId && toolbox.getPanel(toolId))) {
+      info("Toolbox is already opened");
+      return toolbox;
+    }
+  }
+
+  // If not, load it now.
+  toolbox = yield gDevTools.showToolbox(target, toolId, hostType);
+
+  // Make sure that the toolbox frame is focused.
+  yield new Promise(resolve => waitForFocus(resolve, toolbox.win));
+
+  info("Toolbox opened and focused");
+
+  return toolbox;
+});
+
+/**
+ * Find multiple messages in the output.
+ *
+ * @param object hud
+ *        The web console.
+ * @param string text
+ *        A substring that can be found in the message.
+ * @param selector [optional]
+ *        The selector to use in finding the message.
+ */
+function findMessages(hud, text, selector = ".message") {
+  const messages = hud.ui.experimentalOutputNode.querySelectorAll(selector);
+  const elements = Array.prototype.filter.call(
+    messages,
+    (el) => el.textContent.includes(text)
+  );
+  return elements;
+}
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -23,12 +23,13 @@ MOCHITEST_MANIFESTS += [
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
+    'cors/browser.ini',
     'csp/browser.ini',
     'general/browser.ini',
     'hsts/browser.ini',
 ]
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -49,16 +49,17 @@
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIEventTarget.h"
 #include "nsRedirectHistoryEntry.h"
 #include "nsSocketTransportService2.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
+#include "nsCORSListenerProxy.h"
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #endif
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
@@ -3593,10 +3594,27 @@ HttpChannelChild::ActorDestroy(ActorDest
   // OnStartRequest might be dropped if IPDL is destroyed abnormally
   // and BackgroundChild might have pending IPC messages.
   // Clean up BackgroundChild at this time to prevent memleak.
   if (aWhy != Deletion) {
     CleanupBackgroundChannel();
   }
 }
 
+mozilla::ipc::IPCResult
+HttpChannelChild::RecvLogBlockedCORSRequest(const nsString& aMessage)
+{
+  Unused << LogBlockedCORSRequest(aMessage);
+  return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString & aMessage)
+{
+  if (mLoadInfo) {
+    uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+    nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, aMessage);
+  }
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -174,16 +174,19 @@ protected:
 
   nsresult
   AsyncCall(void (HttpChannelChild::*funcPtr)(),
             nsRunnableMethod<HttpChannelChild> **retval = nullptr) override;
 
   // Get event target for processing network events.
   already_AddRefed<nsIEventTarget> GetNeckoTarget() override;
 
+  virtual mozilla::ipc::IPCResult RecvLogBlockedCORSRequest(const nsString& aMessage) override;
+  NS_IMETHOD LogBlockedCORSRequest(const nsAString & aMessage) override;
+
 private:
   // this section is for main-thread-only object
   // all the references need to be proxy released on main thread.
   nsCOMPtr<nsISupports> mCacheKey;
 
   // Proxy release all members above on main thread.
   void ReleaseMainThreadOnlyReferences();
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -227,16 +227,22 @@ HttpChannelParent::CleanupBackgroundChan
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mBgParent) {
     RefPtr<HttpBackgroundChannelParent> bgParent = mBgParent.forget();
     bgParent->OnChannelClosed();
     return;
   }
 
+  // The nsHttpChannel may have a reference to this parent, release it
+  // to avoid circular references.
+  if (mChannel) {
+    mChannel->SetWarningReporter(nullptr);
+  }
+
   if (!mPromise.IsEmpty()) {
     mRequest.DisconnectIfExists();
     mPromise.Reject(NS_ERROR_FAILURE, __func__);
 
     if (!mChannel) {
       return;
     }
 
@@ -800,16 +806,18 @@ HttpChannelParent::ConnectChannel(const 
   LOG(("  found channel %p, rv=%08" PRIx32, channel.get(), static_cast<uint32_t>(rv)));
   mChannel = do_QueryObject(channel);
   if (!mChannel) {
     LOG(("  but it's not nsHttpChannel"));
     Delete();
     return true;
   }
 
+  mChannel->SetWarningReporter(this);
+
   nsCOMPtr<nsINetworkInterceptController> controller;
   NS_QueryNotificationCallbacks(channel, controller);
   RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
   MOZ_ASSERT(parentListener);
   parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
 
   if (mPBOverride != kPBOverride_Unset) {
     // redirected-to channel may not support PB
@@ -1539,16 +1547,18 @@ HttpChannelParent::OnStopRequest(nsIRequ
   mChannel->GetEncodedBodySize(&timing.encodedBodySize);
   // decodedBodySize can be computed in the child process so it doesn't need
   // to be passed down.
   mChannel->GetProtocolVersion(timing.protocolVersion);
 
   mChannel->GetCacheReadStart(&timing.cacheReadStart);
   mChannel->GetCacheReadEnd(&timing.cacheReadEnd);
 
+  mChannel->SetWarningReporter(nullptr);
+
   // Either IPC channel is closed or background channel
   // is ready to send OnStopRequest.
   MOZ_ASSERT(mIPCClosed || mBgParent);
 
   if (mIPCClosed ||
       !mBgParent || !mBgParent->OnStopRequest(aStatusCode, timing)) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -2222,10 +2232,20 @@ HttpChannelParent::ReadyToVerify(nsresul
 void
 HttpChannelParent::DoSendSetPriority(int16_t aValue)
 {
   if (!mIPCClosed) {
     Unused << SendSetPriority(aValue);
   }
 }
 
+nsresult
+HttpChannelParent::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  if (mIPCClosed ||
+      NS_WARN_IF(!SendLogBlockedCORSRequest(nsString(aMessage)))) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -209,16 +209,17 @@ protected:
   void FailDiversion(nsresult aErrorCode);
 
   friend class HttpChannelParentListener;
   RefPtr<mozilla::dom::TabParent> mTabParent;
 
   MOZ_MUST_USE nsresult
   ReportSecurityMessage(const nsAString& aMessageTag,
                         const nsAString& aMessageCategory) override;
+  nsresult LogBlockedCORSRequest(const nsAString& aMessage) override;
 
   // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
   // send any more messages after that. Bug 1274886
   MOZ_MUST_USE bool DoSendDeleteSelf();
   // Called to notify the parent channel to not send any more IPC messages.
   virtual mozilla::ipc::IPCResult RecvDeletingChannel() override;
   virtual mozilla::ipc::IPCResult RecvFinishInterceptedRedirect() override;
 
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -849,16 +849,22 @@ NullHttpChannel::GetIsMainDocumentChanne
 }
 
 NS_IMETHODIMP
 NullHttpChannel::SetIsMainDocumentChannel(bool aValue)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP
+NullHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 #define IMPL_TIMING_ATTR(name)                                 \
 NS_IMETHODIMP                                                  \
 NullHttpChannel::Get##name##Time(PRTime* _retval) {            \
     TimeStamp stamp;                                           \
     Get##name(&stamp);                                         \
     if (stamp.IsNull()) {                                      \
         *_retval = 0;                                          \
         return NS_OK;                                          \
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -136,16 +136,21 @@ child:
 
   // Tell child to delete channel (all IPDL deletes must be done from child to
   // avoid races: see bug 591708).
   async DeleteSelf();
 
   // Tell the child to issue a deprecation warning.
   async IssueDeprecationWarning(uint32_t warning, bool asError);
 
+  // When CORS blocks the request in the parent process, it doesn't have the
+  // correct window ID, so send the message to the child for logging to the web
+  // console.
+  async LogBlockedCORSRequest(nsString message);
+
 both:
   // After receiving this message, the parent also calls
   // SendFinishInterceptedRedirect, and makes sure not to send any more messages
   // after that. When receiving this message, the child will call
   // Send__delete__() and complete the steps required to finish the redirect.
   async FinishInterceptedRedirect();
 
   async SetPriority(int16_t priority);
--- a/netwerk/protocol/http/nsCORSListenerProxy.cpp
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -37,48 +37,37 @@
 #include "nsIConsoleService.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsINetworkInterceptController.h"
 #include "NullPrincipal.h"
 #include "nsICorsPreflightCallback.h"
 #include "nsISupportsImpl.h"
+#include "nsHttpChannel.h"
 #include "mozilla/LoadInfo.h"
 #include "nsIHttpHeaderVisitor.h"
+#include "nsQueryObject.h"
 #include <algorithm>
 
 using namespace mozilla;
 
 #define PREFLIGHT_CACHE_SIZE 100
 
 static bool gDisableCORS = false;
 static bool gDisableCORSPrivateData = false;
 
 static void
 LogBlockedRequest(nsIRequest* aRequest,
                   const char* aProperty,
-                  const char16_t* aParam)
+                  const char16_t* aParam,
+                  nsIHttpChannel* aCreatingChannel)
 {
   nsresult rv = NS_OK;
 
-  // Build the error object and log it to the console
-  nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (no console)");
-    return;
-  }
-
-  nsCOMPtr<nsIScriptError> scriptError =
-    do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
-    return;
-  }
-
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIURI> aUri;
   channel->GetURI(getter_AddRefs(aUri));
   nsAutoCString spec;
   if (aUri) {
     spec = aUri->GetSpecOrDefault();
   }
 
@@ -93,44 +82,29 @@ LogBlockedRequest(nsIRequest* aRequest,
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
     return;
   }
 
   nsAutoString msg(blockedMessage.get());
 
-  // query innerWindowID and log to web console, otherwise log to
-  // the error to the browser console.
-  uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
-
-  if (innerWindowID > 0) {
-    rv = scriptError->InitWithWindowID(msg,
-                                       EmptyString(), // sourceName
-                                       EmptyString(), // sourceLine
-                                       0,             // lineNumber
-                                       0,             // columnNumber
-                                       nsIScriptError::warningFlag,
-                                       "CORS",
-                                       innerWindowID);
+  if (XRE_IsParentProcess()) {
+    if (aCreatingChannel) {
+      rv = aCreatingChannel->LogBlockedCORSRequest(msg);
+      if (NS_SUCCEEDED(rv)) {
+        return;
+      }
+    }
+    NS_WARNING("Failed to log blocked cross-site request to web console from parent->child, falling back to browser console");
   }
-  else {
-    rv = scriptError->Init(msg,
-                           EmptyString(), // sourceName
-                           EmptyString(), // sourceLine
-                           0,             // lineNumber
-                           0,             // columnNumber
-                           nsIScriptError::warningFlag,
-                           "CORS");
-  }
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
-    return;
-  }
-  console->LogMessage(scriptError);
+
+  // log message ourselves
+  uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+  nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, msg);
 }
 
 //////////////////////////////////////////////////////////////////////////
 // Preflight cache
 
 class nsPreflightCache
 {
 public:
@@ -446,16 +420,17 @@ nsCORSListenerProxy::Init(nsIChannel* aC
   aChannel->SetNotificationCallbacks(this);
 
   nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
   if (NS_FAILED(rv)) {
     mOuterListener = nullptr;
     mRequestingPrincipal = nullptr;
     mOriginHeaderPrincipal = nullptr;
     mOuterNotificationCallbacks = nullptr;
+    mHttpChannel = nullptr;
   }
 #ifdef DEBUG
   mInited = true;
 #endif
   return rv;
 }
 
 NS_IMETHODIMP
@@ -535,19 +510,21 @@ NS_IMPL_ISUPPORTS(CheckOriginHeader, nsI
 
 nsresult
 nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
 {
   // Check if this was actually a cross domain request
   if (!mHasBeenCrossSite) {
     return NS_OK;
   }
+  nsCOMPtr<nsIHttpChannel> topChannel;
+  topChannel.swap(mHttpChannel);
 
   if (gDisableCORS) {
-    LogBlockedRequest(aRequest, "CORSDisabled", nullptr);
+    LogBlockedRequest(aRequest, "CORSDisabled", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   // Check if the request failed
   nsresult status;
   nsresult rv = aRequest->GetStatus(&status);
   if (NS_FAILED(rv)) {
    return rv;
@@ -555,17 +532,17 @@ nsCORSListenerProxy::CheckRequestApprove
 
   if (NS_FAILED(status)) {
     return status;
   }
 
   // Test that things worked on a HTTP level
   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   if (!http) {
-    LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr);
+    LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
   NS_ENSURE_STATE(internal);
   bool responseSynthesized = false;
   if (NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized)) &&
       responseSynthesized) {
@@ -577,76 +554,77 @@ nsCORSListenerProxy::CheckRequestApprove
 
   // Check the Access-Control-Allow-Origin header
   RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
   nsAutoCString allowedOriginHeader;
 
   // check for duplicate headers
   rv = http->VisitOriginalResponseHeaders(visitor);
   if (NS_FAILED(rv)) {
-    LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr);
+    LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr, topChannel);
     return rv;
   }
 
   rv = http->GetResponseHeader(
     NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
   if (NS_FAILED(rv)) {
-    LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr);
+    LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr, topChannel);
     return rv;
   }
 
   // Bug 1210985 - Explicitly point out the error that the credential is
   // not supported if the allowing origin is '*'. Note that this check
   // has to be done before the condition
   //
   // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
   //
   // below since "if (A && B)" is included in "if (A || !B)".
   //
   if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
-    LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr);
+    LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr, topChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
     MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
     nsAutoCString origin;
     nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
 
     if (!allowedOriginHeader.Equals(origin)) {
       LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin",
-                        NS_ConvertUTF8toUTF16(allowedOriginHeader).get());
+                        NS_ConvertUTF8toUTF16(allowedOriginHeader).get(), topChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   // Check Access-Control-Allow-Credentials header
   if (mWithCredentials) {
     nsAutoCString allowCredentialsHeader;
     rv = http->GetResponseHeader(
       NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
 
     if (!allowCredentialsHeader.EqualsLiteral("true")) {
-      LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr);
+      LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr, topChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
                                    nsISupports* aContext,
                                    nsresult aStatusCode)
 {
   MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
   nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
   mOuterListener = nullptr;
   mOuterNotificationCallbacks = nullptr;
+  mHttpChannel = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
 nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
                                      nsISupports* aContext,
                                      nsIInputStream* aInputStream,
                                      uint64_t aOffset,
@@ -989,16 +967,18 @@ nsCORSListenerProxy::UpdateChannel(nsICh
     rv = http->GetLoadFlags(&flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     flags |= nsIRequest::LOAD_ANONYMOUS;
     rv = http->SetLoadFlags(flags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  mHttpChannel = http;
+
   return NS_OK;
 }
 
 nsresult
 nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel, UpdateType aUpdateType)
 {
   // If this caller isn't using AsyncOpen2, or if this *is* a preflight channel,
   // then we shouldn't initiate preflight for this channel.
@@ -1305,21 +1285,22 @@ nsCORSPreflightListener::CheckPreflightR
   nsresult rv = aRequest->GetStatus(&status);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_SUCCESS(status, status);
 
   // Test that things worked on a HTTP level
   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
   NS_ENSURE_STATE(internal);
+  nsCOMPtr<nsIHttpChannel> parentHttpChannel = do_QueryInterface(mCallback);
 
   bool succeedded;
   rv = http->GetRequestSucceeded(&succeedded);
   if (NS_FAILED(rv) || !succeedded) {
-    LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr);
+    LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr, parentHttpChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsAutoCString headerVal;
   // The "Access-Control-Allow-Methods" header contains a comma separated
   // list of method names.
   Unused << http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
                                     headerVal);
@@ -1329,49 +1310,49 @@ nsCORSPreflightListener::CheckPreflightR
   nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
   while(methodTokens.hasMoreTokens()) {
     const nsDependentCSubstring& method = methodTokens.nextToken();
     if (method.IsEmpty()) {
       continue;
     }
     if (!NS_IsValidHTTPToken(method)) {
       LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
-                        NS_ConvertUTF8toUTF16(method).get());
+                        NS_ConvertUTF8toUTF16(method).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
     foundMethod |= mPreflightMethod.Equals(method);
   }
   if (!foundMethod) {
-    LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr);
+    LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr, parentHttpChannel);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   // The "Access-Control-Allow-Headers" header contains a comma separated
   // list of header names.
   Unused << http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
                                     headerVal);
   nsTArray<nsCString> headers;
   nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
   while(headerTokens.hasMoreTokens()) {
     const nsDependentCSubstring& header = headerTokens.nextToken();
     if (header.IsEmpty()) {
       continue;
     }
     if (!NS_IsValidHTTPToken(header)) {
       LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
-                        NS_ConvertUTF8toUTF16(header).get());
+                        NS_ConvertUTF8toUTF16(header).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
     headers.AppendElement(header);
   }
   for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
     if (!headers.Contains(mPreflightHeaders[i],
                           nsCaseInsensitiveCStringArrayComparator())) {
       LogBlockedRequest(aRequest, "CORSMissingAllowHeaderFromPreflight",
-                        NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get());
+                        NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(), parentHttpChannel);
       return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -1391,26 +1372,28 @@ nsCORSListenerProxy::RemoveFromCorsPrefl
                                                   nsIPrincipal* aRequestingPrincipal)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   if (sPreflightCache) {
     sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal);
   }
 }
 
+// static
 nsresult
 nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel,
                                         nsICorsPreflightCallback* aCallback,
                                         nsTArray<nsCString>& aUnsafeHeaders,
                                         nsIChannel** aPreflightChannel)
 {
   *aPreflightChannel = nullptr;
 
   if (gDisableCORS) {
-    LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr);
+    nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequestChannel);
+    LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr, http);
     return NS_ERROR_DOM_BAD_URI;
   }
 
   nsAutoCString method;
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
   NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
   Unused << httpChannel->GetRequestMethod(method);
 
@@ -1496,16 +1479,23 @@ nsCORSListenerProxy::StartCORSPreflight(
   rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = preHttp->
     SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
                      method, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Set the CORS preflight channel's warning reporter to be the same as the
+  // requesting channel so that all log messages are able to be reported through
+  // the warning reporter.
+  RefPtr<nsHttpChannel> reqCh = do_QueryObject(aRequestChannel);
+  RefPtr<nsHttpChannel> preCh = do_QueryObject(preHttp);
+  preCh->SetWarningReporter(reqCh->GetWarningReporter());
+
   nsTArray<nsCString> preflightHeaders;
   if (!aUnsafeHeaders.IsEmpty()) {
     for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
       preflightHeaders.AppendElement();
       ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
     }
     preflightHeaders.Sort();
     nsAutoCString headers;
@@ -1533,8 +1523,57 @@ nsCORSListenerProxy::StartCORSPreflight(
   rv = preflightChannel->AsyncOpen2(preflightListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return newly created preflight channel
   preflightChannel.forget(aPreflightChannel);
 
   return NS_OK;
 }
+
+// static
+void
+nsCORSListenerProxy::LogBlockedCORSRequest(uint64_t aInnerWindowID,
+                                           const nsAString& aMessage)
+{
+  nsresult rv = NS_OK;
+
+  // Build the error object and log it to the console
+  nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (no console)");
+    return;
+  }
+
+  nsCOMPtr<nsIScriptError> scriptError =
+    do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
+    return;
+  }
+
+  // query innerWindowID and log to web console, otherwise log to
+  // the error to the browser console.
+  if (aInnerWindowID > 0) {
+    rv = scriptError->InitWithWindowID(aMessage,
+                                       EmptyString(), // sourceName
+                                       EmptyString(), // sourceLine
+                                       0,             // lineNumber
+                                       0,             // columnNumber
+                                       nsIScriptError::warningFlag,
+                                       "CORS",
+                                       aInnerWindowID);
+  }
+  else {
+    rv = scriptError->Init(aMessage,
+                           EmptyString(), // sourceName
+                           EmptyString(), // sourceLine
+                           0,             // lineNumber
+                           0,             // columnNumber
+                           nsIScriptError::warningFlag,
+                           "CORS");
+  }
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
+    return;
+  }
+  console->LogMessage(scriptError);
+}
--- a/netwerk/protocol/http/nsCORSListenerProxy.h
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -65,16 +65,20 @@ public:
 
   static void Shutdown();
 
   MOZ_MUST_USE nsresult Init(nsIChannel* aChannel,
                              DataURIHandling aAllowDataURI);
 
   void SetInterceptController(nsINetworkInterceptController* aInterceptController);
 
+  // When CORS blocks a request, log the message to the web console, or the
+  // browser console if no valid inner window ID is found.
+  static void LogBlockedCORSRequest(uint64_t aInnerWindowID,
+                                    const nsAString& aMessage);
 private:
   // Only HttpChannelParent can call RemoveFromCorsPreflightCache
   friend class mozilla::net::HttpChannelParent;
   // Only nsHttpChannel can invoke CORS preflights
   friend class mozilla::net::nsHttpChannel;
 
   static void RemoveFromCorsPreflightCache(nsIURI* aURI,
                                            nsIPrincipal* aRequestingPrincipal);
@@ -103,14 +107,18 @@ private:
   nsCOMPtr<nsINetworkInterceptController> mInterceptController;
   bool mWithCredentials;
   bool mRequestApproved;
   // Please note that the member variable mHasBeenCrossSite may rely on the
   // promise that the CSP directive 'upgrade-insecure-requests' upgrades
   // an http: request to https: in nsHttpChannel::Connect() and hence
   // a request might not be marked as cross site request based on that promise.
   bool mHasBeenCrossSite;
+  // Under e10s, logging happens in the child process. Keep a reference to the
+  // creator nsIHttpChannel in order to find the way back to the child. Released
+  // in OnStopRequest().
+  nsCOMPtr<nsIHttpChannel> mHttpChannel;
 #ifdef DEBUG
   bool mInited;
 #endif
 };
 
 #endif
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -411,16 +411,25 @@ nsHttpChannel::AddSecurityMessage(const 
     if (mWarningReporter) {
         return mWarningReporter->ReportSecurityMessage(aMessageTag,
                                                        aMessageCategory);
     }
     return HttpBaseChannel::AddSecurityMessage(aMessageTag,
                                                aMessageCategory);
 }
 
+NS_IMETHODIMP
+nsHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+    if (mWarningReporter) {
+        return mWarningReporter->LogBlockedCORSRequest(aMessage);
+    }
+    return NS_ERROR_UNEXPECTED;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel <private>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpChannel::Connect()
 {
     nsresult rv;
@@ -853,16 +862,17 @@ nsHttpChannel::DoNotifyListenerCleanup()
     CleanRedirectCacheChainIfNecessary();
 }
 
 void
 nsHttpChannel::ReleaseListeners()
 {
     HttpBaseChannel::ReleaseListeners();
     mChannelClassifier = nullptr;
+    mWarningReporter = nullptr;
 }
 
 void
 nsHttpChannel::HandleAsyncRedirect()
 {
     NS_PRECONDITION(!mCallOnResume, "How did that happen?");
 
     if (mSuspendCount) {
@@ -5917,16 +5927,17 @@ nsHttpChannel::Cancel(nsresult status)
     MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
 
     LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n",
          this, static_cast<uint32_t>(status)));
     if (mCanceled) {
         LOG(("  ignoring; already canceled\n"));
         return NS_OK;
     }
+
     if (mWaitingForRedirectCallback) {
         LOG(("channel canceled during wait for redirect callback"));
     }
     mCanceled = true;
     mStatus = status;
     if (mProxyRequest)
         mProxyRequest->Cancel(status);
     CancelNetworkRequest(status);
@@ -9245,10 +9256,24 @@ nsHttpChannel::Notify(nsITimer *aTimer)
         return TriggerNetwork(0);
     } else {
         MOZ_CRASH("Unknown timer");
     }
 
     return NS_OK;
 }
 
+void
+nsHttpChannel::SetWarningReporter(HttpChannelSecurityWarningReporter *aReporter)
+{
+    LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
+    mWarningReporter = aReporter;
+}
+
+HttpChannelSecurityWarningReporter*
+nsHttpChannel::GetWarningReporter()
+{
+    LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this, mWarningReporter.get()));
+    return mWarningReporter.get();
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -38,22 +38,23 @@ class nsIHttpChannelAuthProvider;
 class nsInputStreamPump;
 class nsISSLStatus;
 
 namespace mozilla { namespace net {
 
 class nsChannelClassifier;
 class Http2PushedStream;
 
-class HttpChannelSecurityWarningReporter
+class HttpChannelSecurityWarningReporter : public nsISupports
 {
 public:
   virtual MOZ_MUST_USE nsresult
   ReportSecurityMessage(const nsAString& aMessageTag,
                         const nsAString& aMessageCategory) = 0;
+  virtual nsresult LogBlockedCORSRequest(const nsAString& aMessage) = 0;
 };
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel
 //-----------------------------------------------------------------------------
 
 // Use to support QI nsIChannel to nsHttpChannel
 #define NS_HTTPCHANNEL_IID                         \
@@ -185,20 +186,20 @@ public:
     NS_IMETHOD GetResponseEnd(mozilla::TimeStamp *aResponseEnd) override;
     // nsICorsPreflightCallback
     NS_IMETHOD OnPreflightSucceeded() override;
     NS_IMETHOD OnPreflightFailed(nsresult aError) override;
 
     MOZ_MUST_USE nsresult
     AddSecurityMessage(const nsAString& aMessageTag,
                        const nsAString& aMessageCategory) override;
+    NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage) override;
 
-    void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter)
-      { mWarningReporter = aReporter; }
-
+    void SetWarningReporter(HttpChannelSecurityWarningReporter *aReporter);
+    HttpChannelSecurityWarningReporter* GetWarningReporter();
 public: /* internal necko use only */
 
     using InitLocalBlockListCallback = std::function<void(bool)>;
 
     void InternalSetUploadStream(nsIInputStream *uploadStream)
       { mUploadStream = uploadStream; }
     void SetUploadStreamHasHeaders(bool hasHeaders)
       { mUploadStreamHasHeaders = hasHeaders; }
@@ -667,17 +668,17 @@ private:
 
     MOZ_MUST_USE nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
     nsCString mUsername;
 
     // If non-null, warnings should be reported to this object.
-    HttpChannelSecurityWarningReporter* mWarningReporter;
+    RefPtr<HttpChannelSecurityWarningReporter> mWarningReporter;
 
     RefPtr<ADivertableParentChannel> mParentChannel;
 
     // True if the channel is reading from cache.
     Atomic<bool> mIsReadingFromCache;
 
     // These next members are only used in unit tests to delay the call to
     // cache->AsyncOpenURI in order to race the cache with the network.
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -473,9 +473,19 @@ interface nsIHttpChannel : nsIChannel
     /**
      * ID of the top-level outer content window. Identifies this channel's
      * top-level window it comes from.
      *
      * NOTE: The setter of this attribute is currently for xpcshell test only.
      *       Don't alter it otherwise.
      */
     [must_use] attribute uint64_t topLevelOuterContentWindowId;
+
+    /**
+     * In e10s, the information that the CORS response blocks the load is in the
+     * parent, which doesn't know the true window id of the request, so we may
+     * need to proxy the request to the child.
+     *
+     * @param aMessage
+     *        The message to print in the console.
+     */
+    void logBlockedCORSRequest(in AString aMessage);
 };
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -1039,8 +1039,18 @@ nsViewSourceChannel::SetIsMainDocumentCh
 }
 
 // Have to manually forward SetCorsPreflightParameters since it's [notxpcom]
 void
 nsViewSourceChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
 {
   mHttpChannelInternal->SetCorsPreflightParameters(aUnsafeHeaders);
 }
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogBlockedCORSRequest(const nsAString& aMessage)
+{
+  if (!mHttpChannel) {
+    NS_WARNING("nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
+    return NS_ERROR_UNEXPECTED;
+  }
+  return mHttpChannel->LogBlockedCORSRequest(aMessage);
+}