Bug 1459640 - add CSP also via meta tag, r=bgrins draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 08 May 2018 12:25:47 +0100
changeset 794236 89c7f8a6e1bb77ceb242f05749c22d929ee86929
parent 794135 21f09d7e7214eaebf1e0980494159bd846e1bdd9
push id109619
push usergijskruitbosch@gmail.com
push dateFri, 11 May 2018 15:50:36 +0000
reviewersbgrins
bugs1459640
milestone62.0a1
Bug 1459640 - add CSP also via meta tag, r=bgrins This also fixes all the tests to not rely on eval() anymore. MozReview-Commit-ID: 8H8r497nQVK
devtools/client/jsonview/converter-child.js
devtools/client/jsonview/test/browser_jsonview_content_type.js
devtools/client/jsonview/test/browser_jsonview_row_selection.js
devtools/client/jsonview/test/doc_frame_script.js
devtools/client/jsonview/test/head.js
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -17,16 +17,18 @@ loader.lazyGetter(this, "debugJsModules"
   return !!(AppConstants.DEBUG_JS_MODULES);
 });
 
 const BinaryInput = CC("@mozilla.org/binaryinputstream;1",
                        "nsIBinaryInputStream", "setInputStream");
 const BufferStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
                        "nsIArrayBufferInputStream", "setData");
 
