Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Mon, 24 Sep 2018 19:46:04 +0300
changeset 437911 ea12f08bca89233f4f1037b6061a3877ab81d841
parent 437910 bdc24dded5656cf650d9cac7d6e4e43fd214d58c (current diff)
parent 437880 fcd124fe04e7773064b94640ad4b0545f633e50e (diff)
child 437912 bf90894b0f2eb4aa876496a71dae66107559dd42
child 437983 7942f4faa153bc273d9676bdb041fb3f7c3be7dc
push id108190
push usershindli@mozilla.com
push dateMon, 24 Sep 2018 16:51:13 +0000
treeherdermozilla-inbound@bf90894b0f2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
ea12f08bca89 / 64.0a1 / 20180924220042 / files
nightly linux64
ea12f08bca89 / 64.0a1 / 20180924220042 / files
nightly mac
ea12f08bca89 / 64.0a1 / 20180924220042 / files
nightly win32
ea12f08bca89 / 64.0a1 / 20180924220042 / files
nightly win64
ea12f08bca89 / 64.0a1 / 20180924220042 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
devtools/client/debugger/panel.js
devtools/client/framework/toolbox.js
netwerk/cookie/test/unit/test_bug1267910.js
--- a/browser/base/content/test/sanitize/SiteDataTestUtils.jsm
+++ b/browser/base/content/test/sanitize/SiteDataTestUtils.jsm
@@ -58,17 +58,18 @@ var SiteDataTestUtils = {
    *
    * @param {String} origin - the origin of the site to add test data for
    * @param {String} name [optional] - the cookie name
    * @param {String} value [optional] - the cookie value
    */
   addToCookies(origin, name = "foo", value = "bar") {
     let uri = Services.io.newURI(origin);
     Services.cookies.add(uri.host, uri.pathQueryRef, name, value,
-      false, false, false, Date.now() + 24000 * 60 * 60);
+      false, false, false, Date.now() + 24000 * 60 * 60, {},
+      Ci.nsICookie2.SAMESITE_UNSET);
   },
 
   /**
    * Adds a new serviceworker with the specified path. Note that this
    * method will open a new tab at the domain of the SW path to that effect.
    *
    * @param {String} path - the path to the service worker to add.
    *
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_clear_blocked_cookies.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_clear_blocked_cookies.js
@@ -2,21 +2,21 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const HOSTNAME_DOMAIN = "browser_policy_clear_blocked_cookies.com";
 const ORIGIN_DOMAIN = "browser_policy_clear_blocked_cookies.org";
 
 add_task(async function setup() {
   const expiry = Date.now() + 24 * 60 * 60;
-  Services.cookies.add(HOSTNAME_DOMAIN, "/", "secure", "true", true, false, false, expiry, {});
-  Services.cookies.add(HOSTNAME_DOMAIN, "/", "insecure", "true", false, false, false, expiry, {});
-  Services.cookies.add(ORIGIN_DOMAIN, "/", "secure", "true", true, false, false, expiry, {});
-  Services.cookies.add(ORIGIN_DOMAIN, "/", "insecure", "true", false, false, false, expiry, {});
-  Services.cookies.add("example.net", "/", "secure", "true", true, false, false, expiry, {});
+  Services.cookies.add(HOSTNAME_DOMAIN, "/", "secure", "true", true, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  Services.cookies.add(HOSTNAME_DOMAIN, "/", "insecure", "true", false, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  Services.cookies.add(ORIGIN_DOMAIN, "/", "secure", "true", true, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  Services.cookies.add(ORIGIN_DOMAIN, "/", "insecure", "true", false, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  Services.cookies.add("example.net", "/", "secure", "true", true, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   await setupPolicyEngineWithJson({
     "policies": {
       "Cookies": {
         "Block": [
           `http://${HOSTNAME_DOMAIN}`,
           `https://${ORIGIN_DOMAIN}:8080`,
         ],
       },
--- a/browser/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js
@@ -19,18 +19,18 @@ const COOKIE_NET = {
 const COOKIE_ORG = {
   host: "example.org",
   name: "test_cookie",
   path: "/",
 };
 let since, oldCookie;
 
 function addCookie(cookie) {
-  Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, Date.now() / 1000 + 10000);
-  ok(Services.cookies.cookieExists(cookie), `Cookie ${cookie.name} was created.`);
+  Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, Date.now() / 1000 + 10000, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  ok(Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {}), `Cookie ${cookie.name} was created.`);
 }
 
 async function setUpCookies() {
   Services.cookies.removeAll();
 
   // Add a cookie which will end up with an older creationTime.
   oldCookie = Object.assign({}, COOKIE, {name: Date.now()});
   addCookie(oldCookie);
@@ -116,37 +116,37 @@ add_task(async function testCookies() {
   });
 
   async function testRemovalMethod(method) {
     // Clear cookies with a recent since value.
     await setUpCookies();
     extension.sendMessage(method, {since});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(Services.cookies.cookieExists(oldCookie), "Old cookie was not removed.");
-    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+    ok(Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was not removed.");
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
     // Clear cookies with an old since value.
     await setUpCookies();
     addCookie(COOKIE);
     extension.sendMessage(method, {since: since - 100000});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(!Services.cookies.cookieExists(oldCookie), "Old cookie was removed.");
-    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+    ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was removed.");
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
     // Clear cookies with no since value and valid originTypes.
     await setUpCookies();
     extension.sendMessage(
       method,
       {originTypes: {unprotectedWeb: true, protectedWeb: false}});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(!Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was removed.`);
-    ok(!Services.cookies.cookieExists(oldCookie), `Cookie ${oldCookie.name}  was removed.`);
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name}  was removed.`);
+    ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), `Cookie ${oldCookie.name}  was removed.`);
   }
 
   await extension.startup();
 
   await testRemovalMethod("removeCookies");
   await testRemovalMethod("remove");
 
   await extension.unload();
@@ -171,69 +171,69 @@ add_task(async function testCacheAndCook
 
   // Clear cache and cookies with a recent since value.
   await setUpCookies();
   let awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({since});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(Services.cookies.cookieExists(oldCookie), "Old cookie was not removed.");
-  ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+  ok(Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was not removed.");
+  ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
   // Clear cache and cookies with an old since value.
   await setUpCookies();
   awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({since: since - 100000});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(!Services.cookies.cookieExists(oldCookie), "Old cookie was removed.");
-  ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+  ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was removed.");
+  ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
   // Clear cache and cookies with hostnames value.
   await setUpCookies();
   awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({hostnames: ["example.net", "example.org", "unknown.com"]});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was not removed.`);
-  ok(!Services.cookies.cookieExists(COOKIE_NET), `Cookie ${COOKIE_NET.name}  was removed.`);
-  ok(!Services.cookies.cookieExists(COOKIE_ORG), `Cookie ${COOKIE_ORG.name}  was removed.`);
+  ok(Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name}  was not removed.`);
+  ok(!Services.cookies.cookieExists(COOKIE_NET.host, COOKIE_NET.path, COOKIE_NET.name, {}), `Cookie ${COOKIE_NET.name}  was removed.`);
+  ok(!Services.cookies.cookieExists(COOKIE_ORG.host, COOKIE_ORG.path, COOKIE_ORG.name, {}), `Cookie ${COOKIE_ORG.name}  was removed.`);
 
   // Clear cache and cookies with (empty) hostnames value.
   await setUpCookies();
   awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({hostnames: []});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was not removed.`);
-  ok(Services.cookies.cookieExists(COOKIE_NET), `Cookie ${COOKIE_NET.name}  was not removed.`);
-  ok(Services.cookies.cookieExists(COOKIE_ORG), `Cookie ${COOKIE_ORG.name}  was not removed.`);
+  ok(Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name}  was not removed.`);
+  ok(Services.cookies.cookieExists(COOKIE_NET.host, COOKIE_NET.path, COOKIE_NET.name, {}), `Cookie ${COOKIE_NET.name}  was not removed.`);
+  ok(Services.cookies.cookieExists(COOKIE_ORG.host, COOKIE_ORG.path, COOKIE_ORG.name, {}), `Cookie ${COOKIE_ORG.name}  was not removed.`);
 
   // Clear cache and cookies with both hostnames and since values.
   await setUpCookies();
   awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({hostnames: ["example.com"], since});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(Services.cookies.cookieExists(oldCookie), "Old cookie was not removed.");
-  ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
-  ok(Services.cookies.cookieExists(COOKIE_NET), "Cookie with different hostname was not removed");
-  ok(Services.cookies.cookieExists(COOKIE_ORG), "Cookie with different hostname was not removed");
+  ok(Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was not removed.");
+  ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
+  ok(Services.cookies.cookieExists(COOKIE_NET.host, COOKIE_NET.path, COOKIE_NET.name, {}), "Cookie with different hostname was not removed");
+  ok(Services.cookies.cookieExists(COOKIE_ORG.host, COOKIE_ORG.path, COOKIE_ORG.name, {}), "Cookie with different hostname was not removed");
 
   // Clear cache and cookies with no since or hostnames value.
   await setUpCookies();
   awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({});
   await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
-  ok(!Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was removed.`);
-  ok(!Services.cookies.cookieExists(oldCookie), `Cookie ${oldCookie.name}  was removed.`);
-  ok(!Services.cookies.cookieExists(COOKIE_NET), `Cookie ${COOKIE_NET.name}  was removed.`);
-  ok(!Services.cookies.cookieExists(COOKIE_ORG), `Cookie ${COOKIE_ORG.name}  was removed.`);
+  ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name}  was removed.`);
+  ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), `Cookie ${oldCookie.name}  was removed.`);
+  ok(!Services.cookies.cookieExists(COOKIE_NET.host, COOKIE_NET.path, COOKIE_NET.name, {}), `Cookie ${COOKIE_NET.name}  was removed.`);
+  ok(!Services.cookies.cookieExists(COOKIE_ORG.host, COOKIE_ORG.path, COOKIE_ORG.name, {}), `Cookie ${COOKIE_ORG.name}  was removed.`);
 
   await extension.unload();
 });
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -342,17 +342,18 @@ async function GetCookiesResource(aProfi
           Services.cookies.add(host_key,
                                row.getResultByName("path"),
                                row.getResultByName("name"),
                                row.getResultByName("value"),
                                row.getResultByName("secure"),
                                row.getResultByName("httponly"),
                                false,
                                parseInt(expiresUtc),
-                               {});
+                               {},
+                               Ci.nsICookie2.SAMESITE_UNSET);
         } catch (e) {
           Cu.reportError(e);
         }
       }
       aCallback(true);
     },
   };
 }
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -634,17 +634,18 @@ Cookies.prototype = {
       Services.cookies.add(host,
                            path,
                            name,
                            value,
                            Number(flags) & 0x1, // secure
                            false, // httpOnly
                            false, // session
                            expireTime,
-                           {});
+                           {},
+                           Ci.nsICookie2.SAMESITE_UNSET);
     }
   },
 };
 
 function getTypedURLs(registryKeyPath) {
   // The list of typed URLs is a sort of annotation stored in the registry.
   // The number of entries stored is not UI-configurable, but has changed
   // between different Windows versions. We just keep reading up to the first
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -136,17 +136,17 @@ class TestFirefoxRefresh(MarionetteTestC
           }).then(resolve);
         """)
 
     def createCookie(self):
         self.runCode("""
           // Expire in 15 minutes:
           let expireTime = Math.floor(Date.now() / 1000) + 15 * 60;
           Services.cookies.add(arguments[0], arguments[1], arguments[2], arguments[3],
-                               true, false, false, expireTime);
+                               true, false, false, expireTime, {}, Ci.nsICookie2.SAMESITE_UNSET);
         """, script_args=(self._cookieHost, self._cookiePath, self._cookieName, self._cookieValue))
 
     def createSession(self):
         self.runAsyncCode("""
           let resolve = arguments[arguments.length - 1];
           const COMPLETE_STATE = Ci.nsIWebProgressListener.STATE_STOP +
                                  Ci.nsIWebProgressListener.STATE_IS_NETWORK;
           let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
@@ -311,17 +311,17 @@ class TestFirefoxRefresh(MarionetteTestC
                          "Should have exactly 1 saved address, got %d" % formAutofillAddressCount)
         if formAutofillAddressCount == 1:
             self.assertEqual(
                 formAutofillResults[0]['guid'], self._formAutofillAddressGuid)
 
     def checkCookie(self):
         cookieInfo = self.runCode("""
           try {
-            let cookieEnum = Services.cookies.getCookiesFromHost(arguments[0]);
+            let cookieEnum = Services.cookies.getCookiesFromHost(arguments[0], {});
             let cookie = null;
             while (cookieEnum.hasMoreElements()) {
               let hostCookie = cookieEnum.getNext();
               hostCookie.QueryInterface(Ci.nsICookie2);
               // getCookiesFromHost returns any cookie from the BASE host.
               if (hostCookie.rawHost != arguments[0])
                 continue;
               if (cookie != null) {
--- a/browser/components/preferences/in-content/tests/siteData/browser_siteData.js
+++ b/browser/components/preferences/in-content/tests/siteData/browser_siteData.js
@@ -131,32 +131,36 @@ add_task(async function() {
 });
 
 // Test showing and removing sites with cookies.
 add_task(async function() {
   // Add some test cookies.
   let uri = Services.io.newURI("https://example.com");
   let uri2 = Services.io.newURI("https://example.org");
   Services.cookies.add(uri.host, uri.pathQueryRef, "test1", "1",
-    false, false, false, Date.now() + 1000 * 60 * 60);
+    false, false, false, Date.now() + 1000 * 60 * 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
   Services.cookies.add(uri.host, uri.pathQueryRef, "test2", "2",
-    false, false, false, Date.now() + 1000 * 60 * 60);
+    false, false, false, Date.now() + 1000 * 60 * 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
   Services.cookies.add(uri2.host, uri2.pathQueryRef, "test1", "1",
-    false, false, false, Date.now() + 1000 * 60 * 60);
+    false, false, false, Date.now() + 1000 * 60 * 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   // Ensure that private browsing cookies are ignored.
   Services.cookies.add(uri.host, uri.pathQueryRef, "test3", "3",
-    false, false, false, Date.now() + 1000 * 60 * 60, { privateBrowsingId: 1 });
+    false, false, false, Date.now() + 1000 * 60 * 60, { privateBrowsingId: 1 },
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   // Get the exact creation date from the cookies (to avoid intermittents
   // from minimal time differences, since we round up to minutes).
-  let cookiesEnum1 = Services.cookies.getCookiesFromHost(uri.host);
+  let cookiesEnum1 = Services.cookies.getCookiesFromHost(uri.host, {});
   // We made two valid cookies for example.com.
   cookiesEnum1.getNext();
-  let cookiesEnum2 = Services.cookies.getCookiesFromHost(uri2.host);
+  let cookiesEnum2 = Services.cookies.getCookiesFromHost(uri2.host, {});
   let cookie1 = cookiesEnum1.getNext().QueryInterface(Ci.nsICookie2);
   let cookie2 = cookiesEnum2.getNext().QueryInterface(Ci.nsICookie2);
 
   let fullFormatter = new Services.intl.DateTimeFormat(undefined, {
     dateStyle: "short", timeStyle: "short",
   });
 
   await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
--- a/browser/components/preferences/in-content/tests/siteData/head.js
+++ b/browser/components/preferences/in-content/tests/siteData/head.js
@@ -170,17 +170,18 @@ const mockSiteDataManager = {
         site.baseDomain = Services.eTLD.getBaseDomainFromHost(uri.host);
       } catch (e) {
         site.baseDomain = uri.host;
       }
 
       // Add some cookies if needed.
       for (let i = 0; i < (site.cookies || 0); i++) {
         Services.cookies.add(uri.host, uri.pathQueryRef, Cu.now(), i,
-          false, false, false, Date.now() + 1000 * 60 * 60);
+          false, false, false, Date.now() + 1000 * 60 * 60, {},
+          Ci.nsICookie2.SAMESITE_UNSET);
       }
     }
   },
 
   async unregister() {
     await this._SiteDataManager.removeAll();
     this.fakeSites = null;
     this._SiteDataManager._qms = this._originalQMS;
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -44,34 +44,32 @@ var SessionCookiesInternal = {
   },
 
   /**
    * Restores a given list of session cookies.
    */
   restore(cookies) {
     for (let cookie of cookies) {
       let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
-      let cookieObj = {
-        host: cookie.host,
-        path: cookie.path || "",
-        name: cookie.name || "",
-      };
-
-      let originAttributes = cookie.originAttributes || {};
       let exists = false;
       try {
-        exists = Services.cookies.cookieExists(cookieObj, originAttributes);
+        exists = Services.cookies.cookieExists(cookie.host,
+                                               cookie.path || "",
+                                               cookie.name || "",
+                                               cookie.originAttributes || {});
       } catch (ex) {
         Cu.reportError(`nsCookieService::CookieExists failed with error '${ex}' for '${JSON.stringify(cookie)}'.`);
       }
       if (!exists) {
         try {
           Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
                                cookie.value, !!cookie.secure, !!cookie.httponly,
-                               /* isSession = */ true, expiry, originAttributes);
+                               /* isSession = */ true, expiry,
+                               cookie.originAttributes || {},
+                               Ci.nsICookie2.SAMESITE_UNSET);
         } catch (ex) {
           Cu.reportError(`nsCookieService::Add failed with error '${ex}' for cookie ${JSON.stringify(cookie)}.`);
         }
       }
     }
   },
 
   /**
--- a/browser/components/sessionstore/test/browser_cookies_privacy.js
+++ b/browser/components/sessionstore/test/browser_cookies_privacy.js
@@ -3,17 +3,18 @@
 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
 const MAX_EXPIRY = Math.pow(2, 62);
 
 function addCookie(scheme, secure = false) {
   let cookie = createTestCookie("http", secure);
   Services.cookies.add(cookie.host, cookie.path, cookie.name, cookie.value,
                        cookie.secure, /* isHttpOnly = */ false,
                        /* isSession = */ true, MAX_EXPIRY,
