Bug 1277803 - Part 7 : Add a test case of favicon loading for first party isolation. r=baku, arthuredelstein
☠☠ backed out by 22be4ae74653 ☠ ☠
authorTim Huang <tihuang@mozilla.com>
Wed, 12 Oct 2016 17:32:15 +0800
changeset 317629 49da326bfe68bc9f045bfed41b5c162bba73457c
parent 317628 2d17a40a90770e0dddb92af3a35aebff03d1b593
child 317630 477890efdb8817ea5aaa54ef3cb17a2ba335bdb5
push id20698
push userkwierso@gmail.com
push dateWed, 12 Oct 2016 21:45:33 +0000
treeherderfx-team@d68def1644c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, arthuredelstein
bugs1277803
milestone52.0a1
Bug 1277803 - Part 7 : Add a test case of favicon loading for first party isolation. r=baku, arthuredelstein
browser/components/originattributes/test/browser/browser.ini
browser/components/originattributes/test/browser/browser_favicon_firstParty.js
browser/components/originattributes/test/browser/file_favicon_cache.html
browser/components/originattributes/test/browser/file_favicon_cache.png
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 skip-if = buildapp == "mulet"
 tags = usercontextid firstpartyisolation originattributes
 support-files =
   dummy.html
   file_favicon.html
   file_favicon.png
   file_favicon.png^headers^
+  file_favicon_cache.html
+  file_favicon_cache.png
   file_favicon_thirdParty.html
   file_firstPartyBasic.html
   file_sharedworker.html
   file_sharedworker.js
   head.js
   test.js
   test.js^headers^
   test.html
@@ -23,15 +25,16 @@ support-files =
   test_firstParty_http_redirect.html
   test_firstParty_http_redirect.html^headers^
   test_firstParty_iframe_http_redirect.html
   test_firstParty_postMessage.html
   window.html
   worker_blobify.js
   worker_deblobify.js
 
