Bug 1508355 - Add a test case for assuring all "Save ... As" options honor the first party domain. r=baku,richard@torproject.org
authorTim Huang <tihuang@mozilla.com>
Mon, 21 Jan 2019 21:54:39 +0200
changeset 514778 0f709682e4a01b425d7b863d1e17b9496903a8ff
parent 514777 ed02a9881c0d43d5ab3e3040e878ed771dd1f10c
child 514779 754481bfbe992e8754ce9af79b5a33f9412d186f
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, richard
bugs1508355
milestone66.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 1508355 - Add a test case for assuring all "Save ... As" options honor the first party domain. r=baku,richard@torproject.org This patch adds a test case which tests following "Save ... As" options: * File menu: - Save Page As * Context menu in content pages: - Save Page As - Save Image As - Save Video As - Save Link As - Save Frame As * Page Info "Media" Panel: - Save As It triggers the save process and checks if the OA of the saving channel has the correct first party domain.
browser/components/originattributes/test/browser/browser.ini
browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js
browser/components/originattributes/test/browser/file_saveAs.sjs
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
   file_favicon.png
   file_favicon.png^headers^
   file_favicon_cache.html
   file_favicon_cache.png
   file_favicon_thirdParty.html
   file_firstPartyBasic.html
   file_postMessage.html
   file_postMessageSender.html
+  file_saveAs.sjs
   file_sharedworker.html
   file_sharedworker.js
   file_thirdPartyChild.audio.ogg
   file_thirdPartyChild.embed.png
   file_thirdPartyChild.fetch.html
   file_thirdPartyChild.iframe.html
   file_thirdPartyChild.img.png
   file_thirdPartyChild.import.js
@@ -54,29 +55,31 @@ support-files =
   test_firstParty_postMessage.html
   test_form.html
   window.html
   window2.html
   window3.html
   window_redirect.html
   worker_blobify.js
   worker_deblobify.js
+  !/toolkit/content/tests/browser/common/mockTransfer.js
 
 [browser_broadcastChannel.js]
 [browser_cache.js]
 skip-if = verify
 [browser_cookieIsolation.js]
 [browser_favicon_firstParty.js]
 [browser_favicon_userContextId.js]
 [browser_firstPartyIsolation.js]
 skip-if = debug #Bug 1345346
 [browser_firstPartyIsolation_about_newtab.js]
 [browser_firstPartyIsolation_aboutPages.js]
 [browser_firstPartyIsolation_blobURI.js]
 [browser_firstPartyIsolation_js_uri.js]