-                       /* originAttributes = */ {});
+                       /* originAttributes = */ {},
+                       Ci.nsICookie2.SAMESITE_UNSET);
   return cookie;
 }
 
 function createTestCookie(scheme, secure = false) {
   let r = Math.round(Math.random() * 100000);
 
   let cookie = {
     host: `${scheme}://example.com`,
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -858,17 +858,17 @@ toolbarbutton[constrain-size="true"][cui
 
 .subview-subheader,
 panelview .toolbarbutton-1,
 .subviewbutton,
 .widget-overflow-list .toolbarbutton-1 {
   -moz-appearance: none;
   margin: 0;
   min-height: 24px;
-  padding: 4px 12px !important;
+  padding: 4px 12px;
   background-color: transparent;
 }
 
 .subviewbutton:focus {
   outline: 0;
 }
 
 .subviewbutton[disabled="true"] {
@@ -1033,29 +1033,29 @@ panelmultiview .toolbaritem-combined-but
   width: 20px; /* a little bigger than the width of the scrollbar */
 }
 
 .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton {
   -moz-box-flex: 0;
   height: auto;
   margin-inline-start: 18px;
   min-width: auto;
-  padding: 4px 5px !important;
+  padding: 4px 5px;
 }
 
 #appMenu-zoom-controls > .subviewbutton {
   margin-inline-start: 10px;
 }
 
 /* Unset the min-height constraint, because that works better for a text-only button. */
 #appMenu-zoomReset-button {
   min-height: unset;
   border: 1px solid var(--panel-separator-color);
   border-radius: 10000px;
-  padding: 1px 8px !important;
+  padding: 1px 8px;
   background-color: var(--arrowpanel-dimmed);
 }
 
 #appMenu-zoomReset-button@buttonStateHover@ {
   background-color: var(--arrowpanel-dimmed-further);
 }
 
 #appMenu-zoomReset-button@buttonStateActive@ {
@@ -1160,17 +1160,17 @@ menuitem.panel-subview-footer@menuStateA
 #BMB_unsortedBookmarksPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
 #BMB_mobileBookmarksPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
   /* And so they need some bottom padding: */
   padding-bottom: 4px;
 }
 
 /* Disabled (empty) item is always alone and never has an icon, so fix its left padding */
 #BMB_bookmarksPopup menupopup[emptyplacesresult] .bookmark-item.subviewbutton {
-  padding-left: 6px !important;
+  padding-left: 6px;
 }
 
 #widget-overflow-mainView > .panel-subview-body > toolbarseparator,
 .PanelUI-subView menuseparator,
 .PanelUI-subView toolbarseparator,
 .cui-widget-panelview menuseparator,
 .cui-widget-panel toolbarseparator {
   -moz-appearance: none;
@@ -1253,18 +1253,18 @@ toolbarpaletteitem[place="palette"] > #s
   width: 7em;
   min-height: 37px;
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
   border: 0;
   margin: 0;
   -moz-box-flex: 1;
-  padding-top: 4px !important;
-  padding-bottom: 4px !important;
+  padding-top: 4px;
+  padding-bottom: 4px;
   -moz-box-orient: horizontal;
 }
 
 /* In customize mode, extend the buttons *only* in the panel, just to make them not look stupid */
 toolbarpaletteitem[place="menu-panel"] > .toolbaritem-combined-buttons > toolbarbutton {
   min-width: calc(@menuPanelButtonWidth@ - 1px);
   max-width: calc(@menuPanelButtonWidth@ - 1px);
 }
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -95,23 +95,23 @@ toolbar[brighttext] {
   -moz-appearance: none;
   padding: 0;
   color: inherit;
 }
 
 toolbar .toolbarbutton-1 {
   -moz-appearance: none;
   margin: 0;
-  padding: 0 var(--toolbarbutton-outer-padding) !important;
+  padding: 0 var(--toolbarbutton-outer-padding);
   -moz-box-pack: center;
 }
 
 :root:not([uidensity=compact]) #PanelUI-menu-button {
-  padding-inline-start: 5px !important;
-  padding-inline-end: 5px !important;
+  padding-inline-start: 5px;
+  padding-inline-end: 5px;
 }
 
 toolbar .toolbarbutton-1 > menupopup {
   margin-top: -3px;
 }
 
 .findbar-button > .toolbarbutton-text,
 toolbarbutton.bookmark-item:not(.subviewbutton),
@@ -144,18 +144,18 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 }
 
 toolbar .toolbaritem-combined-buttons {
   margin-left: 2px;
   margin-right: 2px;
 }
 
 toolbar .toolbaritem-combined-buttons > .toolbarbutton-1 {
-  padding-left: 0 !important;
-  padding-right: 0 !important;
+  padding-left: 0;
+  padding-right: 0;
 }
 
 toolbar .toolbaritem-combined-buttons:not(:hover) > separator {
   content: "";
   display: -moz-box;
   width: 1px;
   height: 16px;
   margin-inline-end: -1px;
@@ -194,20 +194,20 @@ toolbar .toolbarbutton-1:not([disabled=t
 }
 
 toolbar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
   background-color: var(--toolbarbutton-hover-background);
   transition: background-color .4s;
 }
 
 :root:not([uidensity=compact]) #back-button {
-  padding-top: 3px !important;
-  padding-bottom: 3px !important;
-  padding-inline-start: 3px !important;
-  padding-inline-end: 0 !important;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  padding-inline-start: 3px;
+  padding-inline-end: 0;
   position: relative !important;
   z-index: 1 !important;
   border-radius: 0 10000px 10000px 0;
 }
 
 :root:not([uidensity=compact]) #back-button:-moz-locale-dir(rtl) {
   border-radius: 10000px 0 0 10000px;
 }
@@ -225,19 +225,19 @@ toolbar .toolbarbutton-1[checked]:not(:a
   width: 34px;
   height: 34px;
   padding: 8px;
   transition-property: box-shadow;
   transition-duration: var(--toolbarbutton-hover-transition-duration);
 }
 
 :root[uidensity=touch] #back-button {
-  padding-top: 1px !important;
-  padding-bottom: 1px !important;
-  padding-inline-start: 1px !important;
+  padding-top: 1px;
+  padding-bottom: 1px;
+  padding-inline-start: 1px;
 }
 
 :root[uidensity=touch] #back-button > .toolbarbutton-icon {
   width: 38px;
   height: 38px;
   padding: 10px;
 }
 
@@ -338,11 +338,11 @@ toolbarbutton.bookmark-item {
 #PersonalToolbar .toolbarbutton-1 > .toolbarbutton-text,
 #PersonalToolbar .toolbarbutton-1 > .toolbarbutton-badge-stack {
   padding: 0 !important;
   background: none !important;
   min-height: 16px;
 }
 
 #PersonalToolbar .toolbarbutton-1 {
-  padding: 1px var(--toolbarbutton-inner-padding) !important;
+  padding: 1px var(--toolbarbutton-inner-padding);
   border-radius: var(--toolbarbutton-border-radius);
 }
