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.
This commit is contained in:
Tim Huang 2019-01-21 21:54:39 +02:00
parent 92a09b4912
commit 58f7b5b978
3 changed files with 309 additions and 0 deletions

View File

@ -14,6 +14,7 @@ support-files =
file_firstPartyBasic.html
file_postMessage.html
file_postMessageSender.html
file_saveAs.sjs
file_sharedworker.html
file_sharedworker.js
file_thirdPartyChild.audio.ogg
@ -59,6 +60,7 @@ support-files =
window_redirect.html
worker_blobify.js
worker_deblobify.js
!/toolkit/content/tests/browser/common/mockTransfer.js
[browser_broadcastChannel.js]
[browser_cache.js]
@ -72,6 +74,7 @@ skip-if = debug #Bug 1345346
[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'))

View File

@ -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);
});

View File

@ -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>`);
}