+[browser_favicon_firstParty.js]
 [browser_favicon_userContextId.js]
 [browser_firstPartyIsolation.js]
 [browser_localStorageIsolation.js]
 [browser_blobURLIsolation.js]
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
 [browser_httpauth.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -0,0 +1,334 @@
+/**
+ * Bug 1277803 - A test case for testing favicon loading across different first party domains.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+const FIRST_PARTY_ONE = "example.com";
+const FIRST_PARTY_TWO = "example.org";
+const THIRD_PARTY = "mochi.test:8888";
+
+const TEST_SITE_ONE = "http://" + FIRST_PARTY_ONE;
+const TEST_SITE_TWO = "http://" + FIRST_PARTY_TWO;
+const THIRD_PARTY_SITE = "http://" + 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";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+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", false);
+        }
+      }
+    };
+
+    Services.obs.addObserver(observer, "places-favicons-expired", false);
+    faviconService.expireAllFavicons();
+  });
+}
+
+function observeFavicon(aFirstPartyDomain, aExpectedCookie, aPageURI) {
+  let faviconReqXUL = false;
+  let faviconReqPlaces = false;
+  let expectedPrincipal = Services.scriptSecurityManager
+                                  .createCodebasePrincipal(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");
+
+          if (loadingPrincipal.equals(systemPrincipal)) {
+            faviconReqXUL = true;
+            ok(triggeringPrincipal.equals(expectedPrincipal),
+              "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+          } else {
+            faviconReqPlaces = true;
+            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);
+        }
+
+        if (faviconReqXUL && faviconReqPlaces) {
+          Services.obs.removeObserver(observer, "http-on-modify-request", false);
+          resolve();
+        }
+      }
+    };
+
+    Services.obs.addObserver(observer, "http-on-modify-request", false);
+  });
+}
+
+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", false);
+          Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+        }
+      }
+    };
+
+    Services.obs.addObserver(observer, "http-on-examine-response", false);
+    Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
+  });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+  return new Promise(resolve => {
+    let observer = {
+      onPageChanged(uri, attr, value, id) {
+
+        if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+            value === aFaviconURL) {
+          resolve();
+          PlacesUtils.history.removeObserver(observer, false);
+        }
+      },
+    };
+
+    PlacesUtils.history.addObserver(observer, false);
+  });
+}
+
+function* openTab(aURL) {
+  let tab = gBrowser.addTab(aURL);
+
+  // Select tab and make sure its browser is focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerGlobal.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return {tab, browser};
+}
+
+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 = yield openTabInFirstParty(aURL, aFirstParty);
+
+  // Add cookies into the iframe.
+  yield ContentTask.spawn(tabInfo.browser, aCookieValue, function* (value) {
+    content.document.cookie = value;
+  });
+
+  yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+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;
+  }
+
+  yield assignCookiesUnderFirstParty(firstSiteURL, TEST_SITE_ONE, cookies[0]);
+  yield assignCookiesUnderFirstParty(secondSiteURL, TEST_SITE_TWO, cookies[1]);
+
+  return cookies;
+}
+
+function* doTest(aTestPage, aExpectedCookies, aFaviconURL) {
+  let firstPageURI = makeURI(TEST_SITE_ONE + aTestPage);
+  let secondPageURI = makeURI(TEST_SITE_TWO + aTestPage);
+
+  // Start to observer the event of that favicon has been fully loaded.
+  let promiseFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+  // Open the tab for the first site.
+  let tabInfo = yield openTab(TEST_SITE_ONE + aTestPage);
+
+  // Waiting until favicon requests are all made.
+  yield observeFavicon(FIRST_PARTY_ONE, aExpectedCookies[0], firstPageURI);
+
+  // Waiting until favicon loaded.
+  yield promiseFaviconLoaded;
+
+  // Close the tab.
+  yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+  // Open the tab for the second site.
+  tabInfo = yield openTab(TEST_SITE_TWO + aTestPage);
+
+  // Waiting until favicon requests are all made.
+  yield observeFavicon(FIRST_PARTY_TWO, aExpectedCookies[1], secondPageURI);
+
+  yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+add_task(function* setup() {
+  // Make sure first party isolation is enabled.
+  yield SpecialPowers.pushPrefEnv({"set": [
+      ["privacy.firstparty.isolate", true]
+  ]});
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+  // Clear all cookies.
+  let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+                     .getService(Ci.nsICookieManager);
+  cookieMgr.removeAll();
+
+  // Clear all image caches and network caches.
+  clearAllImageCaches();
+
+  let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                        .getService(Ci.nsICacheStorageService);
+  networkCache.clear();
+});
+
+add_task(function* test_favicon_firstParty() {
+  for (let testThirdParty of [false, true]) {
+    // Clear all image caches and network caches before running the test.
+    clearAllImageCaches();
+
+    let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                        .getService(Ci.nsICacheStorageService);
+    networkCache.clear();
+
+    // Clear Places favicon caches.
+    yield clearAllPlacesFavicons();
+
+    let cookies = yield generateCookies(testThirdParty);
+
+    if (testThirdParty) {
+      yield doTest(TEST_THIRD_PARTY_PAGE, cookies, THIRD_PARTY_SITE + FAVICON_URI);
+    } else {
+      yield doTest(TEST_PAGE, cookies, TEST_SITE_ONE + FAVICON_URI);
+    }
+  }
+});
+
+add_task(function* test_favicon_cache_firstParty() {
+  // Clear all image caches and network caches before running the test.
+  clearAllImageCaches();
+
+  let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                        .getService(Ci.nsICacheStorageService);
+  networkCache.clear();
+
+  // Open the tab for the first site.
+  let tabInfoA = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+  // Start to observer the event of that favicon has been fully loaded and cached.
+  let promiseForFaviconLoaded = waitOnFaviconLoaded(THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI);
+
+  // Wait for the favicon response of the first tab.
+  let response = yield waitOnFaviconResponse(THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI);
+
+  // 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.
+  yield promiseForFaviconLoaded;
+
+  // Open the tab again for checking the image cache is working correctly.
+  let tabInfoB = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+  // Start to observe the favicon response, the second tab actually will not
+  // make any network request since the favicon will be loaded by the cache for
+  // both Places and XUL image. So 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 tabInfoC = yield 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 = yield 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.");
+
+  yield BrowserTestUtils.removeTab(tabInfoA.tab);
+  yield BrowserTestUtils.removeTab(tabInfoB.tab);
+  yield BrowserTestUtils.removeTab(tabInfoC.tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_cache.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <title>Favicon Test for originAttributes</title>
+    <link rel="icon" type="image/png" href="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/file_favicon_cache.png" />
+  </head>
+  <body>
+    Third Party Favicon!!
+  </body>
+</html>
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5535363c94df7314765551f65311ec6f98729d8e
GIT binary patch
literal 344
zc$@)P0jK_nP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003WNkl<ZSi@ud
z|NsAgh5-aj3<C^C9l%0R#sBwE#yKee29*B}N^?LcMg=I}1WL<7#aV`G2>ibbr58i#
zeNc((P#P98>`-}V2r!yL=>({_1(eS*I75IDqa1+6`Tu4pJs&FZfkdNSpkC~N^7XN%
ziNTl#4nlptmQ0_+z#gbM8_{x!@L&&t|69@WIcn7X|AQkj;gW}$#~3qYLI76MeMc4k
zL!iLKDUaQ}p<fv>vJfohhiYxer~!@C3s8YCB*r;9fSSj^Ib=e>8|uYfC?P<0IRG;c
zE&mVZy1*Xl@?ezO@c*9=cpEsFAp@-q8U3Mr{vneF1fh&<D9wk`BKrRaM~jF7M~jF-
q8*kfi5VnX$TC13Gv~M`#9RL8-wO_*zBf!o80000<MNUMnLSTX;M3C|T