--- a/devtools/client/inspector/flexbox/flexbox.js
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -109,16 +109,62 @@ class FlexboxInspector {
     return {
       onSetFlexboxOverlayColor: this.onSetFlexboxOverlayColor,
       onToggleFlexboxHighlighter: this.onToggleFlexboxHighlighter,
       onToggleFlexItemShown: this.onToggleFlexItemShown,
     };
   }
 
   /**
+   * Returns an object containing the custom flexbox colors for different hosts.
+   *
+   * @return {Object} that maps a host name to a custom flexbox color for a given host.
+   */
+  async getCustomHostColors() {
+    if (this._customHostColors) {
+      return this._customHostColors;
+    }
+
+    // Cache the custom host colors to avoid refetching from async storage.
+    this._customHostColors = await asyncStorage.getItem("flexboxInspectorHostColors")
+      || {};
+    return this._customHostColors;
+  }
+
+  /**
+   * Returns an array of flex items object for the given flex container front.
+   *
+   * @param  {FlexboxFront} flexboxFront
+   *         A flex container FlexboxFront.
+   * @return {Array} of objects containing the flex item front properties.
+   */
+  async getFlexItems(flexboxFront) {
+    const flexItemFronts = await flexboxFront.getFlexItems();
+    const flexItems = [];
+
+    for (const flexItemFront of flexItemFronts) {
+      // Fetch the NodeFront of the flex items.
+      let itemNodeFront = flexItemFront.nodeFront;
+      if (!itemNodeFront) {
+        itemNodeFront = await this.walker.getNodeFromActor(flexItemFront.actorID,
+          ["element"]);
+      }
+
+      flexItems.push({
+        actorID: flexItemFront.actorID,
+        flexItemSizing: flexItemFront.flexItemSizing,
+        nodeFront: itemNodeFront,
+        properties: flexItemFront.properties,
+      });
+    }
+
+    return flexItems;
+  }
+
+  /**
    * Returns the custom overlay color for the current host or the default flexbox color.
    *
    * @return {String} overlay color.
    */
   async getOverlayColor() {
     if (this._overlayColor) {
       return this._overlayColor;
     }
@@ -130,32 +176,16 @@ class FlexboxInspector {
     // Get the hostname, if there is no hostname, fall back on protocol
     // ex: `data:` uri, and `about:` pages
     const hostName = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
     this._overlayColor = customColors[hostName] ? customColors[hostName] : FLEXBOX_COLOR;
     return this._overlayColor;
   }
 
   /**
-   * Returns an object containing the custom flexbox colors for different hosts.
-   *
-   * @return {Object} that maps a host name to a custom flexbox color for a given host.
-   */
-  async getCustomHostColors() {
-    if (this._customHostColors) {
-      return this._customHostColors;
-    }
-
-    // Cache the custom host colors to avoid refetching from async storage.
-    this._customHostColors = await asyncStorage.getItem("flexboxInspectorHostColors")
-      || {};
-    return this._customHostColors;
-  }
-
-  /**
    * Returns true if the layout panel is visible, and false otherwise.
    */
   isPanelVisible() {
     return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
            this.inspector.toolbox.currentToolId === "inspector" &&
            this.inspector.sidebar.getCurrentTabID() === "layoutview";
   }
   /**
@@ -377,52 +407,30 @@ class FlexboxInspector {
       // then get it from the walker. This happens when the walker hasn't seen this
       // particular DOM Node in the tree yet or when we are connected to an older server.
       let containerNodeFront = flexboxFront.containerNodeFront;
       if (!containerNodeFront) {
         containerNodeFront = await this.walker.getNodeFromActor(flexboxFront.actorID,
           ["containerEl"]);
       }
 
-      // Fetch the flex items for the given flex container.
-      const flexItemFronts = await flexboxFront.getFlexItems();
-      const flexItems = [];
-      let flexItemShown = null;
-
-      for (const flexItemFront of flexItemFronts) {
-        // Fetch the NodeFront of the flex items.
-        let itemNodeFront = flexItemFront.nodeFront;
-        if (!itemNodeFront) {
-          itemNodeFront = await this.walker.getNodeFromActor(flexItemFront.actorID,
-            ["element"]);
-        }
-
-        // If the current selected node is a flex item, display its flex item sizing
-        // properties.
-        if (!flexItemShown && itemNodeFront === this.selection.nodeFront) {
-          flexItemShown = itemNodeFront.actorID;
-        }
-
-        flexItems.push({
-          actorID: flexItemFront.actorID,
-          flexItemSizing: flexItemFront.flexItemSizing,
-          nodeFront: itemNodeFront,
-          properties: flexItemFront.properties,
-        });
-      }
-
+      const flexItems = await this.getFlexItems(flexboxFront);
+      // If the current selected node is a flex item, display its flex item sizing
+      // properties.
+      const flexItemShown = flexItems.find(item =>
+        item.nodeFront === this.selection.nodeFront);
       const highlighted = this._highlighters &&
         containerNodeFront == this.highlighters.flexboxHighlighterShown;
       const color = await this.getOverlayColor();
 
       this.store.dispatch(updateFlexbox({
         actorID: flexboxFront.actorID,
         color,
         flexItems,
-        flexItemShown,
+        flexItemShown: flexItemShown ? flexItemShown.nodeFront.actorID : null,
         highlighted,
         nodeFront: containerNodeFront,
         properties: flexboxFront.properties,
       }));
     } catch (e) {
       // This call might fail if called asynchrously after the toolbox is finished
       // closing.
     }
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -915,17 +915,18 @@ var cookieHelpers = {
       cookie.host,
       cookie.path,
       cookie.name,
       cookie.value,
       cookie.isSecure,
       cookie.isHttpOnly,
       cookie.isSession,
       cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expires,
-      cookie.originAttributes
+      cookie.originAttributes,
+      cookie.sameSite
     );
   },
 
   _removeCookies(host, opts = {}) {
     // We use a uniqueId to emulate compound keys for cookies. We need to
     // extract the cookie name to remove the correct cookie.
     if (opts.name) {
       const split = opts.name.split(SEPARATOR_GUID);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1490,16 +1490,17 @@ nsIDocument::nsIDocument()
     mWindow(nullptr),
     mBFCacheEntry(nullptr),
     mInSyncOperationCount(0),
     mBlockDOMContentLoaded(0),
     mUseCounters(0),
     mChildDocumentUseCounters(0),
     mNotifiedPageForUseCounter(0),
     mUserHasInteracted(false),
+    mHasUserInteractionTimerScheduled(false),
     mUserGestureActivated(false),
     mStackRefCnt(0),
     mUpdateNestLevel(0),
     mViewportType(Unknown),
     mViewportOverflowType(ViewportOverflowType::NoOverflow),
     mSubDocuments(nullptr),
     mHeaderData(nullptr),
     mFlashClassification(FlashClassification::Unclassified),
@@ -12617,30 +12618,36 @@ nsIDocument::ReportHasScrollLinkedEffect
   mHasScrollLinkedEffect = true;
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("Async Pan/Zoom"),
                                   this, nsContentUtils::eLAYOUT_PROPERTIES,
                                   "ScrollLinkedEffectFound2");
 }
 
 void
-nsIDocument::SetUserHasInteracted(bool aUserHasInteracted)
+nsIDocument::SetUserHasInteracted()
 {
   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
           ("Document %p has been interacted by user.", this));
-  mUserHasInteracted = aUserHasInteracted;
+
+  // We maybe need to update the user-interaction permission.
+  MaybeStoreUserInteractionAsPermission();
+
+  if (mUserHasInteracted) {
+    return;
+  }
+
+  mUserHasInteracted = true;
 
   nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->GetLoadInfo() : nullptr;
   if (loadInfo) {
-    loadInfo->SetDocumentHasUserInteracted(aUserHasInteracted);
-  }
-
-  if (aUserHasInteracted) {
-    MaybeAllowStorageForOpener();
-  }
+    loadInfo->SetDocumentHasUserInteracted(true);
+  }
+
+  MaybeAllowStorageForOpener();
 }
 
 void
 nsIDocument::NotifyUserGestureActivation()
 {
   // Activate this document and all documents up to the top level
   // content document.
   nsIDocument* doc = this;
@@ -12750,16 +12757,202 @@ nsIDocument::MaybeAllowStorageForOpener(
   }
 
   // We don't care when the asynchronous work finishes here.
   Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin,
                                                                      openerInner,
                                                                      AntiTrackingCommon::eHeuristic);
 }
 
+namespace {
+
+// Documents can stay alive for days. We don't want to update the permission
+// value at any user-interaction, and, using a timer triggered any X seconds
+// should be good enough. 'X' is taken from
+// privacy.userInteraction.document.interval pref.
+//  We also want to store the user-interaction before shutting down, and, for
+//  this reason, this class implements nsIAsyncShutdownBlocker interface.
+class UserIntractionTimer final : public Runnable
+                                , public nsITimerCallback
+                                , public nsIAsyncShutdownBlocker
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  explicit UserIntractionTimer(nsIDocument* aDocument)
+    : Runnable("UserIntractionTimer")
+    , mPrincipal(aDocument->NodePrincipal())
+    , mDocument(do_GetWeakReference(aDocument))
+  {
+    static int32_t userInteractionTimerId = 0;
+    // Blocker names must be unique. Let's create it now because when needed,
+    // the document could be already gone.
+    mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
+                              ++userInteractionTimerId, aDocument);
+  }
+
+  // Runnable interface
+
+  NS_IMETHOD
+  Run() override
+  {
+    uint32_t interval =
+      StaticPrefs::privacy_userInteraction_document_interval();
+    if (!interval) {
+      return NS_OK;
+    }
+
+    RefPtr<UserIntractionTimer> self = this;
+    auto raii = MakeScopeExit([self] {
+      self->CancelTimerAndStoreUserInteraction();
+    });
+
+    nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer),
+                                          this, interval * 1000,
+                                          nsITimer::TYPE_ONE_SHOT,
+                                          SystemGroup::EventTargetFor(TaskCategory::Other));
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+    NS_ENSURE_TRUE(!!phase, NS_OK);
+
+    rv = phase->AddBlocker(this, NS_LITERAL_STRING(__FILE__), __LINE__,
+                           NS_LITERAL_STRING("UserIntractionTimer shutdown"));
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    raii.release();
+    return NS_OK;
+  }
+
+  // nsITimerCallback interface
+
+  NS_IMETHOD
+  Notify(nsITimer* aTimer) override
+  {
+    StoreUserInteraction();
+    return NS_OK;
+  }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+  using nsINamed::GetName;
+#endif
+
+  // nsIAsyncShutdownBlocker interface
+
+  NS_IMETHOD
+  GetName(nsAString& aName) override
+  {
+    aName = mBlockerName;
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  BlockShutdown(nsIAsyncShutdownClient* aClient) override
+  {
+    CancelTimerAndStoreUserInteraction();
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  GetState(nsIPropertyBag**) override
+  {
+    return NS_OK;
+  }
+
+private:
+  ~UserIntractionTimer() = default;
+
+  void
+  StoreUserInteraction()
+  {
+    // Remove the shutting down blocker
+    nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+    if (phase) {
+      phase->RemoveBlocker(this);
+    }
+
+    // If the document is not gone, let's reset its timer flag.
+    nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocument);
+    if (document) {
+      AntiTrackingCommon::StoreUserInteractionFor(mPrincipal);
+      document->ResetUserInteractionTimer();
+    }
+  }
+
+  void
+  CancelTimerAndStoreUserInteraction()
+  {
+    if (mTimer) {
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+
+    StoreUserInteraction();
+  }
+
+  static already_AddRefed<nsIAsyncShutdownClient>
+  GetShutdownPhase()
+  {
+    nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
+    NS_ENSURE_TRUE(!!svc, nullptr);
+
+    nsCOMPtr<nsIAsyncShutdownClient> phase;
+    nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    return phase.forget();
+  }
+
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsWeakPtr mDocument;
+
+  nsCOMPtr<nsITimer> mTimer;
+
+  nsString mBlockerName;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(UserIntractionTimer, Runnable, nsITimerCallback,
+                            nsIAsyncShutdownBlocker)
+
+} // anonymous
+
+void
+nsIDocument::MaybeStoreUserInteractionAsPermission()
+{
+  // We care about user-interaction stored only for top-level documents.
+  if (GetSameTypeParentDocument()) {
+    return;
+  }
+
+  if (!mUserHasInteracted) {
+    // First interaction, let's store this info now.
+    AntiTrackingCommon::StoreUserInteractionFor(NodePrincipal());
+    return;
+  }
+
+  if (mHasUserInteractionTimerScheduled) {
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> task = new UserIntractionTimer(this);
+  nsresult rv = NS_IdleDispatchToCurrentThread(task.forget(), 2500);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // This value will be reset by the timer.
+  mHasUserInteractionTimerScheduled = true;
+}
+
+void
+nsIDocument::ResetUserInteractionTimer()
+{
+  mHasUserInteractionTimerScheduled = false;
+}
+
 bool
 nsIDocument::HasBeenUserGestureActivated()
 {
   if (mUserGestureActivated) {
     return true;
   }
 
   // If any ancestor document is activated, so are we.
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3495,21 +3495,22 @@ public:
     SetDocumentUseCounter(aUseCounter);
     SetPageUseCounter(aUseCounter);
   }
 
   void PropagateUseCounters(nsIDocument* aParentDocument);
 
   // Called to track whether this document has had any interaction.
   // This is used to track whether we should permit "beforeunload".
-  void SetUserHasInteracted(bool aUserHasInteracted);
+  void SetUserHasInteracted();
   bool UserHasInteracted()
   {
     return mUserHasInteracted;
   }
+  void ResetUserInteractionTimer();
 
   // This should be called when this document receives events which are likely
   // to be user interaction with the document, rather than the byproduct of
   // interaction with the browser (i.e. a keypress to scroll the view port,
   // keyboard shortcuts, etc). This is used to decide whether we should
   // permit autoplay audible media. This also gesture activates all other
   // content documents in this tab.
   void NotifyUserGestureActivation();
@@ -3916,16 +3917,18 @@ protected:
   // Helper for GetScrollingElement/IsScrollingElement.
   bool IsPotentiallyScrollable(mozilla::dom::HTMLBodyElement* aBody);
 
   // Return the same type parent docuement if exists, or return null.
   nsIDocument* GetSameTypeParentDocument();
 
   void MaybeAllowStorageForOpener();
 
+  void MaybeStoreUserInteractionAsPermission();
+
   // Helpers for GetElementsByName.
   static bool MatchNameAttribute(mozilla::dom::Element* aElement,
                                  int32_t aNamespaceID,
                                  nsAtom* aAtom, void* aData);
   static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
 
   void MaybeResolveReadyForIdle();
 
@@ -4490,16 +4493,21 @@ protected:
   std::bitset<mozilla::eUseCounter_Count> mNotifiedPageForUseCounter;
 
   // The CSS property use counters.
   mozilla::UniquePtr<StyleUseCounters> mStyleUseCounters;
 
   // Whether the user has interacted with the document or not:
   bool mUserHasInteracted;
 
+  // We constantly update the user-interaction anti-tracking permission at any
+  // user-interaction using a timer. This boolean value is set to true when this
+  // timer is scheduled.
+  bool mHasUserInteractionTimerScheduled;
+
   // Whether the user has interacted with the document via a restricted
   // set of gestures which are likely to be interaction with the document,
   // and not events that are fired as a byproduct of the user interacting
   // with the browser (events for like scrolling the page, keyboard short
   // cuts, etc).
   bool mUserGestureActivated;
 
   mozilla::TimeStamp mPageUnloadingEventTimeStamp;
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -499,18 +499,18 @@ EventStateManager::PreHandleEvent(nsPres
     ++gMouseOrKeyboardEventCounter;
 
     nsCOMPtr<nsINode> node = do_QueryInterface(aTargetContent);
     if (node &&
         (aEvent->mMessage == eKeyUp || aEvent->mMessage == eMouseUp ||
          aEvent->mMessage == eWheel || aEvent->mMessage == eTouchEnd ||
          aEvent->mMessage == ePointerUp)) {
       nsIDocument* doc = node->OwnerDoc();
-      while (doc && !doc->UserHasInteracted()) {
-        doc->SetUserHasInteracted(true);
+      while (doc) {
+        doc->SetUserHasInteracted();
         doc = nsContentUtils::IsChildOfSameType(doc) ?
           doc->GetParentDocument() : nullptr;
       }
     }
   }
 
   WheelTransaction::OnEvent(aEvent);
 
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -3413,12 +3413,10 @@ nsHTMLDocument::GetFormsAndFormControls(
 
   NS_ADDREF(*aFormList = holder->mFormList);
   NS_ADDREF(*aFormControlList = holder->mFormControlList);
 }
 
 void
 nsHTMLDocument::UserInteractionForTesting()
 {
-  if (!UserHasInteracted()) {
-    SetUserHasInteracted(true);
-  }
+  SetUserHasInteracted();
 }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5942,16 +5942,23 @@ ContentParent::RecvFirstPartyStorageAcce
   AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(aParentPrincipal,
                                                                                  aTrackingOrigin,
                                                                                  aGrantedOrigin,
                                                                                  std::move(aResolver));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentParent::RecvStoreUserInteractionAsPermission(const Principal& aPrincipal)
+{
+  AntiTrackingCommon::StoreUserInteractionFor(aPrincipal);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentParent::RecvAttachBrowsingContext(
   const BrowsingContextId& aParentId,
   const BrowsingContextId& aChildId,
   const nsString& aName)
 {
   RefPtr<ChromeBrowsingContext> parent = ChromeBrowsingContext::Get(aParentId);
   if (aParentId && !parent) {
     // Unless 'aParentId' is 0 (which it is when the child is a root
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1251,16 +1251,19 @@ public:
     const HangDetails& aHangDetails) override;
 
   virtual mozilla::ipc::IPCResult
   RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
                                               const nsCString& aTrackingOrigin,
                                               const nsCString& aGrantedOrigin,
                                               FirstPartyStorageAccessGrantedForOriginResolver&& aResolver) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvStoreUserInteractionAsPermission(const Principal& aPrincipal) override;
+
   // Notify the ContentChild to enable the input event prioritization when
   // initializing.
   void MaybeEnableRemoteInputEventQueue();
 
 public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1163,16 +1163,18 @@ parent:
      * A 3rd party tracking origin (aTrackingOrigin) has received the permission
      * granted to have access to aGrantedOrigin when loaded by aParentPrincipal.
      */
     async FirstPartyStorageAccessGrantedForOrigin(Principal aParentPrincipal,
                                                   nsCString aTrackingOrigin,
                                                   nsCString aGrantedOrigin)
           returns (bool unused);
 
+    async StoreUserInteractionAsPermission(Principal aPrincipal);
+
     /**
      * Sync the BrowsingContext with id 'aContextId' and name 'aName'
      * to the parent, and attach it to the BrowsingContext with id
      * 'aParentContextId'. If 'aParentContextId' is '0' the
      * BrowsingContext is a root in the BrowsingContext
      * tree. AttachBrowsingContext must only be called at most once
      * for any child BrowsingContext, and only for BrowsingContexts
      * where the parent and the child context contains their
--- a/extensions/cookie/test/file_testloadflags_chromescript.js
+++ b/extensions/cookie/test/file_testloadflags_chromescript.js
@@ -77,17 +77,18 @@ addMessageListener("init", ({ domain }) 
              .getService(Ci.nsICookieManager);
 
   info("we are going to remove these cookies");
 
   let count = getCookieCount(cs);
   info(count + " cookies");
 
   cs.removeAll();
-  cs.add(domain, "", "oh", "hai", false, false, true, Math.pow(2, 62), {});
+  cs.add(domain, "", "oh", "hai", false, false, true, Math.pow(2, 62), {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   is(cs.countCookiesFromHost(domain), 1, "number of cookies for domain " + domain);
 
   gObs = new obs();
   sendAsyncMessage("init:return");
 });
 
 addMessageListener("getCookieCount", () => {
   let cs = Cc["@mozilla.org/cookiemanager;1"]
--- a/extensions/cookie/test/unit/test_bug526789.js
+++ b/extensions/cookie/test/unit/test_bug526789.js
@@ -8,17 +8,18 @@ function run_test() {
 
   cm.removeAll();
 
   // Allow all cookies.
   Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
 
   // test that variants of 'baz.com' get normalized appropriately, but that
   // malformed hosts are rejected
-  cm.add("baz.com", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("baz.com", "/", "foo", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("baz.com"), 1);
   Assert.equal(cm.countCookiesFromHost("BAZ.com"), 1);
   Assert.equal(cm.countCookiesFromHost(".baz.com"), 1);
   Assert.equal(cm.countCookiesFromHost("baz.com."), 0);
   Assert.equal(cm.countCookiesFromHost(".baz.com."), 0);
   do_check_throws(function() {
     cm.countCookiesFromHost("baz.com..");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
@@ -29,50 +30,54 @@ function run_test() {
     cm.countCookiesFromHost("..baz.com");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   cm.remove("BAZ.com.", "foo", "/", false, {});
   Assert.equal(cm.countCookiesFromHost("baz.com"), 1);
   cm.remove("baz.com", "foo", "/", false, {});
   Assert.equal(cm.countCookiesFromHost("baz.com"), 0);
 
   // Test that 'baz.com' and 'baz.com.' are treated differently
-  cm.add("baz.com.", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("baz.com.", "/", "foo", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("baz.com"), 0);
   Assert.equal(cm.countCookiesFromHost("BAZ.com"), 0);
   Assert.equal(cm.countCookiesFromHost(".baz.com"), 0);
   Assert.equal(cm.countCookiesFromHost("baz.com."), 1);
   Assert.equal(cm.countCookiesFromHost(".baz.com."), 1);
   cm.remove("baz.com", "foo", "/", false, {});
   Assert.equal(cm.countCookiesFromHost("baz.com."), 1);
   cm.remove("baz.com.", "foo", "/", false, {});
   Assert.equal(cm.countCookiesFromHost("baz.com."), 0);
 
   // test that domain cookies are illegal for IP addresses, aliases such as
   // 'localhost', and eTLD's such as 'co.uk'
-  cm.add("192.168.0.1", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("192.168.0.1", "/", "foo", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("192.168.0.1"), 1);
   Assert.equal(cm.countCookiesFromHost("192.168.0.1."), 0);
   do_check_throws(function() {
     cm.countCookiesFromHost(".192.168.0.1");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   do_check_throws(function() {
     cm.countCookiesFromHost(".192.168.0.1.");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
 
-  cm.add("localhost", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("localhost", "/", "foo", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("localhost"), 1);
   Assert.equal(cm.countCookiesFromHost("localhost."), 0);
   do_check_throws(function() {
     cm.countCookiesFromHost(".localhost");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   do_check_throws(function() {
     cm.countCookiesFromHost(".localhost.");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
 
-  cm.add("co.uk", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("co.uk", "/", "foo", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("co.uk"), 1);
   Assert.equal(cm.countCookiesFromHost("co.uk."), 0);
   do_check_throws(function() {
     cm.countCookiesFromHost(".co.uk");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   do_check_throws(function() {
     cm.countCookiesFromHost(".co.uk.");
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
@@ -149,20 +154,22 @@ function run_test() {
   do_check_throws(function() {
     cm.getCookiesFromHost(".", {});
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
 
   cm.removeAll();
 
   // test that an empty host to add() or remove() works,
   // but a host of '.' doesn't
-  cm.add("", "/", "foo2", "bar", false, false, true, expiry, {});
+  cm.add("", "/", "foo2", "bar", false, false, true, expiry, {},
+         Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(getCookieCount(), 1);
   do_check_throws(function() {
-    cm.add(".", "/", "foo3", "bar", false, false, true, expiry, {});
+    cm.add(".", "/", "foo3", "bar", false, false, true, expiry, {},
+           Ci.nsICookie2.SAMESITE_UNSET);
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
   Assert.equal(getCookieCount(), 1);
 
   cm.remove("", "foo2", "/", false, {});
   Assert.equal(getCookieCount(), 0);
   do_check_throws(function() {
     cm.remove(".", "foo3", "/", false, {});
   }, Cr.NS_ERROR_ILLEGAL_VALUE);
--- a/extensions/cookie/test/unit/test_bug650522.js
+++ b/extensions/cookie/test/unit/test_bug650522.js
@@ -5,12 +5,12 @@ ChromeUtils.import("resource://gre/modul
 
 function run_test() {
   var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
   var expiry = (Date.now() + 1000) * 1000;
 
   // Test our handling of host names with a single character at the beginning
   // followed by a dot.
-  cm.add("e.mail.com", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("e.mail.com", "/", "foo", "bar", false, false, true, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("e.mail.com"), 1);
   Assert.equal(cs.getCookieString(NetUtil.newURI("http://e.mail.com"), null), "foo=bar");
 }
--- a/extensions/cookie/test/unit/test_bug667087.js
+++ b/extensions/cookie/test/unit/test_bug667087.js
@@ -5,12 +5,12 @@ ChromeUtils.import("resource://gre/modul
 
 function run_test() {
   var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
   var expiry = (Date.now() + 1000) * 1000;
 
   // Test our handling of host names with a single character consisting only
   // of a single character
-  cm.add("a", "/", "foo", "bar", false, false, true, expiry, {});
+  cm.add("a", "/", "foo", "bar", false, false, true, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(cm.countCookiesFromHost("a"), 1);
   Assert.equal(cs.getCookieString(NetUtil.newURI("http://a"), null), "foo=bar");
 }
--- a/extensions/cookie/test/unit/test_cookies_async_failure.js
+++ b/extensions/cookie/test/unit/test_cookies_async_failure.js
@@ -153,17 +153,18 @@ function* run_test_1(generator)
   Assert.equal(do_count_cookies_in_db(db.db), 1);
 
   // Insert a row.
   db.insertCookie(cookie);
   db.close();
 
   // Attempt to insert a cookie with the same (name, host, path) triplet.
   Services.cookiemgr.add(cookie.host, cookie.path, cookie.name, "hallo",
-    cookie.isSecure, cookie.isHttpOnly, cookie.isSession, cookie.expiry, {});
+    cookie.isSecure, cookie.isHttpOnly, cookie.isSession, cookie.expiry, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   // Check that the cookie service accepted the new cookie.
   Assert.equal(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
 
   let isRebuildingDone = false;
   let rebuildingObserve = function (subject, topic, data) {
     isRebuildingDone = true;
     Services.obs.removeObserver(rebuildingObserve, "cookie-db-rebuilding");
--- a/extensions/cookie/test/unit/test_cookies_profile_close.js
+++ b/extensions/cookie/test/unit/test_cookies_profile_close.js
@@ -27,17 +27,17 @@ function* do_run_test() {
   // Start the cookieservice.
   Services.cookies;
 
   // Set a cookie.
   let uri = NetUtil.newURI("http://foo.com");
   Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   let enumerator = Services.cookiemgr.enumerator;
   Assert.ok(enumerator.hasMoreElements());
-  let cookie = enumerator.getNext();
+  let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
   Assert.ok(!enumerator.hasMoreElements());
 
   // Fire 'profile-before-change'.
   do_close_profile();
 
   // Check that the APIs behave appropriately.
   Assert.equal(Services.cookies.getCookieString(uri, null), null);
   Assert.equal(Services.cookies.getCookieStringFromHttp(uri, null, null), null);
@@ -49,44 +49,45 @@ function* do_run_test() {
     Services.cookiemgr.removeAll();
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
     Services.cookiemgr.enumerator;
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
-    Services.cookiemgr.add("foo.com", "", "oh4", "hai", false, false, false, 0, {});
+    Services.cookiemgr.add("foo.com", "", "oh4", "hai", false, false, false, 0, {},
+                           Ci.nsICookie2.SAMESITE_UNSET);
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
     Services.cookiemgr.remove("foo.com", "", "oh4", false, {});
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
     let file = profile.clone();
     file.append("cookies.txt");
     Services.cookiemgr.importCookies(file);
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
-    Services.cookiemgr.cookieExists(cookie);
+    Services.cookiemgr.cookieExists(cookie.host, cookie.path, cookie.name, {});
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
     Services.cookies.countCookiesFromHost("foo.com");
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   do_check_throws(function() {
     Services.cookies.getCookiesFromHost("foo.com", {});
   }, Cr.NS_ERROR_NOT_AVAILABLE);
 
   // Wait for the database to finish closing.
   new _observer(test_generator, "cookie-db-closed");
   yield;
 
   // Load the profile and check that the API is available.
   do_load_profile();
-  Assert.ok(Services.cookiemgr.cookieExists(cookie));
+  Assert.ok(Services.cookiemgr.cookieExists(cookie.host, cookie.path, cookie.name, {}));
 
   finish_test();
 }
 
--- a/extensions/cookie/test/unit/test_domain_eviction.js
+++ b/extensions/cookie/test/unit/test_domain_eviction.js
@@ -63,39 +63,39 @@ function* do_run_test()
     if (cookie.host == "horse.radish")
       do_throw("cookies not evicted by lastAccessed order");
   }
 
   // Test that expired cookies for a domain are evicted before live ones.
   let shortExpiry = Math.floor(Date.now() / 1000 + 2);
   setCookies("captchart.com", 49, futureExpiry);
   Services.cookiemgr.add("captchart.com", "", "test100", "eviction",
-    false, false, false, shortExpiry, {});
+    false, false, false, shortExpiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   do_timeout(2100, continue_test);
   yield;
 
   Assert.equal(countCookies("captchart.com", "captchart.com"), 50);
   Services.cookiemgr.add("captchart.com", "", "test200", "eviction",
-    false, false, false, futureExpiry, {});
+    false, false, false, futureExpiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(countCookies("captchart.com", "captchart.com"), 50);
 
   for (let cookie of Services.cookiemgr.getCookiesFromHost("captchart.com", {})) {
     Assert.ok(cookie.expiry == futureExpiry);
   }
 
   do_finish_generator_test(test_generator);
 }
 
 // set 'aNumber' cookies with host 'aHost', with distinct names.
 function
 setCookies(aHost, aNumber, aExpiry)
 {
   for (let i = 0; i < aNumber; ++i)
     Services.cookiemgr.add(aHost, "", "test" + i, "eviction",
-      false, false, false, aExpiry, {});
+      false, false, false, aExpiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
 }
 
 // count how many cookies are within domain 'aBaseDomain', using three
 // independent interface methods on nsICookieManager:
 // 1) 'enumerator', an enumerator of all cookies;
 // 2) 'countCookiesFromHost', which returns the number of cookies within the
 //    base domain of 'aHost',
 // 3) 'getCookiesFromHost', which returns an enumerator of 2).
--- a/extensions/cookie/test/unit/test_eviction.js
+++ b/extensions/cookie/test/unit/test_eviction.js
@@ -194,17 +194,17 @@ function* do_run_test()
 function set_cookies(begin, end, expiry)
 {
   Assert.ok(begin != end);
 
   let beginTime;
   for (let i = begin; i < end; ++i) {
     let host = "eviction." + i + ".tests";
     Services.cookiemgr.add(host, "", "test", "eviction", false, false, false,
-      expiry, {});
+      expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
 
     if (i == begin)
       beginTime = get_creationTime(i);
   }
 
   let endTime = get_creationTime(end - 1);
   Assert.ok(begin == end - 1 || endTime > beginTime);
   if (endTime - beginTime > gPurgeAge * 1000000) {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1784,16 +1784,26 @@ GetSpaceWidthAppUnits(const gfxTextRun* 
   gfxFloat spaceWidthAppUnits =
     NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
                                  aTextRun->UseCenterBaseline()).spaceWidth *
              aTextRun->GetAppUnitsPerDevUnit());
 
   return spaceWidthAppUnits;
 }
 
+static gfxFloat
+GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun)
+{
+  gfxFloat chWidthAppUnits =
+    NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
+                                 aTextRun->IsVertical()).zeroOrAveCharWidth *
+             aTextRun->GetAppUnitsPerDevUnit());
+  return 0.5 * chWidthAppUnits;
+}
+
 static nscoord
 LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
 {
   if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
     return 0;
   }
   if (!aStyleText) {
     aStyleText = aFrame->StyleText();
@@ -3095,16 +3105,17 @@ public:
     : mTextRun(aTextRun), mFontGroup(nullptr),
       mTextStyle(aTextStyle), mFrag(aFrag),
       mLineContainer(aLineContainer),
       mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
       mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
       mLength(aLength),
       mWordSpacing(WordSpacing(aFrame, mTextRun, aTextStyle)),
       mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
+      mMinTabAdvance(-1.0),
       mHyphenWidth(-1),
       mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
       mJustificationArrayStart(0),
       mReflowing(true),
       mWhichTextRun(aWhichTextRun)
   {
     NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
   }
@@ -3120,16 +3131,17 @@ public:
       mTextStyle(aFrame->StyleText()),
       mFrag(aFrame->GetContent()->GetText()),
       mLineContainer(nullptr),
       mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
       mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
       mLength(aFrame->GetContentLength()),
       mWordSpacing(WordSpacing(aFrame, mTextRun)),
       mLetterSpacing(LetterSpacing(aFrame)),
+      mMinTabAdvance(-1.0),
       mHyphenWidth(-1),
       mOffsetFromBlockOriginForTabs(0),
       mJustificationArrayStart(0),
       mReflowing(false),
       mWhichTextRun(aWhichTextRun)
   {
     NS_ASSERTION(mTextRun, "Textrun not initialized!");
   }
@@ -3186,16 +3198,23 @@ public:
     if (!mFontMetrics) {
       InitFontGroupAndFontMetrics();
     }
     return mFontMetrics;
   }
 
   void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const;
 
+  gfxFloat MinTabAdvance() const {
+    if (mMinTabAdvance < 0.0) {
+      mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
+    }
+    return mMinTabAdvance;
+  }
+
   const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
 
 protected:
   void SetupJustificationSpacing(bool aPostReflow);
 
   void InitFontGroupAndFontMetrics() const {
     float inflation = (mWhichTextRun == nsTextFrame::eInflated)
       ? mFrame->GetFontSizeInflation() : 1.0f;
@@ -3218,16 +3237,17 @@ protected:
   // How far we've done tab-width calculation; this is ONLY valid when
   // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
   // It's a DOM offset relative to the current frame's offset.
   mutable uint32_t                mTabWidthsAnalyzedLimit;
 
   int32_t                         mLength;  // DOM string length, may be INT32_MAX
   const gfxFloat                  mWordSpacing; // space for each whitespace char
   const gfxFloat                  mLetterSpacing; // space for each letter
+  mutable gfxFloat                mMinTabAdvance; // min advance for <tab> char
   mutable gfxFloat                mHyphenWidth;
   mutable gfxFloat                mOffsetFromBlockOriginForTabs;
 
   // The values in mJustificationSpacings corresponds to unskipped
   // characters start from mJustificationArrayStart.
   uint32_t                        mJustificationArrayStart;
   nsTArray<Spacing>               mJustificationSpacings;
 
@@ -3475,23 +3495,21 @@ PropertyProvider::GetSpacingInternal(Ran
       aSpacing[offset].mBefore += spacing.mBefore;
       aSpacing[offset].mAfter += spacing.mAfter;
     }
   }
 }
 
 // aX and the result are in whole appunits.
 static gfxFloat
-AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth)
-{
-
-  // Advance aX to the next multiple of *aCachedTabWidth. We must advance
-  // by at least 1 appunit.
-  // XXX should we make this 1 CSS pixel?
-  return ceil((aX + 1) / aTabWidth) * aTabWidth;
+AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth, gfxFloat aMinAdvance)
+{
+  // Advance aX to the next multiple of aTabWidth. We must advance
+  // by at least aMinAdvance.
+  return ceil((aX + aMinAdvance) / aTabWidth) * aTabWidth;
 }
 
 void
 PropertyProvider::CalcTabWidths(Range aRange, gfxFloat aTabWidth) const
 {
   MOZ_ASSERT(aTabWidth > 0);
 
   if (!mTabWidths) {
@@ -3544,17 +3562,17 @@ PropertyProvider::CalcTabWidths(Range aR
             mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
         }
       } else {
         if (!mTabWidths) {
           mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
           mFrame->SetProperty(TabWidthProperty(), mTabWidths);
         }
         double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
-                                          aTabWidth);
+                                          aTabWidth, MinTabAdvance());
         mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
                 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
         mOffsetFromBlockOriginForTabs = nextTab;
       }
 
       mOffsetFromBlockOriginForTabs += spacing.mAfter;
     }
 
@@ -8625,17 +8643,18 @@ nsTextFrame::AddInlineMinISizeForFlow(gf
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
       provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->mCurrentLine += nscoord(spacing.mBefore);
       if (tabWidth < 0) {
         tabWidth = ComputeTabWidthAppUnits(this, textRun);
       }
       gfxFloat afterTab =
-        AdvanceToNextTab(aData->mCurrentLine, tabWidth);
+        AdvanceToNextTab(aData->mCurrentLine, tabWidth,
+                         provider.MinTabAdvance());
       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
       wordStart = i + 1;
     } else if (i < flowEndInTextRun ||
         (i == textRun->GetLength() &&
          (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK))) {
       if (preformattedNewline) {
         aData->ForceBreak();
       } else if (i < flowEndInTextRun && hyphenating &&
@@ -8788,17 +8807,18 @@ nsTextFrame::AddInlinePrefISizeForFlow(g
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
       provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->mCurrentLine += nscoord(spacing.mBefore);
       if (tabWidth < 0) {
         tabWidth = ComputeTabWidthAppUnits(this, textRun);
       }
       gfxFloat afterTab =
-        AdvanceToNextTab(aData->mCurrentLine, tabWidth);
+        AdvanceToNextTab(aData->mCurrentLine, tabWidth,
+                         provider.MinTabAdvance());
       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
       aData->mLineIsEmpty = false;
       lineStart = i + 1;
     } else if (preformattedNewline) {
       aData->ForceBreak();
       lineStart = i;
     }
   }
--- a/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_cookies_cache.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_cookies_cache.html
@@ -19,18 +19,18 @@ const COOKIE = {
   host: "example.com",
   name: "test_cookie",
   path: "/",
 };
 let since, oldCookie;
 
 function addCookie(cookie) {
   let expiry =  Date.now() / 1000 + 10000;
-  Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, expiry);
-  ok(Services.cookies.cookieExists(cookie), `Cookie ${cookie.name} was created.`);
+  Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+  ok(Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {}), `Cookie ${cookie.name} was created.`);
 }
 
 async function setUpCookies() {
   // Add a cookie which will end up with an older creationTime.
   oldCookie = Object.assign({}, COOKIE, {name: Date.now()});
   addCookie(oldCookie);
   await new Promise(resolve => setTimeout(resolve, 20));
   since = Date.now();
@@ -60,37 +60,37 @@ add_task(async function testCookies() {
   });
 
   async function testRemovalMethod(method) {
     // Clear cookies with a recent since value.
     await setUpCookies();
     extension.sendMessage(method, {since});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(Services.cookies.cookieExists(oldCookie), "Old cookie was not removed.");
-    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+    ok(Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was not removed.");
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
     // Clear cookies with an old since value.
     await setUpCookies();
     addCookie(COOKIE);
     extension.sendMessage(method, {since: since - 100000});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(!Services.cookies.cookieExists(oldCookie), "Old cookie was removed.");
-    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+    ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), "Old cookie was removed.");
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), "Recent cookie was removed.");
 
     // Clear cookies with no since value and valid originTypes.
     await setUpCookies();
     extension.sendMessage(
       method,
       {originTypes: {unprotectedWeb: true, protectedWeb: false}});
     await extension.awaitMessage("cookiesRemoved");
 
-    ok(!Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was removed.`);
-    ok(!Services.cookies.cookieExists(oldCookie), `Cookie ${oldCookie.name}  was removed.`);
+    ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name}  was removed.`);
+    ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), `Cookie ${oldCookie.name}  was removed.`);
   }
 
   await extension.startup();
 
   await testRemovalMethod("removeCookies");
   await testRemovalMethod("remove");
 
   await extension.unload();
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1616,16 +1616,30 @@ VARCACHE_PREF(
 
 // Anti-tracking permission expiration
 VARCACHE_PREF(
   "privacy.restrict3rdpartystorage.expiration",
    privacy_restrict3rdpartystorage_expiration,
   uint32_t, 2592000 // 30 days (in seconds)
 )
 
+// Anti-tracking user-interaction expiration
+VARCACHE_PREF(
+  "privacy.userInteraction.expiration",
+   privacy_userInteraction_expiration,
+  uint32_t, 2592000 // 30 days (in seconds)
+)
+
+// Anti-tracking user-interaction document interval
+VARCACHE_PREF(
+  "privacy.userInteraction.document.interval",
+   privacy_userInteraction_document_interval,
+  uint32_t, 1800 // 30 minutes (in seconds)
+)
+
 // Anti-fingerprinting, disabled by default
 VARCACHE_PREF(
   "privacy.resistFingerprinting",
    privacy_resistFingerprinting,
   RelaxedAtomicBool, false
 )
 
 VARCACHE_PREF(
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2577,79 +2577,35 @@ nsCookieService::GetSessionEnumerator(ns
         cookieList.AppendObject(cookie);
       }
     }
   }
 
   return NS_NewArrayEnumerator(aEnumerator, cookieList, NS_GET_IID(nsICookie2));
 }
 
-static nsresult
-InitializeOriginAttributes(OriginAttributes* aAttrs,
-                           JS::HandleValue aOriginAttributes,
-                           JSContext* aCx,
-                           uint8_t aArgc,
-                           const char16_t* aAPI,
-                           const char16_t* aInterfaceSuffix)
-{
-  MOZ_ASSERT(aAttrs);
-  MOZ_ASSERT(aCx);
-  MOZ_ASSERT(aAPI);
-  MOZ_ASSERT(aInterfaceSuffix);
-
-  if (aArgc == 0) {
-    const char16_t* params[] = {
-      aAPI,
-      aInterfaceSuffix
-    };
-
-    // This is supposed to be temporary and in 1 or 2 releases we want to
-    // have originAttributes param as mandatory. But for now, we don't want to
-    // break existing addons, so we write a console message to inform the addon
-    // developers about it.
-    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                    NS_LITERAL_CSTRING("Cookie Manager"),
-                                    nullptr,
-                                    nsContentUtils::eNECKO_PROPERTIES,
-                                    "nsICookieManagerAPIDeprecated",
-                                    params, ArrayLength(params));
-  } else if (aArgc == 1) {
-    if (!aOriginAttributes.isObject() ||
-        !aAttrs->Init(aCx, aOriginAttributes)) {
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 nsCookieService::Add(const nsACString &aHost,
                      const nsACString &aPath,
                      const nsACString &aName,
                      const nsACString &aValue,
                      bool              aIsSecure,
                      bool              aIsHttpOnly,
                      bool              aIsSession,
                      int64_t           aExpiry,
                      JS::HandleValue   aOriginAttributes,
                      int32_t           aSameSite,
-                     JSContext*        aCx,
-                     uint8_t           aArgc)
+                     JSContext*        aCx)
 {
-  MOZ_ASSERT(aArgc == 0 || aArgc == 1 || aArgc == 2);
-
   OriginAttributes attrs;
-  nsresult rv = InitializeOriginAttributes(&attrs,
-                                           aOriginAttributes,
-                                           aCx,
-                                           aArgc == 0 ? 0 : 1,
-                                           u"nsICookieManager.add()",
-                                           u"2");
-  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!aOriginAttributes.isObject() ||
+      !attrs.Init(aCx, aOriginAttributes)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
                    aIsSession, aExpiry, &attrs, aSameSite);
 }
 
 NS_IMETHODIMP_(nsresult)
 nsCookieService::AddNative(const nsACString &aHost,
                            const nsACString &aPath,
@@ -2768,29 +2724,24 @@ nsCookieService::Remove(const nsACString
 }
 
 NS_IMETHODIMP
 nsCookieService::Remove(const nsACString &aHost,
                         const nsACString &aName,
                         const nsACString &aPath,
                         bool             aBlocked,
                         JS::HandleValue  aOriginAttributes,
-                        JSContext*       aCx,
-                        uint8_t          aArgc)
+                        JSContext*       aCx)
 {
-  MOZ_ASSERT(aArgc == 0 || aArgc == 1);
-
   OriginAttributes attrs;
-  nsresult rv = InitializeOriginAttributes(&attrs,
-                                           aOriginAttributes,
-                                           aCx,
-                                           aArgc,
-                                           u"nsICookieManager.remove()",
-                                           u"");
-  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!aOriginAttributes.isObject() ||
+      !attrs.Init(aCx, aOriginAttributes)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
 }
 
 NS_IMETHODIMP_(nsresult)
 nsCookieService::RemoveNative(const nsACString &aHost,
                               const nsACString &aName,
                               const nsACString &aPath,
@@ -4741,73 +4692,63 @@ nsCookieService::PurgeCookies(int64_t aC
      aCurrentTimeInUsec - mDBState->cookieOldestTime));
 
   return removedList.forget();
 }
 
 // find whether a given cookie has been previously set. this is provided by the
 // nsICookieManager interface.
 NS_IMETHODIMP
-nsCookieService::CookieExists(nsICookie2* aCookie,
+nsCookieService::CookieExists(const nsACString& aHost,
+                              const nsACString& aPath,
+                              const nsACString& aName,
                               JS::HandleValue aOriginAttributes,
                               JSContext* aCx,
-                              uint8_t aArgc,
                               bool* aFoundCookie)
 {
-  NS_ENSURE_ARG_POINTER(aCookie);
   NS_ENSURE_ARG_POINTER(aCx);
   NS_ENSURE_ARG_POINTER(aFoundCookie);
-  MOZ_ASSERT(aArgc == 0 || aArgc == 1);
 
   OriginAttributes attrs;
-  nsresult rv = InitializeOriginAttributes(&attrs,
-                                           aOriginAttributes,
-                                           aCx,
-                                           aArgc,
-                                           u"nsICookieManager.cookieExists()",
-                                           u"2");
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return CookieExistsNative(aCookie, &attrs, aFoundCookie);
+  if (!aOriginAttributes.isObject() ||
+      !attrs.Init(aCx, aOriginAttributes)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie);
 }
 
 NS_IMETHODIMP_(nsresult)
-nsCookieService::CookieExistsNative(nsICookie2* aCookie,
+nsCookieService::CookieExistsNative(const nsACString& aHost,
+                                    const nsACString& aPath,
+                                    const nsACString& aName,
                                     OriginAttributes* aOriginAttributes,
                                     bool* aFoundCookie)
 {
-  NS_ENSURE_ARG_POINTER(aCookie);
   NS_ENSURE_ARG_POINTER(aOriginAttributes);
   NS_ENSURE_ARG_POINTER(aFoundCookie);
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   EnsureReadComplete(true);
 
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
-  nsAutoCString host, name, path;
-  nsresult rv = aCookie->GetHost(host);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = aCookie->GetName(name);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = aCookie->GetPath(path);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsAutoCString baseDomain;
-  rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
+  nsresult rv = GetBaseDomainFromHost(mTLDService, aHost, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsListIter iter;
   *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
-                             host, name, path, iter);
+                             PromiseFlatCString(aHost),
+                             PromiseFlatCString(aName),
+                             PromiseFlatCString(aPath), iter);
   return NS_OK;
 }
 
 // Cookie comparator for the priority queue used in FindStaleCookies.
 // Note that the expired cookie has the highest priority.
 // Other non-expired cookies are sorted by their age.
 class CookieIterComparator {
 private:
@@ -4927,21 +4868,18 @@ nsCookieService::CountCookiesFromHost(co
 }
 
 // get an enumerator of cookies stored by a particular host. this is provided by the
 // nsICookieManager interface.
 NS_IMETHODIMP
 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
                                     JS::HandleValue       aOriginAttributes,
                                     JSContext*            aCx,
-                                    uint8_t               aArgc,
                                     nsISimpleEnumerator **aEnumerator)
 {
-  MOZ_ASSERT(aArgc == 0 || aArgc == 1);
-
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   EnsureReadComplete(true);
 
   // first, normalize the hostname, and fail if it contains illegal characters.
@@ -4949,23 +4887,20 @@ nsCookieService::GetCookiesFromHost(cons
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   OriginAttributes attrs;
-  rv = InitializeOriginAttributes(&attrs,
-                                  aOriginAttributes,
-                                  aCx,
-                                  aArgc,
-                                  u"nsICookieManager.getCookiesFromHost()",
-                                  u"2");
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (!aOriginAttributes.isObject() ||
+      !attrs.Init(aCx, aOriginAttributes)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   nsCookieKey key = nsCookieKey(baseDomain, attrs);
 
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   if (!entry)
--- a/netwerk/cookie/nsICookieManager.idl
+++ b/netwerk/cookie/nsICookieManager.idl
@@ -60,29 +60,27 @@ interface nsICookieManager : nsISupports
    * directly from the desired nsICookie object.
    *
    * @param aHost The host or domain for which the cookie was set. @see
    *              nsICookieManager::add for a description of acceptable host
    *              strings. If the target cookie is a domain cookie, a leading
    *              dot must be present.
    * @param aName The name specified in the cookie
    * @param aPath The path for which the cookie was set
-   * @param aOriginAttributes The originAttributes of this cookie. This
-   *                          attribute is optional to avoid breaking add-ons.
-   *                          In 1 or 2 releases it will be mandatory: bug 1260399.
+   * @param aOriginAttributes The originAttributes of this cookie.
    * @param aBlocked Indicates if cookies from this host should be permanently
    *                 blocked.
    *
    */
-  [implicit_jscontext, optional_argc]
+  [implicit_jscontext]
   void remove(in AUTF8String   aHost,
               in ACString      aName,
               in AUTF8String   aPath,
               in boolean       aBlocked,
-              [optional] in jsval aOriginAttributes);
+              in jsval         aOriginAttributes);
 
   [notxpcom]
   nsresult removeNative(in AUTF8String   aHost,
                         in ACString      aName,
                         in AUTF8String   aPath,
                         in boolean       aBlocked,
                         in OriginAttributesPtr aOriginAttributes);
 