+const kCSP = "default-src 'none' ; script-src resource:; ";
+
 // Localization
 loader.lazyGetter(this, "jsonViewStrings", () => {
   return Services.strings.createBundle(
     "chrome://devtools/locale/jsonview.properties");
 });
 
 /**
  * This object detects 'application/vnd.mozilla.json.view' content type
@@ -79,18 +81,18 @@ Converter.prototype = {
     request.QueryInterface(Ci.nsIChannel);
     request.contentType = "text/html";
 
     let headers = getHttpHeaders(request);
 
     // Enforce strict CSP:
     try {
       request.QueryInterface(Ci.nsIHttpChannel);
-      request.setResponseHeader("Content-Security-Policy",
-        "default-src 'none' ; script-src resource:; ", false);
+      request.setResponseHeader("Content-Security-Policy", kCSP, false);
+      request.setResponseHeader("Content-Security-Policy-Report-Only", "", false);
     } catch (ex) {
       // If this is not an HTTP channel we can't and won't do anything.
     }
 
     // Don't honor the charset parameter and use UTF-8 (see bug 741776).
     request.contentCharset = "UTF-8";
     this.decoder = new TextDecoder("UTF-8");
 
@@ -243,16 +245,20 @@ function initialHTML(doc) {
 
   return "<!DOCTYPE html>\n" +
     element("html", {
       "platform": os,
       "class": "theme-" + Services.prefs.getCharPref("devtools.theme"),
       "dir": Services.locale.isAppLocaleRTL ? "rtl" : "ltr"
     }, [
       element("head", {}, [
+        element("meta", {
+          "http-equiv": "Content-Security-Policy",
+          content: kCSP,
+        }),
         element("link", {
           rel: "stylesheet",
           type: "text/css",
           href: baseURI + "css/main.css",
         }),
       ]),
       element("body", {}, [
         element("div", {"id": "content"}, [
--- a/devtools/client/jsonview/test/browser_jsonview_content_type.js
+++ b/devtools/client/jsonview/test/browser_jsonview_content_type.js
@@ -65,16 +65,18 @@ add_task(async function() {
     SpecialPowers.setBoolPref("browser.download.useDownloadDir", useDownloadDir);
   });
 });
 
 function testType(isValid, type, params = "") {
   const TEST_JSON_URL = "data:" + type + params + ",[1,2,3]";
   return addJsonViewTab(TEST_JSON_URL).then(async function() {
     ok(isValid, "The JSON Viewer should only load for valid content types.");
-    is(await evalInContent("document.contentType"), type, "Got the right content type");
+    await ContentTask.spawn(gBrowser.selectedBrowser, type, function(contentType) {
+      is(content.document.contentType, contentType, "Got the right content type");
+    });
 
     let count = await getElementCount(".jsonPanelBox .treeTable .treeRow");
     is(count, 3, "There must be expected number of rows");
   }, function() {
     ok(!isValid, "The JSON Viewer should only not load for invalid content types.");
   });
 }
--- a/devtools/client/jsonview/test/browser_jsonview_row_selection.js
+++ b/devtools/client/jsonview/test/browser_jsonview_row_selection.js
@@ -10,37 +10,47 @@ add_task(async function() {
 
   // Create a tall JSON so that there is a scrollbar.
   let numRows = 1e3;
   let json = JSON.stringify(Array(numRows).fill().map((_, i) => i));
   let tab = await addJsonViewTab("data:application/json," + json);
 
   is(await getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
   await assertRowSelected(null);
-  await evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
-  ok(await evalInContent("scroller.clientHeight < scroller.scrollHeight"),
-     "There is a scrollbar.");
-  is(await evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    ok(scroller.clientHeight < scroller.scrollHeight, "There is a scrollbar.");
+    is(scroller.scrollTop, 0, "Initially scrolled to the top.");
 
-  // Click to select last row.
-  await evalInContent("$('.treeRow:last-child').click()");
+    // Click to select last row.
+    content.document.querySelector(".treeRow:last-child").click();
+  });
   await assertRowSelected(numRows);
-  is(await evalInContent("scroller.scrollTop + scroller.clientHeight"),
-     await evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
 
-  // Click to select 2nd row.
-  await evalInContent("$('.treeRow:nth-child(2)').click()");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    is(scroller.scrollTop + scroller.clientHeight, scroller.scrollHeight,
+       "Scrolled to the bottom.");
+    // Click to select 2nd row.
+    content.document.querySelector(".treeRow:nth-child(2)").click();
+  });
   await assertRowSelected(2);
-  ok(await evalInContent("scroller.scrollTop > 0"), "Not scrolled to the top.");
 
-  // Synthetize up arrow key to select first row.
-  await evalInContent("$('.treeTable').focus()");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    ok(scroller.scrollTop > 0, "Not scrolled to the top.");
+    // Synthetize up arrow key to select first row.
+    content.document.querySelector(".treeTable").focus();
+  });
   await BrowserTestUtils.synthesizeKey("VK_UP", {}, tab.linkedBrowser);
   await assertRowSelected(1);
-  is(await evalInContent("scroller.scrollTop"), 0, "Scrolled to the top.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    is(scroller.scrollTop, 0, "Scrolled to the top.");
+  });
 });
 
 add_task(async function() {
   info("Test 2 JSON row selection started");
 
   let numRows = 4;
   let tab = await addJsonViewTab("data:application/json,[0,1,2,3]");
 
@@ -66,52 +76,73 @@ add_task(async function() {
   info("Test 3 JSON row selection started");
 
   // Create a JSON with a row taller than the panel.
   let json = JSON.stringify([0, "a ".repeat(1e4), 1]);
   await addJsonViewTab("data:application/json," + encodeURI(json));
 
   is(await getElementCount(".treeRow"), 3, "Got the expected number of rows.");
   await assertRowSelected(null);
-  await evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
-  await evalInContent("var row = $('.treeRow:nth-child(2)')");
-  ok(await evalInContent("scroller.clientHeight < row.clientHeight"),
-     "The row is taller than the scroller.");
-  is(await evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    let row = content.document.querySelector(".treeRow:nth-child(2)");
+    ok(scroller.clientHeight < row.clientHeight, "The row is taller than the scroller.");
+    is(scroller.scrollTop, 0, "Initially scrolled to the top.");
 
-  // Select the tall row.
-  await evalInContent("row.click()");
+    // Select the tall row.
+    row.click();
+  });
   await assertRowSelected(2);
-  is(await evalInContent("scroller.scrollTop"), await evalInContent("row.offsetTop"),
-     "Scrolled to the top of the row.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    let row = content.document.querySelector(".treeRow:nth-child(2)");
+    is(scroller.scrollTop, row.offsetTop,
+       "Scrolled to the top of the row.");
+  });
 
   // Select the last row.
-  await evalInContent("$('.treeRow:last-child').click()");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    content.document.querySelector(".treeRow:last-child").click();
+  });
   await assertRowSelected(3);
-  is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
-     await evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    is(scroller.scrollTop + scroller.offsetHeight,
+       scroller.scrollHeight, "Scrolled to the bottom.");
 
-  // Select the tall row.
-  await evalInContent("row.click()");
-  await assertRowSelected(2);
-  is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
-     await evalInContent("row.offsetTop + row.offsetHeight"),
-     "Scrolled to the bottom of the row.");
+    // Select the tall row.
+    let row = content.document.querySelector(".treeRow:nth-child(2)");
+    row.click();
+  });
 
-  // Scroll up a bit, so that both the top and bottom of the row are not visible.
-  let scroll = await evalInContent(
-    "scroller.scrollTop = Math.ceil((scroller.scrollTop + row.offsetTop) / 2)");
-  ok(await evalInContent("scroller.scrollTop > row.offsetTop"),
-     "The top of the row is not visible.");
-  ok(await evalInContent("scroller.scrollTop + scroller.offsetHeight")
-     < await evalInContent("row.offsetTop + row.offsetHeight"),
-     "The bottom of the row is not visible.");
+  await assertRowSelected(2);
+  let scroll = await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    let row = content.document.querySelector(".treeRow:nth-child(2)");
+    is(scroller.scrollTop + scroller.offsetHeight, row.offsetTop + row.offsetHeight,
+       "Scrolled to the bottom of the row.");
 
-  // Select the tall row.
-  await evalInContent("row.click()");
+    // Scroll up a bit, so that both the top and bottom of the row are not visible.
+    let scrollPos =
+      scroller.scrollTop = Math.ceil((scroller.scrollTop + row.offsetTop) / 2);
+    ok(scroller.scrollTop > row.offsetTop,
+       "The top of the row is not visible.");
+    ok(scroller.scrollTop + scroller.offsetHeight < row.offsetTop + row.offsetHeight,
+       "The bottom of the row is not visible.");
+
+    // Select the tall row.
+    row.click();
+    return scrollPos;
+  });
   await assertRowSelected(2);
-  is(await evalInContent("scroller.scrollTop"), scroll, "Scroll did not change");
+  await ContentTask.spawn(gBrowser.selectedBrowser, scroll, function(scrollPos) {
+    let scroller = content.document.querySelector(".jsonPanelBox .panelContent");
+    is(scroller.scrollTop, scrollPos, "Scroll did not change");
+  });
 });
 
 async function assertRowSelected(rowNum) {
-  let idx = evalInContent("[].indexOf.call($$('.treeRow'), $('.treeRow.selected'))");
-  is(await idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);
+  let idx = await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    return [].indexOf.call(content.document.querySelectorAll(".treeRow"),
+      content.document.querySelector(".treeRow.selected"));
+  });
+  is(idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);
 }
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -118,18 +118,8 @@ addMessageListener("Test:JsonView:WaitFo
           break;
         }
       }
     }
   });
 
   observer.observe(firstRow, { attributes: true });
 });
-
-addMessageListener("Test:JsonView:Eval", function(msg) {
-  let result = content.eval(msg.data.code);
-  sendAsyncMessage(msg.name, {result});
-});
-
-Cu.exportFunction(content.document.querySelector.bind(content.document),
-  content, {defineAs: "$"});
-Cu.exportFunction(content.document.querySelectorAll.bind(content.document),
-  content, {defineAs: "$$"});
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -203,13 +203,8 @@ function waitForTime(delay) {
 
 function waitForFilter() {
   return executeInContent("Test:JsonView:WaitForFilter");
 }
 
 function normalizeNewLines(value) {
   return value.replace("(\r\n|\n)", "\n");
 }
-
-function evalInContent(code) {
-  return executeInContent("Test:JsonView:Eval", {code})
-  .then(result => result.result);
-}