+[browser_firstPartyIsolation_saveAs.js]
 [browser_localStorageIsolation.js]
 [browser_blobURLIsolation.js]
 skip-if = (verify && debug && (os == 'win'))
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
 [browser_httpauth.js]
 [browser_clientAuth.js]
 skip-if = verify
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js
@@ -0,0 +1,266 @@
+/**
+ * Bug 1508355 - A test case for ensuring the saving channel has a correct first
+ *               party domain when going through different "Save ... AS."
+ */
+
+"use strict";
+
+/* import-globals-from ../../../../../toolkit/content/tests/browser/common/mockTransfer.js */
+Services.scriptloader
+        .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+                       this);
+
+const TEST_FIRST_PARTY = "example.com";
+const TEST_ORIGIN = `http://${TEST_FIRST_PARTY}`;
+const TEST_BASE_PATH = "/browser/browser/components/originattributes/test/browser/";
+const TEST_PATH = `${TEST_BASE_PATH}file_saveAs.sjs`;
+const TEST_PATH_VIDEO = `${TEST_BASE_PATH}file_thirdPartyChild.video.ogv`;
+const TEST_PATH_IMAGE = `${TEST_BASE_PATH}file_favicon.png`;
+
+// For the "Save Page As" test, we will check the channel of the sub-resource
+// within the page. In this case, it is a image.
+const TEST_PATH_PAGE = `${TEST_BASE_PATH}file_favicon.png`;
+
+// For the "Save Frame As" test, we will check the channel of the sub-resource
+// within the frame. In this case, it is a image.
+const TEST_PATH_FRAME = `${TEST_BASE_PATH}file_favicon.png`;
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+add_task(async function setup() {
+  info("Setting the prefs.");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.firstparty.isolate", true],
+  ]});
+
+  info("Setting MockFilePicker.");
+  let tempDir = createTemporarySaveDirectory();
+
+  MockFilePicker.displayDirectory = tempDir;
+  MockFilePicker.showCallback = fp => {
+    info("MockFilePicker showCallback");
+
+    let fileName = fp.defaultString;
+    let destFile = tempDir.clone();
+    destFile.append(fileName);
+
+    MockFilePicker.setFiles([destFile]);
+    MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+
+    info("MockFilePicker showCallback done");
+  };
+
+  mockTransferRegisterer.register();
+
+  registerCleanupFunction(function() {
+    mockTransferRegisterer.unregister();
+    MockFilePicker.cleanup();
+    tempDir.remove(true);
+  });
+});
+
+function createTemporarySaveDirectory() {
+  let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+  saveDir.append("testsavedir");
+  saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+  return saveDir;
+}
+
+function createPromiseForObservingChannel(aURL, aFirstParty) {
+  return new Promise(resolve => {
+    let observer = (aSubject, aTopic) => {
+      if (aTopic === "http-on-modify-request") {
+        let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+        let reqLoadInfo = httpChannel.loadInfo;
+
+        // Make sure this is the request which we want to check.
+        if (!httpChannel.URI.spec.endsWith(aURL)) {
+          return;
+        }
+
+        info(`Checking loadInfo for URI: ${httpChannel.URI.spec}\n`);
+        is(reqLoadInfo.originAttributes.firstPartyDomain, aFirstParty,
+          "The loadInfo has correct first party domain");
+
+        Services.obs.removeObserver(observer, "http-on-modify-request");
+        resolve();
+      }
+    };
+
+    Services.obs.addObserver(observer, "http-on-modify-request");
+  });
+}
+
+function createPromiseForTransferComplete() {
+  return new Promise((resolve) => {
+    function onTransferComplete(downloadSuccess) {
+      ok(downloadSuccess, "File should have been downloaded successfully");
+      // Clear the callback for now.
+      mockTransferCallback = () => { };
+      resolve();
+    }
+
+    mockTransferCallback = onTransferComplete;
+  });
+}
+
+async function doCommandForFrameType() {
+  info("Opening the frame sub-menu under the context menu.");
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let frameMenuPopup = contextMenu.querySelector("#frame").menupopup;
+  let frameMenuPopupPromise = BrowserTestUtils.waitForEvent(frameMenuPopup,
+                                                            "popupshown");
+
+  frameMenuPopup.openPopup();
+  await frameMenuPopupPromise;
+
+  info("Triggering the save process.");
+  let saveFrameCommand = contextMenu.querySelector("#context-saveframe");
+  saveFrameCommand.doCommand();
+}
+
+add_task(async function testContextMenuSaveAs() {
+  const TEST_DATA = [
+    { type: "link", path: TEST_PATH, target: "#link1" },
+    { type: "video", path: TEST_PATH_VIDEO, target: "#video1" },
+    { type: "image", path: TEST_PATH_IMAGE, target: "#image1" },
+    { type: "page", path: TEST_PATH_PAGE, target: "body" },
+    { type: "frame", path: TEST_PATH_FRAME, target: "#frame1",
+      doCommandFunc: doCommandForFrameType },
+  ];
+
+  for (const data of TEST_DATA) {
+    info(`Open a new tab for testing "Save ${data.type} as" in context menu.`);
+    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser,
+      `${TEST_ORIGIN}${TEST_PATH}?${data.type}=1`);
+
+    let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+    info("Open the context menu.");
+    await BrowserTestUtils.synthesizeMouseAtCenter(data.target, {
+      type: "contextmenu",
+      button: 2,
+      },
+      gBrowser.selectedBrowser);
+
+    await popupShownPromise;
+
+    let transferCompletePromise = createPromiseForTransferComplete();
+    let observerPromise = createPromiseForObservingChannel(data.path, TEST_FIRST_PARTY);
+
+    // Select "Save As" option from context menu.
+    if (!data.doCommandFunc) {
+      let saveElement = document.getElementById(`context-save${data.type}`);
+      info("Triggering the save process.");
+      saveElement.doCommand();
+    } else {
+      await data.doCommandFunc();
+    }
+
+    info("Waiting for the channel.");
+    await observerPromise;
+
+    info("Close the context menu.");
+    let contextMenu = document.getElementById("contentAreaContextMenu");
+    let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+    contextMenu.hidePopup();
+    await popupHiddenPromise;
+
+    info("Wait until the save is finished.");
+    await transferCompletePromise;
+
+    BrowserTestUtils.removeTab(tab);
+  }
+});
+
+add_task(async function testFileMenuSavePageAs() {
+  info(`Open a new tab for testing "Save Page AS" in the file menu.`);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser,
+    `${TEST_ORIGIN}${TEST_PATH}?page=1`);
+
+  let transferCompletePromise = createPromiseForTransferComplete();
+  let observerPromise = createPromiseForObservingChannel(TEST_PATH_PAGE, TEST_FIRST_PARTY);
+
+  let menubar = document.getElementById("main-menubar");
+  let filePopup = document.getElementById("menu_FilePopup");
+
+  // We only use the shortcut keys to open the file menu in Windows and Linux.
+  // Mac doesn't have a shortcut to only open the file menu. Instead, we directly
+  // trigger the save in MAC without any UI interactions.
+  if (Services.appinfo.OS !== "Darwin") {
+    let menubarActive = BrowserTestUtils.waitForEvent(menubar, "DOMMenuBarActive");
+    EventUtils.synthesizeKey("KEY_F10");
+    await menubarActive;
+
+    let popupShownPromise = BrowserTestUtils.waitForEvent(filePopup, "popupshown");
+    // In window, it still needs one extra down key to open the file menu.
+    if (Services.appinfo.OS === "WINNT") {
+      EventUtils.synthesizeKey("KEY_ArrowDown");
+    }
+    await popupShownPromise;
+  }
+
+  info("Triggering the save process.");
+  let fileSavePageAsElement = document.getElementById("menu_savePage");
+  fileSavePageAsElement.doCommand();
+
+  info("Waiting for the channel.");
+  await observerPromise;
+
+  // Close the file menu.
+  if (Services.appinfo.OS !== "Darwin") {
+    let popupHiddenPromise = BrowserTestUtils.waitForEvent(filePopup, "popuphidden");
+    filePopup.hidePopup();
+    await popupHiddenPromise;
+  }
+
+  info("Wait until the save is finished.");
+  await transferCompletePromise;
+
+  BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testPageInfoMediaSaveAs() {
+  info(`Open a new tab for testing "Save AS" in the media panel of the page info.`);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser,
+    `${TEST_ORIGIN}${TEST_PATH}?pageinfo=1`);
+
+  info("Open the media panel of the pageinfo.");
+  let pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+    "mediaTab");
+
+  await BrowserTestUtils.waitForEvent(pageInfo, "load");
+  await new Promise(resolve => pageInfo.onFinished.push(() => executeSoon(resolve)));
+
+  let imageTree = pageInfo.document.getElementById("imagetree");
+  let imageRowsNum = imageTree.view.rowCount;
+
+  is(imageRowsNum, 2, "There should be two media items here.");
+
+  for (let i = 0; i < imageRowsNum; i++) {
+    imageTree.view.selection.select(i);
+    imageTree.ensureRowIsVisible(i);
+    imageTree.focus();
+
+    let url = pageInfo.gImageView.data[i][0]; // COL_IMAGE_ADDRESS
+    info(`Start to save the media item with URL: ${url}`);
+
+    let transferCompletePromise = createPromiseForTransferComplete();
+    let observerPromise = createPromiseForObservingChannel(url, TEST_FIRST_PARTY);
+
+    info("Triggering the save process.");
+    let saveElement = pageInfo.document.getElementById("imagesaveasbutton");
+    saveElement.doCommand();
+
+    info("Waiting for the channel.");
+    await observerPromise;
+
+    info("Wait until the save is finished.");
+    await transferCompletePromise;
+  }
+
+  pageInfo.close();
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_saveAs.sjs
@@ -0,0 +1,40 @@
+const HTTP_ORIGIN = "http://example.com";
+const SECOND_ORIGIN = "http://example.org";
+const URI_PATH = "/browser/browser/components/originattributes/test/browser/";
+const LINK_PATH = `${URI_PATH}file_saveAs.sjs`;
+// Reusing existing ogv file for testing.
+const VIDEO_PATH = `${URI_PATH}file_thirdPartyChild.video.ogv`;
+// Reusing existing png file for testing.
+const IMAGE_PATH = `${URI_PATH}file_favicon.png`;
+const FRAME_PATH = `${SECOND_ORIGIN}${URI_PATH}file_saveAs.sjs?image=1`
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(aRequest, aResponse) {
+  var params = new URLSearchParams(aRequest.queryString);
+  aResponse.setStatusLine(aRequest.httpVersion, 200);
+  aResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  let contentBody = "";
+
+  if (params.has("link")) {
+    contentBody = `<a href="${LINK_PATH}" id="link1">this is a link</a>`;
+  } else if (params.has("video")) {
+    contentBody = `<video src="${VIDEO_PATH}" id="video1"> </video>`;
+  } else if (params.has("image")) {
+    contentBody = `<img src="${IMAGE_PATH}" id="image1">`;
+  } else if (params.has("page")) {
+    // We need at least one resource, like a img, a link or a script, to trigger
+    // downloading resources in "Save Page As". Otherwise, it will output the
+    // document directly without any network request.
+    contentBody = `<img src="${IMAGE_PATH}">`;
+  } else if (params.has("frame")) {
+    // Like "Save Page As", we need to put at least one resource in the frame.
+    // Here we also use a image.
+    contentBody = `<iframe src="${FRAME_PATH}" id="frame1"></iframe>`;
+  } else if (params.has("pageinfo")) {
+    contentBody = `<img src="${IMAGE_PATH}" id="image1">
+                   <video src="${VIDEO_PATH}" id="video1"> </video>`;
+  }
+
+  aResponse.write(`<html><body>${contentBody}</body></html>`);
+}