summaryrefslogtreecommitdiffstats
path: root/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/originattributes/test/browser/browser_favicon_firstParty.js')
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_firstParty.js511
1 files changed, 511 insertions, 0 deletions
diff --git a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
new file mode 100644
index 0000000000..300c2f9f25
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -0,0 +1,511 @@
+/**
+ * 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(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+let EventUtils = {};
+Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+ 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, aData) {
+ 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, aData) {
+ // 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, aData) {
+ 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);
+});