Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Fri, 26 Oct 2018 18:50:07 +0300
changeset 443170 d43cadbbec77723f74af8f545dcaf8549e2a6c22
parent 443169 d5a6095b6931949ff78e5999b01b695ead711eba (current diff)
parent 443132 a79126bab347bcb78193e156800178f9227ab294 (diff)
child 443171 da6477e4baae96296f9a6876ceaeda9eed997ae0
push id34943
push usercsabou@mozilla.com
push dateFri, 26 Oct 2018 21:57:01 +0000
treeherdermozilla-central@3dc7cdbb2b5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
browser/themes/linux/places/livemark-item.png
browser/themes/osx/places/livemark-item.png
browser/themes/shared/places/folder-live.svg
browser/themes/windows/places/livemark-item.png
devtools/client/inspector/changes/utils/moz.build
toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js
--- a/browser/base/content/test/urlbar/browser_canonizeURL.js
+++ b/browser/base/content/test/urlbar/browser_canonizeURL.js
@@ -28,24 +28,18 @@ add_task(async function checkCtrlWorks()
   await SpecialPowers.pushPrefEnv({set: [
     ["browser.urlbar.autoFill", false],
     ["browser.urlbar.ctrlCanonizesURLs", true],
   ]});
 
   for (let [inputValue, expectedURL, options] of testcases) {
     let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
     gURLBar.focus();
-    if (Object.keys(options).length > 0) {
-      gURLBar.selectionStart = gURLBar.selectionEnd =
-        gURLBar.inputField.value.length;
-      gURLBar.inputField.value = inputValue.slice(0, -1);
-      EventUtils.sendString(inputValue.slice(-1));
-    } else {
-      gURLBar.textValue = inputValue;
-    }
+    gURLBar.inputField.value = inputValue.slice(0, -1);
+    EventUtils.sendString(inputValue.slice(-1));
     EventUtils.synthesizeKey("KEY_Enter", options);
     await promiseLoad;
   }
 });
 
 add_task(async function checkPrefTurnsOffCanonize() {
   // Add a dummy search engine to avoid hitting the network.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
--- a/browser/base/content/test/urlbar/browser_urlbarDecode.js
+++ b/browser/base/content/test/urlbar/browser_urlbarDecode.js
@@ -1,15 +1,20 @@
 "use strict";
 
 // This test makes sure (1) you can't break the urlbar by typing particular JSON
 // or JS fragments into it, (2) urlbar.textValue shows URLs unescaped, and (3)
 // the urlbar also shows the URLs embedded in action URIs unescaped.  See bug
 // 1233672.
 
+XPCOMUtils.defineLazyModuleGetters(this, {
+  UrlbarMatch: "resource:///modules/UrlbarMatch.jsm",
+  UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
+});
+
 add_task(async function injectJSON() {
   let inputStrs = [
     'http://example.com/ ", "url": "bar',
     "http://example.com/\\",
     'http://example.com/"',
     'http://example.com/","url":"evil.com',
     "http://mozilla.org/\\u0020",
     'http://www.mozilla.org/","url":1e6,"some-key":"foo',
@@ -22,17 +27,22 @@ add_task(async function injectJSON() {
   gURLBar.value = "";
   gURLBar.handleRevert();
   gURLBar.blur();
 });
 
 add_task(function losslessDecode() {
   let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
   let url = "http://" + urlNoScheme;
-  gURLBar.textValue = url;
+  if (Services.prefs.getBoolPref("browser.urlbar.quantumbar", true)) {
+    const result = new UrlbarMatch(UrlbarUtils.MATCH_TYPE.TAB_SWITCH, {url});
+    gURLBar.setValueFromResult(result);
+  } else {
+    gURLBar.textValue = url;
+  }
   // Since this is directly setting textValue, it is expected to be trimmed.
   Assert.equal(gURLBar.inputField.value, urlNoScheme,
                "The string displayed in the textbox should not be escaped");
   gURLBar.value = "";
   gURLBar.handleRevert();
   gURLBar.blur();
 });
 
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
@@ -104,18 +104,19 @@ add_task(async function test_webnavigati
   });
 
   await extension.startup();
   await SimpleTest.promiseFocus(window);
 
   await extension.awaitMessage("ready");
 
   gURLBar.focus();
-  gURLBar.textValue = "http://example.com/?q=typed";
-
+  const inputValue = "http://example.com/?q=typed";
+  gURLBar.inputField.value = inputValue.slice(0, -1);
+  EventUtils.sendString(inputValue.slice(-1));
   EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
 
   await extension.awaitFinish("webNavigation.from_address_bar.typed");
 
   await extension.unload();
   info("extension unloaded");
 });
 
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -7,16 +7,18 @@ support-files =
   file_cache.html
   file_favicon.html
   file_favicon.png
   file_favicon.png^headers^
   file_favicon_cache.html
   file_favicon_cache.png
   file_favicon_thirdParty.html
   file_firstPartyBasic.html
+  file_postMessage.html
+  file_postMessageSender.html
   file_sharedworker.html
   file_sharedworker.js
   file_thirdPartyChild.audio.ogg
   file_thirdPartyChild.embed.png
   file_thirdPartyChild.fetch.html
   file_thirdPartyChild.iframe.html
   file_thirdPartyChild.img.png
   file_thirdPartyChild.import.js
@@ -75,10 +77,11 @@ skip-if = verify || debug #Bug 1345346
 skip-if = (verify && debug && (os == 'win'))
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
 [browser_httpauth.js]
 [browser_clientAuth.js]
 skip-if = verify
 [browser_cacheAPI.js]
 [browser_permissions.js]
+[browser_postMessage.js]
 [browser_sanitize.js]
 [browser_windowOpenerRestriction.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_postMessage.js
@@ -0,0 +1,96 @@
+/**
+ * Bug 1492607 - Test for assuring that postMessage cannot go across OAs.
+ */
+
+const FPD_ONE = "http://example.com";
+const FPD_TWO = "http://example.org";
+
+const TEST_BASE = "/browser/browser/components/originattributes/test/browser/";
+
+add_task(async function setup() {
+  // Make sure first party isolation is enabled.
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.firstparty.isolate", true],
+  ]});
+});
+
+async function runTestWithOptions(aDifferentFPD, aStarTargetOrigin, aBlockAcrossFPD) {
+  let testPageURL = aDifferentFPD ?
+    FPD_ONE + TEST_BASE + "file_postMessage.html" :
+    FPD_TWO + TEST_BASE + "file_postMessage.html";
+
+  // Deciding the targetOrigin according to the test setting.
+  let targetOrigin;
+  if (aStarTargetOrigin) {
+    targetOrigin = "*";
+  } else {
+    targetOrigin = aDifferentFPD ? FPD_ONE : FPD_TWO;
+  }
+  let senderURL = FPD_TWO + TEST_BASE + `file_postMessageSender.html?${targetOrigin}`;
+
+  // Open a tab to listen messages.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL);
+
+  // Use window.open() in the tab to open the sender tab. The sender tab
+  // will send a message through postMessage to window.opener.
+  let senderTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, senderURL, true);
+  ContentTask.spawn(tab.linkedBrowser, senderURL,
+    aSenderPath => {
+      content.open(aSenderPath, "_blank");
+    }
+  );
+
+  // Wait and get the tab of the sender tab.
+  let senderTab = await senderTabPromise;
+
+  // The postMessage should be blocked when the first parties are different with
+  // the following two cases. First, it is using a non-star target origin.
+  // Second, it is using the star target origin and the pref
+  // 'privacy.firstparty.isolate.block_post_message' is true.
+  let shouldBlock = aDifferentFPD && (!aStarTargetOrigin || aBlockAcrossFPD);
+
+  await ContentTask.spawn(tab.linkedBrowser, shouldBlock, async (aValue) => {
+    await new Promise(resolve => {
+      content.addEventListener("message", function eventHandler(aEvent) {
+        if (aEvent.data === "Self") {
+          if (aValue) {
+            is(content.document.getElementById("display").innerHTML, "",
+              "It should not get a message from other OA.");
+          } else {
+            is(content.document.getElementById("display").innerHTML, "Message",
+              "It should get a message from the same OA.");
+          }
+
+          content.removeEventListener("message", eventHandler);
+          resolve();
+        }
+      });
+
+      // Trigger the content to send a postMessage to itself.
+      content.document.getElementById("button").click();
+    });
+  });
+
+  BrowserTestUtils.removeTab(tab);
+  BrowserTestUtils.removeTab(senderTab);
+}
+
+add_task(async function runTests() {
+  for (let useDifferentFPD of [true, false]) {
+    for (let useStarTargetOrigin of [true, false]) {
+      for (let enableBlocking of [true, false]) {
+        if (enableBlocking) {
+          await SpecialPowers.pushPrefEnv({"set": [
+            ["privacy.firstparty.isolate.block_post_message", true],
+          ]});
+        }
+
+        await runTestWithOptions(useDifferentFPD, useStarTargetOrigin, enableBlocking);
+
+        if (enableBlocking) {
+          await SpecialPowers.popPrefEnv();
+        }
+      }
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_postMessage.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8">
+  <title>Test page for window.postMessage</title>
+</head>
+<body>
+  <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+  <input type="button" id="button" title="button"/>
+  <script>
+
+  const TEST_PATH = "http://example.org/browser/browser/components/originattributes/test/browser/file_postMessageSender.html";
+
+  window.onload = () => {
+    window.addEventListener("message", aEvent => {
+      if (aEvent.data === "Message") {
+        document.getElementById("display").innerHTML = "Message";
+      }
+    });
+
+    document.getElementById("button").onclick = () => {
+      window.postMessage("Self", "/");
+    };
+  };
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_postMessageSender.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8">
+  <title>Test page for always sending a message to its opener through postMessage</title>
+</head>
+<body>
+  <script>
+  window.onload = () => {
+    // The target origin is coming from the query string.
+    let targetOrigin = window.location.search.substring(1);
+    window.opener.postMessage("Message", targetOrigin);
+  };
+  </script>
+</body>
+</html>
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -513,21 +513,19 @@ var PlacesUIUtils = {
   },
 
   /**
    * Check whether or not the given node represents a removable entry (either in
    * history or in bookmarks).
    *
    * @param aNode
    *        a node, except the root node of a query.
-   * @param aView
-   *        The view originating the request.
    * @return true if the aNode represents a removable entry, false otherwise.
    */
-  canUserRemove(aNode, aView) {
+  canUserRemove(aNode) {
     let parentNode = aNode.parent;
     if (!parentNode) {
       // canUserRemove doesn't accept root nodes.
       return false;
     }
 
     // Is it a query pointing to one of the special root folders?
     if (PlacesUtils.nodeIsQuery(parentNode)) {
@@ -553,17 +551,17 @@ var PlacesUIUtils = {
       return !PlacesUtils.nodeIsFolder(parentNode);
     }
 
     // Generally it's always possible to remove children of a query.
     if (PlacesUtils.nodeIsQuery(parentNode))
       return true;
 
     // Otherwise it has to be a child of an editable folder.
-    return !this.isFolderReadOnly(parentNode, aView);
+    return !this.isFolderReadOnly(parentNode);
   },
 
   /**
    * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
    * TO GUIDS IS COMPLETE (BUG 1071511).
    *
    * Check whether or not the given Places node points to a folder which
    * should not be modified by the user (i.e. its children should be unremovable
@@ -571,35 +569,25 @@ var PlacesUIUtils = {
    * These semantics are not inherited, meaning that read-only folder may
    * contain editable items (for instance, the places root is read-only, but all
    * of its direct children aren't).
    *
    * You should only pass folder nodes.
    *
    * @param placesNode
    *        any folder result node.
-   * @param view
-   *        The view originating the request.
    * @throws if placesNode is not a folder result node or views is invalid.
-   * @note livemark "folders" are considered read-only (but see bug 1072833).
    * @return true if placesNode is a read-only folder, false otherwise.
    */
-  isFolderReadOnly(placesNode, view) {
+  isFolderReadOnly(placesNode) {
     if (typeof placesNode != "object" || !PlacesUtils.nodeIsFolder(placesNode)) {
       throw new Error("invalid value for placesNode");
     }
-    if (!view || typeof view != "object") {
-      throw new Error("invalid value for aView");
-    }
-    let itemId = PlacesUtils.getConcreteItemId(placesNode);
-    if (itemId == PlacesUtils.placesRootId ||
-        view.controller.hasCachedLivemarkInfo(placesNode))
-      return true;
 
-    return false;
+    return PlacesUtils.getConcreteItemId(placesNode) == PlacesUtils.placesRootId;
   },
 
   /** aItemsToOpen needs to be an array of objects of the form:
     * {uri: string, isBookmark: boolean}
     */
   _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) {
     if (!aItemsToOpen.length)
       return;
@@ -641,35 +629,16 @@ var PlacesUIUtils = {
     // loadTabs with aReplace set to false.
     browserWindow.gBrowser.loadTabs(urls, {
       inBackground: loadInBackground,
       replace: false,
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     });
   },
 
-  openLiveMarkNodesInTabs:
-  function PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) {
-    let window = aView.ownerWindow;
-
-    PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
-      .then(aLivemark => {
-        let urlsToOpen = [];
-
-        let nodes = aLivemark.getNodesForContainer(aNode);
-        for (let node of nodes) {
-          urlsToOpen.push({uri: node.uri, isBookmark: false});
-        }
-
-        if (OpenInTabsUtils.confirmOpenInTabs(urlsToOpen.length, window)) {
-          this._openTabset(urlsToOpen, aEvent, window);
-        }
-      }, Cu.reportError);
-  },
-
   openContainerNodeInTabs:
   function PUIU_openContainerInTabs(aNode, aEvent, aView) {
     let window = aView.ownerWindow;
 
     let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
     if (OpenInTabsUtils.confirmOpenInTabs(urlsToOpen.length, window)) {
       this._openTabset(urlsToOpen, aEvent, window);
     }
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -268,23 +268,16 @@ PlacesViewBase.prototype = {
     }
   },
 
   _rebuildPopup: function PVB__rebuildPopup(aPopup) {
     let resultNode = aPopup._placesNode;
     if (!resultNode.containerOpen)
       return;
 
-    if (this.controller.hasCachedLivemarkInfo(resultNode)) {
-      this._setEmptyPopupStatus(aPopup, false);
-      aPopup._built = true;
-      this._populateLivemarkPopup(aPopup);
-      return;
-    }
-
     this._cleanPopup(aPopup);
 
     let cc = resultNode.childCount;
     if (cc > 0) {
       this._setEmptyPopupStatus(aPopup, false);
       let fragment = document.createDocumentFragment();
       for (let i = 0; i < cc; ++i) {
         let child = resultNode.getChild(i);
@@ -338,17 +331,16 @@ PlacesViewBase.prototype = {
     this._domNodes.delete(aPlacesNode);
 
     let element;
     let type = aPlacesNode.type;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       element = document.createXULElement("menuseparator");
       element.setAttribute("class", "small-separator");
     } else {
-      let itemId = aPlacesNode.itemId;
       if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
         element = document.createXULElement("menuitem");
         element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
         element.setAttribute("scheme",
                              PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
       } else if (PlacesUtils.containerTypes.includes(type)) {
         element = document.createXULElement("menu");
         element.setAttribute("container", "true");
@@ -356,28 +348,16 @@ PlacesViewBase.prototype = {
         if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
           element.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
             element.setAttribute("tagContainer", "true");
           else if (PlacesUtils.nodeIsDay(aPlacesNode))
             element.setAttribute("dayContainer", "true");
           else if (PlacesUtils.nodeIsHost(aPlacesNode))
             element.setAttribute("hostContainer", "true");
-        } else if (itemId != -1) {
-          PlacesUtils.livemarks.getLivemark({ id: itemId })
-            .then(aLivemark => {
-              element.setAttribute("livemark", "true");
-              if (AppConstants.platform === "macosx") {
-                // OS X native menubar doesn't track list-style-images since
-                // it doesn't have a frame (bug 733415).  Thus enforce updating.
-                element.setAttribute("image", "");
-                element.removeAttribute("image");
-              }
-              this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
-            }, () => undefined);
         }
 
         let popup = document.createXULElement("menupopup");
         popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
 
         if (!this._nativeView) {
           popup.setAttribute("placespopup", "true");
         }
@@ -414,90 +394,16 @@ PlacesViewBase.prototype = {
       if (typeof this.options.extraClasses.entry == "string")
         element.classList.add(this.options.extraClasses.entry);
     }
 
     aInsertionNode.insertBefore(element, aBefore);
     return element;
   },
 
-  _setLivemarkSiteURIMenuItem:
-  function PVB__setLivemarkSiteURIMenuItem(aPopup) {
-    let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
-    let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
-                  livemarkInfo.siteURI.spec : null;
-    if (!siteUrl && aPopup._siteURIMenuitem) {
-      aPopup.removeChild(aPopup._siteURIMenuitem);
-      aPopup._siteURIMenuitem = null;
-      aPopup.removeChild(aPopup._siteURIMenuseparator);
-      aPopup._siteURIMenuseparator = null;
-    } else if (siteUrl && !aPopup._siteURIMenuitem) {
-      // Add "Open (Feed Name)" menuitem.
-      aPopup._siteURIMenuitem = document.createXULElement("menuitem");
-      aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
-      if (typeof this.options.extraClasses.entry == "string") {
-        aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
-      }
-      aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
-      aPopup._siteURIMenuitem.setAttribute("oncommand",
-        "openUILink(this.getAttribute('targetURI'), event, {" +
-        " triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})});");
-
-      // If a user middle-clicks this item we serve the oncommand event.
-      // We are using checkForMiddleClick because of Bug 246720.
-      // Note: stopPropagation is needed to avoid serving middle-click
-      // with BT_onClick that would open all items in tabs.
-      aPopup._siteURIMenuitem.setAttribute("onclick",
-        "checkForMiddleClick(this, event); event.stopPropagation();");
-      let label =
-        PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
-                                         [aPopup.parentNode.getAttribute("label")]);
-      aPopup._siteURIMenuitem.setAttribute("label", label);
-      aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
-
-      aPopup._siteURIMenuseparator = document.createXULElement("menuseparator");
-      aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
-    }
-  },
-
-  /**
-   * Add, update or remove the livemark status menuitem.
-   * @param aPopup
-   *        The livemark container popup
-   * @param aStatus
-   *        The livemark status
-   */
-  _setLivemarkStatusMenuItem:
-  function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
-    let statusMenuitem = aPopup._statusMenuitem;
-    if (!statusMenuitem) {
-      // Create the status menuitem and cache it in the popup object.
-      statusMenuitem = document.createXULElement("menuitem");
-      statusMenuitem.className = "livemarkstatus-menuitem";
-      if (typeof this.options.extraClasses.entry == "string") {
-        statusMenuitem.classList.add(this.options.extraClasses.entry);
-      }
-      statusMenuitem.setAttribute("disabled", true);
-      aPopup._statusMenuitem = statusMenuitem;
-    }
-
-    if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
-        aStatus == Ci.mozILivemark.STATUS_FAILED) {
-      // Status has changed, update the cached status menuitem.
-      let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
-                       "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
-      statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
-      if (aPopup._startMarker.nextElementSibling != statusMenuitem)
-        aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextElementSibling);
-    } else if (aPopup._statusMenuitem.parentNode == aPopup) {
-      // The livemark has finished loading.
-      aPopup.removeChild(aPopup._statusMenuitem);
-    }
-  },
-
   toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
 
     // We may get the popup for menus, but we need the menu itself.
     if (elt.localName == "menupopup")
       elt = elt.parentNode;
     if (aValue)
       elt.setAttribute("cutting", "true");
@@ -527,42 +433,17 @@ PlacesViewBase.prototype = {
     if (elt.localName == "menupopup") {
       elt = elt.parentNode;
     }
     // We must remove and reset the attribute to force an update.
     elt.removeAttribute("image");
     elt.setAttribute("image", aPlacesNode.icon);
   },
 
-  nodeAnnotationChanged:
-  function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
-
-    // All livemarks have a feedURI, so use it as our indicator of a livemark
-    // being modified.
-    if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-      let menu = elt.parentNode;
-      if (!menu.hasAttribute("livemark")) {
-        menu.setAttribute("livemark", "true");
-        if (AppConstants.platform === "macosx") {
-          // OS X native menubar doesn't track list-style-images since
-          // it doesn't have a frame (bug 733415).  Thus enforce updating.
-          menu.setAttribute("image", "");
-          menu.removeAttribute("image");
-        }
-      }
-
-      PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
-        .then(aLivemark => {
-          // Controller will use this to build the meta data for the node.
-          this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
-          this.invalidateContainer(aPlacesNode);
-        }, () => undefined);
-    }
-  },
+  nodeAnnotationChanged() {},
 
   nodeTitleChanged:
   function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
 
     // There's no UI representation for the root node, thus there's
     // nothing to be done when the title changes.
     if (elt == this._rootElt)
@@ -597,36 +478,17 @@ PlacesViewBase.prototype = {
       // Figure out if we need to show the "<Empty>" menu-item.
       // TODO Bug 517701: This doesn't seem to handle the case of an empty
       // root.
       if (parentElt._startMarker.nextElementSibling == parentElt._endMarker)
         this._setEmptyPopupStatus(parentElt, true);
     }
   },
 
-  nodeHistoryDetailsChanged:
-  function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
-    if (aPlacesNode.parent &&
-        this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
-      // Find the node in the parent.
-      let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
-      for (let child = popup._startMarker.nextElementSibling;
-           child != popup._endMarker;
-           child = child.nextElementSibling) {
-        if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
-          if (aPlacesNode.accessCount)
-            child.setAttribute("visited", "true");
-          else
-            child.removeAttribute("visited");
-          break;
-        }
-      }
-    }
-  },
-
+  nodeHistoryDetailsChanged() { },
   nodeTagsChanged() { },
   nodeDateAddedChanged() { },
   nodeLastModifiedChanged() { },
   nodeKeywordChanged() { },
   sortingChanged() { },
   batching() { },
 
   nodeInserted:
@@ -671,76 +533,19 @@ PlacesViewBase.prototype = {
     }
   },
 
   containerStateChanged:
   function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
     if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
         aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
       this.invalidateContainer(aPlacesNode);