@@ -113,66 +111,69 @@ interface nsICookieManager : nsISupports
    * @param aIsSession
    *        true if the cookie should exist for the current session only.
    *        see aExpiry.
    * @param aExpiry
    *        expiration date, in seconds since midnight (00:00:00), January 1,
    *        1970 UTC. note that expiry time will also be honored for session cookies;
    *        in this way, the more restrictive of the two will take effect.
    * @param aOriginAttributes
-   *        the originAttributes of this cookie. This attribute is optional to
-   *        avoid breaking add-ons.
+   *        the originAttributes of this cookie.
    * @param aSameSite
-   *        the SameSite attribute. This attribute is optional to avoid breaking
-   *        addons
+   *        the SameSite attribute.
    */
-  [implicit_jscontext, optional_argc]
+  [implicit_jscontext]
   void add(in AUTF8String aHost,
            in AUTF8String aPath,
            in ACString    aName,
            in ACString    aValue,
            in boolean     aIsSecure,
            in boolean     aIsHttpOnly,
            in boolean     aIsSession,
            in int64_t     aExpiry,
-           [optional] in jsval aOriginAttributes,
-           [optional] in int32_t aSameSite);
+           in jsval aOriginAttributes,
+           in int32_t aSameSite);
 
   [notxpcom]
   nsresult addNative(in AUTF8String aHost,
                      in AUTF8String aPath,
                      in ACString    aName,
                      in ACString    aValue,
                      in boolean     aIsSecure,
                      in boolean     aIsHttpOnly,
                      in boolean     aIsSession,
                      in int64_t     aExpiry,
                      in OriginAttributesPtr aOriginAttributes,
                      in int32_t aSameSite);
 
   /**
    * Find whether a given cookie already exists.
    *
-   * @param aCookie
-   *        the cookie to look for
+   * @param aHost
+   *        the cookie's host to look for
+   * @param aPath
+   *        the cookie's path to look for
+   * @param aName
+   *        the cookie's name to look for
    * @param aOriginAttributes
-   *        nsICookie2 contains an originAttributes but if nsICookie2 is
-   *        implemented in JS, we can't retrieve its originAttributes because
-   *        the getter is marked [implicit_jscontext]. This optional parameter
-   *        is a workaround.
+   *        the cookie's originAttributes to look for
    *
-   * @return true if a cookie was found which matches the host, path, and name
-   *         fields of aCookie
+   * @return true if a cookie was found which matches the host, path, name and
+   *         originAttributes fields of aCookie
    */
