/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // This test make sure that the favicon of the private browsing is isolated. const TEST_SITE = "https://example.com"; const TEST_CACHE_SITE = "https://test1.example.com"; const TEST_DIRECTORY = "/browser/browser/components/privatebrowsing/test/browser/"; const TEST_PAGE = TEST_SITE + TEST_DIRECTORY + "file_favicon.html"; const TEST_CACHE_PAGE = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.html"; const FAVICON_URI = TEST_SITE + TEST_DIRECTORY + "file_favicon.png"; const FAVICON_CACHE_URI = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.png"; 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(aIsPrivate, aExpectedCookie, aPageURI) { let attr = {}; if (aIsPrivate) { attr.privateBrowsingId = 1; } let expectedPrincipal = Services.scriptSecurityManager.createContentPrincipal( aPageURI, attr ); 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 privateBrowsingId for the originAttributes of the loading // channel. All requests for the favicon should contain the correct // privateBrowsingId. 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; // Make sure this is a favicon request. if (httpChannel.URI.spec !== FAVICON_URI) { return; } // Check the privateBrowsingId. if (aIsPrivate) { is( reqLoadInfo.originAttributes.privateBrowsingId, 1, "The loadInfo has correct privateBrowsingId" ); } else { is( reqLoadInfo.originAttributes.privateBrowsingId, 0, "The loadInfo has correct privateBrowsingId" ); } ok( loadingPrincipal.equals(expectedPrincipal), "The loadingPrincipal of favicon loading from Places 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); } resolve(); Services.obs.removeObserver(observer, "http-on-modify-request"); }, }; 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, privateBrowsingId: loadInfo.originAttributes.privateBrowsingId, }; 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 assignCookies(aBrowser, aURL, aCookieValue) { let tabInfo = await openTab(aBrowser, aURL); await SpecialPowers.spawn( tabInfo.browser, [aCookieValue], async function (value) { content.document.cookie = value; } ); BrowserTestUtils.removeTab(tabInfo.tab); } async function openTab(aBrowser, aURL) { let tab = BrowserTestUtils.addTab(aBrowser, aURL); // Select tab and make sure its browser is focused. aBrowser.selectedTab = tab; tab.ownerGlobal.focus(); let browser = aBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); return { tab, browser }; } registerCleanupFunction(async () => { Services.cookies.removeAll(); clearAllImageCaches(); Services.cache2.clear(); await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); }); add_task(async function test_favicon_privateBrowsing() { // Clear all image caches before running the test. clearAllImageCaches(); // Clear all favicons in Places. await clearAllPlacesFavicons(); // Create a private browsing window. let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true, }); let pageURI = Services.io.newURI(TEST_PAGE); // Generate two random cookies for non-private window and private window // respectively. let cookies = []; cookies.push(Math.random().toString()); cookies.push(Math.random().toString()); // Open a tab in private window and add a cookie into it. await assignCookies(privateWindow.gBrowser, TEST_SITE, cookies[0]); // Open a tab in non-private window and add a cookie into it. await assignCookies(gBrowser, TEST_SITE, cookies[1]); // Add the observer earlier in case we don't capture events in time. let promiseObserveFavicon = observeFavicon(true, cookies[0], pageURI); // The page must be bookmarked for favicon requests to go through in PB mode. await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: TEST_PAGE, }); // Open a tab for the private window. let tabInfo = await openTab(privateWindow.gBrowser, TEST_PAGE); info("Waiting until favicon requests are all made in private window."); await promiseObserveFavicon; // Close the 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); // Add the observer earlier in case we don't capture events in time. promiseObserveFavicon = observeFavicon(false, cookies[1], pageURI); // Open a tab for the non-private window. tabInfo = await openTab(gBrowser, TEST_PAGE); info("Waiting until favicon requests are all made in non-private window."); await promiseObserveFavicon; // Close the tab. BrowserTestUtils.removeTab(tabInfo.tab); await BrowserTestUtils.closeWindow(privateWindow); }); add_task(async function test_favicon_cache_privateBrowsing() { // Clear all image caches and network cache before running the test. clearAllImageCaches(); Services.cache2.clear(); // Clear all favicons in Places. await clearAllPlacesFavicons(); // Add an observer for making sure the favicon has been loaded and cached. let promiseFaviconLoaded = waitOnFaviconLoaded(FAVICON_CACHE_URI); let promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI); // Open a tab for the non-private window. let tabInfoNonPrivate = await openTab(gBrowser, TEST_CACHE_PAGE); let response = await promiseFaviconResponse; await promiseFaviconLoaded; // Check that the favicon response has come from the network and it has the // correct privateBrowsingId. is( response.topic, "http-on-examine-response", "The favicon image should be loaded through network." ); is( response.privateBrowsingId, 0, "We should observe the network response for the non-private tab." ); // Create a private browsing window. let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true, }); // The page must be bookmarked for favicon requests to go through in PB mode. await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: TEST_CACHE_PAGE, }); promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI); // Open a tab for the private window. let tabInfoPrivate = await openTab(privateWindow.gBrowser, TEST_CACHE_PAGE); // Wait for the favicon response of the private tab. response = await promiseFaviconResponse; // Make sure the favicon is loaded through the network and its privateBrowsingId is correct. is( response.topic, "http-on-examine-response", "The favicon image should be loaded through the network again." ); is( response.privateBrowsingId, 1, "We should observe the network response for the private tab." ); BrowserTestUtils.removeTab(tabInfoPrivate.tab); BrowserTestUtils.removeTab(tabInfoNonPrivate.tab); await BrowserTestUtils.closeWindow(privateWindow); });