-
-      if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
-        let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
-        if (queryOptions.excludeItems) {
-          return;
-        }
-
-        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
-          .then(aLivemark => {
-            if (!this.controller) {
-              // We might have been destroyed in the interim...
-              return;
-            }
-            let shouldInvalidate =
-              !this.controller.hasCachedLivemarkInfo(aPlacesNode);
-            this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
-            if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
-              aLivemark.registerForUpdates(aPlacesNode, this);
-              // Prioritize the current livemark.
-              aLivemark.reload();
-              PlacesUtils.livemarks.reloadLivemarks();
-              if (shouldInvalidate)
-                this.invalidateContainer(aPlacesNode);
-            } else {
-              aLivemark.unregisterForUpdates(aPlacesNode);
-            }
-          }, () => undefined);
-      }
     }
   },
 
-  _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup) {
-    this._setLivemarkSiteURIMenuItem(aPopup);
-    // Show the loading status only if there are no entries yet.
-    if (aPopup._startMarker.nextElementSibling == aPopup._endMarker)
-      this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
-
-    PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
-      .then(aLivemark => {
-        let placesNode = aPopup._placesNode;
-        if (!placesNode.containerOpen)
-          return;
-
-        if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
-          this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
-        this._cleanPopup(aPopup,
-          this._nativeView && aPopup.parentNode.hasAttribute("open"));
-
-        let children = aLivemark.getNodesForContainer(placesNode);
-        for (let i = 0; i < children.length; i++) {
-          let child = children[i];
-          this.nodeInserted(placesNode, child, i);
-          if (child.accessCount)
-            this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
-          else
-            this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
-        }
-      }, Cu.reportError);
-  },
-
   /**
    * Checks whether the popup associated with the provided element is open.
    * This method may be overridden by classes that extend this base class.
    *
    * @param  {Element} elt
    * @return {Boolean}
    */
   _isPopupOpen(elt) {
@@ -817,22 +622,16 @@ PlacesViewBase.prototype = {
           if (++numURINodes == 2)
             break;
         }
         currentChild = currentChild.nextElementSibling;
       }
       hasMultipleURIs = numURINodes > 1;
     }
 
-    let isLiveMark = false;
-    if (this.controller.hasCachedLivemarkInfo(aPopup._placesNode)) {
-      hasMultipleURIs = true;
-      isLiveMark = true;
-    }
-
     if (!hasMultipleURIs) {
       aPopup.setAttribute("singleitempopup", "true");
     } else {
       aPopup.removeAttribute("singleitempopup");
     }
 
     if (!hasMultipleURIs) {
       // We don't have to show any option.
@@ -853,25 +652,19 @@ PlacesViewBase.prototype = {
       aPopup._endOptOpenAllInTabs = document.createXULElement("menuitem");
       aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
 
       if (typeof this.options.extraClasses.entry == "string")
         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
       if (typeof this.options.extraClasses.footer == "string")
         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
 
-      if (isLiveMark) {
-        aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
-          "PlacesUIUtils.openLiveMarkNodesInTabs(this.parentNode._placesNode, event, " +
-                                                 "PlacesUIUtils.getViewForNode(this));");
-      } else {
-        aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
-          "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
-                                                 "PlacesUIUtils.getViewForNode(this));");
-      }
+      aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
+        "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
+                                               "PlacesUIUtils.getViewForNode(this));");
       aPopup._endOptOpenAllInTabs.setAttribute("onclick",
         "checkForMiddleClick(this, event); event.stopPropagation();");
       aPopup._endOptOpenAllInTabs.setAttribute("label",
         gNavigatorBundle.getString("menuOpenAllInTabs.label"));
       aPopup.appendChild(aPopup._endOptOpenAllInTabs);
     }
   },
 
@@ -1115,22 +908,16 @@ PlacesToolbar.prototype = {
       if (PlacesUtils.containerTypes.includes(type)) {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
           button.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aChild))
             button.setAttribute("tagContainer", "true");
-        } else if (PlacesUtils.nodeIsFolder(aChild)) {
-          PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
-            .then(aLivemark => {
-              button.setAttribute("livemark", "true");
-              this.controller.cacheLivemarkInfo(aChild, aLivemark);
-            }, () => undefined);
         }
 
         let popup = document.createXULElement("menupopup");
         popup.setAttribute("placespopup", "true");
         button.appendChild(popup);
         popup._placesNode = PlacesUtils.asContainer(aChild);
         popup.setAttribute("context", "placesContext");
 
@@ -1448,28 +1235,17 @@ PlacesToolbar.prototype = {
     // Nothing to do if it's a never-visible node.
     if (!elt || elt == this._rootElt)
       return;
 
     // We're notified for the menupopup, not the containing toolbarbutton.
     if (elt.localName == "menupopup")
       elt = elt.parentNode;
 
-    if (elt.parentNode == this._rootElt) { // Node is on the toolbar.
-      // All livemarks have a feedURI, so use it as our indicator.
-      if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-        elt.setAttribute("livemark", true);
-
-        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
-          .then(aLivemark => {
-            this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
-            this.invalidateContainer(aPlacesNode);
-          }, Cu.reportError);
-      }
-    } else {
+    if (elt.parentNode != this._rootElt) { // Node is on the toolbar.
       // Node is in a submenu.
       PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
     }
   },
 
   nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 
@@ -1543,17 +1319,17 @@ PlacesToolbar.prototype = {
 
     let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
     let elt = aEvent.target;
     if (elt._placesNode && elt != this._rootElt &&
         elt.localName != "menupopup") {
       let eltRect = elt.getBoundingClientRect();
       let eltIndex = Array.prototype.indexOf.call(this._rootElt.children, elt);
       if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
-          !PlacesUIUtils.isFolderReadOnly(elt._placesNode, this)) {
+          !PlacesUIUtils.isFolderReadOnly(elt._placesNode)) {
         // This is a folder.
         // If we are in the middle of it, drop inside it.
         // Otherwise, drop before it, with regards to RTL mode.
         let threshold = eltRect.width * 0.25;
         if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
                        : (aEvent.clientX < eltRect.left + threshold)) {
           // Drop before this folder.
           dropPoint.ip =
@@ -1859,25 +1635,21 @@ PlacesToolbar.prototype = {
 
     PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
   },
 
   _onPopupHidden: function PT__onPopupHidden(aEvent) {
     let popup = aEvent.target;
     let placesNode = popup._placesNode;
     // Avoid handling popuphidden of inner views
-    if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
-      // UI performance: folder queries are cheap, keep the resultnode open
-      // so we don't rebuild its contents whenever the popup is reopened.
-      // Though, we want to always close feed containers so their expiration
-      // status will be checked at next opening.
-      if (!PlacesUtils.nodeIsFolder(placesNode) ||
-          this.controller.hasCachedLivemarkInfo(placesNode)) {
-        placesNode.containerOpen = false;
-      }
+    if (placesNode && PlacesUIUtils.getViewForNode(popup) == this &&
+        // UI performance: folder queries are cheap, keep the resultnode open
+        // so we don't rebuild its contents whenever the popup is reopened.
+        !PlacesUtils.nodeIsFolder(placesNode)) {
+      placesNode.containerOpen = false;
     }
 
     let parent = popup.parentNode;
     if (parent.localName == "toolbarbutton") {
       this._openedMenuButton = null;
       // Clear the dragover attribute if present, if we are dragging into a
       // folder in the hierachy of current opened popup we don't clear
       // this attribute on clearOverFolder.  See Notify for closeTimer.
@@ -1962,21 +1734,19 @@ PlacesMenu.prototype = {
     // Avoid handling popuphidden of inner views.
     let popup = aEvent.originalTarget;
     let placesNode = popup._placesNode;
     if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
       return;
 
     // UI performance: folder queries are cheap, keep the resultnode open
     // so we don't rebuild its contents whenever the popup is reopened.
-    // Though, we want to always close feed containers so their expiration
-    // status will be checked at next opening.
-    if (!PlacesUtils.nodeIsFolder(placesNode) ||
-        this.controller.hasCachedLivemarkInfo(placesNode))
+    if (!PlacesUtils.nodeIsFolder(placesNode)) {
       placesNode.containerOpen = false;
+    }
 
     // The autoopened attribute is set for folders which have been
     // automatically opened when dragged over.  Turn off this attribute
     // when the folder closes because it is no longer applicable.
     popup.removeAttribute("autoopened");
     popup.removeAttribute("dragstart");
   },
 };
@@ -2018,22 +1788,16 @@ PlacesPanelMenuView.prototype = {
 
       if (PlacesUtils.containerTypes.includes(type)) {
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
           button.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aChild))
             button.setAttribute("tagContainer", "true");
-        } else if (PlacesUtils.nodeIsFolder(aChild)) {
-          PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
-            .then(aLivemark => {
-              button.setAttribute("livemark", "true");
-              this.controller.cacheLivemarkInfo(aChild, aLivemark);
-            }, () => undefined);
         }
       } else if (PlacesUtils.nodeIsURI(aChild)) {
         button.setAttribute("scheme",
                             PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
       }
     }
 
     button._placesNode = aChild;
@@ -2073,37 +1837,17 @@ PlacesPanelMenuView.prototype = {
     if (parentElt != this._rootElt)
       return;
 
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
     this._removeChild(elt);
     this._rootElt.insertBefore(elt, this._rootElt.children[aNewIndex]);
   },
 
-  nodeAnnotationChanged:
-  function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
-    // There's no UI representation for the root node.
-    if (elt == this._rootElt)
-      return;
-
-    if (elt.parentNode != this._rootElt)
-      return;
-
-    // All livemarks have a feedURI, so use it as our indicator.
-    if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-      elt.setAttribute("livemark", true);
-
-      PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
-        .then(aLivemark => {
-          this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
-          this.invalidateContainer(aPlacesNode);
-        }, Cu.reportError);
-    }
-  },
+  nodeAnnotationChanged() {},
 
   nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
 
     // There's no UI representation for the root node.
     if (elt == this._rootElt)
       return;
 
@@ -2281,25 +2025,21 @@ this.PlacesPanelview = class extends Pla
   _isPopupOpen() {
     return PanelView.forNode(this._viewElt).active;
   }
 
   _onPopupHidden(event) {
     let panelview = event.originalTarget;
     let placesNode = panelview._placesNode;
     // Avoid handling ViewHiding of inner views
-    if (placesNode && PlacesUIUtils.getViewForNode(panelview) == this) {
-      // UI performance: folder queries are cheap, keep the resultnode open
-      // so we don't rebuild its contents whenever the popup is reopened.
-      // Though, we want to always close feed containers so their expiration
-      // status will be checked at next opening.
-      if (!PlacesUtils.nodeIsFolder(placesNode) ||
-          this.controller.hasCachedLivemarkInfo(placesNode)) {
-        placesNode.containerOpen = false;
-      }
+    if (placesNode && PlacesUIUtils.getViewForNode(panelview) == this &&
+        // UI performance: folder queries are cheap, keep the resultnode open
+        // so we don't rebuild its contents whenever the popup is reopened.
+        !PlacesUtils.nodeIsFolder(placesNode)) {
+      placesNode.containerOpen = false;
     }
   }
 
   _onPopupShowing(event) {
     // If the event came from the root element, this is the first time
     // we ever get here.
     if (event.originalTarget == this._rootElt) {
       // Start listening for events from all panels inside the panelmultiview.
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -184,17 +184,17 @@ PlacesController.prototype = {
       // Livemark containers
       let selectedNode = this._view.selectedNode;
       return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
     }
     case "placesCmd_sortBy:name": {
       let selectedNode = this._view.selectedNode;
       return selectedNode &&
              PlacesUtils.nodeIsFolder(selectedNode) &&
-             !PlacesUIUtils.isFolderReadOnly(selectedNode, this._view) &&
+             !PlacesUIUtils.isFolderReadOnly(selectedNode) &&
              this._view.result.sortingMode ==
                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
     }
     case "placesCmd_createBookmark":
       var node = this._view.selectedNode;
       return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
     default:
       return false;
@@ -303,17 +303,17 @@ PlacesController.prototype = {
 
     for (var j = 0; j < ranges.length; j++) {
       var nodes = ranges[j];
       for (var i = 0; i < nodes.length; ++i) {
         // Disallow removing the view's root node
         if (nodes[i] == root)
           return false;
 
-        if (!PlacesUIUtils.canUserRemove(nodes[i], this._view))
+        if (!PlacesUIUtils.canUserRemove(nodes[i]))
           return false;
       }
     }
 
     return true;
   },
 
   /**
@@ -1237,17 +1237,17 @@ PlacesController.prototype = {
    *          The container were we are want to drop
    */
   disallowInsertion(container) {
     if (!container)
       throw new Error("empty container");
     // Allow dropping into Tag containers and editable folders.
     return !PlacesUtils.nodeIsTagQuery(container) &&
            (!PlacesUtils.nodeIsFolder(container) ||
-            PlacesUIUtils.isFolderReadOnly(container, this._view));
+            PlacesUIUtils.isFolderReadOnly(container));
   },
 
   /**
    * Determines if a node can be moved.
    *
    * @param   aNode
    *          A nsINavHistoryResultNode node.
    * @return True if the node can be moved, false otherwise.
@@ -1257,17 +1257,17 @@ PlacesController.prototype = {
     if (node.itemId == -1)
       return false;
 
     // Once tags and bookmarked are divorced, the tag-query check should be
     // removed.
     let parentNode = node.parent;
     return parentNode != null &&
            PlacesUtils.nodeIsFolder(parentNode) &&
-           !PlacesUIUtils.isFolderReadOnly(parentNode, this._view) &&
+           !PlacesUIUtils.isFolderReadOnly(parentNode) &&
            !PlacesUtils.nodeIsTagQuery(parentNode);
   },
 };
 
 /**
  * Handles drag and drop operations for views. Note that this is view agnostic!
  * You should not use PlacesController._view within these methods, since
  * the view that the item(s) have been dropped on was not necessarily active.
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -105,17 +105,17 @@
                   elt.lastElementChild.hasAttribute("placespopup"))
                 dropPoint.folderElt = elt;
               return dropPoint;
             }
 
             let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
                             elt._placesNode.title : null;
             if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
-                 !PlacesUIUtils.isFolderReadOnly(elt._placesNode, this._rootView)) ||
+                 !PlacesUIUtils.isFolderReadOnly(elt._placesNode)) ||
                 PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
               // This is a folder or a tag container.
               if (eventY - eltY < eltHeight * 0.20) {
                 // If mouse is in the top part of the element, drop above folder.
                 dropPoint.ip = new PlacesInsertionPoint({
                   parentId: PlacesUtils.getConcreteItemId(resultNode),
                   parentGuid: PlacesUtils.getConcreteItemGuid(resultNode),
                   orientation: Ci.nsITreeView.DROP_BEFORE,
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -102,20 +102,16 @@ PlacesTreeView.prototype = {
    * bookmark folders are never built lazily, as described above.
    *
    * @param aContainer
    *        A container result node.
    *
    * @return true if aContainer is a plain container, false otherwise.
    */
   _isPlainContainer: function PTV__isPlainContainer(aContainer) {
-    // Livemarks are always plain containers.
-    if (this._controller.hasCachedLivemarkInfo(aContainer))
-      return true;
-
     // We don't know enough about non-query containers.
     if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
       return false;
 
     switch (aContainer.queryOptions.resultType) {
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
@@ -338,18 +334,17 @@ PlacesTreeView.prototype = {
 
       this._nodeDetails.delete(makeNodeDetailsKey(this._rows[row]));
       this._nodeDetails.set(makeNodeDetailsKey(curChild), curChild);
       this._rows[row] = curChild;
       rowsInserted++;
 
       // Recursively do containers.
       if (!this._flatList &&
-          curChild instanceof Ci.nsINavHistoryContainerResultNode &&
-          !this._controller.hasCachedLivemarkInfo(curChild)) {
+          curChild instanceof Ci.nsINavHistoryContainerResultNode) {
         let uri = curChild.uri;
         let isopen = false;
 
         if (uri) {
           let val = Services.xulStore.getValue(document.documentURI, uri, "open");
           isopen = (val == "true");
         }
 
@@ -826,32 +821,16 @@ PlacesTreeView.prototype = {
     if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
       let lastModifiedColumn =
         this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
       if (lastModifiedColumn && !lastModifiedColumn.hidden)
         this._tree.invalidateCell(row, lastModifiedColumn);
     }
   },
 
-  _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
-    PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
-      .then(aLivemark => {
-        let placesNode = aNode;
-        // Need to check containerOpen since getLivemark is async.
-        if (!placesNode.containerOpen)
-          return;
-
-        let children = aLivemark.getNodesForContainer(placesNode);
-        for (let i = 0; i < children.length; i++) {
-          let child = children[i];
-          this.nodeInserted(placesNode, child, i);
-        }
-      }, Cu.reportError);
-  },
-
   nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
   },
 
   nodeURIChanged: function PTV_nodeURIChanged(aNode, aOldURI) {
     this._nodeDetails.delete(makeNodeDetailsKey({uri: aOldURI,
                                                  itemId: aNode.itemId,
                                                  time: aNode.time}));
@@ -865,91 +844,41 @@ PlacesTreeView.prototype = {
 
   nodeHistoryDetailsChanged:
   function PTV_nodeHistoryDetailsChanged(aNode, aOldVisitDate,
                                          aOldVisitCount) {
     this._nodeDetails.delete(makeNodeDetailsKey({uri: aNode.uri,
                                                  itemId: aNode.itemId,
                                                  time: aOldVisitDate}));
     this._nodeDetails.set(makeNodeDetailsKey(aNode), aNode);
-    if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) {
-      // Find the node in the parent.
-      let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
-      for (let i = parentRow; i < this._rows.length; i++) {
-        let child = this.nodeForTreeIndex(i);
-        if (child.uri == aNode.uri) {
-          this._cellProperties.delete(child);
-          this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
-          break;
-        }
-      }
-      return;
-    }
 
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
   },
 
   nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
   },
 
   nodeKeywordChanged(aNode, aNewKeyword) {},
 
-  nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
-    if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-      PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
-        .then(aLivemark => {
-          this._controller.cacheLivemarkInfo(aNode, aLivemark);
-          let properties = this._cellProperties.get(aNode);
-          this._cellProperties.set(aNode, properties += " livemark");
-          // The livemark attribute is set as a cell property on the title cell.
-          this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
-        }, Cu.reportError);
-    }
-  },
+  nodeAnnotationChanged() {},
 
   nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
   },
 
   nodeLastModifiedChanged:
   function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
   },
 
   containerStateChanged:
   function PTV_containerStateChanged(aNode, aOldState, aNewState) {
     this.invalidateContainer(aNode);
-
-    if (PlacesUtils.nodeIsFolder(aNode) ||
-        (this._flatList && aNode == this._rootNode)) {
-      let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions;
-      if (queryOptions.excludeItems) {
-        return;
-      }
-      if (aNode.itemId != -1) { // run when there's a valid node id
-        PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
-          .then(aLivemark => {
-            let shouldInvalidate =
-              !this._controller.hasCachedLivemarkInfo(aNode);
-            this._controller.cacheLivemarkInfo(aNode, aLivemark);
-            if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
-              aLivemark.registerForUpdates(aNode, this);
-              // Prioritize the current livemark.
-              aLivemark.reload();
-              PlacesUtils.livemarks.reloadLivemarks();
-              if (shouldInvalidate)
-                this.invalidateContainer(aNode);
-            } else {
-              aLivemark.unregisterForUpdates(aNode);
-            }
-          }, () => undefined);
-      }
-    }
   },
 
   invalidateContainer: function PTV_invalidateContainer(aContainer) {
     console.assert(this._result, "Need to have a result to update");
     if (!this._tree)
       return;
 
     // If we are currently editing, don't invalidate the container until we
@@ -1060,23 +989,16 @@ PlacesTreeView.prototype = {
 
         // If we don't have a parent, we made it all the way to the root
         // and didn't find a match, so we can open our item.
         if (!parent && !item.containerOpen)
           item.containerOpen = true;
       }
     }
 