-  [implicit_jscontext, optional_argc]
-  boolean cookieExists(in nsICookie2 aCookie,
-                       [optional] in jsval aOriginAttributes);
+  [implicit_jscontext]
+  boolean cookieExists(in AUTF8String aHost,
+                       in AUTF8String aPath,
+                       in ACString    aName,
+                       in jsval aOriginAttributes);
 
   [notxpcom]
-  nsresult cookieExistsNative(in nsICookie2 aCookie,
+  nsresult cookieExistsNative(in AUTF8String aHost,
+                              in AUTF8String aPath,
+                              in ACString    aName,
                               in OriginAttributesPtr aOriginAttributes,
                               out boolean aExists);
 
   /**
    * Count how many cookies exist within the base domain of 'aHost'.
    * Thus, for a host "weather.yahoo.com", the base domain would be "yahoo.com",
    * and any host or domain cookies for "yahoo.com" and its subdomains would be
    * counted.
@@ -192,26 +193,25 @@ interface nsICookieManager : nsISupports
    * "yahoo.com", and any host or domain cookies for "yahoo.com" and its
    * subdomains would be returned.
    *
    * @param aHost
    *        the host string to search for, e.g. "google.com". this should consist
    *        of only the host portion of a URI. see @add for a description of
    *        acceptable host strings.
    * @param aOriginAttributes The originAttributes of cookies that would be
-   *                          retrived. This attribute is optional to avoid
-   *                          breaking add-ons.
+   *                          retrived.
    *
    * @return an nsISimpleEnumerator of nsICookie2 objects.
    *
    * @see countCookiesFromHost
    */
-  [implicit_jscontext, optional_argc]
+  [implicit_jscontext]
   nsISimpleEnumerator getCookiesFromHost(in AUTF8String aHost,
-                                         [optional] in jsval aOriginAttributes);
+                                         in jsval aOriginAttributes);
 
   /**
    * Import an old-style cookie file. Imported cookies will be added to the
    * existing database. If the database contains any cookies the same as those
    * being imported (i.e. domain, name, and path match), they will be replaced.
    *
    * @param aCookieFile the file to import, usually cookies.txt
    */
