Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/**
* Bug 1277803 - A test case for testing favicon loading across different first party domains.
*/
if (SpecialPowers.useRemoteSubframes) {
requestLongerTimeout(2);
}
const CC = Components.Constructor;
const { PlacesTestUtils } = ChromeUtils.importESModule(
);
let EventUtils = {};
Services.scriptloader.loadSubScript(
EventUtils
);
const FIRST_PARTY_ONE = "example.com";
const FIRST_PARTY_TWO = "example.org";
const THIRD_PARTY = "example.net";
const TEST_SITE_ONE = "https://" + FIRST_PARTY_ONE;
const TEST_SITE_TWO = "https://" + FIRST_PARTY_TWO;
const THIRD_PARTY_SITE = "https://" + THIRD_PARTY;
const TEST_DIRECTORY =
"/browser/browser/components/originattributes/test/browser/";
const TEST_PAGE = TEST_DIRECTORY + "file_favicon.html";
const TEST_THIRD_PARTY_PAGE = TEST_DIRECTORY + "file_favicon_thirdParty.html";
const TEST_CACHE_PAGE = TEST_DIRECTORY + "file_favicon_cache.html";
const FAVICON_URI = TEST_DIRECTORY + "file_favicon.png";
const TEST_FAVICON_CACHE_URI = TEST_DIRECTORY + "file_favicon_cache.png";
const ICON_DATA =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABH0lEQVRYw2P8////f4YBBEwMAwxGHcBCUMX/91DGOSj/BpT/DkpzQChGBSjfBErLQsVZhmoI/L8LpRdD6X1QietQGhYy7FB5aAgwmkLpBKi4BZTPMThDgBGjHIDF+f9mKD0fKvGBRKNdoF7sgPL1saaJwZgGDkJ9vpZMn8PAHqg5G9FyifBgD4H/W9HyOWrU/f+DIzHhkoeZxxgzZEIAVtJ9RxX+Q6DAxCmP3byhXxkxshAs5odqbcioAY3UC1CBLyTGOTqAmsfAOWRCwBvqxV0oIUB2OQAzDy3/D+a6wB7q8mCU2vD/nw94GziYIQOtDRn9oXz+IZMGBKGMbCjNh9Ii+v8HR4uIAUeLiEEbb9twELaIRlqrmHG0bzjiHQAA1LVfww8jwM4AAAAASUVORK5CYII=";
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
function clearAllImageCaches() {
let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
SpecialPowers.Ci.imgITools
);
let imageCache = tools.getImgCacheForDocument(window.document);
imageCache.clearCache(true); // true=chrome
imageCache.clearCache(false); // false=content
}
function clearAllPlacesFavicons() {
let faviconService = Cc["@mozilla.org/browser/favicon-service;1"].getService(
Ci.nsIFaviconService
);
return new Promise(resolve => {
let observer = {
observe(aSubject, aTopic) {
if (aTopic === "places-favicons-expired") {
resolve();
Services.obs.removeObserver(observer, "places-favicons-expired");
}
},
};
Services.obs.addObserver(observer, "places-favicons-expired");
faviconService.expireAllFavicons();
});
}
function observeFavicon(aFirstPartyDomain, aExpectedCookie, aPageURI) {
let expectedPrincipal = Services.scriptSecurityManager.createContentPrincipal(
aPageURI,
{ firstPartyDomain: aFirstPartyDomain }
);
return new Promise(resolve => {
let observer = {
observe(aSubject, aTopic) {
// Make sure that the topic is 'http-on-modify-request'.
if (aTopic === "http-on-modify-request") {
// We check the firstPartyDomain for the originAttributes of the loading
// channel. All requests for the favicon should contain the correct
// firstPartyDomain. There are two requests for a favicon loading, one
// from the Places library and one from the XUL image. The difference
// of them is the loading principal. The Places will use the content
// principal and the XUL image will use the system principal.
let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
let reqLoadInfo = httpChannel.loadInfo;
let loadingPrincipal = reqLoadInfo.loadingPrincipal;
let triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
// Make sure this is a favicon request.
if (!httpChannel.URI.spec.endsWith(FAVICON_URI)) {
return;
}
// Check the first party domain.
is(
reqLoadInfo.originAttributes.firstPartyDomain,
aFirstPartyDomain,
"The loadInfo has correct first party domain"
);
ok(
loadingPrincipal.equals(expectedPrincipal),
"The loadingPrincipal of favicon loads should be the content prinicpal"
);
ok(
triggeringPrincipal.equals(expectedPrincipal),
"The triggeringPrincipal of favicon loads should be the content prinicpal"
);
let faviconCookie = httpChannel.getRequestHeader("cookie");
is(
faviconCookie,
aExpectedCookie,
"The cookie of the favicon loading is correct."
);
} else {
ok(false, "Received unexpected topic: ", aTopic);
}
Services.obs.removeObserver(observer, "http-on-modify-request");
resolve();
},
};
Services.obs.addObserver(observer, "http-on-modify-request");
});
}
function waitOnFaviconResponse(aFaviconURL) {
return new Promise(resolve => {
let observer = {
observe(aSubject, aTopic) {
if (
aTopic === "http-on-examine-response" ||
aTopic === "http-on-examine-cached-response"
) {
let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
let loadInfo = httpChannel.loadInfo;
if (httpChannel.URI.spec !== aFaviconURL) {
return;
}
let result = {
topic: aTopic,
firstPartyDomain: loadInfo.originAttributes.firstPartyDomain,
};
resolve(result);
Services.obs.removeObserver(observer, "http-on-examine-response");
Services.obs.removeObserver(
observer,
"http-on-examine-cached-response"
);
}
},
};
Services.obs.addObserver(observer, "http-on-examine-response");
Services.obs.addObserver(observer, "http-on-examine-cached-response");
});
}
function waitOnFaviconLoaded(aFaviconURL) {
return PlacesTestUtils.waitForNotification("favicon-changed", events =>
events.some(e => e.faviconUrl == aFaviconURL)
);
}
async function openTab(aURL) {
let tab = BrowserTestUtils.addTab(gBrowser, aURL);
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
return { tab, browser };
}
async function assignCookiesUnderFirstParty(aURL, aFirstParty, aCookieValue) {
// Open a tab under the given aFirstParty, and this tab will have an
// iframe which loads the aURL.
let tabInfo = await openTabInFirstParty(aURL, aFirstParty);
// Add cookies into the iframe.
await SpecialPowers.spawn(
tabInfo.browser,
[aCookieValue],
async function (value) {
content.document.cookie = value + "; SameSite=None; Secure;";
}
);
BrowserTestUtils.removeTab(tabInfo.tab);
}
async function generateCookies(aThirdParty) {
// we generate two different cookies for two first party domains.
let cookies = [];
cookies.push(Math.random().toString());
cookies.push(Math.random().toString());
let firstSiteURL;
let secondSiteURL;
if (aThirdParty) {
// Add cookies into the third party site with different first party domain.
firstSiteURL = THIRD_PARTY_SITE;
secondSiteURL = THIRD_PARTY_SITE;
} else {
// Add cookies into sites.
firstSiteURL = TEST_SITE_ONE;
secondSiteURL = TEST_SITE_TWO;
}
await assignCookiesUnderFirstParty(firstSiteURL, TEST_SITE_ONE, cookies[0]);
await assignCookiesUnderFirstParty(secondSiteURL, TEST_SITE_TWO, cookies[1]);
return cookies;
}
function assertIconIsData(item) {
let icon = item.getAttribute("image");
is(
icon.substring(0, 5),
"data:",
"Expected the image element to be a data URI"
);
is(icon, ICON_DATA, "Expected to see the correct data.");
}
async function doTest(aTestPage, aExpectedCookies, aFaviconURL) {
let firstPageURI = Services.io.newURI(TEST_SITE_ONE + aTestPage);
let secondPageURI = Services.io.newURI(TEST_SITE_TWO + aTestPage);
// Start to observe the event of that favicon has been fully loaded.
let promiseFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
// Start to observe the favicon requests earlier in case we miss it.
let promiseObserveFavicon = observeFavicon(
FIRST_PARTY_ONE,
aExpectedCookies[0],
firstPageURI
);
// Open the tab for the first site.
let tabInfo = await openTab(TEST_SITE_ONE + aTestPage);
// Waiting until favicon requests are all made.
await promiseObserveFavicon;
// Waiting until favicon loaded.
await promiseFaviconLoaded;
assertIconIsData(tabInfo.tab);
BrowserTestUtils.removeTab(tabInfo.tab);
// FIXME: We need to wait for the next event tick here to avoid observing
// the previous tab info in the next step (bug 1446725).
await new Promise(executeSoon);
// Start to observe the favicon requests earlier in case we miss it.
promiseObserveFavicon = observeFavicon(
FIRST_PARTY_TWO,
aExpectedCookies[1],
secondPageURI
);
// Open the tab for the second site.
tabInfo = await openTab(TEST_SITE_TWO + aTestPage);
// Waiting until favicon requests are all made.
await promiseObserveFavicon;
BrowserTestUtils.removeTab(tabInfo.tab);
}
async function doTestForAllTabsFavicon(
aTestPage,
aExpectedCookies,
aIsThirdParty
) {
let faviconURI = aIsThirdParty
? THIRD_PARTY_SITE + FAVICON_URI
: TEST_SITE_ONE + FAVICON_URI;
// Set the 'overflow' attribute to make allTabs button available.
let tabBrowser = document.getElementById("tabbrowser-tabs");
tabBrowser.setAttribute("overflow", true);
// Start to observe the event of that the favicon has been fully loaded.
let promiseFaviconLoaded = waitOnFaviconLoaded(faviconURI);
// Open the tab for the first site.
let tabInfo = await openTab(TEST_SITE_ONE + aTestPage);
// Waiting until the favicon loaded.
await promiseFaviconLoaded;
assertIconIsData(tabInfo.tab);
gTabsPanel.init();
// Make the popup of allTabs showing up and trigger the loading of the favicon.
let allTabsView = document.getElementById("allTabsMenu-allTabsView");
let allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
allTabsView,
"ViewShown"
);
gTabsPanel.showAllTabsPanel();
await allTabsPopupShownPromise;
assertIconIsData(
gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
);
// Close the popup of allTabs and wait until it's done.
let allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
allTabsView.panelMultiView,
"PanelMultiViewHidden"
);
gTabsPanel.hideAllTabsPanel();
await allTabsPopupHiddenPromise;
// Close the tab.
BrowserTestUtils.removeTab(tabInfo.tab);
faviconURI = aIsThirdParty
? THIRD_PARTY_SITE + FAVICON_URI
: TEST_SITE_TWO + FAVICON_URI;
// Start to observe the event of that favicon has been fully loaded.
promiseFaviconLoaded = waitOnFaviconLoaded(faviconURI);
// Open the tab for the second site.
tabInfo = await openTab(TEST_SITE_TWO + aTestPage);
// Wait until the favicon is fully loaded.
await promiseFaviconLoaded;
assertIconIsData(tabInfo.tab);
// Make the popup of allTabs showing up again.
allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
allTabsView,
"ViewShown"
);
gTabsPanel.showAllTabsPanel();
await allTabsPopupShownPromise;
assertIconIsData(
gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
);
// Close the popup of allTabs and wait until it's done.
allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
allTabsView.panelMultiView,
"PanelMultiViewHidden"
);
gTabsPanel.hideAllTabsPanel();
await allTabsPopupHiddenPromise;
// Close the tab.
BrowserTestUtils.removeTab(tabInfo.tab);
// Reset the 'overflow' attribute to make the allTabs button hidden again.
tabBrowser.removeAttribute("overflow");
}
add_setup(async function () {
// Make sure first party isolation is enabled.
await SpecialPowers.pushPrefEnv({
set: [["privacy.firstparty.isolate", true]],
});
});
// A clean up function to prevent affecting other tests.
registerCleanupFunction(() => {
// Clear all cookies.
Services.cookies.removeAll();
// Clear all image caches and network caches.
clearAllImageCaches();
Services.cache2.clear();
});
add_task(async function test_favicon_firstParty() {
for (let testThirdParty of [false, true]) {
// Clear all image caches and network caches before running the test.
clearAllImageCaches();
Services.cache2.clear();
// Clear Places favicon caches.
await clearAllPlacesFavicons();
let cookies = await generateCookies(testThirdParty);
if (testThirdParty) {
await doTest(
TEST_THIRD_PARTY_PAGE,
cookies,
THIRD_PARTY_SITE + FAVICON_URI
);
} else {
await doTest(TEST_PAGE, cookies, TEST_SITE_ONE + FAVICON_URI);
}
}
});
add_task(async function test_allTabs_favicon_firstParty() {
for (let testThirdParty of [false, true]) {
// Clear all image caches and network caches before running the test.
clearAllImageCaches();
Services.cache2.clear();
// Clear Places favicon caches.
await clearAllPlacesFavicons();
let cookies = await generateCookies(testThirdParty);
if (testThirdParty) {
await doTestForAllTabsFavicon(
TEST_THIRD_PARTY_PAGE,
cookies,
testThirdParty
);
} else {
await doTestForAllTabsFavicon(TEST_PAGE, cookies, testThirdParty);
}
}
});
add_task(async function test_favicon_cache_firstParty() {
// Clear all image caches and network caches before running the test.
clearAllImageCaches();
Services.cache2.clear();
// Start to observer the event of that favicon has been fully loaded and cached.
let promiseForFaviconLoaded = waitOnFaviconLoaded(
THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
);
// Start to observer for the favicon response of the first tab.
let responsePromise = waitOnFaviconResponse(
THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
);
// Open the tab for the first site.
let tabInfoA = await openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
// Waiting for the favicon response.
let response = await responsePromise;
// Make sure the favicon is loaded through the network and its first party domain is correct.
is(
response.topic,
"http-on-examine-response",
"The favicon image should be loaded through network."
);
is(
response.firstPartyDomain,
FIRST_PARTY_ONE,
"We should only observe the network response for the first first party."
);
// Waiting until the favicon has been loaded and cached.
await promiseForFaviconLoaded;
// Here, we are going to observe the favicon response for the third tab which
// opens with the second first party.
let promiseForFaviconResponse = waitOnFaviconResponse(
THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
);
// Open the tab for the second site.
let tabInfoB = await openTab(TEST_SITE_TWO + TEST_CACHE_PAGE);
// Wait for the favicon response. In this case, we suppose to catch the
// response for the third tab but not the second tab since it will not
// go through the network.
response = await promiseForFaviconResponse;
// Check that the favicon response has came from the network and it has the
// correct first party domain.
is(
response.topic,
"http-on-examine-response",
"The favicon image should be loaded through network again."
);
is(
response.firstPartyDomain,
FIRST_PARTY_TWO,
"We should only observe the network response for the second first party."
);
BrowserTestUtils.removeTab(tabInfoA.tab);
BrowserTestUtils.removeTab(tabInfoB.tab);
});