-    if (this._controller.hasCachedLivemarkInfo(aContainer)) {
-      let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
-      if (!queryOptions.excludeItems) {
-        this._populateLivemarkContainer(aContainer);
-      }
-    }
-
     this._tree.endUpdateBatch();
 
     // Restore selection.
     this._restoreSelection(nodesToReselect, aContainer);
     this.selection.selectEventsSuppressed = false;
   },
 
   _columns: [],
@@ -1243,32 +1165,16 @@ PlacesTreeView.prototype = {
         if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
           properties += " query";
           if (PlacesUtils.nodeIsTagQuery(node))
             properties += " tagContainer";
           else if (PlacesUtils.nodeIsDay(node))
             properties += " dayContainer";
           else if (PlacesUtils.nodeIsHost(node))
             properties += " hostContainer";
-        } else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
-                 nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
-          if (itemId != -1) {
-            if (this._controller.hasCachedLivemarkInfo(node)) {
-              properties += " livemark";
-            } else {
-              PlacesUtils.livemarks.getLivemark({ id: itemId })
-                .then(aLivemark => {
-                  this._controller.cacheLivemarkInfo(node, aLivemark);
-                  let livemarkProps = this._cellProperties.get(node);
-                  this._cellProperties.set(node, livemarkProps += " livemark");
-                  // The livemark attribute is set as a cell property on the title cell.
-                  this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
-                }, () => undefined);
-            }
-          }
         }
 
         if (itemId == -1) {
           switch (node.bookmarkGuid) {
           case PlacesUtils.bookmarks.virtualToolbarGuid:
             properties += ` queryFolder_${PlacesUtils.bookmarks.toolbarGuid}`;
             break;
           case PlacesUtils.bookmarks.virtualMenuGuid:
@@ -1284,23 +1190,16 @@ PlacesTreeView.prototype = {
             properties += ` OrganizerQuery_${node.bookmarkGuid}`;
             break;
           }
         }
       } else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
         properties += " separator";
       else if (PlacesUtils.nodeIsURI(node)) {
         properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
-
-        if (this._controller.hasCachedLivemarkInfo(node.parent)) {
-          properties += " livemarkItem";
-          if (node.accessCount) {
-            properties += " visited";
-          }
-        }
       }
 
       this._cellProperties.set(node, properties);
     }
 
     return props + " " + properties;
   },
 
@@ -1333,24 +1232,18 @@ PlacesTreeView.prototype = {
     // All containers are listed in the rows array.
     return this._rows[aRow].containerOpen;
   },
 
   isContainerEmpty: function PTV_isContainerEmpty(aRow) {
     if (this._flatList)
       return true;
 
-    let node = this._rows[aRow];
-    if (this._controller.hasCachedLivemarkInfo(node)) {
-      let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
-      return queryOptions.excludeItems;
-    }
-
     // All containers are listed in the rows array.
-    return !node.hasChildren;
+    return !this._rows[aRow].hasChildren;
   },
 
   isSeparator: function PTV_isSeparator(aRow) {
     // All separators are listed in the rows array.
     let node = this._rows[aRow];
     return node && PlacesUtils.nodeIsSeparator(node);
   },
 