deleted file mode 100644
--- a/netwerk/cookie/test/unit/test_bug1267910.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Bug 1267910 - Add test cases for the backward compatiability and originAttributes
- *               of nsICookieManager.
- */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const BASE_URL = "http://example.org/";
-
-const COOKIE = {
-  host: BASE_URL,
-  path: "/",
-  name: "test1",
-  value: "yes",
-  isSecure: false,
-  isHttpOnly: false,
-  isSession: true,
-  expiry: 2145934800,
-};
-
-const COOKIE_OA_DEFAULT = {
-  host: BASE_URL,
-  path: "/",
-  name: "test0",
-  value: "yes0",
-  isSecure: false,
-  isHttpOnly: false,
-  isSession: true,
-  expiry: 2145934800,
-  originAttributes: {},
-};
-
-const COOKIE_OA_1 = {
-  host: BASE_URL,
-  path: "/",
-  name: "test1",
-  value: "yes1",
-  isSecure: false,
-  isHttpOnly: false,
-  isSession: true,
-  expiry: 2145934800,
-  originAttributes: {userContextId: 1},
-};
-
-function checkCookie(cookie, cookieObj) {
-  for (let prop of Object.keys(cookieObj)) {
-    if (prop === "originAttributes") {
-      ok(ChromeUtils.isOriginAttributesEqual(cookie[prop], cookieObj[prop]),
-        "Check cookie: " + prop);
-    } else {
-      equal(cookie[prop], cookieObj[prop], "Check cookie: " + prop);
-    }
-  }
-}
-
-function countCookies(enumerator) {
-  let cnt = 0;
-  for (let cookie of enumerator) {
-    void cookie;
-    cnt++;
-  }
-  return cnt;
-}
-
-function run_test() {
-  // Allow all cookies.
-  Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
-
-  // Enable user context id
-  Services.prefs.setBoolPref("privacy.userContext.enabled", true);
-
-  add_test(test_backward_compatiability);
-  add_test(test_originAttributes);
-
-
-  run_next_test();
-}
-
-/*
- * Test for backward compatiablility that APIs works correctly without
- * originAttributes.
- */
-function test_backward_compatiability() {
-  // Clear cookies.
-  Services.cookies.removeAll();
-
-  // Call Add() to add a cookie without originAttributes
-  Services.cookies.add(COOKIE.host,
-                       COOKIE.path,
-                       COOKIE.name,
-                       COOKIE.value,
-                       COOKIE.isSecure,
-                       COOKIE.isHttpOnly,
-                       COOKIE.isSession,
-                       COOKIE.expiry);
-
-  // Call getCookiesFromHost() to get cookies without originAttributes
-  let enumerator = Services.cookies.getCookiesFromHost(BASE_URL);
-
-  ok(enumerator.hasMoreElements(), "Cookies available");
-  let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
-
-  checkCookie(foundCookie, COOKIE);
-
-  ok(!enumerator.hasMoreElements(), "We should get only one cookie");
-
-  run_next_test();
-}
-
-/*
- * Test for originAttributes.
- */
-function test_originAttributes() {
-  // Clear cookies.
-  Services.cookies.removeAll();
-
-  // Add a cookie for default originAttributes.
-  Services.cookies.add(COOKIE_OA_DEFAULT.host,
-                       COOKIE_OA_DEFAULT.path,
-                       COOKIE_OA_DEFAULT.name,
-                       COOKIE_OA_DEFAULT.value,
-                       COOKIE_OA_DEFAULT.isSecure,
-                       COOKIE_OA_DEFAULT.isHttpOnly,
-                       COOKIE_OA_DEFAULT.isSession,
-                       COOKIE_OA_DEFAULT.expiry,
-                       COOKIE_OA_DEFAULT.originAttributes);
-
-  // Get cookies for default originAttributes.
-  let enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
-
-  // Check that do we get cookie correctly.
-  ok(enumerator.hasMoreElements(), "Cookies available");
-  let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
-  checkCookie(foundCookie, COOKIE_OA_DEFAULT);
-
-  // We should only get one cookie.
-  ok(!enumerator.hasMoreElements(), "We should get only one cookie");
-
-  // Get cookies for originAttributes with user context id 1.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
-
-  // Check that we will not get cookies if the originAttributes is different.
-  ok(!enumerator.hasMoreElements(), "No cookie should be here");
-
-  // Add a cookie for originAttributes with user context id 1.
-  Services.cookies.add(COOKIE_OA_1.host,
-                       COOKIE_OA_1.path,
-                       COOKIE_OA_1.name,
-                       COOKIE_OA_1.value,
-                       COOKIE_OA_1.isSecure,
-                       COOKIE_OA_1.isHttpOnly,
-                       COOKIE_OA_1.isSession,
-                       COOKIE_OA_1.expiry,
-                       COOKIE_OA_1.originAttributes);
-
-  // Get cookies for originAttributes with user context id 1.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
-
-  // Check that do we get cookie correctly.
-  ok(enumerator.hasMoreElements(), "Cookies available");
-  foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
-  checkCookie(foundCookie, COOKIE_OA_1);
-
-  // We should only get one cookie.
-  ok(!enumerator.hasMoreElements(), "We should get only one cookie");
-
-  // Check that add a cookie will not affect cookies in different originAttributes.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
-  equal(countCookies(enumerator), 1, "We should get only one cookie for default originAttributes");
-
-  // Remove a cookie for originAttributes with user context id 1.
-  Services.cookies.remove(COOKIE_OA_1.host, COOKIE_OA_1.name, COOKIE_OA_1.path,
-                          false, COOKIE_OA_1.originAttributes);
-
-  // Check that remove will not affect cookies in default originAttributes.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
-  equal(countCookies(enumerator), 1, "Get one cookie for default originAttributes.");
-
-  // Check that should be no cookie for originAttributes with user context id 1.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
-  equal(countCookies(enumerator), 0, "No cookie shold be here");
-
-  // Remove a cookie for default originAttributes.
-  Services.cookies.remove(COOKIE_OA_DEFAULT.host, COOKIE_OA_DEFAULT.name, COOKIE_OA_DEFAULT.path,
-                          false, COOKIE_OA_DEFAULT.originAttributes);
-
-  // Check remove() works correctly for default originAttributes.
-  enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
-  equal(countCookies(enumerator), 0, "No cookie shold be here");
-
-  run_next_test();
-}
--- a/netwerk/cookie/test/unit/xpcshell.ini
+++ b/netwerk/cookie/test/unit/xpcshell.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
 head = 
 
 [test_bug643051.js]
 [test_bug1155169.js]
-[test_bug1267910.js]
 [test_bug1321912.js]
 [test_parser_0001.js]
 [test_parser_0019.js]
 [test_eviction.js]
--- a/netwerk/locales/en-US/necko.properties
+++ b/netwerk/locales/en-US/necko.properties
@@ -51,12 +51,8 @@ CookieBlockedSlowTrackingContent=The resource at “%1$S” was blocked because content blocking is enabled and the resource was classified as a slow tracking resource.
 # LOCALIZATION NOTE (CookieAllowedForOriginOnTrackerByStorageAccessAPI): %3$S, %2$S and %1$S are URLs.
 CookieAllowedForOriginOnTrackerByStorageAccessAPI=Storage access granted for “%3$S” opened by tracker “%2$S” on “%1$S”.
 # LOCALIZATION NOTE (CookieAllowedForTrackerByStorageAccessAPI): %2$S and %1$S are URLs.
 CookieAllowedForTrackerByStorageAccessAPI=Storage access granted for tracker “%2$S” on “%1$S”.
 # LOCALIZATION NOTE (CookieAllowedForOriginOnTrackerByHeuristic): %3$S, %2$S and %1$S are URLs.
 CookieAllowedForOriginOnTrackerByHeuristic=Storage access automatically granted for “%3$S” opened by tracker “%2$S” on “%1$S”.
 # LOCALIZATION NOTE (CookieAllowedForTrackerByHeuristic): %2$S and %1$S are URLs.
 CookieAllowedForTrackerByHeuristic=Storage access automatically granted for tracker “%2$S” on “%1$S”.
-
-# LOCALIZATION NOTE (nsICookieManagerAPIDeprecated): don't localize originAttributes.
-# %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
-nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -710,47 +710,59 @@ TEST(TestCookie,TestCookieMain)
     GetACookieNoHttp(cookieService, "http://cookiemgr.test/foo/", cookie);
     EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test2=yes"));
     // check CountCookiesFromHost()
     uint32_t hostCookies = 0;
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)));
     EXPECT_EQ(hostCookies, 2u);
     // check CookieExistsNative() using the third cookie
     bool found;
-    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs,  &found)));
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(NS_LITERAL_CSTRING("new.domain"),
+                                                            NS_LITERAL_CSTRING("/rabbit"),
+                                                            NS_LITERAL_CSTRING("test3"),
+                                                            &attrs,  &found)));
     EXPECT_TRUE(found);
 
 
     // remove the cookie, block it, and ensure it can't be added again
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->RemoveNative(NS_LITERAL_CSTRING("new.domain"), // domain
                                                      NS_LITERAL_CSTRING("test3"),      // name
                                                      NS_LITERAL_CSTRING("/rabbit"),    // path
                                                      true,                             // is blocked
                                                      &attrs)));                         // originAttributes
-    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)));
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(NS_LITERAL_CSTRING("new.domain"),
+                                                            NS_LITERAL_CSTRING("/rabbit"),
+                                                            NS_LITERAL_CSTRING("test3"),
+                                                            &attrs,  &found)));
     EXPECT_FALSE(found);
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"),     // domain
                                                    NS_LITERAL_CSTRING("/rabbit"),        // path
                                                    NS_LITERAL_CSTRING("test3"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MIN,                            // expiry time
                                                    &attrs,                            // originAttributes
                                                    nsICookie2::SAMESITE_UNSET)));
-    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)));
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(NS_LITERAL_CSTRING("new.domain"),
+                                                            NS_LITERAL_CSTRING("/rabbit"),
+                                                            NS_LITERAL_CSTRING("test3"),
+                                                            &attrs,  &found)));
     EXPECT_FALSE(found);
     // sleep four seconds, to make sure the second cookie has expired
     PR_Sleep(4 * PR_TicksPerSecond());
     // check that both CountCookiesFromHost() and CookieExistsNative() count the
     // expired cookie
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)));
     EXPECT_EQ(hostCookies, 2u);
-    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(expiredCookie, &attrs, &found)));
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(NS_LITERAL_CSTRING("cookiemgr.test"),
+                                                            NS_LITERAL_CSTRING("/foo"),
+                                                            NS_LITERAL_CSTRING("test2"),
+                                                            &attrs,  &found)));
     EXPECT_TRUE(found);
     // double-check RemoveAll() using the enumerator
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->RemoveAll()));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))) &&
                 NS_SUCCEEDED(enumerator->HasMoreElements(&more)) &&
                 !more);
 
     // *** eviction and creation ordering tests
--- a/netwerk/test/unit/test_bug411952.js
+++ b/netwerk/test/unit/test_bug411952.js
@@ -1,16 +1,16 @@
 function run_test() {
   try {
     var cm = Cc["@mozilla.org/cookiemanager;1"].
                getService(Ci.nsICookieManager);
     Assert.notEqual(cm, null, "Retrieving the cookie manager failed");
 
     const time = (new Date("Jan 1, 2030")).getTime() / 1000;
-    cm.add("example.com", "/", "C", "V", false, true, false, time, {});
+    cm.add("example.com", "/", "C", "V", false, true, false, time, {}, Ci.nsICookie2.SAMESITE_UNSET);
     const now = Math.floor((new Date()).getTime() / 1000);
 
     var found = false;
     for (let cookie of cm.enumerator) {
       if (cookie.host == "example.com" &&
           cookie.path == "/" &&
           cookie.name == "C") {
         Assert.ok("creationTime" in cookie,
--- a/netwerk/test/unit/test_bug455598.js
+++ b/netwerk/test/unit/test_bug455598.js
@@ -23,12 +23,12 @@ function run_test() {
                          Ci.nsICookie2];
       for (var i = 0; i < validIIDs.length; ++i)
         if (iid == validIIDs[i])
           return this;
       throw Cr.NS_ERROR_NO_INTERFACE;
     }
   };
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
-  Assert.ok(!cm.cookieExists(cookie));
+  Assert.ok(!cm.cookieExists(cookie.host, cookie.path, cookie.name, {}));
   // if the above line does not crash, the test was successful
   do_test_finished();
 }
--- a/netwerk/test/unit_ipc/test_cookie_header_stripped.js
+++ b/netwerk/test/unit_ipc/test_cookie_header_stripped.js
@@ -54,15 +54,15 @@ function run_test() {
   // manually.
   do_await_remote_message("second-check-cookie-count").then(() => {
     do_send_remote_message("second-check-cookie-count-done", Services.cookies.countCookiesFromHost(TEST_DOMAIN));
   });
 
   // Sets a cookie for the test domain
   do_await_remote_message("set-cookie").then(() => {
     const expiry = Date.now() + 24 * 60 * 60;
-    Services.cookies.add(TEST_DOMAIN, "/", "cookieName", "cookieValue", false, false, false, expiry, {});
+    Services.cookies.add(TEST_DOMAIN, "/", "cookieName", "cookieValue", false, false, false, expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
     do_send_remote_message("set-cookie-done");
   });
 
   // Run the actual test logic
   run_test_in_child("child_cookie_header.js");
 }
--- a/testing/marionette/browser.js
+++ b/testing/marionette/browser.js
@@ -239,27 +239,30 @@ browser.Context = class {
     if (this.contentBrowser) {
       return this.contentBrowser.currentURI;
     }
     throw new NoSuchWindowError(
         "Current window does not have a content browser");
   }
 
   /**
-   * Gets the position and dimensions of the top-level browsing context.
+   * Gets the position, dimensions, and state of the top-level
+   * browsing context.
    *
    * @return {Map.<string, number>}
-   *     Object with |x|, |y|, |width|, and |height| properties.
+   *     Object with `height`, `width`, `x`, `y`, and `state` properties.
    */
   get rect() {
     return {
+      height: this.window.outerHeight,
+      width: this.window.outerWidth,
       x: this.window.screenX,
       y: this.window.screenY,
-      width: this.window.outerWidth,
-      height: this.window.outerHeight,
+
+      // proprietary
       state: WindowState.from(this.window.windowState),
     };
   }
 
   /**
    * Retrieves the current tabmodal UI object.  According to the browser
    * associated with the currently selected tab.
    */
--- a/testing/marionette/cookie.js
+++ b/testing/marionette/cookie.js
@@ -163,17 +163,18 @@ cookie.add = function(newCookie, {restri
         newCookie.domain,
         newCookie.path,
         newCookie.name,
         newCookie.value,
         newCookie.secure,
         newCookie.httpOnly,
         newCookie.session,
         newCookie.expiry,
-        {} /* origin attributes */);
+        {} /* origin attributes */,
+        Ci.nsICookie2.SAMESITE_UNSET);
   } catch (e) {
     throw new UnableToSetCookieError(e);
   }
 };
 
 /**
  * Remove cookie from the cookie store.
  *
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-text/tab-size/tab-min-rendered-width-1-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Text Test: minimum rendered width of tab character</title>
+  <link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+  <style>
+    span { background-color: yellow; display: inline-block; letter-spacing: -.1em; }
+    pre { position: absolute; top: 0; }
+  </style>
+</head>
+<body>
+<pre>
+</pre>
+<pre>
+</pre>
+<script>
+  let pre = document.getElementsByTagName("pre")[0];
+  let test = "";
+  for (i = 7.0; i <= 8.125; i += 0.125) {
+    test += `<span style="width:${i}ch">${i}ch</span>\n`;
+  }
+  pre.innerHTML = test;
+  pre = document.getElementsByTagName("pre")[1];
+  test = "";
+  for (i = 0; i < 5; i++) {
+    test += `\tfoo\n`;
+  }
+  for (i = 0; i < 5; i++) {
+    test += `\t\tfoo\n`;
+  }
+  pre.innerHTML = test;
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-text/tab-size/tab-min-rendered-width-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Text Test: minimum rendered width of tab character</title>
+  <link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+  <link rel="reviewer" title="Xidorn Quan" href="https://www.upsuper.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+  <link rel="match" href="tab-min-rendered-width-1-ref.html">
+  <meta name="assert" content="If [rendered width of tab would be] less than 0.5ch, then the subsequent tab stop is used instead.">
+  <style>
+    span { background-color: yellow; display: inline-block; letter-spacing: -.1em; }
+    pre { position: absolute; top: 0; }
+  </style>
+</head>
+<body>
+<pre>
+</pre>
+<script>
+  let pre = document.getElementsByTagName("pre")[0];
+  let test = "";
+  for (i = 7.0; i <= 8.125; i += 0.125) {
+    test += `<span style="width:${i}ch">${i}ch</span>&#9;foo\n`;
+  }
+  pre.innerHTML = test;
+</script>
+</body>
+</html>
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -16,29 +16,31 @@
 #include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsCookiePermission.h"
 #include "nsICookieService.h"
 #include "nsIDocShell.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIIOService.h"
 #include "nsIParentChannel.h"
+#include "nsIPermission.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIWebProgressListener.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptSecurityManager.h"
 #include "nsSandboxFlags.h"
 #include "prtime.h"
 
 #define ANTITRACKING_PERM_KEY "3rdPartyStorage"
+#define USER_INTERACTION_PERM "storageAccessAPI"
 
 using namespace mozilla;
 using mozilla::dom::ContentChild;
 
 static LazyLogModule gAntiTrackingLog("AntiTracking");
 static const nsCString::size_type sMaxSpecLength = 128;
 
 #define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format)
@@ -1116,8 +1118,55 @@ AntiTrackingCommon::NotifyRejection(nsPI
   if (!pwin) {
     return;
   }
 
   pwin->NotifyContentBlockingState(aRejectedReason, httpChannel);
 
   ReportBlockingToConsole(pwin, httpChannel, aRejectedReason);
 }
+
+/* static */ void
+AntiTrackingCommon::StoreUserInteractionFor(nsIPrincipal* aPrincipal)
+{
+  if (XRE_IsParentProcess()) {
+    nsCOMPtr<nsIURI> uri;
+    Unused << aPrincipal->GetURI(getter_AddRefs(uri));
+    LOG_SPEC(("Saving the userInteraction for %s", _spec), uri);
+
+    nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
+    if (NS_WARN_IF(!pm)) {
+      LOG(("Permission manager is null, bailing out early"));
+      return;
+    }
+
+    // Remember that this pref is stored in seconds!
+    uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
+    uint32_t expirationTime =
+      StaticPrefs::privacy_userInteraction_expiration() * 1000;
+    int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
+
+    uint32_t privateBrowsingId = 0;
+    nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+    if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
+      // If we are coming from a private window, make sure to store a session-only
+      // permission which won't get persisted to disk.
+      expirationType = nsIPermissionManager::EXPIRE_SESSION;
+      when = 0;
+    }
+
+    rv = pm->AddFromPrincipal(aPrincipal,
+                              USER_INTERACTION_PERM,
+                              nsIPermissionManager::ALLOW_ACTION,
+                              expirationType, when);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+    return;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+
+  nsCOMPtr<nsIURI> uri;
+  Unused << aPrincipal->GetURI(getter_AddRefs(uri));
+  LOG_SPEC(("Asking the parent process to save the user-interaction for us: %s",
+            _spec), uri);
+  cc->SendStoreUserInteractionAsPermission(IPC::Principal(aPrincipal));
+}
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -103,16 +103,19 @@ public:
   //   the user interacts with it. tracker.com is allowed when loaded by
   //   example.net.
   typedef MozPromise<bool, bool, false> StorageAccessGrantPromise;
   static MOZ_MUST_USE RefPtr<StorageAccessGrantPromise>
   AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigin,
                                        nsPIDOMWindowInner* aParentWindow,
                                        StorageAccessGrantedReason aReason);
 