@@ -1585,28 +1478,25 @@ PlacesTreeView.prototype = {
       throw Cr.NS_ERROR_UNEXPECTED;
 
     let node = this._rows[aRow];
     if (this._flatList && this._openContainerCallback) {
       this._openContainerCallback(node);
       return;
     }
 
-    // Persist containers open status, but never persist livemarks.
-    if (!this._controller.hasCachedLivemarkInfo(node)) {
-      let uri = node.uri;
+    let uri = node.uri;
+
+    if (uri) {
+      let docURI = document.documentURI;
 
-      if (uri) {
-        let docURI = document.documentURI;
-
-        if (node.containerOpen) {
-          Services.xulStore.removeValue(docURI, uri, "open");
-        } else {
-          Services.xulStore.setValue(docURI, uri, "open", "true");
-        }
+      if (node.containerOpen) {
+        Services.xulStore.removeValue(docURI, uri, "open");
+      } else {
+        Services.xulStore.setValue(docURI, uri, "open", "true");
       }
     }
 
     node.containerOpen = !node.containerOpen;
   },
 
   cycleHeader: function PTV_cycleHeader(aColumn) {
     if (!this._result)
@@ -1711,19 +1601,18 @@ PlacesTreeView.prototype = {
 
     let node = this._rows[aRow];
     if (!node) {
       Cu.reportError("isEditable called for an unbuilt row.");
       return false;
     }
     let itemGuid = node.bookmarkGuid;
 
-    // Only bookmark-nodes are editable.  Fortunately, this checks also takes
-    // care of livemark children.
-    if (itemGuid == "")
+    // Only bookmark-nodes are editable.
+    if (!itemGuid)
       return false;
 
     // The following items are also not editable, even though they are bookmark
     // items.
     // * places-roots
     // * the left pane special folders and queries (those are place: uri
     //   bookmarks)
     // * separators
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -542,17 +542,19 @@ var gSearchResultsPane = {
   },
 
   /**
    * Remove all search tooltips.
    */
   removeAllSearchTooltips() {
     for (let anchorNode of this.listSearchTooltips) {
       anchorNode.parentElement.classList.remove("search-tooltip-parent");
-      anchorNode.tooltipNode.remove();
+      if (anchorNode.tooltipNode) {
+        anchorNode.tooltipNode.remove();
+      }
       anchorNode.tooltipNode = null;
     }
     this.listSearchTooltips.clear();
   },
 
   /**
    * Remove all indicators on menuitem.
    */
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -225,28 +225,35 @@ class UrlbarInput {
 
   /**
    * Called by the view when a result is selected.
    *
    * @param {Event} event The event that selected the result.
    * @param {UrlbarMatch} result The result that was selected.
    */
   resultSelected(event, result) {
-    // Set the input value to the target url.
+    this.setValueFromResult(result);
+    this.controller.resultSelected(event, result);
+  }
+
+  /**
+   * Called by the view when moving through results with the keyboard.
+   *
+   * @param {UrlbarMatch} result The result that was selected.
+   */
+  setValueFromResult(result) {
     let val = result.url;
     let uri;
     try {
       uri = Services.io.newURI(val);
     } catch (ex) {}
     if (uri) {
       val = this.window.losslessDecodeURI(uri);
     }
     this.value = val;
-
-    this.controller.resultSelected(event, result);
   }
 
   // Getters and Setters below.
 
   get focused() {
     return this.textbox.getAttribute("focused") == "true";
   }
 
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -63,20 +63,16 @@ const PREF_URLBAR_DEFAULTS = new Map([
 
   // Applies URL highlighting and other styling to the text in the urlbar input.
   ["formatting.enabled", false],
 
   // Allows results from one search to be reused in the next search.  One of the
   // INSERTMETHOD values.
   ["insertMethod", UrlbarUtils.INSERTMETHOD.MERGE_RELATED],
 
-  // Controls how URLs are matched against the user's search string.  See
-  // mozIPlacesAutoComplete.
-  ["matchBehavior", Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE],
-
   // Controls the composition of search results.
   ["matchBuckets", "suggestion:4,general:Infinity"],
 
   // If the heuristic result is a search engine result, we use this instead of
   // matchBuckets.
   ["matchBucketsSearch", ""],
 
   // For search suggestion results, we truncate the user's search string to this
@@ -306,26 +302,16 @@ class Preferences {
           val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY;
         } else if (this.get("suggest.bookmark")) {
           val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
         } else {
           val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
         }
         return val;
       }
-      case "matchBehavior": {
-        // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
-        let val = this._readPref(pref);
-        if (![Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
-              Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY,
-              Ci.mozIPlacesAutoComplete.MATCH_BEGINNING].includes(val)) {
-          val = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
-        }
-        return val;
-      }
     }
     return this._readPref(pref);
   }
 
   /**
    * Used to keep some pref values linked.
    * TODO: remove autocomplete.enabled and rely only on suggest.* prefs once we
    * can drop legacy add-ons compatibility.
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -12,21 +12,16 @@ bookmarksBackupTitle=Bookmarks backup fi
 bookmarksRestoreAlertTitle=Revert Bookmarks
 bookmarksRestoreAlert=This will replace all of your current bookmarks with the backup. Are you sure?
 bookmarksRestoreTitle=Select a bookmarks backup
 bookmarksRestoreFilterName=JSON
 
 bookmarksRestoreFormatError=Unsupported file type.
 bookmarksRestoreParseError=Unable to process the backup file.
 
-bookmarksLivemarkLoading=Live Bookmark loading…
-bookmarksLivemarkFailed=Live Bookmark feed failed to load.
-
-menuOpenLivemarkOrigin.label=Open “%S”
-
 sortByName=Sort ‘%S’ by Name
 sortByNameGeneric=Sort by Name
 # LOCALIZATION NOTE (view.sortBy.1.name.label): sortBy properties are versioned.
 # When any of these changes, all of the properties must be bumped, and the
 # change must be annotated here.  Both label and accesskey must be updated.
 # - version 1: changed view.sortBy.1.date.
 view.sortBy.1.name.label=Sort by Name
 view.sortBy.1.name.accesskey=N
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -21,17 +21,16 @@ browser.jar:
 * skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsView.css   (downloads/allDownloadsView.css)
 * skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/notification-icons/geo-blocked.svg  (notification-icons/geo-blocked.svg)
   skin/classic/browser/notification-icons/geo-detailed.svg (notification-icons/geo-detailed.svg)
   skin/classic/browser/notification-icons/geo.svg          (notification-icons/geo.svg)
   skin/classic/browser/places/allBookmarks.png        (places/allBookmarks.png)
   skin/classic/browser/places/editBookmark.css        (places/editBookmark.css)
-  skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
 * skin/classic/browser/places/sidebar.css             (places/sidebar.css)
   skin/classic/browser/places/organizer.css           (places/organizer.css)
   skin/classic/browser/places/organizer.xml           (places/organizer.xml)
   skin/classic/browser/places/toolbarDropMarker.png   (places/toolbarDropMarker.png)
   skin/classic/browser/preferences/alwaysAsk.png      (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/preferences.css    (preferences/preferences.css)
 * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
 * skin/classic/browser/preferences/in-content/dialog.css      (preferences/in-content/dialog.css)
deleted file mode 100644
index 07342bea3bf69a470bfd2a864b9dd0b0f253e9b5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -184,19 +184,17 @@
 /* ----- BOOKMARK TOOLBAR ----- */
 
 #nav-bar-customization-target > #wrapper-personal-bookmarks > #personal-bookmarks {
   min-height: 32px;
   -moz-box-align: center;
 }
 
 /* Workaround for native menubar inheritance */
-.openintabs-menuitem,
-.openlivemarksite-menuitem,
-.livemarkstatus-menuitem {
+.openintabs-menuitem {
   list-style-image: none;
 }
 
 .bookmark-item[cutting] > .toolbarbutton-icon,
 .bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
   opacity: 0.5;
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -30,17 +30,16 @@ browser.jar:
   skin/classic/browser/notification-icons/geo-blocked.svg  (notification-icons/geo-blocked.svg)
   skin/classic/browser/notification-icons/geo.svg          (notification-icons/geo.svg)
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/sidebar.css                   (places/sidebar.css)
   skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/toolbar.png                   (places/toolbar.png)
   skin/classic/browser/places/toolbarDropMarker.png         (places/toolbarDropMarker.png)
   skin/classic/browser/places/editBookmark.css              (places/editBookmark.css)
-  skin/classic/browser/places/livemark-item.png             (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png            (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png          (preferences/application.png)
   skin/classic/browser/preferences/saveFile.png             (preferences/saveFile.png)
 * skin/classic/browser/preferences/preferences.css          (preferences/preferences.css)
 * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
 * skin/classic/browser/preferences/in-content/dialog.css      (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css         (preferences/applications.css)
   skin/classic/browser/share.svg                            (share.svg)
deleted file mode 100644
index 07342bea3bf69a470bfd2a864b9dd0b0f253e9b5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -230,17 +230,16 @@
   skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
   skin/classic/browser/panic-panel/header.png                  (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header@2x.png               (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/icons.png                   (../shared/panic-panel/icons.png)
   skin/classic/browser/panic-panel/icons@2x.png                (../shared/panic-panel/icons@2x.png)
   skin/classic/browser/places/bookmarksMenu.svg                (../shared/places/bookmarksMenu.svg)
   skin/classic/browser/places/bookmarksToolbar.svg             (../shared/places/bookmarksToolbar.svg)
   skin/classic/browser/places/folder.svg                       (../shared/places/folder.svg)
-  skin/classic/browser/places/folder-live.svg                  (../shared/places/folder-live.svg)
   skin/classic/browser/places/folder-smart.svg                 (../shared/places/folder-smart.svg)
   skin/classic/browser/places/history.svg                      (../shared/places/history.svg)
   skin/classic/browser/places/tag.svg                          (../shared/places/tag.svg)
   skin/classic/browser/places/unfiledBookmarks.svg             (../shared/places/unfiledBookmarks.svg)
   skin/classic/browser/places/tree-icons.css                   (../shared/places/tree-icons.css)
   skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
   skin/classic/browser/privatebrowsing/favicon.svg             (../shared/privatebrowsing/favicon.svg)
   skin/classic/browser/privatebrowsing/private-browsing.svg    (../shared/privatebrowsing/private-browsing.svg)
deleted file mode 100644
--- a/browser/themes/shared/places/folder-live.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity" xmlns="http://www.w3.org/2000/svg">
-  <path d="M3.5,10A2.5,2.5,0,1,0,6,12.5,2.5,2.5,0,0,0,3.5,10ZM2,1A1,1,0,0,0,2,3,10.883,10.883,0,0,1,13,14a1,1,0,0,0,2,0A12.862,12.862,0,0,0,2,1ZM2,5A1,1,0,0,0,2,7a6.926,6.926,0,0,1,7,7,1,1,0,0,0,2,0A8.9,8.9,0,0,0,2,5Z"/>
-</svg>
--- a/browser/themes/shared/places/tree-icons.css
+++ b/browser/themes/shared/places/tree-icons.css
@@ -10,41 +10,28 @@ treechildren:-moz-tree-image {
 treechildren::-moz-tree-image(title) {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   padding-inline-end: 2px;
   margin: 0 2px;
   width: 16px;
   height: 16px;
 }
 
-treechildren::-moz-tree-image(title, livemarkItem) {
-  list-style-image: url("chrome://browser/skin/places/livemark-item.png");
-  -moz-image-region: rect(0px, 16px, 16px, 0px);
-}
-
-treechildren::-moz-tree-image(title, livemarkItem, visited) {
-  -moz-image-region: rect(0px, 32px, 16px, 16px);
-}
-
 treechildren::-moz-tree-image(title, container),
 treechildren::-moz-tree-image(title, open) {
   list-style-image: url("chrome://browser/skin/places/folder.svg");
 }
 
 treechildren::-moz-tree-image(title, separator) {
   list-style-image: none;
   width: 0 !important;
   height: 0 !important;
   margin: 0;
 }
 
-treechildren::-moz-tree-image(container, livemark) {
-  list-style-image: url("chrome://browser/skin/places/folder-live.svg");
-}
-
 treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg");
 }
 
 treechildren::-moz-tree-image(container, queryFolder_menu________) {
   list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg");
 }
 
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -509,29 +509,16 @@ toolbar[brighttext] {
   fill: var(--lwt-toolbarbutton-icon-fill, currentColor);
   fill-opacity: var(--toolbarbutton-icon-fill-opacity);
 }
 
 .bookmark-item[container] {
   list-style-image: url("chrome://browser/skin/places/folder.svg");
 }
 
-.bookmark-item[container][livemark] {
-  list-style-image: url("chrome://browser/skin/places/folder-live.svg");
-}
-
-.bookmark-item[container][livemark] .bookmark-item {
-  list-style-image: url("chrome://browser/skin/places/livemark-item.png");
-  -moz-image-region: rect(0px, 16px, 16px, 0px);
-}
-
-.bookmark-item[container][livemark] .bookmark-item[visited] {
-  -moz-image-region: rect(0px, 32px, 16px, 16px);
-}
-
 .bookmark-item[container][query] {
   list-style-image: url("chrome://browser/skin/places/folder-smart.svg");
 }
 
 .bookmark-item[query][tagContainer] {
   list-style-image: url("chrome://browser/skin/places/tag.svg");
   -moz-image-region: auto;
 }
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -25,17 +25,16 @@ browser.jar:
   skin/classic/browser/notification-icons/geo-detailed.svg     (notification-icons/geo-detailed.svg)
   skin/classic/browser/notification-icons/geo.svg              (notification-icons/geo.svg)
 * skin/classic/browser/places/sidebar.css                      (places/sidebar.css)
 * skin/classic/browser/places/organizer.css                    (places/organizer.css)
   skin/classic/browser/places/toolbarDropMarker.png            (places/toolbarDropMarker.png)
   skin/classic/browser/places/editBookmark.css                 (places/editBookmark.css)
   skin/classic/browser/places/libraryToolbar.png               (places/libraryToolbar.png)
   skin/classic/browser/places/allBookmarks.png                 (places/allBookmarks.png)
-  skin/classic/browser/places/livemark-item.png                (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png               (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png             (preferences/application.png)
   skin/classic/browser/preferences/saveFile.png                (preferences/saveFile.png)
   skin/classic/browser/preferences/preferences.css             (preferences/preferences.css)
 * skin/classic/browser/preferences/in-content/preferences.css  (preferences/in-content/preferences.css)
 * skin/classic/browser/preferences/in-content/dialog.css       (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css            (preferences/applications.css)
   skin/classic/browser/share.svg                               (share.svg)
deleted file mode 100644
index 07342bea3bf69a470bfd2a864b9dd0b0f253e9b5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -14,28 +14,31 @@
 #include "nsURLHelper.h"
 
 namespace mozilla {
 
 using dom::URLParams;
 
 bool OriginAttributes::sFirstPartyIsolation = false;
 bool OriginAttributes::sRestrictedOpenerAccess = false;
+bool OriginAttributes::sBlockPostMessageForFPI = false;
 
 void
 OriginAttributes::InitPrefs()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static bool sInited = false;
   if (!sInited) {
     sInited = true;
     Preferences::AddBoolVarCache(&sFirstPartyIsolation,
                                  "privacy.firstparty.isolate");
     Preferences::AddBoolVarCache(&sRestrictedOpenerAccess,
                                  "privacy.firstparty.isolate.restrict_opener_access");
+    Preferences::AddBoolVarCache(&sBlockPostMessageForFPI,
+                                 "privacy.firstparty.isolate.block_post_message");
   }
 }
 
 void
 OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
                                       nsIURI* aURI)
 {
   bool isFirstPartyEnabled = IsFirstPartyEnabled();
--- a/caps/OriginAttributes.h
+++ b/caps/OriginAttributes.h
@@ -56,16 +56,24 @@ public:
            mFirstPartyDomain == aOther.mFirstPartyDomain;
   }
 
   bool operator!=(const OriginAttributes& aOther) const
   {
     return !(*this == aOther);
   }
 
+  MOZ_MUST_USE bool EqualsIgnoringFPD(const OriginAttributes& aOther) const
+  {
+    return mAppId == aOther.mAppId &&
+           mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
+           mUserContextId == aOther.mUserContextId &&
+           mPrivateBrowsingId == aOther.mPrivateBrowsingId;
+  }
+
   // Serializes/Deserializes non-default values into the suffix format, i.e.
   // |!key1=value1&key2=value2|. If there are no non-default attributes, this
   // returns an empty string.
   void CreateSuffix(nsACString& aStr) const;
 
   // Don't use this method for anything else than debugging!
   void CreateAnonymizedSuffix(nsACString& aStr) const;
 
@@ -91,25 +99,33 @@ public:
   // is enabled and "privacy.firstparty.isolate.restrict_opener_access" is on.
   static inline bool IsRestrictOpenerAccessForFPI()
   {
     // We always want to restrict window.opener if first party isolation is
     // disabled.
     return !sFirstPartyIsolation || sRestrictedOpenerAccess;
   }
 
+  // Check whether we block the postMessage across different FPDs when the
+  // targetOrigin is '*'.
+  static inline MOZ_MUST_USE bool IsBlockPostMessageForFPI()
+  {
+    return sFirstPartyIsolation && sBlockPostMessageForFPI;
+  }
+
   // returns true if the originAttributes suffix has mPrivateBrowsingId value
   // different than 0.
   static bool IsPrivateBrowsing(const nsACString& aOrigin);
 
   static void InitPrefs();
 
 private:
   static bool sFirstPartyIsolation;
   static bool sRestrictedOpenerAccess;
+  static bool sBlockPostMessageForFPI;
 };
 
 class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary
 {
 public:
   // To convert a JSON string to an OriginAttributesPattern, do the following:
   //
   // OriginAttributesPattern pattern;
--- a/devtools/client/inspector/changes/ChangesView.js
+++ b/devtools/client/inspector/changes/ChangesView.js
@@ -12,17 +12,18 @@ const { Provider } = require("devtools/c
 const ChangesApp = createFactory(require("./components/ChangesApp"));
 
 const {
   resetChanges,
   trackChange,
 } = require("./actions/changes");
 
 class ChangesView {
-  constructor(inspector) {
+  constructor(inspector, window) {
+    this.document = window.document;
     this.inspector = inspector;
     this.store = this.inspector.store;
     this.toolbox = this.inspector.toolbox;
 
     this.onAddChange = this.onAddChange.bind(this);
     this.onClearChanges = this.onClearChanges.bind(this);
     this.destroy = this.destroy.bind(this);
 
@@ -39,19 +40,17 @@ class ChangesView {
 
     // Expose the provider to let inspector.js use it in setupSidebar.
     this.provider = createElement(Provider, {
       id: "changesview",
       key: "changesview",
       store: this.store,
     }, changesApp);
 
-    // TODO: save store and restore/replay on refresh.
-    // Bug 1478439 - https://bugzilla.mozilla.org/show_bug.cgi?id=1478439
-    this.inspector.target.once("will-navigate", this.destroy);
+    this.inspector.target.on("will-navigate", this.onClearChanges);
 
     // Sync the store to the changes stored on the server. The
     // syncChangesToServer() method is async, but we don't await it since
     // this method itself is NOT async. The call will be made in its own
     // time, which is fine since it definitionally brings us up-to-date
     // with the server at that moment.
     this.syncChangesToServer();
   }
@@ -81,15 +80,16 @@ class ChangesView {
    */
   destroy() {
     this.store.dispatch(resetChanges());
 
     this.changesFront.off("add-change", this.onAddChange);
     this.changesFront.off("clear-changes", this.onClearChanges);
     this.changesFront = null;
 
+    this.document = null;
     this.inspector = null;
     this.store = null;
     this.toolbox = null;
   }
 }
 
 module.exports = ChangesView;
--- a/devtools/client/inspector/changes/actions/changes.js
+++ b/devtools/client/inspector/changes/actions/changes.js
@@ -12,16 +12,16 @@ const {
 module.exports = {
 
   resetChanges() {
     return {
       type: RESET_CHANGES,
     };
   },
 
-  trackChange(data) {
+  trackChange(change) {
     return {
       type: TRACK_CHANGE,
-      data,
+      change,
     };
   },
 
 };
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/components/CSSDeclaration.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+class CSSDeclaration extends PureComponent {
+  static get propTypes() {
+    return {
+      property: PropTypes.string.isRequired,
+      value: PropTypes.string.isRequired,
+      className: PropTypes.string,
+    };
+  }
+
+  static get defaultProps() {
+    return {
+      className: "",
+    };
+  }
+
+  render() {
+    const { property, value, className } = this.props;
+
+    return dom.div({ className },
+      dom.span({ className: "declaration-name theme-fg-color5"}, property),
+      ":",
+      dom.span({ className: "declaration-value theme-fg-color1"}, value),
+      ";"
+    );
+  }
+}
+
+module.exports = CSSDeclaration;
--- a/devtools/client/inspector/changes/components/ChangesApp.js
+++ b/devtools/client/inspector/changes/components/ChangesApp.js
@@ -1,78 +1,119 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { PureComponent } = require("devtools/client/shared/vendor/react");
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
+const CSSDeclaration = createFactory(require("./CSSDeclaration"));
+
 class ChangesApp extends PureComponent {
   static get propTypes() {
     return {
+      // Redux state slice assigned to Track Changes feature; passed as prop by connect()
       changes: PropTypes.object.isRequired,
     };
   }
 
-  renderMutations(remove = {}, add = {}) {
-    const removals = Object.entries(remove).map(([prop, value]) => {
-      return dom.div(
-        { className: "line diff-remove"},
-        `${prop}: ${value};`
-      );
+  constructor(props) {
+    super(props);
+    // In the Redux store, all rules exist in a collection at the same level of nesting.
+    // Parent rules come before child rules. Parent/child dependencies are set
+    // via parameters in each rule pointing to the corresponding rule ids.
+    //
+    // To render rules, we traverse the descendant rule tree and render each child rule
+    // found. This means we get into situations where we can render the same rule multiple
+    // times: once as a child of its parent and once standalone.
+    //
+    // By keeping a log of rules previously rendered we prevent needless multi-rendering.
+    this.renderedRules = [];
+  }
+
+  renderDeclarations(remove = {}, add = {}) {
+    const removals = Object.entries(remove).map(([property, value]) => {
+      return CSSDeclaration({ className: "level diff-remove", property, value });
     });
 
-    const additions = Object.entries(add).map(([prop, value]) => {
-      return dom.div(
-        { className: "line diff-add"},
-        `${prop}: ${value};`
-      );
+    const additions = Object.entries(add).map(([property, value]) => {
+      return CSSDeclaration({ className: "level diff-add", property, value });
     });
 
     return [removals, additions];
   }
 
-  renderSelectors(selectors = {}) {
-    return Object.keys(selectors).map(sel => {
+  renderRule(ruleId, rule, rules) {
+    const selector = rule.selector;
+
+    if (this.renderedRules.includes(ruleId)) {
+      return null;
+    }
+
+    // Mark this rule as rendered so we don't render it again.
+    this.renderedRules.push(ruleId);
+
+    return dom.div(
+      {
+        className: "rule",
+      },
+      dom.div(
+        {
+          className: "level selector",
+          title: selector,
+        },
+        selector,
+        dom.span({ className: "bracket-open" }, "{")
+      ),
+      // Render any nested child rules if they are present.
+      rule.children.length > 0 && rule.children.map(childRuleId => {
+        return this.renderRule(childRuleId, rules[childRuleId], rules);
+      }),
+      // Render any changed CSS declarations.
+      this.renderDeclarations(rule.remove, rule.add),
+      dom.span({ className: "level bracket-close" }, "}")
+    );
+  }
+
+  renderDiff(changes = {}) {
+    // Render groups of style sources: stylesheets and element style attributes.
+    return Object.entries(changes).map(([sourceId, source]) => {
+      const href = source.href || `inline stylesheet #${source.index}`;
+      const rules = source.rules;
+
       return dom.details(
-        { className: "selector", open: true },
+        {
+          className: "source devtools-monospace",
+          open: true,
+        },
         dom.summary(
           {
-            title: sel,
-          },
-          sel),
-        this.renderMutations(selectors[sel].remove, selectors[sel].add)
-      );
-    });
-  }
-
-  renderDiff(diff = {}) {
-    // Render groups of style sources: stylesheets, embedded styles and inline styles
-    return Object.keys(diff).map(href => {
-      return dom.details(
-        { className: "source", open: true },
-        dom.summary(
-          {
+            className: "href",
             title: href,
           },
           href),
-        // Render groups of selectors
-        this.renderSelectors(diff[href])
+        // Render changed rules within this source.
+        Object.entries(rules).map(([ruleId, rule]) => {
+          return this.renderRule(ruleId, rule, rules);
+        })
       );
     });
   }
 
   render() {
+    // Reset log of rendered rules.
+    this.renderedRules = [];
+
     return dom.div(
       {
         className: "theme-sidebar inspector-tabpanel",
         id: "sidebar-panel-changes",
       },
-      this.renderDiff(this.props.changes.diff)
+      this.renderDiff(this.props.changes)
     );
   }
 }
 
 module.exports = connect(state => state)(ChangesApp);
--- a/devtools/client/inspector/changes/components/moz.build
+++ b/devtools/client/inspector/changes/components/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'ChangesApp.js',
+    'CSSDeclaration.js',
 )
--- a/devtools/client/inspector/changes/moz.build
+++ b/devtools/client/inspector/changes/moz.build
@@ -3,14 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'actions',
     'components',
     'reducers',
+    'utils',
 ]
 
 DevToolsModules(
     'ChangesManager.js',
     'ChangesView.js',
 )
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/changes/reducers/changes.js
+++ b/devtools/client/inspector/changes/reducers/changes.js
@@ -1,121 +1,237 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const { getSourceHash, getRuleHash } = require("../utils/changes-utils");
+
 const {
   RESET_CHANGES,
   TRACK_CHANGE,
 } = require("../actions/index");
 
-const INITIAL_STATE = {
-  /**
-   * Diff of changes grouped by stylesheet href, then by selector, then into add/remove
-   * objects with CSS property names and values for corresponding changes.
-   *
-   * Structure:
-   *
-   *   diff = {
-   *    "href": {
-   *      "selector": {
-   *        add: {
-   *          "property": value
-   *          ... // more properties
-   *        },
-   *        remove: {
-   *          "property": value
-   *          ...
-   *        }
-   *      },
-   *      ... // more selectors
-   *    }
-   *    ... // more stylesheet hrefs
-   *   }
-   */
-  diff: {},
-};
+/**
+ * Return a deep clone of the given state object.
+ *
+ * @param {Object} state
+ * @return {Object}
+ */
+function cloneState(state = {}) {
+  return Object.entries(state).reduce((sources, [sourceId, source]) => {
+    sources[sourceId] = {
+      ...source,
+      rules: Object.entries(source.rules).reduce((rules, [ruleId, rule]) => {
+        rules[ruleId] = {
+          ...rule,
+          children: rule.children.slice(0),
+          add: { ...rule.add },
+          remove: { ...rule.remove },
+        };
+
+        return rules;
+      }, {}),
+    };
+
+    return sources;
+  }, {});
+}
 
 /**
- * Mutate the given diff object with data about a new change.
+ * Given information about a CSS rule and its ancestor rules (@media, @supports, etc),
+ * create entries in the given rules collection for each rule and assign parent/child
+ * dependencies.
  *
- * @param  {Object} diff
- *         Diff object from the store.
- * @param  {Object} change
- *         Data about the change: which property was added or removed, on which selector,
- *         in which stylesheet or whether the source is an element's inline style.
+ * @param {Object} ruleData
+ *        Information about a CSS rule:
+ *        {
+ *          selector:  {String}
+ *                     CSS selector text
+ *          ancestors: {Array}
+ *                     Flattened CSS rule tree of the rule's ancestors with the root rule
+ *                     at the beginning of the array and the leaf rule at the end.
+ *          ruleIndex: {Array}
+ *                     Indexes of each ancestor rule within its parent rule.
+ *        }
+ *
+ * @param {Object} rules
+ *        Collection of rules to be mutated.
+ *        This is a reference to the corresponding `rules` object from the state.
+ *
  * @return {Object}
- *         Mutated diff object.
+ *         Entry for the CSS rule created the given collection of rules.
  */
-function updateDiff(diff = {}, change = {}) {
-  // Ensure expected diff structure exists
-  diff[change.href] = diff[change.href] || {};
-  diff[change.href][change.selector] = diff[change.href][change.selector] || {};
-  diff[change.href][change.selector].add = diff[change.href][change.selector].add || {};
-  diff[change.href][change.selector].remove = diff[change.href][change.selector].remove
-    || {};
+function createRule(ruleData, rules) {
+  // Append the rule data to the flattened CSS rule tree with its ancestors.
+  const ruleAncestry = [...ruleData.ancestors, { ...ruleData }];
 
-  // Reference to the diff data for this stylesheet/selector pair.
-  const ref = diff[change.href][change.selector];
+  return ruleAncestry
+    // First, generate a unique identifier for each rule.
+    .map((rule, index) => {
+      // Ensure each rule has ancestors excluding itself (expand the flattened rule tree).
+      rule.ancestors = ruleAncestry.slice(0, index);
+      // Ensure each rule has a selector text.
+      // For the purpose of displaying in the UI, we treat at-rules as selectors.
+      if (!rule.selector) {
+        rule.selector =
+          `${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
+      }
 
-  // Track the remove operation only if the property WAS NOT previously introduced by an
-  // add operation. This ensures that repeated changes of the same property show up as a
-  // single remove operation of the original value.
-  if (change.remove && change.remove.property && !ref.add[change.remove.property]) {
-    ref.remove[change.remove.property] = change.remove.value;
-  }
+      return getRuleHash(rule);
+    })
+    // Then, create new entries in the rules collection and assign dependencies.
+    .map((ruleId, index, array) => {
+      const { selector } = ruleAncestry[index];
+      const prevRuleId = array[index - 1];
+      const nextRuleId = array[index + 1];
 
-  if (change.add && change.add.property) {
-    ref.add[change.add.property] = change.add.value;
-  }
+      // Copy or create an entry for this rule.
+      rules[ruleId] = Object.assign({}, { selector, children: [] }, rules[ruleId]);
 
-  const propertyName = change.add && change.add.property ||
-                   change.remove && change.remove.property;
+      // The next ruleId is lower in the rule tree, therefore it's a child of this rule.
+      if (nextRuleId && !rules[ruleId].children.includes(nextRuleId)) {
+        rules[ruleId].children.push(nextRuleId);
+      }
 
-  // Remove information about operations on the property if they cancel each other out.
-  if (ref.add[propertyName] === ref.remove[propertyName]) {
-    delete ref.add[propertyName];
-    delete ref.remove[propertyName];
+      // The previous ruleId is higher in the rule tree, therefore it's the parent.
+      if (prevRuleId) {
+        rules[ruleId].parent = prevRuleId;
+      }
+
+      return rules[ruleId];
+    })
+    // Finally, return the last rule in the array which is the rule we set out to create.
+    .pop();
+}
 
-    // Remove information about the selector if there are no changes to its declarations.
-    if (Object.keys(ref.add).length === 0 && Object.keys(ref.remove).length === 0) {
-      delete diff[change.href][change.selector];
-    }
+function removeRule(ruleId, rules) {
+  const rule = rules[ruleId];
 
-    // Remove information about the stylesheet if there are no changes to its rules.
-    if (Object.keys(diff[change.href]).length === 0) {
-      delete diff[change.href];
+  // First, remove this rule's id from its parent's list of children
+  if (rule.parent && rules[rule.parent]) {
+    rules[rule.parent].children = rules[rule.parent].children.filter(childRuleId => {
+      return childRuleId !== ruleId;
+    });
+
+    // Remove the parent rule if it has no children left.
+    if (!rules[rule.parent].children.length) {
+      removeRule(rule.parent, rules);
     }
   }
 
-  ref.tag = change.tag;
+  delete rules[ruleId];
+}
 
-  return diff;
-}
+/**
+ * Aggregated changes grouped by sources (stylesheet/element), which contain rules,
+ * which contain collections of added and removed CSS declarations.
+ *
+ * Structure:
+ *    <sourceId>: {
+ *      type: // "stylesheet" or "element"
+ *      href: // Stylesheet or document URL
+ *      rules: {
+ *        <ruleId>: {
+ *          selector: "" // String CSS selector or CSS at-rule text
+ *          children: [] // Array of <ruleId> for child rules of this rule.
+ *          parent:      // <ruleId> of the parent rule
+ *          add: {
+ *            <property>: <value> // CSS declaration
+ *            ...
+ *          },
+ *          remove: {
+ *            <property>: <value> // CSS declaration
+ *           ...
+ *          }
+ *        }
+ *        ... // more rules
+ *      }
+ *    }
+ *    ... // more sources
+ */
+const INITIAL_STATE = {};
 
 const reducers = {
 
-  [TRACK_CHANGE](state, { data }) {
+  [TRACK_CHANGE](state, { change }) {
     const defaults = {
-      href: "",
-      selector: "",
-      tag: null,
-      add: null,
-      remove: null,
+      selector: null,
+      source: {},
+      ancestors: [],
+      add: {},
+      remove: {},
     };
 
-    data = { ...defaults, ...data };
+    change = { ...defaults, ...change };
+    state = cloneState(state);
+
+    const { type, href, index } = change.source;
+    const { selector, ancestors, ruleIndex } = change;
+    const sourceId = getSourceHash(change.source);
+    const ruleId = getRuleHash({ selector, ancestors, ruleIndex });
+
+    // Copy or create object identifying the source (styelsheet/element) for this change.
+    const source = Object.assign({}, state[sourceId], { type, href, index });
+    // Copy or create collection of all rules ever changed in this source.
+    const rules = Object.assign({}, source.rules);
+    // Refrence or create object identifying the rule for this change.
+    let rule = rules[ruleId];
+    if (!rule) {
+      rule = createRule({ selector, ancestors, ruleIndex }, rules);
+    }
+    // Copy or create collection of all CSS declarations ever added to this rule.
+    const add = Object.assign({}, rule.add);
+    // Copy or create collection of all CSS declarations ever removed from this rule.
+    const remove = Object.assign({}, rule.remove);
+
+    if (change.remove && change.remove.property) {
+      // Track the remove operation only if the property was not previously introduced
+      // by an add operation. This ensures repeated changes of the same property
+      // register as a single remove operation of its original value.
+      if (!add[change.remove.property]) {
+        remove[change.remove.property] = change.remove.value;
+      }
 
-    // Update the state in-place with data about a style change (no deep clone of state).
-    // TODO: redefine state as a shallow object structure after figuring how to track
-    // both CSS Declarations and CSS Rules and At-Rules (@media, @keyframes, etc).
-    // @See https://bugzilla.mozilla.org/show_bug.cgi?id=1491263
-    return Object.assign({}, { diff: updateDiff(state.diff, data) });
+      // Delete any previous add operation which would be canceled out by this remove.
+      if (add[change.remove.property] === change.remove.value) {
+        delete add[change.remove.property];
+      }
+    }
+
+    if (change.add && change.add.property) {
+      add[change.add.property] = change.add.value;
+    }
+
+    const property = change.add && change.add.property ||
+                     change.remove && change.remove.property;
+
+    // Remove tracked operations if they cancel each other out.
+    if (add[property] === remove[property]) {
+      delete add[property];
+      delete remove[property];
+    }
+
+    // Remove information about the rule if none its declarations changed.
+    if (!Object.keys(add).length && !Object.keys(remove).length) {
+      removeRule(ruleId, rules);
+      source.rules = { ...rules };
+    } else {
+      source.rules = { ...rules, [ruleId]: { ...rule, add, remove } };
+    }
+
+    // Remove information about the source if none of its rules changed.
+    if (!Object.keys(source.rules).length) {
+      delete state[sourceId];
+    } else {
+      state[sourceId] = source;
+    }
+
+    return state;
   },
 
   [RESET_CHANGES](state) {
     return INITIAL_STATE;
   },
 
 };
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../.eslintrc.mochitests.js",
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  !/devtools/client/inspector/test/head.js
+  !/devtools/client/inspector/test/shared-head.js
+  !/devtools/client/inspector/rules/test/head.js
+  !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
+  !/devtools/client/shared/test/test-actor.js
+  !/devtools/client/shared/test/test-actor-registry.js
+
+[browser_changes_declaration_disable.js]
+[browser_changes_declaration_edit_value.js]
+[browser_changes_declaration_remove.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser_changes_declaration_disable.js
@@ -0,0 +1,47 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that toggling a CSS declaration in the Rule view is tracked.
+
+const TEST_URI = `
+  <style type='text/css'>
+    div {
+      color: red;
+    }
+  </style>
+  <div></div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view: ruleView } = await openRuleView();
+  const { document: doc, store } = selectChangesView(inspector);
+  const panel = doc.querySelector("#sidebar-panel-changes");
+
+  await selectNode("div", inspector);
+  const rule = getRuleViewRuleEditor(ruleView, 1).rule;
+  const prop = rule.textProps[0];
+
+  let onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the first declaration");
+  await togglePropStatus(ruleView, prop);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  let removedDeclarations = panel.querySelectorAll(".diff-remove");
+  is(removedDeclarations.length, 1, "Only one declaration was tracked as removed");
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Re-enable the first declaration");
+  await togglePropStatus(ruleView, prop);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  const addedDeclarations = panel.querySelectorAll(".diff-add");
+  removedDeclarations = panel.querySelectorAll(".diff-remove");
+  is(addedDeclarations.length, 0, "No declarations were tracked as added");
+  is(removedDeclarations.length, 0, "No declarations were tracked as removed");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser_changes_declaration_edit_value.js
@@ -0,0 +1,95 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that editing the value of a CSS declaration in the Rule view is tracked.
+
+const TEST_URI = `
+  <style type='text/css'>
+    div {
+      color: red;
+    }
+  </style>
+  <div></div>
+`;
+
+/*
+  This array will be iterated over in order and the `value` property will be used to
+  update the value of the first CSS declaration.
+  The `add` and `remove` objects hold the expected values of the tracked declarations
+  shown in the Changes panel. If `add` or `remove` are null, it means we don't expect
+  any corresponding tracked declaration to show up in the Changes panel.
+ */
+const VALUE_CHANGE_ITERATIONS = [
+  // No changes should be tracked if the value did not actually change.
+  {
+    value: "red",
+    add: null,
+    remove: null,
+  },
+  // Changing the priority flag "!important" should be tracked.
+  {
+    value: "red !important",
+    add: { value: "red !important" },
+    remove: { value: "red" },
+  },
+  // Repeated changes should still show the original value as the one removed.
+  {
+    value: "blue",
+    add: { value: "blue" },
+    remove: { value: "red" },
+  },
+  // Restoring the original value should clear tracked changes.
+  {
+    value: "red",
+    add: null,
+    remove: null,
+  },
+];
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view: ruleView } = await openRuleView();
+  const { document: doc, store } = selectChangesView(inspector);
+  const panel = doc.querySelector("#sidebar-panel-changes");
+
+  await selectNode("div", inspector);
+  const rule = getRuleViewRuleEditor(ruleView, 1).rule;
+  const prop = rule.textProps[0];
+
+  let onTrackChange;
+  let removeValue;
+  let addValue;
+
+  for (const { value, add, remove } of VALUE_CHANGE_ITERATIONS) {
+    onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+
+    info(`Change the CSS declaration value to ${value}`);
+    await setProperty(ruleView, prop, value);
+    info("Wait for the change to be tracked");
+    await onTrackChange;
+
+    addValue = panel.querySelector(".diff-add .declaration-value");
+    removeValue = panel.querySelector(".diff-remove .declaration-value");
+
+    if (add) {
+      is(addValue.textContent, add.value,
+        `Added declaration has expected value: ${add.value}`);
+      is(panel.querySelectorAll(".diff-add").length, 1,
+        "Only one declaration was tracked as added.");
+    } else {
+      ok(!addValue, `Added declaration was cleared`);
+    }
+
+    if (remove) {
+      is(removeValue.textContent, remove.value,
+        `Removed declaration has expected value: ${remove.value}`);
+      is(panel.querySelectorAll(".diff-remove").length, 1,
+        "Only one declaration was tracked as removed.");
+    } else {
+      ok(!removeValue, `Removed declaration was cleared`);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser_changes_declaration_remove.js
@@ -0,0 +1,38 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that removing a CSS declaration from a rule in the Rule view is tracked.
+
+const TEST_URI = `
+  <style type='text/css'>
+    div {
+      color: red;
+    }
+  </style>
+  <div></div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view: ruleView } = await openRuleView();
+  const { document: doc, store } = selectChangesView(inspector);
+  const panel = doc.querySelector("#sidebar-panel-changes");
+
+  await selectNode("div", inspector);
+  const rule = getRuleViewRuleEditor(ruleView, 1).rule;
+  const prop = rule.textProps[0];
+
+  const onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Remove the first declaration");
+  await removeProperty(ruleView, prop);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  const removeName = panel.querySelector(".diff-remove .declaration-name");
+  const removeValue = panel.querySelector(".diff-remove .declaration-value");
+  is(removeName.textContent, "color", "Correct declaration name was tracked as removed");
+  is(removeValue.textContent, "red", "Correct declaration value was tracked as removed");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/head.js
@@ -0,0 +1,31 @@
+ /* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
+/* import-globals-from ../../../inspector/rules/test/head.js */
+/* import-globals-from ../../../inspector/test/shared-head.js */
+/* import-globals-from ../../../shared/test/shared-redux-head.js */
+"use strict";
+
+// Load the Rule view's test/head.js to make use of its helpers.
+// It loads inspector/test/head.js which itself loads inspector/test/shared-head.js
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/inspector/rules/test/head.js",
+  this);
+
+// Load the shared Redux helpers.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js",
+  this);
+
+// Ensure the Changes panel is enabled before running the tests.
+Services.prefs.setBoolPref("devtools.inspector.changes.enabled", true);
+// Ensure the three-pane mode is enabled before running the tests.
+Services.prefs.setBoolPref("devtools.inspector.three-pane-enabled", true);
+
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.inspector.changes.enabled");
+  Services.prefs.clearUserPref("devtools.inspector.three-pane-enabled");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/utils/changes-utils.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+* Generate a hash that uniquely identifies a stylesheet or element style attribute.
+*
+* @param {Object} source
+*        Information about a stylesheet or element style attribute:
+*        {
+*          type:  {String}
+*                 One of "stylesheet" or "element".
+*          index: {Number|String}
+*                 Position of the styleshet in the list of stylesheets in the document.
+*                 If `type` is "element", `index` is the generated selector which
+*                 uniquely identifies the element in the document.
+*          href:  {String|null}
+*                 URL of the stylesheet or of the document when `type` is "element".
+*                 If the stylesheet is inline, `href` is null.
+*        }
+* @return {String}
+*/
+function getSourceHash(source) {
+  const { type, index, href = "inline" } = source;
+
+  return `${type}${index}${href}`;
+}
+
+/**
+* Generate a hash that uniquely identifies a CSS rule.
+*
+* @param {Object} ruleData
+*        Information about a CSS rule:
+*        {
+*          selector:  {String}
+*                     CSS selector text
+*          ancestors: {Array}
+*                     Flattened CSS rule tree of the rule's ancestors with the root rule
+*                     at the beginning of the array and the leaf rule at the end.
+*          ruleIndex: {Array}
+*                     Indexes of each ancestor rule within its parent rule.
+*        }
+* @return {String}
+*/
+function getRuleHash(ruleData) {
+  const { selector = "", ancestors = [], ruleIndex } = ruleData;
+  const atRules = ancestors.reduce((acc, rule) => {
+    acc += `${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
+    return acc;
+  }, "");
+
+  return `${atRules}${selector}${ruleIndex}`;
+}
+
+module.exports.getSourceHash = getSourceHash;
+module.exports.getRuleHash = getRuleHash;
copy from devtools/client/inspector/changes/moz.build
copy to devtools/client/inspector/changes/utils/moz.build
--- a/devtools/client/inspector/changes/moz.build
+++ b/devtools/client/inspector/changes/utils/moz.build
@@ -1,16 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += [
-    'actions',
-    'components',
-    'reducers',
-]
-
 DevToolsModules(
-    'ChangesManager.js',
-    'ChangesView.js',
+    'changes-utils.js',
 )
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -273,17 +273,17 @@ Inspector.prototype = {
       this.selection.setNodeFront(this._defaultNode, { reason: "inspector-open" });
     }
 
     if (Services.prefs.getBoolPref(TRACK_CHANGES_PREF)) {
       // Get the Changes front, then call a method on it, which will instantiate
       // the ChangesActor. We want the ChangesActor to be guaranteed available before
       // the user makes any changes.
       this.changesFront = this.toolbox.target.getFront("changes");
-      await this.changesFront.allChanges();
+      this.changesFront.allChanges();
     }
 
     // Setup the splitter before the sidebar is displayed so, we don't miss any events.
     this.setupSplitter();
 
     // We can display right panel with: tab bar, markup view and breadbrumb. Right after
     // the splitter set the right and left panel sizes, in order to avoid resizing it
     // during load of the inspector.
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -116,16 +116,34 @@ function openComputedView() {
       inspector: data.inspector,
       testActor: data.testActor,
       view,
     };
   });
 }
 
 /**
+ * Open the toolbox, with the inspector tool visible, and the changes view
+ * sidebar tab selected.
+ *
+ * @return a promise that resolves when the inspector is ready and the changes
+ * view is visible and ready
+ */
+function openChangesView() {
+  return openInspectorSidebarTab("changesview").then(data => {
+    return {
+      toolbox: data.toolbox,
+      inspector: data.inspector,
+      testActor: data.testActor,
+      view: data.inspector.changesView,
+    };
+  });
+}
+
+/**
  * Open the toolbox, with the inspector tool visible, and the layout view
  * sidebar tab selected to display the box model view with properties.
  *
  * @return {Promise} a promise that resolves when the inspector is ready and the layout
  *         view is visible and ready.
  */
 function openLayoutView() {
   return openInspectorSidebarTab("layoutview").then(data => {
@@ -172,16 +190,28 @@ function selectRuleView(inspector) {
  * @return {CssComputedView} the computed view
  */
 function selectComputedView(inspector) {
   inspector.sidebar.select("computedview");
   return inspector.getPanel("computedview").computedView;
 }
 
 /**
+ * Select the changes view sidebar tab on an already opened inspector panel.
+ *
+ * @param {InspectorPanel} inspector
+ *        The opened inspector panel
+ * @return {ChangesView} the changes view
+ */
+function selectChangesView(inspector) {
+  inspector.sidebar.select("changesview");
+  return inspector.changesView;
+}
+
+/**
  * Select the layout view sidebar tab on an already opened inspector panel.
  *
  * @param  {InspectorPanel} inspector
  * @return {BoxModel} the box model
  */
 function selectLayoutView(inspector) {
   inspector.sidebar.select("layoutview");
   return inspector.getPanel("boxmodel");
--- a/devtools/client/themes/changes.css
+++ b/devtools/client/themes/changes.css
@@ -3,78 +3,78 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
  /* CSS Variables specific to the Changes panel that aren't defined by the themes */
  :root {
    --diff-add-background-color: #f1feec;
    --diff-add-text-color: #54983f;
    --diff-remove-background-color: #fbf2f5;
    --diff-remove-text-color: #bf7173;
+   --diff-level-offset: 15px;
  }
 
 #sidebar-panel-changes {
   margin: 0;
+  padding: 0;
   width: 100%;
   height: 100%;
   overflow: auto;
 }
 
-details {
-  font-family: var(--monospace-font-family);
-  font-size: 12px;
-}
-
-summary {
-  outline: none;
-  padding: 5px 0;
-  -moz-user-select: none;
-  cursor: pointer;
-}
-
-details.source[open] {
+#sidebar-panel-changes .source[open] {
   padding-bottom: 10px;
 }
 
-details.source > summary {
+#sidebar-panel-changes .href {
   background: var(--theme-sidebar-background);
   border-top: 1px solid var(--theme-splitter-color);
   border-bottom: 1px solid var(--theme-splitter-color);
-  padding-left: 5px;
+  padding: 5px;
   white-space: nowrap;
   text-overflow: ellipsis;
   overflow: hidden;
+  cursor: pointer;
 }
 
-details.selector > summary {
-  padding-left: 10px;
+#sidebar-panel-changes .rule .level {
+  padding-top: 3px;
+  padding-right: 5px;
+  padding-bottom: 3px;
+  padding-left: var(--diff-level-offset);
+  position: relative;
 }
 
-details.selector summary::after{
-  content: "{…}";
-  display: inline-block;
-  padding-left: 5px;
+#sidebar-panel-changes .rule > .rule .level {
+  padding-left: calc(var(--diff-level-offset) * 2);
+}
+
+#sidebar-panel-changes .rule > .rule > .rule .level {
+  padding-left: calc(var(--diff-level-offset) * 3);
 }
 
-details.selector[open]{
-  margin-bottom: 5px;
+#sidebar-panel-changes .rule > .rule > .rule > .rule .level {
+  padding-left: calc(var(--diff-level-offset) * 4);
 }
 
-details.selector[open] summary::after{
-  content: "{";
+#sidebar-panel-changes .rule .selector,
+#sidebar-panel-changes .rule .bracket-close {
+  margin-left: calc(-1 * var(--diff-level-offset) + 5px);
 }
 
-details.selector[open]::after{
-  content: "}";
-  display: block;
-  padding-left: 10px;
+#sidebar-panel-changes .rule .bracket-open {
+  display: inline-block;
+  margin-left: 5px;
 }
 
-.line {
-  padding: 3px 5px 3px 15px;
-  position: relative;
+#sidebar-panel-changes .declaration-name {
+  margin-left: 10px;
+}
+
+#sidebar-panel-changes .declaration-value {
+  margin-left: 5px;
 }
 
 .diff-add::before,
 .diff-remove::before{
   position: absolute;
   left: 5px;
 }
 
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -160,16 +160,19 @@ class FlexboxHighlighter extends AutoRef
     }
 
     this.markup.destroy();
 
     // Clear the pattern cache to avoid dead object exceptions (Bug 1342051).
     this.clearCache();
 
     this.flexData = null;
+    this.mainAxisDirection = null;
+    this.crossAxisDirection = null;
+    this.axes = null;
 
     AutoRefreshHighlighter.prototype.destroy.call(this);
   }
 
   /**
    * Draw the justify content for a given flex item (left, top, right, bottom) position.
    */
   drawJustifyContent(left, top, right, bottom) {
@@ -296,18 +299,31 @@ class FlexboxHighlighter extends AutoRef
    */
   _hasMoved() {
     const hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
 
     if (!this.computedStyle) {
       this.computedStyle = getComputedStyle(this.currentNode);
     }
 
+    const flex = this.currentNode.getAsFlexContainer();
+
+    const oldCrossAxisDirection = this.crossAxisDirection;
+    this.crossAxisDirection = flex.crossAxisDirection;
+    const newCrossAxisDirection = this.crossAxisDirection;
+
+    const oldMainAxisDirection = this.mainAxisDirection;
+    this.mainAxisDirection = flex.mainAxisDirection;
+    const newMainAxisDirection = this.mainAxisDirection;
+
+    // Concatenate the axes to simplify conditionals.
+    this.axes = `${this.mainAxisDirection} ${this.crossAxisDirection}`;
+
     const oldFlexData = this.flexData;
-    this.flexData = getFlexData(this.currentNode.getAsFlexContainer(), this.win);
+    this.flexData = getFlexData(flex, this.win);
     const hasFlexDataChanged = compareFlexData(oldFlexData, this.flexData);
 
     const oldAlignItems = this.alignItemsValue;
     this.alignItemsValue = this.computedStyle.alignItems;
     const newAlignItems = this.alignItemsValue;
 
     const oldFlexDirection = this.flexDirection;
     this.flexDirection = this.computedStyle.flexDirection;
@@ -321,17 +337,19 @@ class FlexboxHighlighter extends AutoRef
     this.justifyContentValue = this.computedStyle.justifyContent;
     const newJustifyContent = this.justifyContentValue;
 
     return hasMoved ||
            hasFlexDataChanged ||
            oldAlignItems !== newAlignItems ||
            oldFlexDirection !== newFlexDirection ||
            oldFlexWrap !== newFlexWrap ||
-           oldJustifyContent !== newJustifyContent;
+           oldJustifyContent !== newJustifyContent ||
+           oldCrossAxisDirection !== newCrossAxisDirection ||
+           oldMainAxisDirection !== newMainAxisDirection;
   }
 
   _hide() {
     this.alignItemsValue = null;
     this.computedStyle = null;
     this.flexData = null;
     this.flexDirection = null;
     this.flexWrap = null;
@@ -583,103 +601,136 @@ class FlexboxHighlighter extends AutoRef
     const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
 
     this.ctx.save();
     this.ctx.translate(offset - canvasX, offset - canvasY);
     this.ctx.lineWidth = lineWidth;
     this.ctx.strokeStyle = this.color;
 
     const { bounds } = this.currentQuads.content[0];
-    const isColumn = this.flexDirection.startsWith("column");
     const options = { matrix: this.currentMatrix };
 
     for (const flexLine of this.flexData.lines) {
       const { crossStart, crossSize } = flexLine;
 
-      if (isColumn) {
-        clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
-          this.currentMatrix);
+      switch (this.axes) {
+        case "horizontal-lr vertical-tb":
+        case "horizontal-lr vertical-bt":
+        case "horizontal-rl vertical-tb":
+        case "horizontal-rl vertical-bt":
+          clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
+            this.currentMatrix);
 
-        // Avoid drawing the start flex line when they overlap with the flex container.
-        if (crossStart != 0) {
-          drawLine(this.ctx, crossStart, 0, crossStart, bounds.height, options);
-          this.ctx.stroke();
-        }
+          // Avoid drawing the start flex line when they overlap with the flex container.
+          if (crossStart != 0) {
+            drawLine(this.ctx, 0, crossStart, bounds.width, crossStart, options);
+            this.ctx.stroke();
+          }
+
+          // Avoid drawing the end flex line when they overlap with the flex container.
+          if (bounds.height - crossStart - crossSize >= lineWidth) {
+            drawLine(this.ctx, 0, crossStart + crossSize, bounds.width,
+              crossStart + crossSize, options);
+            this.ctx.stroke();
+          }
+          break;
+        case "vertical-tb horizontal-lr":
+        case "vertical-bt horizontal-rl":
+          clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
+            this.currentMatrix);
 
-        // Avoid drawing the end flex line when they overlap with the flex container.
-        if (bounds.width - crossStart - crossSize >= lineWidth) {
-          drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
-            bounds.height, options);
-          this.ctx.stroke();
-        }
-      } else {
-        clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
-          this.currentMatrix);
+          // Avoid drawing the start flex line when they overlap with the flex container.
+          if (crossStart != 0) {
+            drawLine(this.ctx, crossStart, 0, crossStart, bounds.height, options);
+            this.ctx.stroke();
+          }
 
-        // Avoid drawing the start flex line when they overlap with the flex container.
-        if (crossStart != 0) {
-          drawLine(this.ctx, 0, crossStart, bounds.width, crossStart, options);
-          this.ctx.stroke();
-        }
+          // Avoid drawing the end flex line when they overlap with the flex container.
+          if (bounds.width - crossStart - crossSize >= lineWidth) {
+            drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
+              bounds.height, options);
+            this.ctx.stroke();
+          }
+          break;
+        case "vertical-bt horizontal-lr":
+        case "vertical-tb horizontal-rl":
+          clearRect(this.ctx, bounds.width - crossStart, 0,
+            bounds.width - crossStart - crossSize, bounds.height, this.currentMatrix);
 
-        // Avoid drawing the end flex line when they overlap with the flex container.
-        if (bounds.height - crossStart - crossSize >= lineWidth) {
-          drawLine(this.ctx, 0, crossStart + crossSize, bounds.width,
-            crossStart + crossSize, options);
-          this.ctx.stroke();
-        }
+          // Avoid drawing the start flex line when they overlap with the flex container.
+          if (crossStart != 0) {
+            drawLine(this.ctx, bounds.width - crossStart, 0, bounds.width - crossStart,
+              bounds.height, options);
+            this.ctx.stroke();
+          }
+
+          // Avoid drawing the end flex line when they overlap with the flex container.
+          if (bounds.width - crossStart - crossSize >= lineWidth) {
+            drawLine(this.ctx, bounds.width - crossStart - crossSize, 0,
+              bounds.width - crossStart - crossSize, bounds.height, options);
+            this.ctx.stroke();
+          }
+          break;
       }
     }
 
     this.ctx.restore();
   }
 
+  /**
+   * Clear the whole alignment container along the main axis for each flex item.
+   */
   renderJustifyContent() {
     if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     const zoom = getCurrentZoom(this.win);
     const { bounds } = this.currentQuads.content[0];
-    const isColumn = this.flexDirection.startsWith("column");
+
+    // Draw a justify content pattern over the whole flex container.
+    this.drawJustifyContent(0, 0, bounds.width, bounds.height, this.currentMatrix);
 
     for (const flexLine of this.flexData.lines) {
       const { crossStart, crossSize } = flexLine;
-      let mainStart = 0;
 
       for (const flexItem of flexLine.items) {
         const quads = flexItem.quads;
         if (!quads.length) {
           continue;
         }
 
         // Adjust the flex item bounds relative to the current quads.
         const { bounds: flexItemBounds } = quads[0];
         const left = Math.round(flexItemBounds.left / zoom - bounds.left);
         const top = Math.round(flexItemBounds.top / zoom - bounds.top);
         const right = Math.round(flexItemBounds.right / zoom - bounds.left);
         const bottom = Math.round(flexItemBounds.bottom / zoom - bounds.top);
 
-        if (isColumn) {
-          this.drawJustifyContent(crossStart, mainStart, crossStart + crossSize, top);
-          mainStart = bottom;
-        } else {
-          this.drawJustifyContent(mainStart, crossStart, left, crossStart + crossSize);
-          mainStart = right;
+        // Clear a rectangular are covering the alignment container.
+        switch (this.axes) {
+          case "horizontal-lr vertical-tb":
+          case "horizontal-lr vertical-bt":
+          case "horizontal-rl vertical-tb":
+          case "horizontal-rl vertical-bt":
+            clearRect(this.ctx, left, Math.round(crossStart) + 2, right,
+              Math.round(crossStart + crossSize) - 2, this.currentMatrix);
+            break;
+          case "vertical-tb horizontal-lr":
+          case "vertical-bt horizontal-rl":
+            clearRect(this.ctx, Math.round(crossStart) + 1, top,
+              Math.round(crossStart + crossSize), bottom, this.currentMatrix);
+            break;
+          case "vertical-bt horizontal-lr":
+          case "vertical-tb horizontal-rl":
+            clearRect(this.ctx, Math.round(bounds.width - crossStart - crossSize) + 1,
+              top, Math.round(bounds.width - crossStart), bottom, this.currentMatrix);
+            break;
         }
       }
-
-      // Draw the last justify-content area after the last flex item.
-      if (isColumn) {
-        this.drawJustifyContent(crossStart, mainStart, crossStart + crossSize,
-          bounds.height);
-      } else {
-        this.drawJustifyContent(mainStart, crossStart, bounds.width,
-          crossStart + crossSize);
-      }
     }
   }
 
   _update() {
     setIgnoreLayoutChanges(true);
 
     const root = this.getElement("root");
 
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -22,17 +22,20 @@ loader.lazyRequireGetter(this, "getDefin
 loader.lazyRequireGetter(this, "isCssPropertyKnown",
   "devtools/server/actors/css-properties", true);
 loader.lazyRequireGetter(this, "parseNamedDeclarations",
   "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
   "devtools/server/actors/stylesheets", true);
 loader.lazyRequireGetter(this, "UPDATE_GENERAL",
   "devtools/server/actors/stylesheets", true);
-loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "findCssSelector",
+  "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "CSSRuleTypeName",
+  "devtools/shared/inspector/css-logic", true);
 
 loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
   return InspectorUtils.getCSSPseudoElementNames();
 });
 loader.lazyGetter(this, "FONT_VARIATIONS_ENABLED", () => {
   return Services.prefs.getBoolPref("layout.css.font-variations.enabled");
 });
 
@@ -981,23 +984,23 @@ var StyleRuleActor = protocol.ActorClass
     // Parsed CSS declarations from this.form().declarations used to check CSS property
     // names and values before tracking changes. Using cached values instead of accessing
     // this.form().declarations on demand because that would cause needless re-parsing.
     this._declarations = [];
 
     if (CSSRule.isInstance(item)) {
       this.type = item.type;
       this.rawRule = item;
+      this._computeRuleIndex();
       if ((this.type === CSSRule.STYLE_RULE ||
            this.type === CSSRule.KEYFRAME_RULE) &&
           this.rawRule.parentStyleSheet) {
         this.line = InspectorUtils.getRelativeRuleLine(this.rawRule);
         this.column = InspectorUtils.getRuleColumn(this.rawRule);
         this._parentSheet = this.rawRule.parentStyleSheet;
-        this._computeRuleIndex();
         this.sheetActor = this.pageStyle._sheetRef(this._parentSheet);
         this.sheetActor.on("style-applied", this._onStyleApplied);
       }
     } else {
       // Fake a rule
       this.type = ELEMENT_STYLE;
       this.rawNode = item;
       this.rawRule = {
@@ -1044,16 +1047,35 @@ var StyleRuleActor = protocol.ActorClass
             // https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
             !this.sheetActor.hasRulesModifiedByCSSOM() &&
             // Special case about:PreferenceStyleSheet, as it is generated on
             // the fly and the URI is not registered with the about:handler
             // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
             this._parentSheet.href !== "about:PreferenceStyleSheet");
   },
 
+  /**
+   * Return an array with StyleRuleActor instances for each of this rule's ancestor rules
+   * (@media, @supports, @keyframes, etc) obtained by recursively reading rule.parentRule.
+   * If the rule has no ancestors, return an empty array.
+   *
+   * @return {Array}
+   */
+  get ancestorRules() {
+    const ancestors = [];
+    let rule = this.rawRule;
+
+    while (rule.parentRule) {
+      ancestors.push(this.pageStyle._styleRef(rule.parentRule));
+      rule = rule.parentRule;
+    }
+
+    return ancestors;
+  },
+
   getDocument: function(sheet) {
     if (sheet.ownerNode) {
       return sheet.ownerNode.nodeType == sheet.ownerNode.DOCUMENT_NODE ?
              sheet.ownerNode : sheet.ownerNode.ownerDocument;
     } else if (sheet.parentStyleSheet) {
       return this.getDocument(sheet.parentStyleSheet);
     }
     throw (new Error("Failed trying to get the document of an invalid stylesheet"));
@@ -1181,20 +1203,20 @@ var StyleRuleActor = protocol.ActorClass
    * a given CSS rule in its parent.  A vector is used to support
    * nested rules.
    */
   _computeRuleIndex: function() {
     let rule = this.rawRule;
     const result = [];
 
     while (rule) {
-      let cssRules;
+      let cssRules = [];
       if (rule.parentRule) {
         cssRules = rule.parentRule.cssRules;
-      } else {
+      } else if (rule.parentStyleSheet) {
         cssRules = rule.parentStyleSheet.cssRules;
       }
 
       let found = false;
       for (let i = 0; i < cssRules.length; i++) {
         if (rule === cssRules.item(i)) {
           found = true;
           result.unshift(i);
@@ -1462,70 +1484,115 @@ var StyleRuleActor = protocol.ActorClass
   /**
    * Take an object with instructions to modify a CSS declaration and emit a
    * "track-change" event with normalized metadata which describes the change.
    *
    * @param {Object} change
    *        Data about a modification to a rule. @see |modifyProperties()|
    */
   logChange(change) {
-    const prevValue = this._declarations[change.index]
-      ? this._declarations[change.index].value
-      : null;
-    const prevName = this._declarations[change.index]
-      ? this._declarations[change.index].name
-      : null;
+    // Destructure properties from the previous CSS declaration at this index, if any,
+    // to new variable names to indicate the previous state.
+    let {
+      value: prevValue,
+      name: prevName,
+      priority: prevPriority,
+      commentOffsets,
+    } = this._declarations[change.index] || {};
+    // A declaration is disabled if it has a `commentOffsets` array.
+    // Here we type coerce the value to a boolean with double-bang (!!)
+    const prevDisabled = !!commentOffsets;
+    // Append the "!important" string if defined in the previous priority flag.
+    prevValue = (prevValue && prevPriority) ? `${prevValue} !important` : prevValue;
 
     // Metadata about a change.
     const data = {};
-    data.type = change.type;
-    data.selector = this.rawRule.selectorText;
+    // Collect information about the rule's ancestors (@media, @supports, @keyframes).
+    // Used to show context for this change in the UI and to match the rule for undo/redo.
+    data.ancestors = this.ancestorRules.map(rule => {
+      return {
+        // Rule type as number defined by CSSRule.type (ex: 4, 7, 12)
+        // @see https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
+        type: rule.rawRule.type,
+        // Rule type as human-readable string (ex: "@media", "@supports", "@keyframes")
+        typeName: CSSRuleTypeName[rule.rawRule.type],
+        // Conditions of @media and @supports rules (ex: "min-width: 1em")
+        conditionText: rule.rawRule.conditionText,
+        // Name of @keyframes rule; refrenced by the animation-name CSS property.
+        name: rule.rawRule.name,
+        // Selector of individual @keyframe rule within a @keyframes rule (ex: 0%, 100%).
+        keyText: rule.rawRule.keyText,
+        // Array with the indexes of this rule and its ancestors within the CSS rule tree.
+        ruleIndex: rule._ruleIndex,
+      };
+    });
 
-    // For inline style changes, generate a unique selector and pass the node tag.
+    // For changes in element style attributes, generate a unique selector.
     if (this.type === ELEMENT_STYLE) {
-      data.tag = this.rawNode.tagName;
-      data.href = "inline";
       // findCssSelector() fails on XUL documents. Catch and silently ignore that error.
       try {
         data.selector = findCssSelector(this.rawNode);
       } catch (err) {}
+
+      data.source = {
+        type: "element",
+        // Used to differentiate between elements which match the same generated selector
+        // but live in different documents (ex: host document and iframe).
+        href: this.rawNode.baseURI,
+        // Element style attributes don't have a rule index; use the generated selector.
+        index: data.selector,
+      };
+      data.ruleIndex = 0;
     } else {
-      data.href = this._parentSheet.href || "inline stylesheet";
+      data.selector = (this.type === CSSRule.KEYFRAME_RULE)
+        ? this.rawRule.keyText
+        : this.rawRule.selectorText;
+      data.source = {
+        type: "stylesheet",
+        href: this.sheetActor.href,
+        index: this.sheetActor.styleSheetIndex,
+      };
+      // Used to differentiate between changes to rules with identical selectors.
+      data.ruleIndex = this._ruleIndex;
     }
 
     switch (change.type) {
       case "set":
         // If `change.newName` is defined, use it because the property is being renamed.
         // Otherwise, a new declaration is being created or the value of an existing
         // declaration is being updated. In that case, use the provided `change.name`.
         const name = change.newName ? change.newName : change.name;
-        // Reuse the previous value when the property is being renamed.
-        const value = change.newName ? prevValue : change.value;
+        // Append the "!important" string if defined in the incoming priority flag.
+        const newValue = change.priority ? `${change.value} !important` : change.value;
+        // Reuse the previous value string, when the property is renamed.
+        // Otherwise, use the incoming value string.
+        const value = change.newName ? prevValue : newValue;
 
         data.add = { property: name, value };
         // If there is a previous value, log its removal together with the previous
         // property name. Using the previous name handles the case for renaming a property
         // and is harmless when updating an existing value (the name stays the same).
         data.remove = prevValue ? { property: prevName, value: prevValue } : null;
+
+        // When toggling a declaration from OFF to ON, if not renaming the property,
+        // do not mark the previous declaration for removal, otherwise the add and
+        // remove operations will cancel each other out when tracked. Tracked changes
+        // have no context of "disabled", only "add" or remove, like diffs.
+        if (prevDisabled && !change.newName && prevValue === newValue) {
+          data.remove = null;
+        }
+
         break;
 
       case "remove":
         data.add = null;
         data.remove = { property: change.name, value: prevValue };
         break;
     }
 
-    // Do not track non-changes. This can occur when typing a value in the Rule view
-    // inline editor, then committing it by pressing the Enter key.
-    if (data.add && data.remove &&
-        data.add.property === data.remove.property &&
-        data.add.value === data.remove.value) {
-      return;
-    }
-
     TrackChangeEmitter.trackChange(data);
   },
 
   /**
    * Calls modifySelector2() which needs to be kept around for backwards compatibility.
    * TODO: Once Firefox 64 is no longer supported, inline that mehtod's content,
    * then remove its definition from this file and from specs/styles.js
    */
--- a/devtools/shared/css/parsing-utils.js
+++ b/devtools/shared/css/parsing-utils.js
@@ -870,16 +870,17 @@ RuleRewriter.prototype = {
    * @param {Number} index index of the property in the rule.
    * @param {String} name current name of the property
    * @param {Boolean} isEnabled true if the property should be enabled;
    *                        false if it should be disabled
    */
   setPropertyEnabled: function(index, name, isEnabled) {
     this.completeInitialization(index);
     const decl = this.decl;
+    const priority = decl.priority;
     let copyOffset = decl.offsets[1];
     if (isEnabled) {
       // Enable it.  First see if the comment start can be deleted.
       const commentStart = decl.commentOffsets[0];
       if (EMPTY_COMMENT_START_RX.test(this.result.substring(commentStart))) {
         this.result = this.result.substring(0, commentStart);
       } else {
         this.result += "*/ ";
@@ -913,19 +914,19 @@ RuleRewriter.prototype = {
       const declText = this.inputString.substring(decl.offsets[0],
                                                 decl.offsets[1]);
       this.result += "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR +
         " " + escapeCSSComment(declText) + " */";
     }
     this.completeCopying(copyOffset);
 
     if (isEnabled) {
-      this.modifications.push({ type: "set", index, name, value: decl.value });
+      this.modifications.push({ type: "set", index, name, value: decl.value, priority });
     } else {
-      this.modifications.push({ type: "remove", index, name });
+      this.modifications.push({ type: "remove", index, name, priority });
     }
   },
 
   /**
    * Return a promise that will be resolved to the default indentation
    * of the rule.  This is a helper for internalCreateProperty.
    *
    * @return {Promise} a promise that will be resolved to a string
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -78,16 +78,36 @@ exports.STATUS = {
   BEST: 3,
   MATCHED: 2,
   PARENT_MATCH: 1,
   UNMATCHED: 0,
   UNKNOWN: -1,
 };
 
 /**
+ * Mapping of CSSRule type value to CSSRule type name.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
+ */
+exports.CSSRuleTypeName = {
+  1: "", // Regular CSS style rule has no name
+  3: "@import",
+  4: "@media",
+  5: "@font-face",
+  6: "@page",
+  7: "@keyframes",
+  8: "@keyframe",
+  10: "@namespace",
+  11: "@counter-style",
+  12: "@supports",
+  13: "@document",
+  14: "@font-feature-values",
+  15: "@viewport",
+};
+
+/**
  * Lookup a l10n string in the shared styleinspector string bundle.
  *
  * @param {String} name
  *        The key to lookup.
  * @returns {String} A localized version of the given key.
  */
 exports.l10n = name => styleInspectorL10N.getStr(name);
 
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5779,16 +5779,41 @@ nsGlobalWindowOuter::PostMessageMozOuter
     }
 
     // Create a nsIPrincipal inheriting the app/browser attributes from the
     // caller.
     providedPrincipal = BasePrincipal::CreateCodebasePrincipal(originURI, attrs);
     if (NS_WARN_IF(!providedPrincipal)) {
       return;
     }
+  } else {
+    // We still need to check the originAttributes if the target origin is '*'.
+    // But we will ingore the FPD here since the FPDs are possible to be different.
+    auto principal = BasePrincipal::Cast(GetPrincipal());
+    NS_ENSURE_TRUE_VOID(principal);
+
+    OriginAttributes targetAttrs = principal->OriginAttributesRef();
+    OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
+    // We have to exempt the check of OA if the subject prioncipal is a system
+    // principal since there are many tests try to post messages to content from
+    // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
+    // post a message into a private browsing window. The injected code in
+    // ContentTask.spawn() will be executed under the system principal and the
+    // OA of the system principal mismatches with the OA of a private browsing
+    // window.
+    MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.GetIsSystemPrincipal() ||
+                          sourceAttrs.EqualsIgnoringFPD(targetAttrs));
+
+    // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
+    // postMessage across different first party domains.
+    if (OriginAttributes::IsBlockPostMessageForFPI() &&
+        !aSubjectPrincipal.GetIsSystemPrincipal() &&
+        sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
+      return;
+    }
   }
 
   // Create and asynchronously dispatch a runnable which will handle actual DOM
   // event creation and dispatch.
   RefPtr<PostMessageEvent> event =
     new PostMessageEvent(nsContentUtils::IsCallerChrome() || !callerInnerWin
                          ? nullptr
                          : callerInnerWin->GetOuterWindowInternal(),
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1289,17 +1289,18 @@ impl AlphaBatchBuilder {
         match (&brush.segment_desc, &params.segment_data) {
             (Some(ref segment_desc), SegmentDataKind::Instanced(ref segment_data)) => {
                 // In this case, we have both a list of segments, and a list of
                 // per-segment instance data. Zip them together to build batches.
                 debug_assert_eq!(segment_desc.segments.len(), segment_data.len());
                 for (segment_index, (segment, segment_data)) in segment_desc.segments
                     .iter()
                     .zip(segment_data.iter())
-                    .enumerate() {
+                    .enumerate()
+                {
                     self.add_segment_to_batch(
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_instance,
                         prim_header_index,
                         alpha_blend_mode,
@@ -1310,17 +1311,18 @@ impl AlphaBatchBuilder {
                     );
                 }
             }
             (Some(ref segment_desc), SegmentDataKind::Shared(ref segment_data)) => {
                 // A list of segments, but the per-segment data is common
                 // between all segments.
                 for (segment_index, segment) in segment_desc.segments
                     .iter()
-                    .enumerate() {
+                    .enumerate()
+                {
                     self.add_segment_to_batch(
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_instance,
                         prim_header_index,
                         alpha_blend_mode,
@@ -1328,16 +1330,17 @@ impl AlphaBatchBuilder {
                         transform_kind,
                         render_tasks,
                         z_id,
                     );
                 }
             }
             (None, SegmentDataKind::Shared(ref segment_data)) => {
                 // No segments, and thus no per-segment instance data.
+                // Note: the blend mode already takes opacity into account
                 let batch_key = BatchKey {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(params.batch_kind),
                     textures: segment_data.textures,
                 };
                 let instance = PrimitiveInstanceData::from(BrushInstance {
                     segment_index: 0,
                     edge_flags: EdgeAaSegmentMask::all(),
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1927,17 +1927,18 @@ impl PrimitiveStore {
             &prim.details,
             frame_state.resource_cache,
         );
 
         if is_passthrough {
             prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
         } else {
             if prim.local_rect.size.width <= 0.0 ||
-               prim.local_rect.size.height <= 0.0 {
+               prim.local_rect.size.height <= 0.0
+            {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for zero local rectangle");
                 }
                 return false;
             }
 
             // Inflate the local rect for this primitive by the inflation factor of
             // the picture context. This ensures that even if the primitive itself
@@ -1974,16 +1975,19 @@ impl PrimitiveStore {
                     &frame_context.world_rect,
                     &clip_node_collector,
                     &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
+                    if cfg!(debug_assertions) && is_chased {
+                        println!("\tunable to build the clip chain, skipping");
+                    }
                     prim_instance.clipped_world_rect = None;
                     return false;
                 }
             };
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\teffective clip chain from {:?} {}",
                     clip_chain.clips_range,
@@ -2080,18 +2084,18 @@ impl PrimitiveStore {
             .display_list;
 
         for (plane_split_anchor, prim_instance) in prim_instances.iter_mut().enumerate() {
             prim_instance.clipped_world_rect = None;
 
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
-            if is_chased {
-                println!("\tpreparing prim {:?} in pipeline {:?}",
+            if cfg!(debug_assertions) && is_chased {
+                println!("\tpreparing {:?} in {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
             // Do some basic checks first, that can early out
             // without even knowing the local rect.
             if !frame_state
                 .resources
                 .prim_data_store[prim_instance.prim_data_handle]
@@ -2574,28 +2578,29 @@ impl PrimitiveInstance {
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         #[cfg(debug_assertions)]
         {
             self.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
-        match *prim_details {
+        self.opacity = match *prim_details {
             PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_context.spatial_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     &transform,
                     pic_context.allow_subpixel_aa,
                     pic_context.raster_space,
                     display_list,
                     frame_state,
                 );
+                PrimitiveOpacity::translucent()
             }
             PrimitiveDetails::Brush(ref mut brush) => {
                 match brush.kind {
                     BrushKind::Image {
                         request,
                         sub_rect,
                         stretch_size,
                         color,
@@ -2615,23 +2620,16 @@ impl PrimitiveInstance {
                             is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
                                 frame_state.gpu_cache.invalidate(&mut self.gpu_location);
                             }
 
-                            // Update opacity for this primitive to ensure the correct
-                            // batching parameters are used.
-                            self.opacity.is_opaque =
-                                image_properties.descriptor.is_opaque &&
-                                opacity_binding.current == 1.0 &&
-                                color.a == 1.0;
-
                             if *tile_spacing != LayoutSize::zero() && !is_tiled {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: image_properties.descriptor.size.to_i32(),
                                     handle: None,
                                 };
                             }
 
@@ -2643,16 +2641,17 @@ impl PrimitiveInstance {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: rect.size,
                                     handle: None,
                                 };
                             }
 
                             let mut request_source_image = false;
+                            let mut is_opaque = image_properties.descriptor.is_opaque;
 
                             // Every frame, for cached items, we need to request the render
                             // task cache item. The closure will be invoked on the first
                             // time through, and any time the render task output has been
                             // evicted from the texture cache.
                             match *source {
                                 ImageSource::Cache { ref mut size, ref mut handle } => {
                                     let padding = DeviceIntSideOffsets::new(
@@ -2661,19 +2660,17 @@ impl PrimitiveInstance {
                                         (tile_spacing.height * size.height as f32 / stretch_size.height) as i32,
                                         0,
                                     );
 
                                     let inner_size = *size;
                                     size.width += padding.horizontal();
                                     size.height += padding.vertical();
 
-                                    if padding != DeviceIntSideOffsets::zero() {
-                                        self.opacity.is_opaque = false;
-                                    }
+                                    is_opaque &= padding == DeviceIntSideOffsets::zero();
 
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
                                     *handle = Some(frame_state.resource_cache.request_render_task(
@@ -2802,28 +2799,39 @@ impl PrimitiveInstance {
                                     self.clipped_world_rect = None;
                                 }
                             } else if request_source_image {
                                 frame_state.resource_cache.request_image(
                                     request,
                                     frame_state.gpu_cache,
                                 );
                             }
+
+                            if is_opaque {
+                                PrimitiveOpacity::from_alpha(opacity_binding.current * color.a)
+                            } else {
+                                PrimitiveOpacity::translucent()
+                            }
+                        } else {
+                            PrimitiveOpacity::opaque()
                         }
                     }
-                    BrushKind::LineDecoration { ref mut handle, style, orientation, wavy_line_thickness, .. } => {
+                    BrushKind::LineDecoration { color, ref mut handle, style, orientation, wavy_line_thickness } => {
                         // Work out the device pixel size to be used to cache this line decoration.
-
                         let size = get_line_decoration_sizes(
                             &prim_local_rect.size,
                             orientation,
                             style,
                             wavy_line_thickness,
                         );
 
+                        if cfg!(debug_assertions) && is_chased {
+                            println!("\tline decoration opaque={}, sizes={:?}", self.opacity.is_opaque, size);
+                        }
+
                         if let Some((inline_size, block_size)) = size {
                             let size = match orientation {
                                 LineOrientation::Horizontal => LayoutSize::new(inline_size, block_size),
                                 LineOrientation::Vertical => LayoutSize::new(block_size, inline_size),
                             };
 
                             // If dotted, adjust the clip rect to ensure we don't draw a final
                             // partial dot.
@@ -2882,50 +2890,57 @@ impl PrimitiveInstance {
                                         size,
                                     );
                                     let task_id = render_tasks.add(task);
                                     pic_state.tasks.push(task_id);
                                     task_id
                                 }
                             ));
                         }
+
+                        match style {
+                            LineStyle::Solid => PrimitiveOpacity::from_alpha(color.a),
+                            LineStyle::Dotted |
+                            LineStyle::Dashed |
+                            LineStyle::Wavy => PrimitiveOpacity::translucent(),
+                        }
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
-                        self.opacity = PrimitiveOpacity::opaque();
-
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
                                 },
                                 frame_state.gpu_cache,
                             );
                         }
+
+                        PrimitiveOpacity::opaque()
                     }
                     BrushKind::Border { ref mut source, .. } => {
                         match *source {
                             BorderSource::Image(request) => {
                                 let image_properties = frame_state
                                     .resource_cache
                                     .get_image_properties(request.key);
 
                                 if let Some(image_properties) = image_properties {
-                                    // Update opacity for this primitive to ensure the correct
-                                    // batching parameters are used.
-                                    self.opacity.is_opaque =
-                                        image_properties.descriptor.is_opaque;
-
                                     frame_state.resource_cache.request_image(
                                         request,
                                         frame_state.gpu_cache,
                                     );
+                                    PrimitiveOpacity {
+                                        is_opaque: image_properties.descriptor.is_opaque,
+                                    }
+                                } else {
+                                    PrimitiveOpacity::opaque()
                                 }
                             }
                             BorderSource::Border { ref border, ref widths, ref mut segments, .. } => {
                                 // TODO(gw): When drawing in screen raster mode, we should also incorporate a
                                 //           scale factor from the world transform to get an appropriately
                                 //           sized border task.
                                 let world_scale = LayoutToWorldScale::new(1.0);
                                 let mut scale = world_scale * frame_context.device_pixel_scale;
@@ -2958,16 +2973,19 @@ impl PrimitiveInstance {
                                             let task_id = render_tasks.add(task);
 
                                             pic_state.tasks.push(task_id);
 
                                             task_id
                                         }
                                     ));
                                 }
+
+                                // Shouldn't matter, since the segment opacity is used instead
+                                PrimitiveOpacity::translucent()
                             }
                         }
                     }
                     BrushKind::RadialGradient {
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
@@ -3010,43 +3028,33 @@ impl PrimitiveInstance {
                                         pack_as_float(extend_mode as u32),
                                         stretch_size.width,
                                         stretch_size.height,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 },
                             );
                         }
+
+                        //TODO: can we make it opaque in some cases?
+                        PrimitiveOpacity::translucent()
                     }
                     BrushKind::LinearGradient {
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
                         stops_opacity,
                         ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
-                        // If the coverage of the gradient extends to or beyond
-                        // the primitive rect, then the opacity can be determined
-                        // by the colors of the stops. If we have tiling / spacing
-                        // then we just assume the gradient is translucent for now.
-                        // (In the future we could consider segmenting in some cases).
-                        let stride = stretch_size + tile_spacing;
-                        self.opacity = if stride.width >= prim_local_rect.size.width &&
-                           stride.height >= prim_local_rect.size.height {
-                            stops_opacity
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        };
-
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             display_list,
                         );
 
@@ -3073,16 +3081,29 @@ impl PrimitiveInstance {
                                         stretch_size.width,
                                         stretch_size.height,
                                         0.0,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
+
+                        // If the coverage of the gradient extends to or beyond
+                        // the primitive rect, then the opacity can be determined
+                        // by the colors of the stops. If we have tiling / spacing
+                        // then we just assume the gradient is translucent for now.
+                        // (In the future we could consider segmenting in some cases).
+                        let stride = stretch_size + tile_spacing;
+                        if stride.width >= prim_local_rect.size.width &&
+                           stride.height >= prim_local_rect.size.height {
+                            stops_opacity
+                        } else {
+                            PrimitiveOpacity::translucent()
+                        }
                     }
                     BrushKind::Picture { pic_index, .. } => {
                         let pic = &mut pictures[pic_index.0];
                         if pic.prepare_for_render(
                             pic_index,
                             self,
                             &prim_local_rect,
                             pic_state,
@@ -3096,31 +3117,33 @@ impl PrimitiveInstance {
                                     self,
                                     prim_local_rect,
                                     plane_split_anchor,
                                 );
                             }
                         } else {
                             self.clipped_world_rect = None;
                         }
+
+                        PrimitiveOpacity::translucent()
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
                         if opacity_binding.update(frame_context.scene_properties) {
                             frame_state.gpu_cache.invalidate(&mut self.gpu_location);
                         }
-                        self.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
+                        PrimitiveOpacity::from_alpha(opacity_binding.current * color.a)
                     }
-                    BrushKind::Clear => {}
+                    BrushKind::Clear => PrimitiveOpacity::opaque(),
                 }
             }
-        }
+        };
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_location) {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -47,17 +47,17 @@ fn render_task_sanity_check(size: &Devic
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskId(pub u32, FrameId); // TODO(gw): Make private when using GPU cache!
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-925dd051a36f1c1cd02e7f635f7e439ab2804f15
+2537e5f27c2ce7b64a93498c7569a870c190feda
--- a/mobile/android/config/mozconfigs/android-aarch64/nightly
+++ b/mobile/android/config/mozconfigs/android-aarch64/nightly
@@ -1,22 +1,13 @@
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=aarch64-linux-android
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-api-16/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16/nightly
@@ -11,18 +11,9 @@ ac_add_options --target=arm-linux-androi
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_MMA=1
 export MOZ_ANDROID_POCKET=1
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-x86/nightly
+++ b/mobile/android/config/mozconfigs/android-x86/nightly
@@ -9,18 +9,9 @@ ac_add_options --target=i686-linux-andro
 ac_add_options --with-android-min-sdk=16
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/modules/DownloadNotifications.jsm
+++ b/mobile/android/modules/DownloadNotifications.jsm
@@ -175,18 +175,20 @@ var DownloadNotifications = {
         download.cancel().catch(Cu.reportError);
         download.removePartialData().catch(Cu.reportError);
       }
     }).catch(Cu.reportError);
   },
 };
 
 function getCookieFromDownload(download) {
+  // Arbitrary value used to truncate long Data URLs. See bug 1497526
+  const maxUrlLength = 1024;
   return download.target.path +
-         download.source.url +
+         download.source.url.slice(-maxUrlLength) +
          download.startTime;
 }
 
 function DownloadNotification(download) {
   this.download = download;
   this._fileName = OS.Path.basename(download.target.path);
 
   this.id = null;
--- a/netwerk/base/nsIRequest.idl
+++ b/netwerk/base/nsIRequest.idl
@@ -219,11 +219,13 @@ interface nsIRequest : nsISupports
      * to the request when opened. This means that things like authorization
      * tokens or cookie headers should not be added.
      */
     const unsigned long LOAD_ANONYMOUS = 1 << 14;
 
     /**
      * When set, this flag indicates that caches of network connections,
      * particularly HTTP persistent connections, should not be used.
+     * Use this together with LOAD_INITIAL_DOCUMENT_URI as otherwise it has no
+     * effect.
      */
     const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
 };
--- a/taskcluster/docker/funsize-update-generator/runme.sh
+++ b/taskcluster/docker/funsize-update-generator/runme.sh
@@ -38,18 +38,18 @@ then
   AUTH=
 
   if [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
     # Pass the full bucket/path prefix, as the script just appends local files.
     export MBSDIFF_HOOK="/home/worker/bin/mbsdiff_hook.sh -S ${S3_BUCKET_AND_PATH}"
   fi
   set -x
 else
-  # enable locale cache
-  export MBSDIFF_HOOK="/home/worker/bin/mbsdiff_hook.sh -c /tmp/fs-cache"
+  # disable caching
+  export MBSDIFF_HOOK=
 fi
 
 if [ ! -z "$FILENAME_TEMPLATE" ]; then
     EXTRA_PARAMS="--filename-template $FILENAME_TEMPLATE $EXTRA_PARAMS"
 fi
 
 # EXTRA_PARAMS is optional
 # shellcheck disable=SC2086
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1100,18 +1100,18 @@ var PlacesUtils = {
   /**
    * Validate an input PageInfo object, returning a valid PageInfo object.
    *
    * @param pageInfo: (PageInfo)
    * @return (PageInfo)
    */
   validatePageInfo(pageInfo, validateVisits = true) {
     return this.validateItemProperties("PageInfo", PAGEINFO_VALIDATORS, pageInfo,
-      { url: { requiredIf: b => { typeof b.guid != "string"; } },
-        guid: { requiredIf: b => { typeof b.url != "string"; } },
+      { url: { requiredIf: b => !b.guid },
+        guid: { requiredIf: b => !b.url },
         visits: { requiredIf: b => validateVisits  },
       });
   },
   /**
    * Normalize a key to either a string (if it is a valid GUID) or an
    * instance of `URL` (if it is a `URL`, `nsIURI`, or a string
    * representing a valid url).
    *
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -442,76 +442,23 @@ namespace places {
   bool
   MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken,
                                             const nsACString &aSourceString)
   {
     return findInString(aToken, aSourceString, eFindOnBoundary);
   }
 
   /* static */
-  bool
-  MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
-                                           const nsACString &aSourceString)
-  {
-    MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!");
-
-    // We can't use StringBeginsWith here, unfortunately.  Although it will
-    // happily take a case-insensitive UTF8 comparator, it eventually calls
-    // nsACString::Equals, which checks that the two strings contain the same
-    // number of bytes before calling the comparator.  Two characters may be
-    // case-insensitively equal while taking up different numbers of bytes, so
-    // this is not what we want.
-
-    const_char_iterator tokenStart(aToken.BeginReading()),
-                        tokenEnd(aToken.EndReading()),
-                        sourceStart(aSourceString.BeginReading()),
-                        sourceEnd(aSourceString.EndReading());
-
-    bool dummy;
-    while (sourceStart < sourceEnd &&
-           CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
-                                         sourceEnd, tokenEnd,
-                                         &sourceStart, &tokenStart, &dummy)) {
-
-      // We found the token!
-      if (tokenStart >= tokenEnd) {
-        return true;
-      }
-    }
-
-    // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
-    // (stored in |dummy|), since the function will return false if it
-    // encounters an error.
-
-    return false;
-  }
-
-  /* static */
-  bool
-  MatchAutoCompleteFunction::findBeginningCaseSensitive(
-    const nsDependentCSubstring &aToken,
-    const nsACString &aSourceString)
-  {
-    MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!");
-
-    return StringBeginsWith(aSourceString, aToken);
-  }
-
-  /* static */
   MatchAutoCompleteFunction::searchFunctionPtr
   MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
   {
     switch (aBehavior) {
       case mozIPlacesAutoComplete::MATCH_ANYWHERE:
       case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
         return findAnywhere;
-      case mozIPlacesAutoComplete::MATCH_BEGINNING:
-        return findBeginning;
-      case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
-        return findBeginningCaseSensitive;
       case mozIPlacesAutoComplete::MATCH_BOUNDARY:
       default:
         return findOnBoundary;
     };
   }
 
   NS_IMPL_ISUPPORTS(
     MatchAutoCompleteFunction,
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -5,26 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Constants
 
 const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
 
-// Match type constants.
-// These indicate what type of search function we should be using.
-const {
-  MATCH_ANYWHERE,
-  MATCH_BOUNDARY_ANYWHERE,
-  MATCH_BOUNDARY,
-  MATCH_BEGINNING,
-  MATCH_BEGINNING_CASE_SENSITIVE,
-} = Ci.mozIPlacesAutoComplete;
-
 // AutoComplete query type constants.
 // Describes the various types of queries that we can process rows for.
 const QUERYTYPE_FILTERED            = 0;
 const QUERYTYPE_AUTOFILL_ORIGIN     = 1;
 const QUERYTYPE_AUTOFILL_URL        = 2;
 const QUERYTYPE_ADAPTIVE            = 3;
 
 // This separator is used as an RTL-friendly way to split the title and tags.
@@ -571,17 +561,17 @@ function Search(searchString, searchPara
                 autocompleteSearch, prohibitSearchSuggestions, previousResult) {
   // We want to store the original string for case sensitive searches.
   this._originalSearchString = searchString;
   this._trimmedOriginalSearchString = searchString.trim();
   let [prefix, suffix] = stripPrefix(this._trimmedOriginalSearchString);
   this._searchString = Services.textToSubURI.unEscapeURIForUI("UTF-8", suffix);
   this._strippedPrefix = prefix.toLowerCase();
 
-  this._matchBehavior = UrlbarPrefs.get("matchBehavior");
+  this._matchBehavior = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
   // Set the default behavior for this search.
   this._behavior = this._searchString ? UrlbarPrefs.get("defaultBehavior")
                                       : UrlbarPrefs.get("emptySearchDefaultBehavior");
 
   let params = new Set(searchParam.split(" "));
   this._enableActions = params.has("enable-actions");
   this._disablePrivateActions = params.has("disable-private-actions");
   this._inPrivateWindow = params.has("private-window");
@@ -977,24 +967,22 @@ Search.prototype = {
     }
 
     // Ideally we should wait until MATCH_BOUNDARY_ANYWHERE, but that query
     // may be really slow and we may end up showing old results for too long.
     this._cleanUpNonCurrentMatches(UrlbarUtils.MATCH_GROUP.GENERAL);
 
     this._matchAboutPages();
 
-    // If we do not have enough results, and our match type is
-    // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
-    // results.
+    // If we do not have enough matches search again with MATCH_ANYWHERE, to
+    // get more matches.
     let count = this._counts[UrlbarUtils.MATCH_GROUP.GENERAL] +
                 this._counts[UrlbarUtils.MATCH_GROUP.HEURISTIC];
-    if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
-        count < UrlbarPrefs.get("maxRichResults")) {
-      this._matchBehavior = MATCH_ANYWHERE;
+    if (count < UrlbarPrefs.get("maxRichResults")) {
+      this._matchBehavior = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
       for (let [query, params] of [ this._adaptiveQuery,
                                     this._searchQuery ]) {
         await conn.executeCached(query, params, this._onResultRow.bind(this));
         if (!this.pending)
           return;
       }
     }
 
--- a/toolkit/components/places/mozIPlacesAutoComplete.idl
+++ b/toolkit/components/places/mozIPlacesAutoComplete.idl
@@ -14,16 +14,19 @@ interface nsIURI;
  * purposes.
  */
 [scriptable, uuid(61b6348a-09e1-4810-8057-f8cb3cec6ef8)]
 interface mozIPlacesAutoComplete : nsISupports
 {
   //////////////////////////////////////////////////////////////////////////////
   //// Matching Constants
 
+  // A few of these are not used in Firefox, but are still referenced in
+  // comm-central.
+
   /**
    * Match anywhere in each searchable term.
    */
   const long MATCH_ANYWHERE = 0;
 
   /**
    * Match first on word boundaries, and if we do not get enough results, then
    * match anywhere in each searchable term.
--- a/toolkit/components/places/tests/history/test_update.js
+++ b/toolkit/components/places/tests/history/test_update.js
@@ -14,16 +14,23 @@ add_task(async function test_error_cases
     "passing a string as pageInfo should throw an Error"
   );
   Assert.throws(
     () => PlacesUtils.history.update(null),
     /Error: PageInfo: Input should be/,
     "passing a null as pageInfo should throw an Error"
   );
   Assert.throws(
+    () => PlacesUtils.history.update({
+      description: "Test description",
+    }),
+    /Error: PageInfo: The following properties were expected: url, guid/,
+    "not included a url or a guid should throw"
+  );
+  Assert.throws(
     () => PlacesUtils.history.update({url: "not a valid url string"}),
     /Error: PageInfo: Invalid value for property/,
     "passing an invalid url should throw an Error"
   );
   Assert.throws(
     () => PlacesUtils.history.update({
       url: "http://valid.uri.com",
       description: 123,
@@ -150,17 +157,17 @@ add_task(async function test_previewImag
 
   previewImageURL = null;
   await PlacesUtils.history.update({ url: TEST_URL, previewImageURL});
   previewImageURLInDB = await PlacesTestUtils.fieldInDB(TEST_URL, "preview_image_url");
   Assert.strictEqual(null, previewImageURLInDB, "a null previewImageURL should set it to null in the database");
 
   let guid = await PlacesTestUtils.fieldInDB(TEST_URL, "guid");
   previewImageURL = IMAGE_URL;
-  await PlacesUtils.history.update({ url: TEST_URL, guid, previewImageURL });
+  await PlacesUtils.history.update({ guid, previewImageURL });
   previewImageURLInDB = await PlacesTestUtils.fieldInDB(TEST_URL, "preview_image_url");
   Assert.equal(previewImageURL, previewImageURLInDB, "previewImageURL should be updated via GUID as expected");
 
   previewImageURL = "";
   await PlacesUtils.history.update({ url: TEST_URL, previewImageURL});
   previewImageURLInDB = await PlacesTestUtils.fieldInDB(TEST_URL, "preview_image_url");
   Assert.strictEqual(null, previewImageURLInDB, "an empty previewImageURL should set it to null in the database");
 });
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -314,23 +314,25 @@ var addBookmark = async function(aBookma
   await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
     title: aBookmarkObj.title || "A bookmark",
     url: aBookmarkObj.uri,
   });
 
   if (aBookmarkObj.keyword) {
     await PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword,
-                                        url: aBookmarkObj.uri.spec ? aBookmarkObj.uri.spec : aBookmarkObj.uri,
+                                        url: aBookmarkObj.uri instanceof Ci.nsIURI ? aBookmarkObj.uri.spec : aBookmarkObj.uri,
                                         postData: aBookmarkObj.postData,
                                       });
   }
 
   if (aBookmarkObj.tags) {
-    PlacesUtils.tagging.tagURI(aBookmarkObj.uri, aBookmarkObj.tags);
+    let uri = aBookmarkObj.uri instanceof Ci.nsIURI ?
+      aBookmarkObj.uri : Services.io.newURI(aBookmarkObj.uri);
+    PlacesUtils.tagging.tagURI(uri, aBookmarkObj.tags);
   }
 };
 
 function addOpenPages(aUri, aCount = 1, aUserContextId = 0) {
   for (let i = 0; i < aCount; i++) {
     UrlbarProviderOpenTabs.registerOpenTab(aUri.spec, aUserContextId);
   }
 }
deleted file mode 100644
--- a/toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Test bug 451760 which allows matching only at the beginning of urls or
- * titles to simulate Firefox 2 functionality.
- */
-
-add_task(async function test_match_beginning() {
-  Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
-
-  let uri1 = NetUtil.newURI("http://x.com/y");
-  let uri2 = NetUtil.newURI("https://y.com/x");
-  await PlacesTestUtils.addVisits([
-    { uri: uri1, title: "a b" },
-    { uri: uri2, title: "b a" },
-  ]);
-
-  info("Match at the beginning of titles");
-  Services.prefs.setIntPref("browser.urlbar.matchBehavior", 3);
-  await check_autocomplete({
-    search: "a",
-    matches: [ { uri: uri1, title: "a b" } ],
-  });
-
-  info("Match at the beginning of titles");
-  await check_autocomplete({
-    search: "b",
-    matches: [ { uri: uri2, title: "b a" } ],
-  });
-
-  info("Match at the beginning of urls");
-  await check_autocomplete({
-    search: "x",
-    matches: [ { uri: uri1, title: "a b" } ],
-  });
-
-  info("Match at the beginning of urls");
-  await check_autocomplete({
-    search: "y",
-    matches: [ { uri: uri2, title: "b a" } ],
-  });
-
-  info("Sanity check that matching anywhere finds more");
-  Services.prefs.setIntPref("browser.urlbar.matchBehavior", 1);
-  await check_autocomplete({
-    search: "a",
-    matches: [ { uri: uri1, title: "a b" },
-               { uri: uri2, title: "b a" } ],
-  });
-
-  await cleanup();
-});
--- a/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js
@@ -1,176 +1,197 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
- * Test bug 393678 to make sure matches against the url, title, tags are only
- * made on word boundaries instead of in the middle of words.
+ * Test to make sure matches against the url, title, tags are first made on word
+ * boundaries, instead of in the middle of words, and later are extended to the
+ * whole words. For this test it is critical to check sorting of the matches.
  *
  * Make sure we don't try matching one after a CamelCase because the upper-case
  * isn't really a word boundary. (bug 429498)
- *
- * Bug 429531 provides switching between "must match on word boundary" and "can
- * match," so leverage "must match" pref for checking word boundary logic and
- * make sure "can match" matches anywhere.
  */
 
 var katakana = ["\u30a8", "\u30c9"]; // E, Do
 var ideograph = ["\u4efb", "\u5929", "\u5802"]; // Nin Ten Do
 
 add_task(async function test_escape() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false);
   Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
 
-  let uri1 = NetUtil.newURI("http://matchme/");
-  let uri2 = NetUtil.newURI("http://dontmatchme/");
-  let uri3 = NetUtil.newURI("http://title/1");
-  let uri4 = NetUtil.newURI("http://title/2");
-  let uri5 = NetUtil.newURI("http://tag/1");
-  let uri6 = NetUtil.newURI("http://tag/2");
-  let uri7 = NetUtil.newURI("http://crazytitle/");
-  let uri8 = NetUtil.newURI("http://katakana/");
-  let uri9 = NetUtil.newURI("http://ideograph/");
-  let uri10 = NetUtil.newURI("http://camel/pleaseMatchMe/");
-
   await PlacesTestUtils.addVisits([
-    { uri: uri1, title: "title1" },
-    { uri: uri2, title: "title1" },
-    { uri: uri3, title: "matchme2" },
-    { uri: uri4, title: "dontmatchme3" },
-    { uri: uri5, title: "title1" },
-    { uri: uri6, title: "title1" },
-    { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" },
-    { uri: uri8, title: katakana.join("") },
-    { uri: uri9, title: ideograph.join("") },
-    { uri: uri10, title: "title1" },
+    { uri: "http://matchme/", title: "title1" },
+    { uri: "http://dontmatchme/", title: "title1" },
+    { uri: "http://title/1", title: "matchme2" },
+    { uri: "http://title/2", title: "dontmatchme3" },
+    { uri: "http://tag/1", title: "title1" },
+    { uri: "http://tag/2", title: "title1" },
+    { uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
+    { uri: "http://katakana/", title: katakana.join("") },
+    { uri: "http://ideograph/", title: ideograph.join("") },
+    { uri: "http://camel/pleaseMatchMe/", title: "title1" },
   ]);
-  await addBookmark( { uri: uri5, title: "title1", tags: [ "matchme2" ] } );
-  await addBookmark( { uri: uri6, title: "title1", tags: [ "dontmatchme3" ] } );
-
-  // match only on word boundaries
-  Services.prefs.setIntPref("browser.urlbar.matchBehavior", 2);
+  await addBookmark( { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ] } );
+  await addBookmark( { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ] } );
 
   info("Match 'match' at the beginning or after / or on a CamelCase");
   await check_autocomplete({
     search: "match",
-    matches: [ { uri: uri1, title: "title1" },
-               { uri: uri3, title: "matchme2" },
-               { uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
-               { uri: uri10, title: "title1" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://camel/pleaseMatchMe/", title: "title1" },
+      { uri: "http://title/1", title: "matchme2" },
+      { uri: "http://matchme/", title: "title1" },
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://dontmatchme/", title: "title1" },
+    ],
   });
 
   info("Match 'dont' at the beginning or after /");
   await check_autocomplete({
     search: "dont",
-    matches: [ { uri: uri2, title: "title1" },
-               { uri: uri4, title: "dontmatchme3" },
-               { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://dontmatchme/", title: "title1" },
+    ],
   });
 
   info("Match 'match' at the beginning or after / or on a CamelCase");
   await check_autocomplete({
     search: "2",
-    matches: [ { uri: uri3, title: "matchme2" },
-               { uri: uri4, title: "dontmatchme3" },
-               { uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
-               { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://title/1", title: "matchme2" },
+    ],
   });
 
   info("Match 't' at the beginning or after /");
   await check_autocomplete({
     search: "t",
-    matches: [ { uri: uri1, title: "title1" },
-               { uri: uri2, title: "title1" },
-               { uri: uri3, title: "matchme2" },
-               { uri: uri4, title: "dontmatchme3" },
-               { uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
-               { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
-               { uri: uri10, title: "title1" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://camel/pleaseMatchMe/", title: "title1" },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://title/1", title: "matchme2" },
+      { uri: "http://dontmatchme/", title: "title1" },
+      { uri: "http://matchme/", title: "title1" },
+      { uri: "http://katakana/", title: katakana.join("") },
+      { uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
+    ],
   });
 
   info("Match 'word' after many consecutive word boundaries");
   await check_autocomplete({
     search: "word",
-    matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
+    ],
   });
 
   info("Match a word boundary '/' for everything");
   await check_autocomplete({
     search: "/",
-    matches: [ { uri: uri1, title: "title1" },
-               { uri: uri2, title: "title1" },
-               { uri: uri3, title: "matchme2" },
-               { uri: uri4, title: "dontmatchme3" },
-               { uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
-               { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
-               { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" },
-               { uri: uri8, title: katakana.join("") },
-               { uri: uri9, title: ideograph.join("") },
-               { uri: uri10, title: "title1" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://camel/pleaseMatchMe/", title: "title1" },
+      { uri: "http://ideograph/", title: ideograph.join("") },
+      { uri: "http://katakana/", title: katakana.join("") },
+      { uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://title/1", title: "matchme2" },
+      { uri: "http://dontmatchme/", title: "title1" },
+      { uri: "http://matchme/", title: "title1" },
+    ],
   });
 
   info("Match word boundaries '()_+' that are among word boundaries");
   await check_autocomplete({
     search: "()_+",
-    matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
+    ],
   });
 
   info("Katakana characters form a string, so match the beginning");
   await check_autocomplete({
     search: katakana[0],
-    matches: [ { uri: uri8, title: katakana.join("") } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://katakana/", title: katakana.join("") },
+    ],
   });
 
 /*
-  do_print("Middle of a katakana word shouldn't be matched");
-  yield check_autocomplete({
+  info("Middle of a katakana word shouldn't be matched");
+  await check_autocomplete({
     search: katakana[1],
-    matches: [ ]
+    matches: [ ],
   });
 */
+
  info("Ideographs are treated as words so 'nin' is one word");
   await check_autocomplete({
     search: ideograph[0],
-    matches: [ { uri: uri9, title: ideograph.join("") } ],
+    checkSorting: true,
+    matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
   });
 
  info("Ideographs are treated as words so 'ten' is another word");
   await check_autocomplete({
     search: ideograph[1],
-    matches: [ { uri: uri9, title: ideograph.join("") } ],
+    checkSorting: true,
+    matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
   });
 
  info("Ideographs are treated as words so 'do' is yet another word");
   await check_autocomplete({
     search: ideograph[2],
-    matches: [ { uri: uri9, title: ideograph.join("") } ],
+    checkSorting: true,
+    matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
   });
 
- info("Extra negative assert that we don't match in the middle");
+ info("Match in the middle. Should just be sorted by frecency.");
   await check_autocomplete({
     search: "ch",
-    matches: [ ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://camel/pleaseMatchMe/", title: "title1" },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://title/1", title: "matchme2" },
+      { uri: "http://dontmatchme/", title: "title1" },
+      { uri: "http://matchme/", title: "title1" },
+    ],
   });
 
- info("Don't match one character after a camel-case word boundary (bug 429498)");
+ // Also this test should just be sorted by frecency.
+ info("Don't match one character after a camel-case word boundary (bug 429498). Should just be sorted by frecency.");
   await check_autocomplete({
     search: "atch",
-    matches: [ ],
-  });
-
-  // match against word boundaries and anywhere
-  Services.prefs.setIntPref("browser.urlbar.matchBehavior", 1);
-
-  await check_autocomplete({
-    search: "tch",
-    matches: [ { uri: uri1, title: "title1" },
-               { uri: uri2, title: "title1" },
-               { uri: uri3, title: "matchme2" },
-               { uri: uri4, title: "dontmatchme3" },
-               { uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
-               { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
-               { uri: uri10, title: "title1" } ],
+    checkSorting: true,
+    matches: [
+      { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
+      { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
+      { uri: "http://camel/pleaseMatchMe/", title: "title1" },
+      { uri: "http://title/2", title: "dontmatchme3" },
+      { uri: "http://title/1", title: "matchme2" },
+      { uri: "http://dontmatchme/", title: "title1" },
+      { uri: "http://matchme/", title: "title1" },
+    ],
   });
 
   await cleanup();
 });
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -35,17 +35,16 @@ support-files =
 [test_encoded_urls.js]
 [test_escape_self.js]
 [test_extension_matches.js]
 [test_history_autocomplete_tags.js]
 [test_ignore_protocol.js]
 [test_keyword_search.js]
 [test_keyword_search_actions.js]
 [test_keywords.js]
-[test_match_beginning.js]
 [test_multi_word_search.js]
 [test_PlacesSearchAutocompleteProvider.js]
 skip-if = appname == "thunderbird"
 [test_preloaded_sites.js]
 [test_query_url.js]
 [test_remote_tab_matches.js]
 skip-if = !sync
 [test_search_engine_alias.js]