+  static void
+  StoreUserInteractionFor(nsIPrincipal* aPrincipal);
+
   // For IPC only.
   static void
   SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
                                                              const nsCString& aParentOrigin,
                                                              const nsCString& aGrantedOrigin,
                                                              FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
 
 
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -41,11 +41,12 @@ support-files = server.sjs
 [browser_onBeforeRequestNotificationForTrackingResources.js]
 [browser_onModifyRequestNotificationForTrackingResources.js]
 [browser_permissionInNormalWindows.js]
 [browser_permissionInPrivateWindows.js]
 [browser_subResources.js]
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
+[browser_userInteraction.js]
 [browser_storageAccessPrivateWindow.js]
 [browser_storageAccessSandboxed.js]
 [browser_storageAccessWithHeuristics.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_userInteraction.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+add_task(async function() {
+  info("Starting subResources test");
+
+  await SpecialPowers.flushPrefEnv();
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.userInteraction.document.interval", 1],
+    ["browser.contentblocking.enabled", true],
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
+    ["privacy.trackingprotection.enabled", false],
+    ["privacy.trackingprotection.pbmode.enabled", false],
+    ["privacy.trackingprotection.annotate_channels", true],
+  ]});
+
+  await UrlClassifierTestUtils.addTestTrackers();
+
+  info("Creating a new tab");
+  let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let uri = Services.io.newURI(TEST_DOMAIN);
+  is(Services.perms.testPermission(uri, "storageAccessAPI"), Services.perms.UNKNOWN_ACTION,
+     "Before user-interaction we don't have a permission");
+
+  let promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+    let permission = aSubject.QueryInterface(Ci.nsIPermission);
+    return permission.type == "storageAccessAPI" &&
+           permission.principal.URI.equals(uri);
+  });
+
+  info("Simulating user-interaction.");
+  await ContentTask.spawn(browser, null, async function() {
+    content.document.userInteractionForTesting();
+  });
+
+  info("Waiting to have a permissions set.");
+  await promise;
+
+  // Let's see if the document is able to update the permission correctly.
+  for (var i = 0; i < 3; ++i) {
+    // Another perm-changed event should be triggered by the timer.
+    promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+      let permission = aSubject.QueryInterface(Ci.nsIPermission);
+      return permission.type == "storageAccessAPI" &&
+             permission.principal.URI.equals(uri);
+    });
+
+    info("Simulating another user-interaction.");
+    await ContentTask.spawn(browser, null, async function() {
+      content.document.userInteractionForTesting();
+    });
+
+    info("Waiting to have a permissions set.");
+    await promise;
+  }
+
+  // Let's disable the document.interval.
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.userInteraction.document.interval", 0],
+  ]});
+
+  promise = new Promise(resolve => {
+    let id;
+
+    function observer(subject, topic, data) {
+      ok(false, "Notification received!");
+      Services.obs.removeObserver(observer, "perm-changed");
+      clearTimeout(id);
+      resolve();
+    }
+
+    Services.obs.addObserver(observer, "perm-changed");
+
+    id = setTimeout(() => {
+      ok(true, "No notification received!");
+      Services.obs.removeObserver(observer, "perm-changed");
+      resolve();
+    }, 2000);
+  });
+
+  info("Simulating another user-interaction.");
+  await ContentTask.spawn(browser, null, async function() {
+    content.document.userInteractionForTesting();
+  });
+
+  info("Waiting to have a permissions set.");
+  await promise;
+
+  info("Removing the tab");
+  BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function() {
+  info("Cleaning up.");
+  await new Promise(resolve => {
+    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+  });
+});
--- a/toolkit/components/cleardata/tests/unit/test_cookies.js
+++ b/toolkit/components/cleardata/tests/unit/test_cookies.js
@@ -8,34 +8,34 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 add_task(async function test_all_cookies() {
   const expiry = Date.now() + 24 * 60 * 60;
   Services.cookies.add("example.net", "path", "name", "value", true /* secure */,
                        true /* http only */, false /* session */,
-                       expiry, {});
+                       expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1);
 
   await new Promise(aResolve => {
     Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_COOKIES, value => {
       Assert.equal(value, 0);
       aResolve();
     });
   });
 
   Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 0);
 });
 
 add_task(async function test_range_cookies() {
   const expiry = Date.now() + 24 * 60 * 60;
   Services.cookies.add("example.net", "path", "name", "value", true /* secure */,
                        true /* http only */, false /* session */,
-                       expiry, {});
+                       expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1);
 
   // The cookie is out of time range here.
   let from = Date.now() + 60 * 60;
   await new Promise(aResolve => {
     Services.clearData.deleteDataInTimeRange(from * 1000, expiry * 2000, true /* user request */,
                                              Ci.nsIClearDataService.CLEAR_COOKIES, value => {
       Assert.equal(value, 0);
@@ -57,17 +57,17 @@ add_task(async function test_range_cooki
 
   Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 0);
 });
 
 add_task(async function test_principal_cookies() {
   const expiry = Date.now() + 24 * 60 * 60;
   Services.cookies.add("example.net", "path", "name", "value", true /* secure */,
                        true /* http only */, false /* session */,
-                       expiry, {});
+                       expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
   Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1);
 
   let uri = Services.io.newURI("http://example.com");
   let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
   await new Promise(aResolve => {
     Services.clearData.deleteDataFromPrincipal(principal, true /* user request */,
                                                Ci.nsIClearDataService.CLEAR_COOKIES, value => {
       Assert.equal(value, 0);
--- a/toolkit/components/contextualidentity/tests/unit/test_corruptedFile.js
+++ b/toolkit/components/contextualidentity/tests/unit/test_corruptedFile.js
@@ -25,17 +25,18 @@ function createCookie(userContextId) {
   Services.cookies.add(COOKIE.host,
                        COOKIE.path,
                        COOKIE.name,
                        COOKIE.value,
                        COOKIE.isSecure,
                        COOKIE.isHttpOnly,
                        COOKIE.isSession,
                        COOKIE.expiry,
-                       {userContextId});
+                       {userContextId},
+                       Ci.nsICookie2.SAMESITE_UNSET);
 }
 
 function hasCookie(userContextId) {
   let found = false;
   for (let cookie of Services.cookies.getCookiesFromHost(BASE_URL, {userContextId})) {
     if (cookie.originAttributes.userContextId == userContextId) {
       found = true;
       break;
--- a/toolkit/components/extensions/test/mochitest/head_cookies.js
+++ b/toolkit/components/extensions/test/mochitest/head_cookies.js
@@ -91,39 +91,47 @@ async function testCookies(options) {
     background: `(${background})(${JSON.stringify(options)})`,
   });
 
   let stepOne = loadChromeScript(() => {
     const {addMessageListener, sendAsyncMessage} = this;
     addMessageListener("options", options => {
       let domain = options.domain.replace(/^\.?/, ".");
       // This will be evicted after we add a fourth cookie.
-      Services.cookies.add(domain, "/", "evicted", "bar", options.secure, false, false, options.expiry);
+      Services.cookies.add(domain, "/", "evicted", "bar", options.secure, false,
+                           false, options.expiry, {},
+                           Ci.nsICookie2.SAMESITE_UNSET);
       // This will be modified by the background script.
-      Services.cookies.add(domain, "/", "foo", "bar", options.secure, false, false, options.expiry);
+      Services.cookies.add(domain, "/", "foo", "bar", options.secure, false,
+                           false, options.expiry, {},
+                           Ci.nsICookie2.SAMESITE_UNSET);
       // This will be deleted by the background script.
-      Services.cookies.add(domain, "/", "deleted", "bar", options.secure, false, false, options.expiry);
+      Services.cookies.add(domain, "/", "deleted", "bar", options.secure, false,
+                           false, options.expiry, {},
+                           Ci.nsICookie2.SAMESITE_UNSET);
       sendAsyncMessage("done");
     });
   });
   stepOne.sendAsyncMessage("options", options);
   await stepOne.promiseOneMessage("done");
   stepOne.destroy();
 
   await extension.startup();
 
   await extension.awaitMessage("change-cookies");
 
   let stepTwo = loadChromeScript(() => {
     const {addMessageListener, sendAsyncMessage} = this;
     addMessageListener("options", options => {
       let domain = options.domain.replace(/^\.?/, ".");
 
-      Services.cookies.add(domain, "/", "x", "y", options.secure, false, false, options.expiry);
-      Services.cookies.add(domain, "/", "x", "z", options.secure, false, false, options.expiry);
+      Services.cookies.add(domain, "/", "x", "y", options.secure, false, false,
+                           options.expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
+      Services.cookies.add(domain, "/", "x", "z", options.secure, false, false,
+                           options.expiry, {}, Ci.nsICookie2.SAMESITE_UNSET);
       Services.cookies.remove(domain, "x", "/", false, {});
       sendAsyncMessage("done");
     });
   });
   stepTwo.sendAsyncMessage("options", options);
   await stepTwo.promiseOneMessage("done");
   stepTwo.destroy();
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html
@@ -41,28 +41,28 @@ add_task(async function test_cookies_exp
     manifest: {
       "permissions": ["http://example.com/", "cookies"],
     },
     background,
   });
 
   let chromeScript = loadChromeScript(() => {
     const {sendAsyncMessage} = this;
-    Services.cookies.add(".example.com", "/", "first", "one", false, false, false, Date.now() / 1000 + 1);
+    Services.cookies.add(".example.com", "/", "first", "one", false, false, false, Date.now() / 1000 + 1, {}, Ci.nsICookie2.SAMESITE_UNSET);
     sendAsyncMessage("done");
   });
   await chromeScript.promiseOneMessage("done");
   chromeScript.destroy();
 
   await extension.startup();
   await extension.awaitMessage("change-cookies");
 
   chromeScript = loadChromeScript(() => {
     const {sendAsyncMessage} = this;
-    Services.cookies.add(".example.com", "/", "first", "one-again", false, false, false, Date.now() / 1000 + 10);
+    Services.cookies.add(".example.com", "/", "first", "one-again", false, false, false, Date.now() / 1000 + 10, {}, Ci.nsICookie2.SAMESITE_UNSET);
     sendAsyncMessage("done");
   });
   await chromeScript.promiseOneMessage("done");
   chromeScript.destroy();
 
   await extension.awaitFinish("cookie-expiry");
   await extension.unload();
 });
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -4490,17 +4490,17 @@ SearchService.prototype = {
         } else {
           type = "sap";
         }
       } else if (provider == "bing") {
         // Bing requires lots of extra work related to cookies.
         let secondaryCode = queries.get("form");
         // This code is used for all Bing follow-on searches.
         if (secondaryCode == "QBRE") {
-          for (let cookie of Services.cookies.getCookiesFromHost("www.bing.com")) {
+          for (let cookie of Services.cookies.getCookiesFromHost("www.bing.com", {})) {
             if (cookie.name == "SRCHS") {
               // If this cookie is present, it's probably an SAP follow-on.
               // This might be an organic follow-on in the same session,
               // but there is no way to tell the difference.
               if (searchProviderInfo.codePrefixes.some(p => cookie.value.startsWith("PC=" + p))) {
                 type = "sap-follow-on";
                 code = cookie.value.split("=")[1];
                 break;
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -38,35 +38,30 @@ const PREFERENCE_NAME = "test-pref";
 /**
  * Add a cookie to the cookie service.
  *
  * @param aDomain
  */
 function add_cookie(aDomain) {
   check_cookie_exists(aDomain, false);
   Services.cookies.add(aDomain, COOKIE_PATH, COOKIE_NAME, "", false, false, false,
-                       COOKIE_EXPIRY, {});
+                       COOKIE_EXPIRY, {}, Ci.nsICookie2.SAMESITE_UNSET);
   check_cookie_exists(aDomain, true);
 }
 
 /**
  * Checks to ensure that a cookie exists or not for a domain.
  *
  * @param aDomain
  *        The domain to check for the cookie.
  * @param aExists
  *        True if the cookie should exist, false otherwise.
  */
 function check_cookie_exists(aDomain, aExists) {
-  let cookie = {
-    host: aDomain,
-    name: COOKIE_NAME,
-    path: COOKIE_PATH,
-  };
-  Assert.equal(aExists, Services.cookies.cookieExists(cookie));
+  Assert.equal(aExists, Services.cookies.cookieExists(aDomain, COOKIE_PATH, COOKIE_NAME, {}));
 }
 
 /**
  * Adds a disabled host to the login manager.
  *
  * @param aHost
  *        The host to add to the list of disabled hosts.
  */
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js
@@ -3,17 +3,18 @@
 // are set
 // This verifies bug 462739
 function test() {
   Harness.installEndedCallback = install_ended;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   Services.cookies.add("example.com", "/browser/" + RELATIVE_DIR, "xpinstall",
-    "true", false, false, true, (Date.now() / 1000) + 60, {});
+    "true", false, false, true, (Date.now() / 1000) + 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Cookie check": TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi",
   }));
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js
@@ -3,17 +3,18 @@
 // are set and third party cookies are disabled.
 // This verifies bug 462739
 function test() {
   Harness.installEndedCallback = install_ended;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   Services.cookies.add("example.com", "/browser/" + RELATIVE_DIR, "xpinstall",
-    "true", false, false, true, (Date.now() / 1000) + 60, {});
+    "true", false, false, true, (Date.now() / 1000) + 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Cookie check": TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi",
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js
@@ -4,17 +4,18 @@
 // party.
 // This verifies bug 462739
 function test() {
   Harness.downloadFailedCallback = download_failed;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   Services.cookies.add("example.org", "/browser/" + RELATIVE_DIR, "xpinstall",
-    "true", false, false, true, (Date.now() / 1000) + 60, {});
+    "true", false, false, true, (Date.now() / 1000) + 60, {},
+    Ci.nsICookie2.SAMESITE_UNSET);
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Cookie check": TESTROOT2 + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi",
--- a/toolkit/themes/linux/global/toolbarbutton.css
+++ b/toolkit/themes/linux/global/toolbarbutton.css
@@ -37,20 +37,16 @@ toolbarbutton.tabbable {
 }
 
 toolbarbutton:hover {
   color: -moz-buttonhovertext;
 }
 
 toolbarbutton:hover:active:not([disabled="true"]),
 toolbarbutton[open="true"] {
-  padding-top: 4px;
-  padding-bottom: 2px;
-  padding-inline-start: 4px;
-  padding-inline-end: 2px;
   color: ButtonText;
 }
 
 toolbarbutton[disabled="true"] {
   color: GrayText;
 }
 
 toolbarbutton[checked="true"]:not(:hover) {
--- a/toolkit/themes/windows/global/toolbarbutton.css
+++ b/toolkit/themes/windows/global/toolbarbutton.css
@@ -35,44 +35,28 @@ toolbarbutton.tabbable {
 toolbarbutton:-moz-focusring {
   /* -moz-appearance looks redundant here but is necessary.
       Without it, the outline won't appear. */
   -moz-appearance: toolbarbutton;
   outline: 1px dotted -moz-DialogText;
   outline-offset: -2px;
 }
 
-toolbarbutton:hover:active:not([disabled="true"]),
-toolbarbutton[open="true"]:hover,
-toolbarbutton[open="true"] {
-  padding-top: 4px;
-  padding-bottom: 2px;
-  padding-inline-start: 4px;
-  padding-inline-end: 2px;
-}
-
 toolbarbutton[disabled="true"] {
   color: GrayText;
   text-shadow: none;
 }
 
 @media (-moz-windows-classic) {
   toolbarbutton[disabled="true"] {
     color: ThreeDShadow;
     text-shadow: 1px 1px ThreeDHighlight;
   }
 }
 
-toolbarbutton[checked="true"]:not([disabled="true"]) {
-  padding-top: 4px;
-  padding-bottom: 2px;
-  padding-inline-start: 4px;
-  padding-inline-end: 2px;
-}
-
 @media (-moz-windows-default-theme) {
   :root[lwtheme-image] toolbarbutton {
     text-shadow: none;
   }
 
   :root[lwtheme-image] toolbarbutton:not([disabled="true"]) {
     text-shadow: inherit;
   }