Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 21 Apr 2017 11:43:14 +0200
changeset 402482 9cb018faeab3
parent 402481 3ccc9c855766 (current diff)
parent 402380 dd530a59750a (diff)
child 402483 0d56e78960ef
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.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 mozilla-inbound
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/basic.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_white-space_normal_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_white-space_pre-line_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_normal_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_pre-line_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_white-space_normal_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_white-space_pre-line_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_white-space_normal_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_white-space_pre-line_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_white-space_normal_wrapped.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_white-space_pre-line_wrapped.html.ini
--- a/.cron.yml
+++ b/.cron.yml
@@ -16,16 +16,26 @@ jobs:
       when:
           by-project:
             # Match buildbot starts for now
             date: [{hour: 15, minute: 0}]
             mozilla-central: [{hour: 10, minute: 0}]
             mozilla-aurora: [{hour: 7, minute: 45}]  # Buildbot uses minute 40
             # No default
 
+    - name: nightly-desktop-osx
+      job:
+          type: decision-task
+          treeherder-symbol: Nd-OSX
+          triggered-by: nightly
+          target-tasks-method: nightly_macosx
+      run-on-projects:
+          - date
+      when: [] # never (hook only)
+
     - name: nightly-android
       job:
           type: decision-task
           treeherder-symbol: Na
           triggered-by: nightly
           target-tasks-method: nightly_fennec
       run-on-projects:
           - mozilla-central
--- a/accessible/ipc/win/handler/HandlerData.idl
+++ b/accessible/ipc/win/handler/HandlerData.idl
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=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/. */
 
+#include "mozilla-config.h"
 #include "AccessibleHandler.h"
 
 import "ocidl.idl";
 import "ServProv.idl";
 
 import "AccessibleText.idl";
 
 typedef struct _IA2Data
--- a/accessible/ipc/win/handler/Makefile.in
+++ b/accessible/ipc/win/handler/Makefile.in
@@ -15,17 +15,17 @@ MIDL_GENERATED_FILES = \
   HandlerData.tlb \
   $(NULL)
 
 export:: $(MIDL_GENERATED_FILES)
 
 $(MIDL_GENERATED_FILES): midl_done
 
 midl_done: HandlerData.acf HandlerData.idl
-	$(MIDL) $(MIDL_FLAGS) $(DEFINES) -I $(IA2DIR) -I $(MSAADIR) -Oicf -acf $(srcdir)/HandlerData.acf $(srcdir)/HandlerData.idl
+	$(MIDL) $(MIDL_FLAGS) $(DEFINES) -I $(topobjdir) -I $(DIST)/include -I $(IA2DIR) -I $(MSAADIR) -Oicf -acf $(srcdir)/HandlerData.acf $(srcdir)/HandlerData.idl
 	touch $@
 
 INSTALL_TARGETS += midl
 midl_FILES := HandlerData.h \
               HandlerData_i.c \
               $(NULL)
 midl_DEST := $(DIST)/include
 midl_TARGET := midl
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -631,17 +631,18 @@ var FullScreen = {
         if (el.hasAttribute("saved-context")) {
           el.setAttribute("context", el.getAttribute("saved-context"));
           el.removeAttribute("saved-context");
         }
         el.removeAttribute("inFullscreen");
       }
     }
 
-    ToolbarIconColor.inferFromText();
+    ToolbarIconColor.inferFromText("fullscreen", aEnterFS);
+
 
     // For Lion fullscreen, all fullscreen controls are hidden, don't
     // bother to touch them. If we don't stop here, the following code
     // could cause the native fullscreen button be shown unexpectedly.
     // See bug 1165570.
     if (this.useLionFullScreen) {
       return;
     }
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -242,17 +242,18 @@ var TabsInTitlebar = {
 
       // Reset the margins and padding that might have been modified:
       titlebarContent.style.marginTop = "";
       titlebarContent.style.marginBottom = "";
       titlebar.style.marginBottom = "";
       menubar.style.paddingBottom = "";
     }
 
-    ToolbarIconColor.inferFromText();
+    ToolbarIconColor.inferFromText("tabsintitlebar", TabsInTitlebar.enabled);
+
     if (CustomizationHandler.isCustomizing()) {
       gCustomizeMode.updateLWTStyling();
     }
   },
 
   _sizePlaceholder(type, width) {
     Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"),
                   function(node) { node.width = width; });
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5403,18 +5403,16 @@ function setToolbarVisibility(toolbar, i
     },
     bubbles: true
   };
   let event = new CustomEvent("toolbarvisibilitychange", eventParams);
   toolbar.dispatchEvent(event);
 
   PlacesToolbarHelper.init();
   BookmarkingUI.onToolbarVisibilityChange();
-  if (isVisible)
-    ToolbarIconColor.inferFromText();
 }
 
 var TabletModeUpdater = {
   init() {
     if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
       this.update(WindowsUIUtils.inTabletMode);
       Services.obs.addObserver(this, "tablet-mode-change");
     }
@@ -7681,17 +7679,17 @@ var gIdentityHandler = {
     }
     stateLabel.textContent = SitePermissions.getCurrentStateLabel(state, scope);
 
     let button = document.createElement("button");
     button.setAttribute("class", "identity-popup-permission-remove-button");
     let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
     button.setAttribute("tooltiptext", tooltiptext);
     button.addEventListener("command", () => {
-	  let browser = gBrowser.selectedBrowser;
+      let browser = gBrowser.selectedBrowser;
       // Only resize the window if the reload hint was previously hidden.
       this._handleHeightChange(() => this._permissionList.removeChild(container),
                                this._permissionReloadHint.hasAttribute("hidden"));
       if (aPermission.inUse &&
           ["camera", "microphone", "screen"].includes(aPermission.id)) {
         let windowId = this._sharingState.windowId;
         if (aPermission.id == "screen") {
           windowId = "screen:" + windowId;
@@ -8248,78 +8246,123 @@ var MousePosTracker = {
         listener.onMouseEnter();
     } else if (listener.onMouseLeave) {
       listener.onMouseLeave();
     }
   }
 };
 
 var ToolbarIconColor = {
+  _windowState: {
+    "active": false,
+    "fullscreen": false,
+    "tabsintitlebar": false
+  },
   init() {
     this._initialized = true;
 
     window.addEventListener("activate", this);
     window.addEventListener("deactivate", this);
+    window.addEventListener("toolbarvisibilitychange", this);
     Services.obs.addObserver(this, "lightweight-theme-styling-update");
 
     // If the window isn't active now, we assume that it has never been active
     // before and will soon become active such that inferFromText will be
     // called from the initial activate event.
-    if (Services.focus.activeWindow == window)
-      this.inferFromText();
+    if (Services.focus.activeWindow == window) {
+      this.inferFromText("activate");
+    }
   },
 
   uninit() {
     this._initialized = false;
 
     window.removeEventListener("activate", this);
     window.removeEventListener("deactivate", this);
+    window.removeEventListener("toolbarvisibilitychange", this);
     Services.obs.removeObserver(this, "lightweight-theme-styling-update");
   },
 
   handleEvent(event) {
     switch (event.type) {
-      case "activate":
+      case "activate":  // falls through
       case "deactivate":
-        this.inferFromText();
+        this.inferFromText(event.type);
+        break;
+      case "toolbarvisibilitychange":
+        this.inferFromText(event.type, event.visible);
         break;
     }
   },
 
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "lightweight-theme-styling-update":
         // inferFromText needs to run after LightweightThemeConsumer.jsm's
         // lightweight-theme-styling-update observer.
-        setTimeout(() => { this.inferFromText(); }, 0);
+        setTimeout(() => {
+          this.inferFromText(aTopic);
+        }, 0);
         break;
     }
   },
 
-  inferFromText() {
+  // a cache of luminance values for each toolbar
+  // to avoid unnecessary calls to getComputedStyle
+  _toolbarLuminanceCache: new Map(),
+
+  inferFromText(reason, reasonValue) {
     if (!this._initialized)
       return;
-
     function parseRGB(aColorString) {
       let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
       rgb.shift();
       return rgb.map(x => parseInt(x));
     }
 
+    switch (reason) {
+      case "activate": // falls through
+      case "deactivate":
+        this._windowState.active = (reason === "activate");
+        break;
+      case "fullscreen":
+        this._windowState.fullscreen = reasonValue;
+        break;
+      case "lightweight-theme-styling-update":
+        // theme change, we'll need to recalculate all color values
+        this._toolbarLuminanceCache.clear();
+        break;
+      case "toolbarvisibilitychange":
+        // toolbar changes dont require reset of the cached color values
+        break;
+      case "tabsintitlebar":
+        this._windowState.tabsintitlebar = reasonValue;
+        break;
+    }
+
     let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
     if (AppConstants.platform == "macosx")
       toolbarSelector += ":not([type=menubar])";
 
     // The getComputedStyle calls and setting the brighttext are separated in
     // two loops to avoid flushing layout and making it dirty repeatedly.
-
-    let luminances = new Map;
+    let cachedLuminances = this._toolbarLuminanceCache;
+    let luminances = new Map();
     for (let toolbar of document.querySelectorAll(toolbarSelector)) {
-      let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
-      let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+      // toolbars *should* all have ids, but guard anyway to avoid blowing up
+      let cacheKey = toolbar.id && toolbar.id + JSON.stringify(this._windowState);
+      // lookup cached luminance value for this toolbar in this window state
+      let luminance = cacheKey && cachedLuminances.get(cacheKey);
+      if (isNaN(luminance)) {
+        let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+        luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+        if (cacheKey) {
+          cachedLuminances.set(cacheKey, luminance);
+        }
+      }
       luminances.set(toolbar, luminance);
     }
 
     for (let [toolbar, luminance] of luminances) {
       if (luminance <= 110)
         toolbar.removeAttribute("brighttext");
       else
         toolbar.setAttribute("brighttext", "true");
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -249,18 +249,16 @@ var whitelist = new Set([
   // Bug 1351093
   {file: "resource://gre/modules/Sntp.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1351099
   {file: "resource://gre/modules/addons/AddonLogging.jsm"},
   // Bug 1351637
   {file: "resource://gre/modules/sdk/bootstrap.js"},
-  // Bug 1351657
-  {file: "resource://gre/res/langGroups.properties", platforms: ["macosx"]},
 
 ].filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
   (!item.platforms || item.platforms.includes(AppConstants.platform))
 ).map(item => item.file));
 
 const ignorableWhitelist = new Set([
   // chrome://xslt-qa/ isn't referenced, but isn't included in packaged builds,
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/windows/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/windows/browser.ini
@@ -0,0 +1,2 @@
+[DEFAULT]
+[browser_toolbariconcolor_restyles.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/windows/browser_toolbariconcolor_restyles.js
@@ -0,0 +1,43 @@
+"use strict";
+
+/**
+ * Ensure redundant style flushes are not triggered when switching between windows
+ */
+add_task(function* test_toolbar_element_restyles_on_activation() {
+  let restyles = {
+    win1:  {},
+    win2:  {}
+  };
+
+  // create a window and snapshot the elementsStyled
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  yield new Promise(resolve => waitForFocus(resolve, win1));
+
+  // create a 2nd window and snapshot the elementsStyled
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+  yield new Promise(resolve => waitForFocus(resolve, win2));
+
+  let utils1 = SpecialPowers.getDOMWindowUtils(win1);
+  restyles.win1.initial = utils1.elementsRestyled;
+
+  let utils2 = SpecialPowers.getDOMWindowUtils(win2);
+  restyles.win2.initial = utils2.elementsRestyled;
+
+  // switch back to 1st window, and snapshot elementsStyled
+  win1.focus();
+  restyles.win1.activate = utils1.elementsRestyled;
+  restyles.win2.deactivate = utils2.elementsRestyled;
+
+  // switch back to 2nd window, and snapshot elementsStyled
+  win2.focus();
+  restyles.win2.activate = utils2.elementsRestyled;
+  restyles.win1.deactivate = utils1.elementsRestyled;
+
+  is(restyles.win1.activate - restyles.win1.deactivate, 0,
+      "No elements restyled when re-activating/deactivating a window");
+  is(restyles.win2.activate - restyles.win2.deactivate, 0,
+      "No elements restyled when re-activating/deactivating a window");
+
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+});
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -30,16 +30,17 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/social/browser.ini',
     'content/test/static/browser.ini',
     'content/test/tabcrashed/browser.ini',
     'content/test/tabPrompts/browser.ini',
     'content/test/tabs/browser.ini',
     'content/test/urlbar/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
+    'content/test/windows/browser.ini',
 ]
 
 if CONFIG['MOZ_UPDATER']:
     BROWSER_CHROME_MANIFESTS += ['content/test/appUpdate/browser.ini']
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1854,17 +1854,17 @@ BrowserGlue.prototype = {
       let animate = Services.prefs.getBoolPref("browser.tabs.animate", true) &&
                     Services.prefs.getBoolPref("browser.fullscreen.animate", true) &&
                     !Services.prefs.getBoolPref("alerts.disableSlidingEffect", false);
 
       Services.prefs.setBoolPref("toolkit.cosmeticAnimations.enabled", animate);
 
       Services.prefs.clearUserPref("browser.tabs.animate");
       Services.prefs.clearUserPref("browser.fullscreen.animate");
-      Services.prefs.clearUserPref("browser.tabs.animate");
+      Services.prefs.clearUserPref("alerts.disableSlidingEffect");
     }
 
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   // ------------------------------
   // public nsIBrowserGlue members
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -691,20 +691,16 @@
 @RESPATH@/res/table-remove-row.gif
 @RESPATH@/res/grabber.gif
 #ifdef XP_MACOSX
 @RESPATH@/res/cursors/*
 #endif
 @RESPATH@/res/fonts/*
 @RESPATH@/res/dtd/*
 @RESPATH@/res/html/*
-#if defined(XP_MACOSX)
-; For SafariProfileMigrator.js.
-@RESPATH@/res/langGroups.properties
-#endif
 @RESPATH@/res/language.properties
 @RESPATH@/res/entityTables/*
 #ifdef XP_MACOSX
 @RESPATH@/res/MainMenu.nib/
 #endif
 
 ; svg
 @RESPATH@/res/svg.css
--- a/browser/modules/FullZoomUI.jsm
+++ b/browser/modules/FullZoomUI.jsm
@@ -93,18 +93,17 @@ let customizationListener = {
   onAreaNodeRegistered(aAreaType, aAreaNode) {
     if (aAreaType == CustomizableUI.AREA_PANEL) {
       updateZoomUI(aAreaNode.ownerGlobal.gBrowser.selectedBrowser);
     }
   }
 };
 customizationListener.onWidgetAdded =
 customizationListener.onWidgetRemoved =
-customizationListener.onWidgetMoved =
-customizationListener.onWidgetInstanceRemoved = function(aWidgetId) {
+customizationListener.onWidgetMoved = function(aWidgetId) {
   if (aWidgetId == "zoom-controls") {
     for (let window of CustomizableUI.windows) {
       updateZoomUI(window.gBrowser.selectedBrowser);
     }
   }
 };
 customizationListener.onWidgetReset =
 customizationListener.onWidgetUndoMove = function(aWidgetNode) {
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxPrefs.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TextInputProcessor.h"
+#include "mozilla/widget/IMEData.h"
 #include "nsContentUtils.h"
 #include "nsIDocShell.h"
 #include "nsIWidget.h"
 #include "nsPIDOMWindow.h"
 #include "nsPresContext.h"
 
 using namespace mozilla::widget;
 
@@ -22,35 +23,263 @@ namespace mozilla {
 
 /******************************************************************************
  * TextInputProcessorNotification
  ******************************************************************************/
 
 class TextInputProcessorNotification final :
         public nsITextInputProcessorNotification
 {
+  typedef IMENotification::SelectionChangeData SelectionChangeData;
+  typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
+  typedef IMENotification::TextChangeData TextChangeData;
+  typedef IMENotification::TextChangeDataBase TextChangeDataBase;
+
 public:
   explicit TextInputProcessorNotification(const char* aType)
     : mType(aType)
   {
   }
 
+  explicit TextInputProcessorNotification(
+             const TextChangeDataBase& aTextChangeData)
+    : mType("notify-text-change")
+    , mTextChangeData(aTextChangeData)
+  {
+  }
+
+  explicit TextInputProcessorNotification(
+             const SelectionChangeDataBase& aSelectionChangeData)
+    : mType("notify-selection-change")
+    , mSelectionChangeData(aSelectionChangeData)
+  {
+    // SelectionChangeDataBase::mString still refers nsString instance owned
+    // by aSelectionChangeData.  So, this needs to copy the instance.
+    nsString* string = new nsString(aSelectionChangeData.String());
+    mSelectionChangeData.mString = string;
+  }
+
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD GetType(nsACString& aType) override final
   {
     aType = mType;
     return NS_OK;
   }
 
+  // "notify-text-change" and "notify-selection-change"
+  NS_IMETHOD GetOffset(uint32_t* aOffset) override final
+  {
+    if (NS_WARN_IF(!aOffset)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aOffset = mSelectionChangeData.mOffset;
+      return NS_OK;
+    }
+    if (IsTextChange()) {
+      *aOffset = mTextChangeData.mStartOffset;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // "notify-selection-change"
+  NS_IMETHOD GetText(nsAString& aText) override final
+  {
+    if (IsSelectionChange()) {
+      aText = mSelectionChangeData.String();
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetCollapsed(bool* aCollapsed) override final
+  {
+    if (NS_WARN_IF(!aCollapsed)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aCollapsed = mSelectionChangeData.IsCollapsed();
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetLength(uint32_t* aLength) override final
+  {
+    if (NS_WARN_IF(!aLength)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aLength = mSelectionChangeData.Length();
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetReversed(bool* aReversed) override final
+  {
+    if (NS_WARN_IF(!aReversed)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aReversed = mSelectionChangeData.mReversed;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetWritingMode(nsACString& aWritingMode) override final
+  {
+    if (IsSelectionChange()) {
+      WritingMode writingMode = mSelectionChangeData.GetWritingMode();
+      if (!writingMode.IsVertical()) {
+        aWritingMode.AssignLiteral("horizontal-tb");
+      } else if (writingMode.IsVerticalLR()) {
+        aWritingMode.AssignLiteral("vertical-lr");
+      } else {
+        aWritingMode.AssignLiteral("vertical-rl");
+      }
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) override final
+  {
+    if (NS_WARN_IF(!aCausedByComposition)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aCausedByComposition = mSelectionChangeData.mCausedByComposition;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetCausedBySelectionEvent(
+               bool* aCausedBySelectionEvent) override final
+  {
+    if (NS_WARN_IF(!aCausedBySelectionEvent)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetOccurredDuringComposition(
+               bool* aOccurredDuringComposition) override final
+  {
+    if (NS_WARN_IF(!aOccurredDuringComposition)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsSelectionChange()) {
+      *aOccurredDuringComposition =
+        mSelectionChangeData.mOccurredDuringComposition;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // "notify-text-change"
+  NS_IMETHOD GetRemovedLength(uint32_t* aLength) override final
+  {
+    if (NS_WARN_IF(!aLength)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsTextChange()) {
+      *aLength = mTextChangeData.OldLength();
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetAddedLength(uint32_t* aLength) override final
+  {
+    if (NS_WARN_IF(!aLength)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsTextChange()) {
+      *aLength = mTextChangeData.NewLength();
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetCausedOnlyByComposition(
+               bool* aCausedOnlyByComposition) override final
+  {
+    if (NS_WARN_IF(!aCausedOnlyByComposition)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsTextChange()) {
+      *aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetIncludingChangesDuringComposition(
+               bool* aIncludingChangesDuringComposition) override final
+  {
+    if (NS_WARN_IF(!aIncludingChangesDuringComposition)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsTextChange()) {
+      *aIncludingChangesDuringComposition =
+        mTextChangeData.mIncludingChangesDuringComposition;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IMETHOD GetIncludingChangesWithoutComposition(
+               bool* aIncludingChangesWithoutComposition) override final
+  {
+    if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    if (IsTextChange()) {
+      *aIncludingChangesWithoutComposition =
+        mTextChangeData.mIncludingChangesWithoutComposition;
+      return NS_OK;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
 protected:
-  ~TextInputProcessorNotification() { }
+  virtual ~TextInputProcessorNotification()
+  {
+    if (IsSelectionChange()) {
+      delete mSelectionChangeData.mString;
+      mSelectionChangeData.mString = nullptr;
+    }
+  }
+
+  bool IsTextChange() const
+  {
+    return mType.EqualsLiteral("notify-text-change");
+  }
+
+  bool IsSelectionChange() const
+  {
+    return mType.EqualsLiteral("notify-selection-change");
+  }
 
 private:
   nsAutoCString mType;
+  union
+  {
+    TextChangeDataBase mTextChangeData;
+    SelectionChangeDataBase mSelectionChangeData;
+  };
 
   TextInputProcessorNotification() { }
 };
 
 NS_IMPL_ISUPPORTS(TextInputProcessorNotification,
                   nsITextInputProcessorNotification)
 
 /******************************************************************************
@@ -663,16 +892,28 @@ TextInputProcessor::NotifyIME(TextEventD
         break;
       }
       case NOTIFY_IME_OF_FOCUS:
         notification = new TextInputProcessorNotification("notify-focus");
         break;
       case NOTIFY_IME_OF_BLUR:
         notification = new TextInputProcessorNotification("notify-blur");
         break;
+      case NOTIFY_IME_OF_TEXT_CHANGE:
+        notification = new TextInputProcessorNotification(
+                             aNotification.mTextChangeData);
+        break;
+      case NOTIFY_IME_OF_SELECTION_CHANGE:
+        notification = new TextInputProcessorNotification(
+                             aNotification.mSelectionChangeData);
+        break;
+      case NOTIFY_IME_OF_POSITION_CHANGE:
+        notification = new TextInputProcessorNotification(
+                             "notify-position-change");
+        break;
       default:
         return NS_ERROR_NOT_IMPLEMENTED;
     }
     MOZ_RELEASE_ASSERT(notification);
     bool result = false;
     nsresult rv = mCallback->OnNotify(this, notification, &result);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -696,18 +937,20 @@ TextInputProcessor::NotifyIME(TextEventD
     default:
       return NS_ERROR_NOT_IMPLEMENTED;
   }
 }
 
 NS_IMETHODIMP_(IMENotificationRequests)
 TextInputProcessor::GetIMENotificationRequests()
 {
-  // TextInputProcessor::NotifyIME does not require extra change notifications.
-  return IMENotificationRequests();
+  // TextInputProcessor should support all change notifications.
+  return IMENotificationRequests(
+           IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+           IMENotificationRequests::NOTIFY_POSITION_CHANGE);
 }
 
 NS_IMETHODIMP_(void)
 TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
 {
   // If This is called while this is being initialized, ignore the call.
   if (!mDispatcher) {
     return;
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4210,17 +4210,17 @@ nsDOMWindowUtils::IsTimeoutTracking(uint
 
 struct StateTableEntry
 {
   const char* mStateString;
   EventStates mState;
 };
 
 static constexpr StateTableEntry kManuallyManagedStates[] = {
-  // none yet; but for example: { "highlight", NS_EVENT_STATE_HIGHLIGHT },
+  { "-moz-autofill", NS_EVENT_STATE_AUTOFILL },
   { nullptr, EventStates() },
 };
 
 static_assert(!kManuallyManagedStates[ArrayLength(kManuallyManagedStates) - 1]
                .mStateString,
               "last kManuallyManagedStates entry must be a sentinel with "
               "mStateString == nullptr");
 
--- a/dom/base/test/chrome/window_nsITextInputProcessor.xul
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -54,16 +54,18 @@ function finish()
   window.close();
 }
 
 function onunload()
 {
   SimpleTest.finish();
 }
 
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+
 var iframe = document.getElementById("iframe");
 var childWindow = iframe.contentWindow;
 var textareaInFrame;
 var input = document.getElementById("input");
 var otherWindow = window.opener;
 var otherDocument = otherWindow.document;
 var inputInChildWindow = otherDocument.getElementById("input");
 
@@ -3781,17 +3783,17 @@ function runCommitCompositionTests()
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
   doCommitWithNullCheck(undefined);
   is(input.value, "",
      description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
 }
 
-function runUnloadTests1(aNextTest)
+function runUnloadTests1()
 {
   var description = "runUnloadTests1(): ";
 
   var TIP1 = createTIP();
   ok(TIP1.beginInputTransactionForTests(childWindow),
      description + "TIP1.beginInputTransactionForTests() should succeed");
 
   var oldSrc = iframe.src;
@@ -3803,17 +3805,17 @@ function runUnloadTests1(aNextTest)
     childWindow = iframe.contentWindow;
     textareaInFrame = null;
     iframe.addEventListener("load", function () {
       ok(true, description + "old iframe is restored");
       // And also restore the iframe information with restored contents.
       iframe.removeEventListener("load", arguments.callee, true);
       childWindow = iframe.contentWindow;
       textareaInFrame = iframe.contentDocument.getElementById("textarea");
-      setTimeout(aNextTest, 0);
+      SimpleTest.executeSoon(continueTest);
     }, true);
 
     // The composition should be committed internally.  So, another TIP should
     // be able to steal the rights to using TextEventDispatcher.
     var TIP2 = createTIP();
     ok(TIP2.beginInputTransactionForTests(parentWindow),
        description + "TIP2.beginInputTransactionForTests() should succeed");
 
@@ -3843,17 +3845,17 @@ function runUnloadTests1(aNextTest)
   TIP1.flushPendingComposition();
   is(textareaInFrame.value, "foo",
      description + "the textarea in the iframe should have composition string");
 
   // Load different web page on the frame.
   iframe.src = "data:text/html,<body>dummy page</body>";
 }
 
-function runUnloadTests2(aNextTest)
+function runUnloadTests2()
 {
   var description = "runUnloadTests2(): ";
 
   var TIP = createTIP();
   ok(TIP.beginInputTransactionForTests(childWindow),
      description + "TIP.beginInputTransactionForTests() should succeed");
 
   var oldSrc = iframe.src;
@@ -3865,17 +3867,17 @@ function runUnloadTests2(aNextTest)
     childWindow = iframe.contentWindow;
     textareaInFrame = null;
     iframe.addEventListener("load", function () {
       ok(true, description + "old iframe is restored");
       // And also restore the iframe information with restored contents.
       iframe.removeEventListener("load", arguments.callee, true);
       childWindow = iframe.contentWindow;
       textareaInFrame = iframe.contentDocument.getElementById("textarea");
-      setTimeout(aNextTest, 0);
+      SimpleTest.executeSoon(continueTest);
     }, true);
 
     input.focus();
     input.value = "";
 
     // TIP should be still available in the same top level widget.
     TIP.setPendingCompositionString("bar");
     TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
@@ -3906,53 +3908,121 @@ function runUnloadTests2(aNextTest)
   TIP.flushPendingComposition();
   is(textareaInFrame.value, "foo",
      description + "the textarea in the iframe should have composition string");
 
   // Load different web page on the frame.
   iframe.src = "data:text/html,<body>dummy page</body>";
 }
 
-function runCallbackTests(aForTests)
+function* runCallbackTests(aForTests)
 {
   var description = "runCallbackTests(aForTests=" + aForTests + "): ";
 
   input.value = "";
   input.focus();
   input.blur();
 
   var TIP = createTIP();
   var notifications = [];
+  var callContinueTest = false;
   function callback(aTIP, aNotification)
   {
+    if (aTIP == TIP) {
+      notifications.push(aNotification);
+    }
     switch (aNotification.type) {
       case "request-to-commit":
         aTIP.commitComposition();
         break;
       case "request-to-cancel":
         aTIP.cancelComposition();
         break;
     }
-    if (aTIP == TIP) {
-      notifications.push(aNotification);
+    if (callContinueTest) {
+      callContinueTest = false;
+      SimpleTest.executeSoon(continueTest);
     }
     return true;
   }
 
   function dumpUnexpectedNotifications(aExpectedCount)
   {
     if (notifications.length <= aExpectedCount) {
       return;
     }
     for (var i = aExpectedCount; i < notifications.length; i++) {
       ok(false,
          description + "Unexpected notification: " + notifications[i].type);
     }
   }
 
+  function waitUntilNotificationsReceived()
+  {
+    if (notifications.length > 0) {
+      SimpleTest.executeSoon(continueTest);
+    } else {
+      callContinueTest = true;
+    }
+  }
+
+  function checkPositionChangeNotification(aNotification, aDescription)
+  {
+    is(!aNotification || aNotification.type, "notify-position-change",
+       aDescription + " should cause position change notification");
+  }
+
+  function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
+  {
+    is(aNotification.type, "notify-selection-change",
+       aDescription + " should cause selection change notification");
+    if (aNotification.type != "notify-selection-change") {
+      return;
+    }
+    is(aNotification.offset, aExpected.offset,
+       aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
+    is(aNotification.text, aExpected.text,
+       aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
+    is(aNotification.collapsed, aExpected.text.length == 0,
+       aDescription + " should cause selection change notification whose collapsed is " + (aExpected.text.length == 0));
+    is(aNotification.length, aExpected.text.length,
+       aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
+    is(aNotification.reversed, aExpected.reversed || false,
+       aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
+    is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
+       aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
+    is(aNotification.causedByComposition, aExpected.causedByComposition || false,
+       aDescription + " should cause selection change notification whose causedByComposition is " + (aExpected.causedByComposition || false));
+    is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
+       aDescription + " should cause selection change notification whose causedBySelectionEvent is " + (aExpected.causedBySelectionEvent || false));
+    is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
+       aDescription + " should cause cause selection change notification whose occurredDuringComposition is " + (aExpected.occurredDuringComposition || false));
+  }
+
+  function checkTextChangeNotification(aNotification, aDescription, aExpected)
+  {
+    is(aNotification.type, "notify-text-change",
+       aDescription + " should cause text change notification");
+    if (aNotification.type != "notify-text-change") {
+      return;
+    }
+    is(aNotification.offset, aExpected.offset,
+       aDescription + " should cause text change notification whose offset is " + aExpected.offset);
+    is(aNotification.removedLength, aExpected.removedLength,
+       aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
+    is(aNotification.addedLength, aExpected.addedLength,
+       aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
+    is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
+       aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
+    is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
+       aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
+    is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
+       aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
+  }
+
   if (aForTests) {
     TIP.beginInputTransactionForTests(window, callback);
   } else {
     TIP.beginInputTransaction(window, callback);
   }
 
   notifications = [];
   input.focus();
@@ -3966,58 +4036,151 @@ function runCallbackTests(aForTests)
   input.blur();
   is(notifications.length, 1,
      description + "input.blur() should cause a notification");
   is(notifications[0].type, "notify-blur",
      description + "input.blur() should cause \"notify-focus\"");
   dumpUnexpectedNotifications(1);
 
   input.focus();
+  notifications = [];
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.flushPendingComposition();
+  is(notifications.length, 3,
+     description + "creating composition string 'foo' should cause 3 notifications");
+  checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
+                              { offset: 0, removedLength: 0, addedLength: 3,
+                                causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+  checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
+                                   { offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
+  checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
+  dumpUnexpectedNotifications(3);
+
   notifications = [];
   synthesizeMouseAtCenter(input, {});
-  is(notifications.length, 1,
-     description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
+  is(notifications.length, 3,
+     description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
   is(notifications[0].type, "request-to-commit",
      description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+  checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
+                              { offset: 0, removedLength: 3, addedLength: 3,
+                                causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+  checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
+  dumpUnexpectedNotifications(3);
+
+  // XXX On macOS, window.moveBy() doesn't cause notify-position-change.
+  //     Investigate this later (although, we cannot notify position change to
+  //     native IME on macOS).
+  if (!kIsMac) {
+    input.focus();
+    notifications = [];
+    window.moveBy(0, 10);
+    yield waitUntilNotificationsReceived();
+    is(notifications.length, 1,
+       description + "window.moveBy(0, 10) should cause a notification");
+    checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
+    dumpUnexpectedNotifications(1);
+
+    input.focus();
+    notifications = [];
+    window.moveBy(10, 0);
+    yield waitUntilNotificationsReceived();
+    is(notifications.length, 1,
+       description + "window.moveBy(10, 0) should cause a notification");
+    checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
+    dumpUnexpectedNotifications(1);
+  }
+
+  input.focus();
+  input.value = "abc"
+  notifications = [];
+  input.selectionStart = input.selectionEnd = 0;
+  yield waitUntilNotificationsReceived();
+  notifications = [];
+  var rightArrowKeyEvent =
+    new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
+  TIP.keydown(rightArrowKeyEvent);
+  TIP.keyup(rightArrowKeyEvent);
+  is(notifications.length, 1,
+     description + "ArrowRight key press should cause a notification");
+  checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
+  dumpUnexpectedNotifications(1);
+
+  notifications = [];
+  var shiftKeyEvent =
+    new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
+  var leftArrowKeyEvent =
+    new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
+  TIP.keydown(shiftKeyEvent);
+  TIP.keydown(leftArrowKeyEvent);
+  TIP.keyup(leftArrowKeyEvent);
+  TIP.keyup(shiftKeyEvent);
+  is(notifications.length, 1,
+     description + "ArrowLeft key press with Shift should cause a notification");
+  checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
+  dumpUnexpectedNotifications(1);
+
+  TIP.keydown(rightArrowKeyEvent);
+  TIP.keyup(rightArrowKeyEvent);
+  notifications = [];
+  TIP.keydown(shiftKeyEvent);
+  TIP.keydown(rightArrowKeyEvent);
+  TIP.keyup(rightArrowKeyEvent);
+  TIP.keyup(shiftKeyEvent);
+  is(notifications.length, 1,
+     description + "ArrowRight key press with Shift should cause a notification");
+  checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
   dumpUnexpectedNotifications(1);
 
   notifications = [];
   var TIP2 = createTIP();
   if (aForTests) {
     TIP2.beginInputTransactionForTests(window, callback);
   } else {
     TIP2.beginInputTransaction(window, callback);
   }
   is(notifications.length, 1,
      description + "Initializing another TIP should cause a notification");
   is(notifications[0].type, "notify-end-input-transaction",
      description + "Initializing another TIP should cause \"notify-detached\"");
   dumpUnexpectedNotifications(1);
 }
 
-function runTests()
+var gTestContinuation = null;
+
+function continueTest()
 {
-  textareaInFrame = iframe.contentDocument.getElementById("textarea");
-
+  if (!gTestContinuation) {
+    gTestContinuation = testBody();
+  }
+  var ret = gTestContinuation.next();
+  if (ret.done) {
+    finish();
+  }
+}
+
+function* testBody()
+{
   runBeginInputTransactionMethodTests();
   runReleaseTests();
   runCompositionTests();
   runCompositionWithKeyEventTests();
   runConsumingKeydownBeforeCompositionTests();
   runKeyTests();
   runErrorTests();
   runCommitCompositionTests();
-  runCallbackTests(false);
-  runCallbackTests(true);
-  runUnloadTests1(function () {
-    runUnloadTests2(function () {
-      finish();
-    });
-  });
+  yield* runCallbackTests(false);
+  yield* runCallbackTests(true);
+  yield runUnloadTests1();
+  yield runUnloadTests2();
+}
+
+function runTests()
+{
+  textareaInFrame = iframe.contentDocument.getElementById("textarea");
+  continueTest();
 }
 
 ]]>
 </script>
 
 </window>
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -283,16 +283,18 @@ private:
 // Handler for click to play plugin (vulnerable w/no update)
 #define NS_EVENT_STATE_VULNERABLE_NO_UPDATE NS_DEFINE_EVENT_STATE_MACRO(42)
 // Element has focus-within.
 #define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(43)
 // Element is ltr (for :dir pseudo-class)
 #define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(44)
 // Element is rtl (for :dir pseudo-class)
 #define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(45)
+// Element is filled by Autofill feature.
+#define NS_EVENT_STATE_AUTOFILL NS_DEFINE_EVENT_STATE_MACRO(50)
 
 // Event state that is used for values that need to be parsed but do nothing.
 #define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63)
 
 /**
  * NOTE: do not go over 63 without updating EventStates::InternalType!
  */
 
@@ -301,17 +303,17 @@ private:
 // Event states that can be added and removed through
 // Element::{Add,Remove}ManuallyManagedStates.
 //
 // Take care when manually managing state bits.  You are responsible for
 // setting or clearing the bit when an Element is added or removed from a
 // document (e.g. in BindToTree and UnbindFromTree), if that is an
 // appropriate thing to do for your state bit.
 #define MANUALLY_MANAGED_STATES (             \
-  mozilla::EventStates() /* none so far */    \
+  NS_EVENT_STATE_AUTOFILL                     \
 )
 
 // Event states that are managed externally to an element (by the
 // EventStateManager, or by other code).  As opposed to those in
 // INTRINSIC_STATES, which are are computed by the element itself
 // and returned from Element::IntrinsicState.
 #define EXTERNALLY_MANAGED_STATES (           \
   MANUALLY_MANAGED_STATES |                   \
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -130,18 +130,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IM
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootContent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
 
-  tmp->mIMENotificationRequests.mWantUpdates =
-    IMENotificationRequests::NOTIFY_NOTHING;
+  tmp->mIMENotificationRequests = nullptr;
   tmp->mESM = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootContent)
@@ -163,16 +162,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
 
 IMEContentObserver::IMEContentObserver()
   : mESM(nullptr)
+  , mIMENotificationRequests(nullptr)
   , mSuppressNotifications(0)
   , mPreCharacterDataChangeLength(-1)
   , mSendingNotification(NOTIFY_IME_OF_NOTHING)
   , mIsObserving(false)
   , mIMEHasFocus(false)
   , mNeedsToNotifyIMEOfFocusSet(false)
   , mNeedsToNotifyIMEOfTextChange(false)
   , mNeedsToNotifyIMEOfSelectionChange(false)
@@ -204,16 +204,17 @@ IMEContentObserver::Init(nsIWidget* aWid
     // Clear members which may not be initialized again.
     Clear();
   }
 
   mESM = aPresContext->EventStateManager();
   mESM->OnStartToObserveContent(this);
 
   mWidget = aWidget;
+  mIMENotificationRequests = &mWidget->IMENotificationRequestsRef();
 
   if (aWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
     if (!InitWithPlugin(aPresContext, aContent)) {
       Clear();
       return;
     }
   } else {
     if (!InitWithEditor(aPresContext, aContent, aEditor)) {
@@ -413,61 +414,60 @@ void
 IMEContentObserver::ObserveEditableNode()
 {
   MOZ_RELEASE_ASSERT(mSelection);
   MOZ_RELEASE_ASSERT(mRootContent);
   MOZ_RELEASE_ASSERT(GetState() != eState_Observing);
 
   // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when
   // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously),
-  // the update preference of mWidget may be different from after the widget
+  // the notification requests of mWidget may be different from after the widget
   // receives NOTIFY_IME_OF_FOCUS.   So, this should be called again by
   // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS.
   if (!mIMEHasFocus) {
     MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet ||
                mSendingNotification == NOTIFY_IME_OF_FOCUS,
                "Wow, OnIMEReceivedFocus() won't be called?");
     return;
   }
 
   mIsObserving = true;
   if (mEditor) {
     mEditor->AddEditorObserver(this);
   }
 
-  mIMENotificationRequests = mWidget->GetIMENotificationRequests();
   if (!WasInitializedWithPlugin()) {
     // Add selection change listener only when this starts to observe
     // non-plugin content since we cannot detect selection changes in
     // plugins.
     nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
     NS_ENSURE_TRUE_VOID(selPrivate);
     nsresult rv = selPrivate->AddSelectionListener(this);
     NS_ENSURE_SUCCESS_VOID(rv);
-  }
-
-  if (mIMENotificationRequests.WantTextChange()) {
-    // add text change observer
+    // Add text change observer only when this starts to observe
+    // non-plugin content since we cannot detect text changes in
+    // plugins.
     mRootContent->AddMutationObserver(this);
   }
 
-  if (mIMENotificationRequests.WantPositionChanged() && mDocShell) {
-    // Add scroll position listener and reflow observer to detect position and
-    // size changes
+  if (mDocShell) {
+    // Add scroll position listener and reflow observer to detect position
+    // and size changes
     mDocShell->AddWeakScrollObserver(this);
     mDocShell->AddWeakReflowObserver(this);
   }
 }
 
 void
 IMEContentObserver::NotifyIMEOfBlur()
 {
   // Prevent any notifications to be sent IME.
   nsCOMPtr<nsIWidget> widget;
   mWidget.swap(widget);
+  mIMENotificationRequests = nullptr;
 
   // If we hasn't been set focus, we shouldn't send blur notification to IME.
   if (!mIMEHasFocus) {
     return;
   }
 
   // mWidget must have been non-nullptr if IME has focus.
   MOZ_RELEASE_ASSERT(widget);
@@ -510,21 +510,21 @@ IMEContentObserver::UnregisterObservers(
     nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
     if (selPrivate) {
       selPrivate->RemoveSelectionListener(this);
     }
     mSelectionData.Clear();
     mFocusedWidget = nullptr;
   }
 
-  if (mIMENotificationRequests.WantTextChange() && mRootContent) {
+  if (mRootContent) {
     mRootContent->RemoveMutationObserver(this);
   }
 
-  if (mIMENotificationRequests.WantPositionChanged() && mDocShell) {
+  if (mDocShell) {
     mDocShell->RemoveWeakScrollObserver(this);
     mDocShell->RemoveWeakReflowObserver(this);
   }
 }
 
 nsPresContext*
 IMEContentObserver::GetPresContext() const
 {
@@ -536,18 +536,17 @@ IMEContentObserver::Destroy()
 {
   // WARNING: When you change this method, you have to check Unlink() too.
 
   NotifyIMEOfBlur();
   UnregisterObservers();
   Clear();
 
   mWidget = nullptr;
-  mIMENotificationRequests.mWantUpdates =
-    IMENotificationRequests::NOTIFY_NOTHING;
+  mIMENotificationRequests = nullptr;
 
   if (mESM) {
     mESM->OnStopObservingContent(this);
     mESM = nullptr;
   }
 }
 
 void
@@ -685,31 +684,43 @@ IMEContentObserver::NotifySelectionChang
                                     duringComposition);
   }
   return NS_OK;
 }
 
 void
 IMEContentObserver::ScrollPositionChanged()
 {
+  if (!NeedsPositionChangeNotification()) {
+    return;
+  }
+
   MaybeNotifyIMEOfPositionChange();
 }
 
 NS_IMETHODIMP
 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
                            DOMHighResTimeStamp aEnd)
 {
+  if (!NeedsPositionChangeNotification()) {
+    return NS_OK;
+  }
+
   MaybeNotifyIMEOfPositionChange();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
                                         DOMHighResTimeStamp aEnd)
 {
+  if (!NeedsPositionChangeNotification()) {
+    return NS_OK;
+  }
+
   MaybeNotifyIMEOfPositionChange();
   return NS_OK;
 }
 
 nsresult
 IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
 {
   // If the instance has normal selection cache and the query event queries
@@ -781,17 +792,18 @@ IMEContentObserver::HandleQueryContentEv
   }
   return rv;
 }
 
 bool
 IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
                                        WidgetMouseEvent* aMouseEvent)
 {
-  if (!mIMENotificationRequests.WantMouseButtonEventOnChar()) {
+  if (!mIMENotificationRequests ||
+      !mIMENotificationRequests->WantMouseButtonEventOnChar()) {
     return false;
   }
   if (!aMouseEvent->IsTrusted() ||
       aMouseEvent->DefaultPrevented() ||
       !aMouseEvent->mWidget) {
     return false;
   }
   // Now, we need to notify only mouse down and mouse up event.
@@ -868,16 +880,20 @@ IMEContentObserver::CharacterDataWillCha
                                             CharacterDataChangeInfo* aInfo)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "character data changed for non-text node");
   MOZ_ASSERT(mPreCharacterDataChangeLength < 0,
              "CharacterDataChanged() should've reset "
              "mPreCharacterDataChangeLength");
 
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   mPreCharacterDataChangeLength =
     ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
                                              aInfo->mChangeEnd);
   MOZ_ASSERT(mPreCharacterDataChangeLength >=
                aInfo->mChangeEnd - aInfo->mChangeStart,
              "The computed length must be same as or larger than XP length");
@@ -886,16 +902,20 @@ IMEContentObserver::CharacterDataWillCha
 void
 IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument,
                                          nsIContent* aContent,
                                          CharacterDataChangeInfo* aInfo)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "character data changed for non-text node");
 
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
 
   int64_t removedLength = mPreCharacterDataChangeLength;
   mPreCharacterDataChangeLength = -1;
 
   MOZ_ASSERT(removedLength >= 0,
              "mPreCharacterDataChangeLength should've been set by "
@@ -926,16 +946,20 @@ IMEContentObserver::CharacterDataChanged
   MaybeNotifyIMEOfTextChange(data);
 }
 
 void
 IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
                                        int32_t aStartIndex,
                                        int32_t aEndIndex)
 {
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mStartOfRemovingTextRangeCache.Clear();
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) {
     mEndOfAddedTextCache.Clear();
     rv = ContentEventHandler::GetFlatTextLengthInRange(
                                 NodePosition(mRootContent, 0),
@@ -998,16 +1022,20 @@ IMEContentObserver::ContentInserted(nsID
 
 void
 IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
                                    int32_t aIndexInContainer,
                                    nsIContent* aPreviousSibling)
 {
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mEndOfAddedTextCache.Clear();
 
   nsINode* containerNode = NODE_FROM(aContainer, aDocument);
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mStartOfRemovingTextRangeCache.Match(containerNode, aIndexInContainer)) {
     // At removing a child node of aContainer, we need the line break caused
@@ -1056,28 +1084,36 @@ IMEContentObserver::ContentRemoved(nsIDo
 void
 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
                                         dom::Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute,
                                         int32_t aModType,
                                         const nsAttrValue* aNewValue)
 {
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mPreAttrChangeLength =
     ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
 }
 
 void
 IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
                                      dom::Element* aElement,
                                      int32_t aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      int32_t aModType,
                                      const nsAttrValue* aOldValue)
 {
+  if (!NeedsTextChangeNotification()) {
+    return;
+  }
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
 
   uint32_t postAttrChangeLength =
     ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
   if (postAttrChangeLength == mPreAttrChangeLength) {
     return;
   }
@@ -1210,16 +1246,25 @@ IMEContentObserver::MaybeNotifyIMEOfText
      this, TextChangeDataToString(aTextChangeData).get()));
 
   mTextChangeData += aTextChangeData;
   PostTextChangeNotification();
   FlushMergeableNotifications();
 }
 
 void
+IMEContentObserver::CancelNotifyingIMEOfTextChange()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("0x%p IMEContentObserver::CancelNotifyingIMEOfTextChange()", this));
+  mTextChangeData.Clear();
+  mNeedsToNotifyIMEOfTextChange = false;
+}
+
+void
 IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
                       bool aCausedByComposition,
                       bool aCausedBySelectionEvent,
                       bool aOccurredDuringComposition)
 {
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
     ("0x%p IMEContentObserver::MaybeNotifyIMEOfSelectionChange("
      "aCausedByComposition=%s, aCausedBySelectionEvent=%s, "
@@ -1250,16 +1295,24 @@ IMEContentObserver::MaybeNotifyIMEOfPosi
        "NOTIY_IME_OF_POSITION_CHANGE", this));
     return;
   }
   PostPositionChangeNotification();
   FlushMergeableNotifications();
 }
 
 void
+IMEContentObserver::CancelNotifyingIMEOfPositionChange()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("0x%p IMEContentObserver::CancelNotifyIMEOfPositionChange()", this));
+  mNeedsToNotifyIMEOfPositionChange = false;
+}
+
+void
 IMEContentObserver::MaybeNotifyCompositionEventHandled()
 {
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
     ("0x%p IMEContentObserver::MaybeNotifyCompositionEventHandled()",
      this));
 
   PostCompositionEventHandledNotification();
   FlushMergeableNotifications();
@@ -1391,16 +1444,25 @@ IMEContentObserver::FlushMergeableNotifi
   if (mQueuedSender) {
     // So, if this is already called, this should do nothing.
     MOZ_LOG(sIMECOLog, LogLevel::Debug,
       ("0x%p   IMEContentObserver::FlushMergeableNotifications(), "
        "FAILED, due to already flushing pending notifications", this));
     return;
   }
 
+  // If text change notification and/or position change notification becomes
+  // unnecessary, let's cancel them.
+  if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
+    CancelNotifyingIMEOfTextChange();
+  }
+  if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
+    CancelNotifyingIMEOfPositionChange();
+  }
+
   if (!NeedsToNotifyIMEOfSomething()) {
     MOZ_LOG(sIMECOLog, LogLevel::Debug,
       ("0x%p   IMEContentObserver::FlushMergeableNotifications(), "
        "FAILED, due to no pending notifications", this));
     return;
   }
 
   // NOTE: Reset each pending flag because sending notification may cause
@@ -1775,16 +1837,26 @@ IMEContentObserver::IMENotificationSende
     MOZ_LOG(sIMECOLog, LogLevel::Debug,
       ("0x%p   IMEContentObserver::IMENotificationSender::"
        "SendTextChange(), retrying to send NOTIFY_IME_OF_TEXT_CHANGE...",
        this));
     mIMEContentObserver->PostTextChangeNotification();
     return;
   }
 
+  // If text change notification is unnecessary anymore, just cancel it.
+  if (!mIMEContentObserver->NeedsTextChangeNotification()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Warning,
+      ("0x%p   IMEContentObserver::IMENotificationSender::"
+       "SendTextChange(), canceling sending NOTIFY_IME_OF_TEXT_CHANGE",
+       this));
+    mIMEContentObserver->CancelNotifyingIMEOfTextChange();
+    return;
+  }
+
   MOZ_LOG(sIMECOLog, LogLevel::Info,
     ("0x%p IMEContentObserver::IMENotificationSender::"
      "SendTextChange(), sending NOTIFY_IME_OF_TEXT_CHANGE... "
      "mIMEContentObserver={ mTextChangeData=%s }",
      this, TextChangeDataToString(mIMEContentObserver->mTextChangeData).get()));
 
   IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
   notification.SetData(mIMEContentObserver->mTextChangeData);
@@ -1816,16 +1888,26 @@ IMEContentObserver::IMENotificationSende
     MOZ_LOG(sIMECOLog, LogLevel::Debug,
       ("0x%p   IMEContentObserver::IMENotificationSender::"
        "SendPositionChange(), retrying to send "
        "NOTIFY_IME_OF_POSITION_CHANGE...", this));
     mIMEContentObserver->PostPositionChangeNotification();
     return;
   }
 
+  // If position change notification is unnecessary anymore, just cancel it.
+  if (!mIMEContentObserver->NeedsPositionChangeNotification()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Warning,
+      ("0x%p   IMEContentObserver::IMENotificationSender::"
+       "SendPositionChange(), canceling sending NOTIFY_IME_OF_POSITION_CHANGE",
+       this));
+    mIMEContentObserver->CancelNotifyingIMEOfPositionChange();
+    return;
+  }
+
   MOZ_LOG(sIMECOLog, LogLevel::Info,
     ("0x%p IMEContentObserver::IMENotificationSender::"
      "SendPositionChange(), sending NOTIFY_IME_OF_POSITION_CHANGE...", this));
 
   MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
                        NOTIFY_IME_OF_NOTHING);
   mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE;
   IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -94,17 +94,18 @@ public:
                          nsIContent* aContent,
                          nsIEditor* aEditor);
   bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const;
   bool IsManaging(const TextComposition* aTextComposition) const;
   bool WasInitializedWithPlugin() const;
   bool IsEditorHandlingEventForComposition() const;
   bool KeepAliveDuringDeactive() const
   {
-    return mIMENotificationRequests.WantDuringDeactive();
+    return mIMENotificationRequests &&
+           mIMENotificationRequests->WantDuringDeactive();
   }
   nsIWidget* GetWidget() const { return mWidget; }
   nsIEditor* GetEditor() const { return mEditor; }
   void SuppressNotifyingIME();
   void UnsuppressNotifyingIME();
   nsPresContext* GetPresContext() const;
   nsresult GetSelectionAndRoot(nsISelection** aSelection,
                                nsIContent** aRoot) const;
@@ -142,35 +143,47 @@ private:
   bool IsReflowLocked() const;
   bool IsSafeToNotifyIME() const;
   bool IsEditorComposing() const;
 
   void PostFocusSetNotification();
   void MaybeNotifyIMEOfFocusSet();
   void PostTextChangeNotification();
   void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
+  void CancelNotifyingIMEOfTextChange();
   void PostSelectionChangeNotification();
   void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
                                        bool aCausedBySelectionEvent,
                                        bool aOccurredDuringComposition);
   void PostPositionChangeNotification();
   void MaybeNotifyIMEOfPositionChange();
+  void CancelNotifyingIMEOfPositionChange();
   void PostCompositionEventHandledNotification();
 
   void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
   void ObserveEditableNode();
   /**
    *  NotifyIMEOfBlur() notifies IME of blur.
    */
   void NotifyIMEOfBlur();
   /**
    *  UnregisterObservers() unregisters all listeners and observers.
    */
   void UnregisterObservers();
   void FlushMergeableNotifications();
+  bool NeedsTextChangeNotification() const
+  {
+    return mIMENotificationRequests &&
+           mIMENotificationRequests->WantTextChange();
+  }
+  bool NeedsPositionChangeNotification() const
+  {
+    return mIMENotificationRequests &&
+           mIMENotificationRequests->WantPositionChanged();
+  }
   void ClearPendingNotifications()
   {
     mNeedsToNotifyIMEOfFocusSet = false;
     mNeedsToNotifyIMEOfTextChange = false;
     mNeedsToNotifyIMEOfSelectionChange = false;
     mNeedsToNotifyIMEOfPositionChange = false;
     mNeedsToNotifyIMEOfCompositionEventHandled = false;
     mTextChangeData.Clear();
@@ -321,17 +334,17 @@ private:
 
   // mSelectionData is the last selection data which was notified.  The
   // selection information is modified by UpdateSelectionCache().  The reason
   // of the selection change is modified by MaybeNotifyIMEOfSelectionChange().
   SelectionChangeData mSelectionData;
 
   EventStateManager* mESM;
 
-  IMENotificationRequests mIMENotificationRequests;
+  const IMENotificationRequests* mIMENotificationRequests;
   uint32_t mPreAttrChangeLength;
   uint32_t mSuppressNotifications;
   int64_t mPreCharacterDataChangeLength;
 
   // mSendingNotification is a notification which is now sending from
   // IMENotificationSender.  When the value is NOTIFY_IME_OF_NOTHING, it's
   // not sending any notification.
   IMEMessage mSendingNotification;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -17,16 +17,17 @@
 
 #include "base/basictypes.h"
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsIDOMHTMLSourceElement.h"
 #include "TimeRanges.h"
 #include "nsGenericHTMLElement.h"
 #include "nsAttrValueInlines.h"
 #include "nsPresContext.h"
+#include "nsIClassOfService.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "nsSize.h"
 #include "nsIFrame.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDocShell.h"
 #include "nsError.h"
@@ -1154,16 +1155,22 @@ public:
                                 nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
 
     if (NS_FAILED(rv)) {
       // Notify load error so the element will try next resource candidate.
       aElement->NotifyLoadError();
       return;
     }
 
+    nsCOMPtr<nsIClassOfService> cos;
+    if (aElement->mUseUrgentStartForChannel &&
+        (cos = do_QueryInterface(channel))) {
+      cos->AddClassFlags(nsIClassOfService::UrgentStart);
+    }
+
     // The listener holds a strong reference to us.  This creates a
     // reference cycle, once we've set mChannel, which is manually broken
     // in the listener's OnStartRequest method after it is finished with
     // the element. The cycle will also be broken if we get a shutdown
     // notification before OnStartRequest fires.  Necko guarantees that
     // OnStartRequest will eventually fire if we don't shut down first.
     RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
 
@@ -3886,16 +3893,24 @@ HTMLMediaElement::PlayInternal(ErrorResu
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   mPendingPlayPromises.AppendElement(promise);
 
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
+  if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
+    // The media load algorithm will be initiated by a user interaction.
+    // We want to boost the channel priority for better responsiveness.
+    // Note this must be done before UpdatePreloadAction() which will
+    // update |mPreloadAction|.
+    mUseUrgentStartForChannel = true;
+  }
+
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
   // 4.8.12.8 - Step 4:
   // If the media element's networkState attribute has the value NETWORK_EMPTY,
   // invoke the media element's resource selection algorithm.
   MaybeDoLoad();
   if (mSuspendedForPreloadNone) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1619,16 +1619,20 @@ protected:
   bool mSuspendedForPreloadNone;
 
   // True if we've connected mSrcStream to the media element output.
   bool mSrcStreamIsPlaying;
 
   // True if a same-origin check has been done for the media element and resource.
   bool mMediaSecurityVerified;
 
+  // True if we should set nsIClassOfService::UrgentStart to the channel to
+  // get the response ASAP for better user responsiveness.
+  bool mUseUrgentStartForChannel = false;
+
   // The CORS mode when loading the media element
   CORSMode mCORSMode;
 
   // Info about the played media.
   MediaInfo mMediaInfo;
 
   // True if the media has encryption information.
   bool mIsEncrypted;
--- a/dom/interfaces/base/nsITextInputProcessorCallback.idl
+++ b/dom/interfaces/base/nsITextInputProcessorCallback.idl
@@ -39,18 +39,153 @@ interface nsITextInputProcessorNotificat
    * "notify-focus" (optional)
    *   This is notified when an editable editor gets focus and Gecko starts
    *   to observe changes in the content. E.g., selection changes.
    *   IME shouldn't change DOM tree, focus nor something when this is notified.
    *
    * "notify-blur" (optional)
    *   This is notified when an editable editor loses focus and Gecko stops
    *   observing the changes in the content.
+   *
+   * "notify-text-change" (optional)
+   *   This is notified when text in the focused editor is modified.
+   *   Some attributes below are available to retrieve the detail.
+   *   IME shouldn't change DOM tree, focus nor something when this is notified.
+   *   Note that when there is no chance to notify you of some text changes
+   *   safely, this represents all changes as a change.
+   *
+   * "notify-selection-change" (optional)
+   *   This is notified when selection in the focused editor is changed.
+   *   Some attributes below are available to retrieve the detail.
+   *   IME shouldn't change DOM tree, focus nor something when this is notified.
+   *   Note that when there was no chance to notify you of this safely, this
+   *   represents the latest selection change.
+   *
+   * "notify-position-change" (optional)
+   *   This is notified when layout is changed in the editor or the window
+   *   is moved.
+   *   IME shouldn't change DOM tree, focus nor something when this is notified.
+   *   Note that when there was no chance to notify you of this safely, this
+   *   represents the latest layout change.
    */
   readonly attribute ACString type;
+
+  /**
+   * Be careful, line breakers in the editor are treated as native line
+   * breakers.  I.e., on Windows, a line breaker is "\r\n" (CRLF).  On the
+   * others, it is "\n" (LF).  If you want TextInputProcessor to treat line
+   * breakers on Windows as XP line breakers (LF), please file a bug with
+   * the reason why you need the behavior.
+   */
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change" or
+   * "notify-selection-change".
+   * This is offset of the start of modified text range if type is
+   * "notify-text-change".  Or offset of start of selection if type is
+   * "notify-selection-change".
+   */
+  readonly attribute unsigned long offset;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * This is selected text.  I.e., the length is selected length and
+   * it's empty if the selection is collapsed.
+   */
+  readonly attribute AString text;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * This is set to true when the selection is collapsed.  Otherwise, false.
+   */
+  readonly attribute boolean collapsed;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * This is selected length.  I.e., if this is 0, collapsed is set to true.
+   */
+  readonly attribute uint32_t length;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * When selection is created from latter point to former point, this is
+   * set to true.  Otherwise, false.
+   * I.e., if this is true, offset + length is the anchor of selection.
+   */
+  readonly attribute boolean reversed;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * This indicates the start of the selection's writing mode.
+   * The value can be "horizontal-tb", "vertical-rl" or "vertical-lr".
+   */
+  readonly attribute ACString writingMode;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * If the selection change was caused by composition, this is set to true.
+   * Otherwise, false.
+   */
+  readonly attribute boolean causedByComposition;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * If the selection change was caused by selection event, this is set to true.
+   * Otherwise, false.
+   */
+  readonly attribute boolean causedBySelectionEvent;
+
+  /**
+   * This attribute has a valid value when type is "notify-selection-change".
+   * If the selection change occurred during composition, this is set to true.
+   * Otherwise, false.
+   */
+  readonly attribute boolean occurredDuringComposition;
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change".
+   * This is removed text length by the change(s).  If this is empty, new text
+   * was just inserted.  Otherwise, the text is replaced with new text.
+   */
+  readonly attribute unsigned long removedLength;
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change".
+   * This is added text length by the change(s).  If this is empty, old text
+   * was just deleted.  Otherwise, the text replaces the old text.
+   */
+  readonly attribute unsigned long addedLength;
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change".
+   * If the text change(s) was caused only by composition, this is set to true.
+   * Otherwise, false.  I.e., if one of text changes are caused by JS or
+   * modifying without composition, this is set to false.
+   */
+  readonly attribute boolean causedOnlyByComposition;
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change".
+   * If at least one text change not caused by composition occurred during
+   * composition, this is set to true.  Otherwise, false.
+   * Note that this is set to false when new change is caused by neither
+   * composition nor occurred during composition because it's outdated for
+   * new composition.
+   * In other words, when text changes not caused by composition occurred
+   * during composition and it may cause committing composition, this is
+   * set to true.
+   */
+  readonly attribute boolean includingChangesDuringComposition;
+
+  /**
+   * This attribute has a valid value when type is "notify-text-change".
+   * If at least one text change occurred when there was no composition, this
+   * is set to true.  Otherwise, false.
+   */
+  readonly attribute boolean includingChangesWithoutComposition;
 };
 
 /**
  * nsITextInputProcessorCallback is a callback interface for JS to implement
  * IME.  IME implemented by JS can implement onNotify() function and must send
  * it to nsITextInputProcessor at initializing.  Then, onNotify() will be
  * called with nsITextInputProcessorNotification instance.
  * The reason why onNotify() uses string simply is that if we will support
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1737,32 +1737,33 @@ TabParent::RecvNotifyIMEFocus(const Cont
     *aRequests = IMENotificationRequests();
     return IPC_OK();
   }
 
   mContentCache.AssignContent(aContentCache, widget, &aIMENotification);
   IMEStateManager::NotifyIME(aIMENotification, widget, true);
 
   if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
-    *aRequests = widget->GetIMENotificationRequests();
+    *aRequests = widget->IMENotificationRequestsRef();
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvNotifyIMETextChange(const ContentCache& aContentCache,
                                    const IMENotification& aIMENotification)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
 #ifdef DEBUG
-  IMENotificationRequests requests = widget->GetIMENotificationRequests();
+  const IMENotificationRequests& requests =
+    widget->IMENotificationRequestsRef();
   NS_ASSERTION(requests.WantTextChange(),
     "Don't call Send/RecvNotifyIMETextChange without NOTIFY_TEXT_CHANGE");
 #endif
 
   mContentCache.AssignContent(aContentCache, widget, &aIMENotification);
   mContentCache.MaybeNotifyIME(widget, aIMENotification);
   return IPC_OK();
 }
--- a/dom/media/ADTSDemuxer.cpp
+++ b/dom/media/ADTSDemuxer.cpp
@@ -294,16 +294,18 @@ InitAudioSpecificConfig(const Frame& fra
   asc[0] = (audioObjectType & 0x1F) << 3 | (samplingFrequencyIndex & 0x0E) >> 1;
   asc[1] = (samplingFrequencyIndex & 0x01) << 7 | (channelConfig & 0x0F) << 3;
 
   aBuffer->AppendElements(asc, 2);
 }
 
 } // namespace adts
 
+using media::TimeUnit;
+
 // ADTSDemuxer
 
 ADTSDemuxer::ADTSDemuxer(MediaResource* aSource)
   : mSource(aSource)
 {
 }
 
 bool
@@ -379,29 +381,29 @@ ADTSTrackDemuxer::ADTSTrackDemuxer(Media
 ADTSTrackDemuxer::~ADTSTrackDemuxer()
 {
   delete mParser;
 }
 
 bool
 ADTSTrackDemuxer::Init()
 {
-  FastSeek(media::TimeUnit());
+  FastSeek(TimeUnit::Zero());
   // Read the first frame to fetch sample rate and other meta data.
   RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame(true)));
 
   ADTSLOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
           StreamLength(), !!frame);
 
   if (!frame) {
     return false;
   }
 
   // Rewind back to the stream begin to avoid dropping the first frame.
-  FastSeek(media::TimeUnit());
+  FastSeek(TimeUnit::Zero());
 
   if (!mInfo) {
     mInfo = MakeUnique<AudioInfo>();
   }
 
   mInfo->mRate = mSamplesPerSecond;
   mInfo->mChannels = mChannels;
   mInfo->mBitDepth = 16;
@@ -432,28 +434,28 @@ ADTSTrackDemuxer::Init()
 
 UniquePtr<TrackInfo>
 ADTSTrackDemuxer::GetInfo() const
 {
   return mInfo->Clone();
 }
 
 RefPtr<ADTSTrackDemuxer::SeekPromise>
-ADTSTrackDemuxer::Seek(const media::TimeUnit& aTime)
+ADTSTrackDemuxer::Seek(const TimeUnit& aTime)
 {
   // Efficiently seek to the position.
   FastSeek(aTime);
   // Correct seek position by scanning the next frames.
-  const media::TimeUnit seekTime = ScanUntil(aTime);
+  const TimeUnit seekTime = ScanUntil(aTime);
 
   return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
-media::TimeUnit
-ADTSTrackDemuxer::FastSeek(const media::TimeUnit& aTime)
+TimeUnit
+ADTSTrackDemuxer::FastSeek(const TimeUnit& aTime)
 {
   ADTSLOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
          " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
          aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
          mFrameIndex, mOffset);
 
   const int64_t firstFrameOffset = mParser->FirstFrame().Offset();
   if (!aTime.ToMicroseconds()) {
@@ -475,18 +477,18 @@ ADTSTrackDemuxer::FastSeek(const media::
           " mFrameIndex=%" PRId64 " mFirstFrameOffset=%" PRIu64 " mOffset=%" PRIu64
           " SL=%" PRIu64 "",
           AverageFrameLength(), mNumParsedFrames, mFrameIndex,
           firstFrameOffset, mOffset, StreamLength());
 
   return Duration(mFrameIndex);
 }
 
-media::TimeUnit
-ADTSTrackDemuxer::ScanUntil(const media::TimeUnit& aTime)
+TimeUnit
+ADTSTrackDemuxer::ScanUntil(const TimeUnit& aTime)
 {
   ADTSLOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
           " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
           aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
           mFrameIndex, mOffset);
 
   if (!aTime.ToMicroseconds()) {
     return FastSeek(aTime);
@@ -552,75 +554,74 @@ ADTSTrackDemuxer::GetSamples(int32_t aNu
 void
 ADTSTrackDemuxer::Reset()
 {
   ADTSLOG("Reset()");
   MOZ_ASSERT(mParser);
   if (mParser) {
     mParser->Reset();
   }
-  FastSeek(media::TimeUnit());
+  FastSeek(TimeUnit::Zero());
 }
 
 RefPtr<ADTSTrackDemuxer::SkipAccessPointPromise>
-ADTSTrackDemuxer::SkipToNextRandomAccessPoint(
-  const media::TimeUnit& aTimeThreshold)
+ADTSTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
 {
   // Will not be called for audio-only resources.
   return SkipAccessPointPromise::CreateAndReject(
     SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__);
 }
 
 int64_t
 ADTSTrackDemuxer::GetResourceOffset() const
 {
   return mOffset;
 }
 
 media::TimeIntervals
 ADTSTrackDemuxer::GetBuffered()
 {
-  media::TimeUnit duration = Duration();
+  auto duration = Duration();
 
-  if (duration <= media::TimeUnit()) {
+  if (!duration.IsPositive()) {
     return media::TimeIntervals();
   }
 
   AutoPinned<MediaResource> stream(mSource.GetResource());
   return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds());
 }
 
 int64_t
 ADTSTrackDemuxer::StreamLength() const
 {
   return mSource.GetLength();
 }
 
-media::TimeUnit
+TimeUnit
 ADTSTrackDemuxer::Duration() const
 {
   if (!mNumParsedFrames) {
-    return media::TimeUnit::FromMicroseconds(-1);
+    return TimeUnit::FromMicroseconds(-1);
   }
 
   const int64_t streamLen = StreamLength();
   if (streamLen < 0) {
     // Unknown length, we can't estimate duration.
-    return media::TimeUnit::FromMicroseconds(-1);
+    return TimeUnit::FromMicroseconds(-1);
   }
   const int64_t firstFrameOffset = mParser->FirstFrame().Offset();
   int64_t numFrames = (streamLen - firstFrameOffset) / AverageFrameLength();
   return Duration(numFrames);
 }
 
-media::TimeUnit
+TimeUnit
 ADTSTrackDemuxer::Duration(int64_t aNumFrames) const
 {
   if (!mSamplesPerSecond) {
-    return media::TimeUnit::FromMicroseconds(-1);
+    return TimeUnit::FromMicroseconds(-1);
   }
 
   return FramesToTimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond);
 }
 
 const adts::Frame&
 ADTSTrackDemuxer::FindNextFrame(bool findFirstFrame /*= false*/)
 {
@@ -779,17 +780,17 @@ ADTSTrackDemuxer::FrameIndexFromOffset(i
       (aOffset - mParser->FirstFrame().Offset()) / AverageFrameLength();
   }
 
   ADTSLOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
   return std::max<int64_t>(0, frameIndex);
 }
 
 int64_t
-ADTSTrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const
+ADTSTrackDemuxer::FrameIndexFromTime(const TimeUnit& aTime) const
 {
   int64_t frameIndex = 0;
   if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {
     frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1;
   }
 
   ADTSLOGV("FrameIndexFromOffset(%fs) -> %" PRId64,
            aTime.ToSeconds(), frameIndex);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1386,30 +1386,30 @@ MediaDecoder::GetSeekable()
   if (mMediaSeekableOnlyInBufferedRanges) {
     return GetBuffered();
   } else if (!IsMediaSeekable()) {
     return media::TimeIntervals();
   } else if (!IsTransportSeekable()) {
     return GetBuffered();
   } else {
     return media::TimeIntervals(
-      media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
+      media::TimeInterval(TimeUnit::Zero(),
                           IsInfinite()
-                          ? media::TimeUnit::FromInfinity()
-                          : media::TimeUnit::FromSeconds(GetDuration())));
+                          ? TimeUnit::FromInfinity()
+                          : TimeUnit::FromSeconds(GetDuration())));
   }
 }
 
 void
 MediaDecoder::SetFragmentEndTime(double aTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoderStateMachine) {
     mDecoderStateMachine->DispatchSetFragmentEndTime(
-      media::TimeUnit::FromSeconds(aTime));
+      TimeUnit::FromSeconds(aTime));
   }
 }
 
 void
 MediaDecoder::Suspend()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mResource) {
@@ -1765,22 +1765,21 @@ MediaDecoder::RemoveMediaTracks()
 }
 
 MediaDecoderOwner::NextFrameStatus
 MediaDecoder::NextFrameBufferedStatus()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Next frame hasn't been decoded yet.
   // Use the buffered range to consider if we have the next frame available.
-  media::TimeUnit currentPosition =
-    media::TimeUnit::FromMicroseconds(CurrentPosition());
+  TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
   media::TimeInterval interval(
     currentPosition,
     currentPosition
-    + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
+    + TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
   return GetBuffered().Contains(interval)
          ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
          : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 nsCString
 MediaDecoder::GetDebugInfo()
 {
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -551,17 +551,17 @@ public:
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override
   {
     return mDecoder->IsHardwareAccelerated(aFailureReason);
   }
   const char* GetDescriptionName() const override
   {
     return mDecoder->GetDescriptionName();
   }
-  void SetSeekThreshold(const media::TimeUnit& aTime) override
+  void SetSeekThreshold(const TimeUnit& aTime) override
   {
     mDecoder->SetSeekThreshold(aTime);
   }
   bool SupportDecoderRecycling() const override
   {
     return mDecoder->SupportDecoderRecycling();
   }
   RefPtr<ShutdownPromise> Shutdown() override
@@ -876,17 +876,17 @@ public:
   UniquePtr<TrackInfo> GetInfo() const override
   {
     if (!mInfo) {
       return nullptr;
     }
     return mInfo->Clone();
   }
 
-  RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override
+  RefPtr<SeekPromise> Seek(const TimeUnit& aTime) override
   {
     RefPtr<Wrapper> self = this;
     return InvokeAsync(
              mTaskQueue, __func__,
              [self, aTime]() { return self->mTrackDemuxer->Seek(aTime); })
       ->Then(mTaskQueue, __func__,
              [self]() { self->UpdateRandomAccessPoint(); },
              [self]() { self->UpdateRandomAccessPoint(); });
@@ -922,17 +922,17 @@ public:
     MutexAutoLock lock(mMutex);
     if (NS_SUCCEEDED(mNextRandomAccessPointResult)) {
       *aTime = mNextRandomAccessPoint;
     }
     return mNextRandomAccessPointResult;
   }
 
   RefPtr<SkipAccessPointPromise>
-  SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override
+  SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) override
   {
     RefPtr<Wrapper> self = this;
     return InvokeAsync(
              mTaskQueue, __func__,
              [self, aTimeThreshold]()  {
                return self->mTrackDemuxer->SkipToNextRandomAccessPoint(
                  aTimeThreshold);
              })
@@ -1419,18 +1419,18 @@ MediaFormatReader::OnDemuxerInitDone(con
   mTags = Move(tags);
   mInitDone = true;
 
   // Try to get the start time.
   // For MSE case, the start time of each track is assumed to be 0.
   // For others, we must demux the first sample to know the start time for each
   // track.
   if (!mDemuxer->ShouldComputeStartTime()) {
-    mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
-    mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
+    mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
+    mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
   } else {
     if (HasAudio()) {
       RequestDemuxSamples(TrackInfo::kAudioTrack);
     }
 
     if (HasVideo()) {
       RequestDemuxSamples(TrackInfo::kVideoTrack);
     }
@@ -1507,34 +1507,34 @@ MediaFormatReader::GetDecoderData(TrackT
   if (aTrack == TrackInfo::kAudioTrack) {
     return mAudio;
   }
   return mVideo;
 }
 
 bool
 MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe,
-                              media::TimeUnit aTimeThreshold)
+                              TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(HasVideo());
-  media::TimeUnit nextKeyframe;
+  TimeUnit nextKeyframe;
   nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
   if (NS_FAILED(rv)) {
     return aSkipToNextKeyframe;
   }
   return (nextKeyframe < aTimeThreshold
           || (mVideo.mTimeThreshold
               && mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold))
          && nextKeyframe.ToMicroseconds() >= 0
          && !nextKeyframe.IsInfinite();
 }
 
 RefPtr<MediaDecoderReader::VideoDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
-                                    const media::TimeUnit& aTimeThreshold)
+                                    const TimeUnit& aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(),
                         "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists()
                         || mVideo.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
@@ -2047,17 +2047,17 @@ MediaFormatReader::InternalSeek(TrackTyp
 
   auto& decoder = GetDecoderData(aTrack);
   decoder.Flush();
   decoder.ResetDemuxer();
   decoder.mTimeThreshold = Some(aTarget);
   RefPtr<MediaFormatReader> self = this;
   decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().Time())
     ->Then(OwnerThread(), __func__,
-           [self, aTrack] (media::TimeUnit aTime) {
+           [self, aTrack] (TimeUnit aTime) {
              auto& decoder = self->GetDecoderData(aTrack);
              decoder.mSeekRequest.Complete();
              MOZ_ASSERT(
                decoder.mTimeThreshold,
                "Seek promise must be disconnected when timethreshold is reset");
              decoder.mTimeThreshold.ref().mHasSeeked = true;
              self->SetVideoDecodeThreshold();
              self->ScheduleUpdate(aTrack);
@@ -2181,17 +2181,17 @@ MediaFormatReader::Update(TrackType aTra
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
 
   // Drop any frames found prior our internal seek target.
   while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
     RefPtr<MediaData>& output = decoder.mOutput[0];
     InternalSeekTarget target = decoder.mTimeThreshold.ref();
-    media::TimeUnit time = output->mTime;
+    auto time = output->mTime;
     if (time >= target.Time()) {
       // We have reached our internal seek target.
       decoder.mTimeThreshold.reset();
       // We might have dropped some keyframes.
       mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
     }
     if (time < target.Time() || (target.mDropTarget && target.Contains(time))) {
       LOGV("Internal Seeking: Dropping %s frame time:%f wanted:%f (kf:%d)",
@@ -2310,17 +2310,17 @@ MediaFormatReader::Update(TrackType aTra
     if (!needsNewDecoder
         && ++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
       NotifyError(aTrack, decoder.mError.ref());
       return;
     }
     decoder.mError.reset();
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
-    media::TimeUnit nextKeyframe;
+    TimeUnit nextKeyframe;
     if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending()
         && NS_SUCCEEDED(
              decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
       if (needsNewDecoder) {
         ShutdownDecoder(aTrack);
       }
       SkipVideoDemuxToNextKeyFrame(
         decoder.mLastSampleTime.refOr(TimeInterval()).Length());
@@ -2501,31 +2501,31 @@ MediaFormatReader::Reset(TrackType aTrac
 
 void
 MediaFormatReader::DropDecodedSamples(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   size_t lengthDecodedQueue = decoder.mOutput.Length();
   if (lengthDecodedQueue && decoder.mTimeThreshold.isSome()) {
-    TimeUnit time = decoder.mOutput.LastElement()->mTime;
+    auto time = decoder.mOutput.LastElement()->mTime;
     if (time >= decoder.mTimeThreshold.ref().Time()) {
       // We would have reached our internal seek target.
       decoder.mTimeThreshold.reset();
     }
   }
   decoder.mOutput.Clear();
   decoder.mSizeOfQueue -= lengthDecodedQueue;
   if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
     mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
   }
 }
 
 void
-MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
+MediaFormatReader::SkipVideoDemuxToNextKeyFrame(TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping up to %" PRId64, aTimeThreshold.ToMicroseconds());
 
   // We've reached SkipVideoDemuxToNextKeyFrame when our decoding is late.
   // As such we can drop all already decoded samples and discard all pending
   // samples.
   DropDecodedSamples(TrackInfo::kVideoTrack);
@@ -2706,17 +2706,17 @@ MediaFormatReader::OnSeekFailed(TrackTyp
         && mFallbackSeekTime.isSome()
         && mPendingSeekTime.ref() != mFallbackSeekTime.ref()) {
       // We have failed to seek audio where video seeked to earlier.
       // Attempt to seek instead to the closest point that we know we have in
       // order to limit A/V sync discrepency.
 
       // Ensure we have the most up to date buffered ranges.
       UpdateReceivedNewData(TrackType::kAudioTrack);
-      Maybe<media::TimeUnit> nextSeekTime;
+      Maybe<TimeUnit> nextSeekTime;
       // Find closest buffered time found after video seeked time.
       for (const auto& timeRange : mAudio.mTimeRanges) {
         if (timeRange.mStart >= mPendingSeekTime.ref()) {
           nextSeekTime.emplace(timeRange.mStart);
           break;
         }
       }
       if (nextSeekTime.isNothing()
@@ -2740,26 +2740,26 @@ MediaFormatReader::OnSeekFailed(TrackTyp
   mSeekPromise.Reject(SeekRejectValue(type, aError), __func__);
 }
 
 void
 MediaFormatReader::DoVideoSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking video to %" PRId64, mPendingSeekTime.ref().ToMicroseconds());
-  media::TimeUnit seekTime = mPendingSeekTime.ref();
+  auto seekTime = mPendingSeekTime.ref();
   mVideo.mTrackDemuxer->Seek(seekTime)
     ->Then(OwnerThread(), __func__, this,
            &MediaFormatReader::OnVideoSeekCompleted,
            &MediaFormatReader::OnVideoSeekFailed)
     ->Track(mVideo.mSeekRequest);
 }
 
 void
-MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
+MediaFormatReader::OnVideoSeekCompleted(TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Video seeked to %" PRId64, aTime.ToMicroseconds());
   mVideo.mSeekRequest.Complete();
 
   mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
 
   SetVideoDecodeThreshold();
@@ -2823,26 +2823,26 @@ MediaFormatReader::SetVideoDecodeThresho
   mVideo.mDecoder->SetSeekThreshold(threshold);
 }
 
 void
 MediaFormatReader::DoAudioSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking audio to %" PRId64, mPendingSeekTime.ref().ToMicroseconds());
-  media::TimeUnit seekTime = mPendingSeekTime.ref();
+  auto seekTime = mPendingSeekTime.ref();
   mAudio.mTrackDemuxer->Seek(seekTime)
     ->Then(OwnerThread(), __func__, this,
            &MediaFormatReader::OnAudioSeekCompleted,
            &MediaFormatReader::OnAudioSeekFailed)
     ->Track(mAudio.mSeekRequest);
 }
 
 void
-MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
+MediaFormatReader::OnAudioSeekCompleted(TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Audio seeked to %" PRId64, aTime.ToMicroseconds());
   mAudio.mSeekRequest.Complete();
   mPendingSeekTime.reset();
   mSeekPromise.Resolve(aTime, __func__);
 }
 
@@ -2934,31 +2934,31 @@ MediaFormatReader::UpdateBuffered()
   if (!mInitDone || !mHasStartTime) {
     mBuffered = TimeIntervals();
     return;
   }
 
   if (HasVideo()) {
     mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
     bool hasLastEnd;
-    media::TimeUnit lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
+    auto lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
     if (hasLastEnd) {
       if (mVideo.mLastTimeRangesEnd
           && mVideo.mLastTimeRangesEnd.ref() < lastEnd) {
         // New data was added after our previous end, we can clear the EOS flag.
         mVideo.mDemuxEOS = false;
         ScheduleUpdate(TrackInfo::kVideoTrack);
       }
       mVideo.mLastTimeRangesEnd = Some(lastEnd);
     }
   }
   if (HasAudio()) {
     mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
     bool hasLastEnd;
-    media::TimeUnit lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
+    auto lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
     if (hasLastEnd) {
       if (mAudio.mLastTimeRangesEnd
           && mAudio.mLastTimeRangesEnd.ref() < lastEnd) {
         // New data was added after our previous end, we can clear the EOS flag.
         mAudio.mDemuxEOS = false;
         ScheduleUpdate(TrackInfo::kAudioTrack);
       }
       mAudio.mLastTimeRangesEnd = Some(lastEnd);
@@ -2970,22 +2970,22 @@ MediaFormatReader::UpdateBuffered()
     intervals = media::Intersection(mVideo.mTimeRanges, mAudio.mTimeRanges);
   } else if (HasAudio()) {
     intervals = mAudio.mTimeRanges;
   } else if (HasVideo()) {
     intervals = mVideo.mTimeRanges;
   }
 
   if (!intervals.Length()
-      || intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
+      || intervals.GetStart() == TimeUnit::Zero()) {
     // IntervalSet already starts at 0 or is empty, nothing to shift.
     mBuffered = intervals;
   } else {
     mBuffered =
-      intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
+      intervals.Shift(TimeUnit::Zero() - mInfo.mStartTime);
   }
 }
 
 layers::ImageContainer*
 MediaFormatReader::GetImageContainer()
 {
   return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer()
                               : nullptr;
--- a/dom/media/TimeUnits.h
+++ b/dom/media/TimeUnits.h
@@ -34,61 +34,16 @@ static const int64_t USECS_PER_S = 10000
 // Number of microseconds per millisecond.
 static const int64_t USECS_PER_MS = 1000;
 
 namespace media {
 
 // Number of nanoseconds per second. 1e9.
 static const int64_t NSECS_PER_S = 1000000000;
 
-struct Microseconds {
-  Microseconds()
-    : mValue(0)
-  {}
-
-  explicit Microseconds(int64_t aValue)
-    : mValue(aValue)
-  {}
-
-  double ToSeconds() {
-    return double(mValue) / USECS_PER_S;
-  }
-
-  static Microseconds FromSeconds(double aValue) {
-    MOZ_ASSERT(!IsNaN(aValue));
-
-    double val = aValue * USECS_PER_S;
-    if (val >= double(INT64_MAX)) {
-      return Microseconds(INT64_MAX);
-    } else if (val <= double(INT64_MIN)) {
-      return Microseconds(INT64_MIN);
-    } else {
-      return Microseconds(int64_t(val));
-    }
-  }
-
-  bool operator == (const Microseconds& aOther) const {
-    return mValue == aOther.mValue;
-  }
-  bool operator > (const Microseconds& aOther) const {
-    return mValue > aOther.mValue;
-  }
-  bool operator >= (const Microseconds& aOther) const {
-    return mValue >= aOther.mValue;
-  }
-  bool operator < (const Microseconds& aOther) const {
-    return mValue < aOther.mValue;
-  }
-  bool operator <= (const Microseconds& aOther) const {
-    return mValue <= aOther.mValue;
-  }
-
-  int64_t mValue;
-};
-
 // TimeUnit at present uses a CheckedInt64 as storage.
 // INT64_MAX has the special meaning of being +oo.
 class TimeUnit final {
 public:
   static TimeUnit FromSeconds(double aValue) {
     MOZ_ASSERT(!IsNaN(aValue));
 
     if (mozilla::IsInfinite<double>(aValue)) {
@@ -105,20 +60,16 @@ public:
       return FromMicroseconds(int64_t(val));
     }
   }
 
   static constexpr TimeUnit FromMicroseconds(int64_t aValue) {
     return TimeUnit(aValue);
   }
 
-  static TimeUnit FromMicroseconds(Microseconds aValue) {
-    return TimeUnit(aValue.mValue);
-  }
-
   static constexpr TimeUnit FromNanoseconds(int64_t aValue) {
     return TimeUnit(aValue / 1000);
   }
 
   static constexpr TimeUnit FromInfinity() {
     return TimeUnit(INT64_MAX);
   }
 
@@ -231,25 +182,16 @@ public:
   {
     return mValue.isValid();
   }
 
   constexpr TimeUnit()
     : mValue(CheckedInt64(0))
   {}
 
-  explicit TimeUnit(const Microseconds& aMicroseconds)
-    : mValue(aMicroseconds.mValue)
-  {}
-  TimeUnit& operator = (const Microseconds& aMicroseconds)
-  {
-    mValue = aMicroseconds.mValue;
-    return *this;
-  }
-
   TimeUnit(const TimeUnit&) = default;
 
   TimeUnit& operator = (const TimeUnit&) = default;
 
 private:
   explicit constexpr TimeUnit(CheckedInt64 aMicroseconds)
     : mValue(aMicroseconds)
   {}
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -29,44 +29,45 @@
 #include <stdint.h>
 
 namespace mozilla {
 
 NS_NAMED_LITERAL_CSTRING(kEMEKeySystemClearkey, "org.w3.clearkey");
 NS_NAMED_LITERAL_CSTRING(kEMEKeySystemWidevine, "com.widevine.alpha");
 
 using layers::PlanarYCbCrImage;
+using media::TimeUnit;
 
 CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv) {
   int64_t major = aValue / aDiv;
   int64_t remainder = aValue % aDiv;
   return CheckedInt64(remainder) * aMul / aDiv + CheckedInt64(major) * aMul;
 }
 
 // Converts from number of audio frames to microseconds, given the specified
 // audio rate.
 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
   return SaferMultDiv(aFrames, USECS_PER_S, aRate);
 }
 
-media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
+TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
   int64_t major = aFrames / aRate;
   int64_t remainder = aFrames % aRate;
-  return media::TimeUnit::FromMicroseconds(major) * USECS_PER_S +
-    (media::TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
+  return TimeUnit::FromMicroseconds(major) * USECS_PER_S +
+    (TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
 }
 
 // Converts from microseconds to number of audio frames, given the specified
 // audio rate.
 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
   return SaferMultDiv(aUsecs, aRate, USECS_PER_S);
 }
 
 // Format TimeUnit as number of frames at given rate.
-CheckedInt64 TimeUnitToFrames(const media::TimeUnit& aTime, uint32_t aRate) {
+CheckedInt64 TimeUnitToFrames(const TimeUnit& aTime, uint32_t aRate) {
   return UsecsToFrames(aTime.ToMicroseconds(), aRate);
 }
 
 nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs) {
   if (aSeconds * double(USECS_PER_S) > INT64_MAX) {
     return NS_ERROR_FAILURE;
   }
   aOutUsecs = int64_t(aSeconds * double(USECS_PER_S));
@@ -106,18 +107,18 @@ media::TimeIntervals GetEstimatedBuffere
   media::TimeIntervals buffered;
   // Nothing to cache if the media takes 0us to play.
   if (aDurationUsecs <= 0 || !aStream)
     return buffered;
 
   // Special case completely cached files.  This also handles local files.
   if (aStream->IsDataCachedToEndOfResource(0)) {
     buffered +=
-      media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
-                          media::TimeUnit::FromMicroseconds(aDurationUsecs));
+      media::TimeInterval(TimeUnit::Zero(),
+                          TimeUnit::FromMicroseconds(aDurationUsecs));
     return buffered;
   }
 
   int64_t totalBytes = aStream->GetLength();
 
   // If we can't determine the total size, pretend that we have nothing
   // buffered. This will put us in a state of eternally-low-on-undecoded-data
   // which is not great, but about the best we can do.
@@ -130,19 +131,18 @@ media::TimeIntervals GetEstimatedBuffere
     // Bytes [startOffset..endOffset] are cached.
     NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered");
     NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered");
 
     int64_t startUs = BytesToTime(startOffset, totalBytes, aDurationUsecs);
     int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
     if (startUs != endUs) {
       buffered +=
-        media::TimeInterval(media::TimeUnit::FromMicroseconds(startUs),
-
-                              media::TimeUnit::FromMicroseconds(endUs));
+        media::TimeInterval(TimeUnit::FromMicroseconds(startUs),
+                            TimeUnit::FromMicroseconds(endUs));
     }
     startOffset = aStream->GetNextCachedData(endOffset);
   }
   return buffered;
 }
 
 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
                          uint32_t aFrames)
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -304,23 +304,23 @@ VideoTrackEncoder::AppendVideoSegment(co
       mLastChunk = chunk;
       chunk.mDuration = 0;
 
       TRACK_LOG(LogLevel::Verbose,
                 ("[VideoTrackEncoder]: Got first video chunk after %" PRId64 " ticks.",
                  nullDuration));
       // Adapt to the time before the first frame. This extends the first frame
       // from [start, end] to [0, end], but it'll do for now.
-      CheckedInt64 diff = FramesToUsecs(nullDuration, mTrackRate);
-      if (!diff.isValid()) {
+      auto diff = FramesToTimeUnit(nullDuration, mTrackRate);
+      if (!diff.IsValid()) {
         NS_ERROR("null duration overflow");
         return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
       }
 
-      mLastChunk.mTimeStamp -= TimeDuration::FromMicroseconds(diff.value());
+      mLastChunk.mTimeStamp -= diff.ToTimeDuration();
       mLastChunk.mDuration += nullDuration;
     }
 
     MOZ_ASSERT(!mLastChunk.IsNull());
     if (mLastChunk.CanCombineWithFollowing(chunk) || chunk.IsNull()) {
       TRACK_LOG(LogLevel::Verbose,
                 ("[VideoTrackEncoder]: Got dupe or null chunk."));
       // This is the same frame as before (or null). We extend the last chunk
--- a/dom/media/fmp4/MP4Demuxer.cpp
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -567,18 +567,17 @@ MP4TrackDemuxer::Reset()
   SetNextKeyFrameTime();
 }
 
 nsresult
 MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
 {
   if (mNextKeyframeTime.isNothing()) {
     // There's no next key frame.
-    *aTime =
-      media::TimeUnit::FromMicroseconds(std::numeric_limits<int64_t>::max());
+    *aTime = media::TimeUnit::FromInfinity();
   } else {
     *aTime = mNextKeyframeTime.value();
   }
   return NS_OK;
 }
 
 RefPtr<MP4TrackDemuxer::SkipAccessPointPromise>
 MP4TrackDemuxer::SkipToNextRandomAccessPoint(
--- a/dom/media/gtest/TestIntervalSet.cpp
+++ b/dom/media/gtest/TestIntervalSet.cpp
@@ -49,50 +49,51 @@ TEST(IntervalSet, Constructors)
   media::IntervalSet<uint8_t> blah5 = CreateByteIntervalSet(start, end);
   (void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
   (void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
 }
 
 media::TimeInterval CreateTimeInterval(int32_t aStart, int32_t aEnd)
 {
   // Copy constructor test
-  media::Microseconds startus(aStart);
-  media::TimeUnit start(startus);
+  media::TimeUnit start = media::TimeUnit::FromMicroseconds(aStart);
   media::TimeUnit end;
   // operator=  test
-  end = media::Microseconds(aEnd);
+  end = media::TimeUnit::FromMicroseconds(aEnd);
   media::TimeInterval ti(start, end);
   return ti;
 }
 
 media::TimeIntervals CreateTimeIntervals(int32_t aStart, int32_t aEnd)
 {
   media::TimeIntervals test;
   test += CreateTimeInterval(aStart, aEnd);
   return test;
 }
 
 TEST(IntervalSet, TimeIntervalsConstructors)
 {
-  const media::Microseconds start(1);
-  const media::Microseconds end(2);
-  const media::Microseconds fuzz;
+  const auto start = media::TimeUnit::FromMicroseconds(1);
+  const auto end = media::TimeUnit::FromMicroseconds(2);
+  const media::TimeUnit fuzz;
 
   // Compiler exercise.
   media::TimeInterval test1(start, end);
   media::TimeInterval test2(test1);
   media::TimeInterval test3(start, end, fuzz);
   media::TimeInterval test4(test3);
-  media::TimeInterval test5 = CreateTimeInterval(start.mValue, end.mValue);
+  media::TimeInterval test5 =
+    CreateTimeInterval(start.ToMicroseconds(), end.ToMicroseconds());
 
   media::TimeIntervals blah1(test1);
   media::TimeIntervals blah2(blah1);
   media::TimeIntervals blah3 = blah1 + test1;
   media::TimeIntervals blah4 = test1 + blah1;
-  media::TimeIntervals blah5 = CreateTimeIntervals(start.mValue, end.mValue);
+  media::TimeIntervals blah5 =
+    CreateTimeIntervals(start.ToMicroseconds(), end.ToMicroseconds());
   (void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
   (void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
 
   media::TimeIntervals i0{media::TimeInterval(media::TimeUnit::FromSeconds(0),
                                               media::TimeUnit::FromSeconds(0))};
   EXPECT_EQ(0u, i0.Length()); // Constructing with an empty time interval.
 }
 
@@ -588,31 +589,25 @@ TEST(IntervalSet, TimeRangesConversion)
   i1 = tr.get();
   CheckTimeRanges(tr, i1);
 }
 
 TEST(IntervalSet, TimeRangesMicroseconds)
 {
   media::TimeIntervals i0;
 
-  // Test media::Microseconds and TimeUnit interchangeability (compilation only)
-  media::TimeUnit time1{media::Microseconds(5)};
-  media::Microseconds microseconds(5);
-  media::TimeUnit time2 = media::TimeUnit(microseconds);
-  EXPECT_EQ(time1, time2);
-
-  i0 += media::TimeInterval(media::Microseconds(20), media::Microseconds(25));
-  i0 += media::TimeInterval(media::Microseconds(40), media::Microseconds(60));
-  i0 += media::TimeInterval(media::Microseconds(5), media::Microseconds(10));
+  i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(20), media::TimeUnit::FromMicroseconds(25));
+  i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(40), media::TimeUnit::FromMicroseconds(60));
+  i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(5), media::TimeUnit::FromMicroseconds(10));
 
   media::TimeIntervals i1;
-  i1.Add(media::TimeInterval(media::Microseconds(16), media::Microseconds(27)));
-  i1.Add(media::TimeInterval(media::Microseconds(7), media::Microseconds(15)));
-  i1.Add(media::TimeInterval(media::Microseconds(53), media::Microseconds(57)));
-  i1.Add(media::TimeInterval(media::Microseconds(45), media::Microseconds(50)));
+  i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(16), media::TimeUnit::FromMicroseconds(27)));
+  i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(7), media::TimeUnit::FromMicroseconds(15)));
+  i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(53), media::TimeUnit::FromMicroseconds(57)));
+  i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(45), media::TimeUnit::FromMicroseconds(50)));
 
   media::TimeIntervals i(i0 + i1);
   RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
   i.ToTimeRanges(tr);
   EXPECT_EQ(tr->Length(), i.Length());
   for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
     ErrorResult rv;
     EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds());
--- a/dom/media/gtest/TestMP3Demuxer.cpp
+++ b/dom/media/gtest/TestMP3Demuxer.cpp
@@ -432,61 +432,64 @@ TEST_F(MP3DemuxerTest, Duration) {
     if (target.mFileSize <= 0) {
       continue;
     }
 
     target.mDemuxer->Reset();
     RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frameData);
 
-    const int64_t duration = target.mDemuxer->Duration().ToMicroseconds();
-    const int64_t pos = duration + 1e6;
+    const auto duration = target.mDemuxer->Duration();
+    const auto pos = duration + TimeUnit::FromMicroseconds(1e6);
 
     // Attempt to seek 1 second past the end of stream.
-    target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
+    target.mDemuxer->Seek(pos);
     // The seek should bring us to the end of the stream.
-    EXPECT_NEAR(duration, target.mDemuxer->SeekPosition().ToMicroseconds(),
-                target.mSeekError * duration);
+    EXPECT_NEAR(duration.ToMicroseconds(),
+                target.mDemuxer->SeekPosition().ToMicroseconds(),
+                target.mSeekError * duration.ToMicroseconds());
 
     // Since we're at the end of the stream, there should be no frames left.
     frameData = target.mDemuxer->DemuxSample();
     ASSERT_FALSE(frameData);
   }
 }
 
 TEST_F(MP3DemuxerTest, Seek) {
   for (const auto& target: mTargets) {
     RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frameData);
 
-    const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
-    int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
+    const auto seekTime = TimeUnit::FromSeconds(1);
+    auto pos = target.mDemuxer->SeekPosition();
 
     while (frameData) {
-      EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
-                  target.mSeekError * pos);
+      EXPECT_NEAR(pos.ToMicroseconds(),
+                  target.mDemuxer->SeekPosition().ToMicroseconds(),
+                  target.mSeekError * pos.ToMicroseconds());
 
       pos += seekTime;
-      target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
+      target.mDemuxer->Seek(pos);
       frameData = target.mDemuxer->DemuxSample();
     }
   }
 
   // Seeking should work with in-between resets, too.
   for (const auto& target: mTargets) {
     target.mDemuxer->Reset();
     RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frameData);
 
-    const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
-    int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
+    const auto seekTime = TimeUnit::FromSeconds(1);
+    auto pos = target.mDemuxer->SeekPosition();
 
     while (frameData) {
-      EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
-                  target.mSeekError * pos);
+      EXPECT_NEAR(pos.ToMicroseconds(),
+                  target.mDemuxer->SeekPosition().ToMicroseconds(),
+                  target.mSeekError * pos.ToMicroseconds());
 
       pos += seekTime;
       target.mDemuxer->Reset();
-      target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
+      target.mDemuxer->Seek(pos);
       frameData = target.mDemuxer->DemuxSample();
     }
   }
 }
--- a/dom/media/gtest/TestMP4Demuxer.cpp
+++ b/dom/media/gtest/TestMP4Demuxer.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/ArrayUtils.h"
 #include "MockMediaResource.h"
 #include "VideoUtils.h"
 
 using namespace mozilla;
 using namespace mp4_demuxer;
+using media::TimeUnit;
 
 class AutoTaskQueue;
 
 #define DO_FAIL [binding]()->void { EXPECT_TRUE(false); binding->mTaskQueue->BeginShutdown(); }
 
 class MP4DemuxerBinding
 {
 public:
@@ -58,17 +59,17 @@ public:
   RefPtr<GenericPromise>
   CheckTrackKeyFrame(MediaTrackDemuxer* aTrackDemuxer)
   {
     MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
     RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
     RefPtr<MP4DemuxerBinding> binding = this;
 
-    auto time = media::TimeUnit::Invalid();
+    auto time = TimeUnit::Invalid();
     while (mIndex < mSamples.Length()) {
       uint32_t i = mIndex++;
       if (mSamples[i]->mKeyframe) {
         time = mSamples[i]->mTime;
         break;
       }
     }
 
@@ -410,24 +411,24 @@ TEST(MP4Demuxer, GetNextKeyframe)
   binding->RunTestAndWait([binding] () {
     // Insert a [0,end] buffered range, to simulate Moof's being buffered
     // via MSE.
     auto len = binding->resource->GetLength();
     binding->resource->MockAddBufferedRange(0, len);
 
     // gizmp-frag has two keyframes; one at dts=cts=0, and another at
     // dts=cts=1000000. Verify we get expected results.
-    media::TimeUnit time;
+    TimeUnit time;
     binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
     binding->mVideoTrack->Reset();
     binding->mVideoTrack->GetNextRandomAccessPoint(&time);
     EXPECT_EQ(time.ToMicroseconds(), 0);
     binding->mVideoTrack->GetSamples()->Then(binding->mTaskQueue, __func__,
       [binding] () {
-        media::TimeUnit time;
+        TimeUnit time;
         binding->mVideoTrack->GetNextRandomAccessPoint(&time);
         EXPECT_EQ(time.ToMicroseconds(), 1000000);
         binding->mTaskQueue->BeginShutdown();
       },
       DO_FAIL
     );
   });
 }
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -15,16 +15,17 @@
 #ifdef XP_WIN
 #include "WMFDecoderModule.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 
 using base::Thread;
+using media::TimeUnit;
 using namespace ipc;
 using namespace layers;
 using namespace gfx;
 
 class KnowsCompositorVideo : public layers::KnowsCompositor
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KnowsCompositorVideo, override)
@@ -132,19 +133,19 @@ VideoDecoderParent::RecvInput(const Medi
   RefPtr<MediaRawData> data = new MediaRawData(aData.buffer().get<uint8_t>(),
                                                aData.buffer().Size<uint8_t>());
   if (aData.buffer().Size<uint8_t>() && !data->Data()) {
     // OOM
     Error(NS_ERROR_OUT_OF_MEMORY);
     return IPC_OK();
   }
   data->mOffset = aData.base().offset();
-  data->mTime = media::TimeUnit::FromMicroseconds(aData.base().time());
-  data->mTimecode = media::TimeUnit::FromMicroseconds(aData.base().timecode());
-  data->mDuration = media::TimeUnit::FromMicroseconds(aData.base().duration());
+  data->mTime = TimeUnit::FromMicroseconds(aData.base().time());
+  data->mTimecode = TimeUnit::FromMicroseconds(aData.base().timecode());
+  data->mDuration = TimeUnit::FromMicroseconds(aData.base().duration());
   data->mKeyframe = aData.base().keyframe();
 
   DeallocShmem(aData.buffer());
 
   RefPtr<VideoDecoderParent> self = this;
   mDecoder->Decode(data)->Then(
     mManagerTaskQueue, __func__,
     [self, this](const MediaDataDecoder::DecodedData& aResults) {
@@ -252,17 +253,17 @@ VideoDecoderParent::RecvShutdown()
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 VideoDecoderParent::RecvSetSeekThreshold(const int64_t& aTime)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
-  mDecoder->SetSeekThreshold(media::TimeUnit::FromMicroseconds(aTime));
+  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
   return IPC_OK();
 }
 
 void
 VideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -17,22 +17,24 @@
 #include "MediaStreamListener.h"
 #include "OutputStreamManager.h"
 #include "SharedBuffer.h"
 #include "VideoSegment.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
+using media::TimeUnit;
+
 /*
  * A container class to make it easier to pass the playback info all the
  * way to DecodedStreamGraphListener from DecodedStream.
  */
 struct PlaybackInfoInit {
-  media::TimeUnit mStartTime;
+  TimeUnit mStartTime;
   MediaInfo mInfo;
 };
 
 class DecodedStreamGraphListener : public MediaStreamListener {
 public:
   DecodedStreamGraphListener(MediaStream* aStream,
                              MozPromiseHolder<GenericPromise>&& aPromise,
                              AbstractThread* aMainThread)
@@ -139,18 +141,18 @@ public:
   /* The following group of fields are protected by the decoder's monitor
    * and can be read or written on any thread.
    */
   // Count of audio frames written to the stream
   int64_t mAudioFramesWritten;
   // mNextVideoTime is the end timestamp for the last packet sent to the stream.
   // Therefore video packets starting at or after this time need to be copied
   // to the output stream.
-  media::TimeUnit mNextVideoTime;
-  media::TimeUnit mNextAudioTime;
+  TimeUnit mNextVideoTime;
+  TimeUnit mNextAudioTime;
   // The last video image sent to the stream. Useful if we need to replicate
   // the image.
   RefPtr<layers::Image> mLastVideoImage;
   gfx::IntSize mLastVideoImageDisplaySize;
   bool mHaveSentFinish;
   bool mHaveSentFinishAudio;
   bool mHaveSentFinishVideo;
 
@@ -289,23 +291,23 @@ DecodedStream::OnEnded(TrackType aType)
     return mFinishPromise;
   } else if (aType == TrackInfo::kVideoTrack && mInfo.HasVideo()) {
     return mFinishPromise;
   }
   return nullptr;
 }
 
 void
-DecodedStream::Start(const media::TimeUnit& aStartTime, const MediaInfo& aInfo)
+DecodedStream::Start(const TimeUnit& aStartTime, const MediaInfo& aInfo)
 {
   AssertOwnerThread();
   MOZ_ASSERT(mStartTime.isNothing(), "playback already started.");
 
   mStartTime.emplace(aStartTime);
-  mLastOutputTime = media::TimeUnit::Zero();
+  mLastOutputTime = TimeUnit::Zero();
   mInfo = aInfo;
   mPlaying = true;
   ConnectListener();
 
   class R : public Runnable {
     typedef MozPromiseHolder<GenericPromise> Promise;
   public:
     R(PlaybackInfoInit&& aInit, Promise&& aPromise,
@@ -443,17 +445,17 @@ DecodedStream::SetPlaybackRate(double aP
 void
 DecodedStream::SetPreservesPitch(bool aPreservesPitch)
 {
   AssertOwnerThread();
   mParams.mPreservesPitch = aPreservesPitch;
 }
 
 static void
-SendStreamAudio(DecodedStreamData* aStream, const media::TimeUnit& aStartTime,
+SendStreamAudio(DecodedStreamData* aStream, const TimeUnit& aStartTime,
                 AudioData* aData, AudioSegment* aOutput, uint32_t aRate,
                 const PrincipalHandle& aPrincipalHandle)
 {
   // The amount of audio frames that is used to fuzz rounding errors.
   static const int64_t AUDIO_FUZZ_FRAMES = 1;
 
   MOZ_ASSERT(aData);
   AudioData* audio = aData;
@@ -536,18 +538,18 @@ DecodedStream::SendAudio(double aVolume,
     sourceStream->EndTrack(audioTrackId);
     mData->mHaveSentFinishAudio = true;
   }
 }
 
 static void
 WriteVideoToMediaStream(MediaStream* aStream,
                         layers::Image* aImage,
-                        const media::TimeUnit& aEnd,
-                        const media::TimeUnit& aStart,
+                        const TimeUnit& aEnd,
+                        const TimeUnit& aStart,
                         const mozilla::gfx::IntSize& aIntrinsicSize,
                         const TimeStamp& aTimeStamp,
                         VideoSegment* aOutput,
                         const PrincipalHandle& aPrincipalHandle)
 {
   RefPtr<layers::Image> image = aImage;
   auto end = aStream->MicrosecondsToStreamTimeRoundDown(aEnd.ToMicroseconds());
   auto start = aStream->MicrosecondsToStreamTimeRoundDown(aStart.ToMicroseconds());
@@ -706,33 +708,33 @@ DecodedStream::SendData()
                   (!mInfo.HasVideo() || mVideoQueue.IsFinished());
 
   if (finished && !mData->mHaveSentFinish) {
     mData->mHaveSentFinish = true;
     mData->mStream->Finish();
   }
 }
 
-media::TimeUnit
+TimeUnit
 DecodedStream::GetEndTime(TrackType aType) const
 {
   AssertOwnerThread();
   if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     auto t = mStartTime.ref() + FramesToTimeUnit(
       mData->mAudioFramesWritten, mInfo.mAudio.mRate);
     if (t.IsValid()) {
       return t;
     }
   } else if (aType == TrackInfo::kVideoTrack && mData) {
     return mData->mNextVideoTime;
   }
-  return media::TimeUnit::Zero();
+  return TimeUnit::Zero();
 }
 
-media::TimeUnit
+TimeUnit
 DecodedStream::GetPosition(TimeStamp* aTimeStamp) const
 {
   AssertOwnerThread();
   // This is only called after MDSM starts playback. So mStartTime is
   // guaranteed to be something.
   MOZ_ASSERT(mStartTime.isSome());
   if (aTimeStamp) {
     *aTimeStamp = TimeStamp::Now();
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -100,22 +100,21 @@ MediaSourceDecoder::GetSeekable()
       // time in union ranges and an end time equal to the highest end time in
       // union ranges and abort these steps.
       seekable +=
         media::TimeInterval(unionRanges.GetStart(), unionRanges.GetEnd());
       return seekable;
     }
 
     if (buffered.Length()) {
-      seekable +=
-        media::TimeInterval(media::TimeUnit::FromSeconds(0), buffered.GetEnd());
+      seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd());
     }
   } else {
-    seekable += media::TimeInterval(media::TimeUnit::FromSeconds(0),
-                                    media::TimeUnit::FromSeconds(duration));
+    seekable += media::TimeInterval(TimeUnit::Zero(),
+                                    TimeUnit::FromSeconds(duration));
   }
   MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
   return seekable;
 }
 
 media::TimeIntervals
 MediaSourceDecoder::GetBuffered()
 {
@@ -125,32 +124,31 @@ MediaSourceDecoder::GetBuffered()
     NS_WARNING("MediaSource element isn't attached");
     return media::TimeIntervals::Invalid();
   }
   dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
   if (!sourceBuffers) {
     // Media source object is shutting down.
     return TimeIntervals();
   }
-  media::TimeUnit highestEndTime;
+  TimeUnit highestEndTime;
   nsTArray<media::TimeIntervals> activeRanges;
   media::TimeIntervals buffered;
 
   for (uint32_t i = 0; i < sourceBuffers->Length(); i++) {
     bool found;
     dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found);
     MOZ_ASSERT(found);
 
     activeRanges.AppendElement(sb->GetTimeIntervals());
     highestEndTime =
       std::max(highestEndTime, activeRanges.LastElement().GetEnd());
   }
 
-  buffered +=
-    media::TimeInterval(media::TimeUnit::FromMicroseconds(0), highestEndTime);
+  buffered += media::TimeInterval(TimeUnit::Zero(), highestEndTime);
 
   for (auto& range : activeRanges) {
     if (mEnded && range.Length()) {
       // Set the end time on the last range to highestEndTime by adding a
       // new range spanning the current end time to highestEndTime, which
       // Normalize() will then merge with the old last range.
       range +=
         media::TimeInterval(range.GetEnd(), highestEndTime);
@@ -284,17 +282,17 @@ MediaSourceDecoder::NextFrameBufferedSta
   // Next frame hasn't been decoded yet.
   // Use the buffered range to consider if we have the next frame available.
   TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
   TimeIntervals buffered = GetBuffered();
   buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
   TimeInterval interval(
     currentPosition,
     currentPosition
-    + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
+    + TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
   return buffered.ContainsStrict(ClampIntervalToEnd(interval))
          ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
          : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 bool
 MediaSourceDecoder::CanPlayThrough()
 {
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -24,20 +24,17 @@ using media::TimeIntervals;
 MediaSourceDemuxer::MediaSourceDemuxer(AbstractThread* aAbstractMainThread)
   : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
                                  /* aSupportsTailDispatch = */ false))
   , mMonitor("MediaSourceDemuxer")
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
-// Due to inaccuracies in determining buffer end
-// frames (Bug 1065207). This value is based on videos seen in the wild.
-const TimeUnit MediaSourceDemuxer::EOS_FUZZ =
-  media::TimeUnit::FromMicroseconds(500000);
+constexpr TimeUnit MediaSourceDemuxer::EOS_FUZZ;
 
 RefPtr<MediaSourceDemuxer::InitPromise>
 MediaSourceDemuxer::Init()
 {
   RefPtr<MediaSourceDemuxer> self = this;
   return InvokeAsync(GetTaskQueue(), __func__,
     [self](){
       if (self->ScanSourceBuffersForContent()) {
@@ -316,20 +313,20 @@ MediaSourceTrackDemuxer::MediaSourceTrac
 
 UniquePtr<TrackInfo>
 MediaSourceTrackDemuxer::GetInfo() const
 {
   return mParent->GetTrackInfo(mType)->Clone();
 }
 
 RefPtr<MediaSourceTrackDemuxer::SeekPromise>
-MediaSourceTrackDemuxer::Seek(const media::TimeUnit& aTime)
+MediaSourceTrackDemuxer::Seek(const TimeUnit& aTime)
 {
   MOZ_ASSERT(mParent, "Called after BreackCycle()");
-  return InvokeAsync<media::TimeUnit&&>(
+  return InvokeAsync<TimeUnit&&>(
            mParent->GetTaskQueue(), this, __func__,
            &MediaSourceTrackDemuxer::DoSeek, aTime);
 }
 
 RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
 MediaSourceTrackDemuxer::GetSamples(int32_t aNumSamples)
 {
   MOZ_ASSERT(mParent, "Called after BreackCycle()");
@@ -341,39 +338,39 @@ void
 MediaSourceTrackDemuxer::Reset()
 {
   MOZ_ASSERT(mParent, "Called after BreackCycle()");
   RefPtr<MediaSourceTrackDemuxer> self = this;
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self] () {
       self->mNextSample.reset();
       self->mReset = true;
-      self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
+      self->mManager->Seek(self->mType, TimeUnit::Zero(), TimeUnit::Zero());
       {
         MonitorAutoLock mon(self->mMonitor);
         self->mNextRandomAccessPoint = self->mManager->GetNextRandomAccessPoint(
           self->mType, MediaSourceDemuxer::EOS_FUZZ);
       }
     });
   mParent->GetTaskQueue()->Dispatch(task.forget());
 }
 
 nsresult
-MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
+MediaSourceTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime)
 {
   MonitorAutoLock mon(mMonitor);
   *aTime = mNextRandomAccessPoint;
   return NS_OK;
 }
 
 RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
 MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(
-  const media::TimeUnit& aTimeThreshold)
+  const TimeUnit& aTimeThreshold)
 {
-  return InvokeAsync<media::TimeUnit&&>(
+  return InvokeAsync<TimeUnit&&>(
            mParent->GetTaskQueue(), this, __func__,
            &MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint,
            aTimeThreshold);
 }
 
 media::TimeIntervals
 MediaSourceTrackDemuxer::GetBuffered()
 {
@@ -388,30 +385,29 @@ MediaSourceTrackDemuxer::BreakCycles()
     NS_NewRunnableFunction([self]() {
       self->mParent = nullptr;
       self->mManager = nullptr;
     } );
   mParent->GetTaskQueue()->Dispatch(task.forget());
 }
 
 RefPtr<MediaSourceTrackDemuxer::SeekPromise>
-MediaSourceTrackDemuxer::DoSeek(const media::TimeUnit& aTime)
+MediaSourceTrackDemuxer::DoSeek(const TimeUnit& aTime)
 {
   TimeIntervals buffered = mManager->Buffered(mType);
   // Fuzz factor represents a +/- threshold. So when seeking it allows the gap
   // to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap.
   buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
-  TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::FromMicroseconds(0));
+  TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::Zero());
 
   if (mManager->IsEnded() && seekTime >= buffered.GetEnd()) {
     // We're attempting to seek past the end time. Cap seekTime so that we seek
     // to the last sample instead.
     seekTime =
-      std::max(mManager->HighestStartTime(mType) - mPreRoll,
-               TimeUnit::FromMicroseconds(0));
+      std::max(mManager->HighestStartTime(mType) - mPreRoll, TimeUnit::Zero());
   }
   if (!buffered.ContainsWithStrictEnd(seekTime)) {
     if (!buffered.ContainsWithStrictEnd(aTime)) {
       // We don't have the data to seek to.
       return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
                                           __func__);
     }
     // Theoretically we should reject the promise with WAITING_FOR_DATA,
@@ -422,17 +418,17 @@ MediaSourceTrackDemuxer::DoSeek(const me
     TimeIntervals::IndexType index = buffered.Find(aTime);
     MOZ_ASSERT(index != TimeIntervals::NoIndex);
     seekTime = buffered[index].mStart;
   }
   seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ);
   MediaResult result = NS_OK;
   RefPtr<MediaRawData> sample =
     mManager->GetSample(mType,
-                        media::TimeUnit(),
+                        TimeUnit::Zero(),
                         result);
   MOZ_ASSERT(NS_SUCCEEDED(result) && sample);
   mNextSample = Some(sample);
   mReset = false;
   {
     MonitorAutoLock mon(mMonitor);
     mNextRandomAccessPoint =
       mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
@@ -448,17 +444,17 @@ MediaSourceTrackDemuxer::DoGetSamples(in
     // we are about to retrieve is still available.
     TimeIntervals buffered = mManager->Buffered(mType);
     buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
 
     if (!buffered.Length() && mManager->IsEnded()) {
       return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
                                              __func__);
     }
-    if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) {
+    if (!buffered.ContainsWithStrictEnd(TimeUnit::Zero())) {
       return SamplesPromise::CreateAndReject(
         NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
     }
     mReset = false;
   }
   RefPtr<MediaRawData> sample;
   if (mNextSample) {
     sample = mNextSample.ref();
@@ -484,17 +480,17 @@ MediaSourceTrackDemuxer::DoGetSamples(in
     mNextRandomAccessPoint =
       mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
   }
   return SamplesPromise::CreateAndResolve(samples, __func__);
 }
 
 RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
 MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(
-  const media::TimeUnit& aTimeThreadshold)
+  const TimeUnit& aTimeThreadshold)
 {
   uint32_t parsed = 0;
   // Ensure that the data we are about to skip to is still available.
   TimeIntervals buffered = mManager->Buffered(mType);
   buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
   if (buffered.ContainsWithStrictEnd(aTimeThreadshold)) {
     bool found;
     parsed = mManager->SkipToNextRandomAccessPoint(mType,
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -53,17 +53,20 @@ public:
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsACString& aString);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
 
   // Gap allowed between frames.
-  static const media::TimeUnit EOS_FUZZ;
+  // Due to inaccuracies in determining buffer end
+  // frames (Bug 1065207). This value is based on videos seen in the wild.
+  static constexpr media::TimeUnit EOS_FUZZ =
+    media::TimeUnit::FromMicroseconds(500000);
 
 private:
   ~MediaSourceDemuxer();
   friend class MediaSourceTrackDemuxer;
   // Scan source buffers and update information.
   bool ScanSourceBuffersForContent();
   TrackBuffersManager* GetManager(TrackInfo::TrackType aType);
   TrackInfo* GetTrackInfo(TrackInfo::TrackType);
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -31,16 +31,18 @@
 #endif
 #endif
 
 namespace mozilla {
 
 extern LazyLogModule gMediaDecoderLog;
 #define LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
 
+using media::TimeUnit;
+
 /** Decoder base class for Ogg-encapsulated streams. */
 OggCodecState*
 OggCodecState::Create(ogg_page* aPage)
 {
   NS_ASSERTION(ogg_page_bos(aPage), "Only call on BOS page!");
   nsAutoPtr<OggCodecState> codecState;
   if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
     codecState = new TheoraState(aPage);
@@ -254,19 +256,19 @@ OggCodecState::PacketOutAsMediaRawData()
   }
 
   int64_t end_tstamp = Time(packet->granulepos);
   NS_ASSERTION(end_tstamp >= 0, "timestamp invalid");
 
   int64_t duration = PacketDuration(packet.get());
   NS_ASSERTION(duration >= 0, "duration invalid");
 
-  sample->mTimecode = media::TimeUnit::FromMicroseconds(packet->granulepos);
-  sample->mTime = media::TimeUnit::FromMicroseconds(end_tstamp - duration);
-  sample->mDuration = media::TimeUnit::FromMicroseconds(duration);
+  sample->mTimecode = TimeUnit::FromMicroseconds(packet->granulepos);
+  sample->mTime = TimeUnit::FromMicroseconds(end_tstamp - duration);
+  sample->mDuration = TimeUnit::FromMicroseconds(duration);
   sample->mKeyframe = IsKeyframe(packet.get());
   sample->mEOS = packet->e_o_s;
 
   return sample.forget();
 }
 
 nsresult
 OggCodecState::PageIn(ogg_page* aPage)
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -183,17 +183,17 @@ bool
 OggDemuxer::HaveStartTime(TrackInfo::TrackType aType)
 {
   return OggState(aType).mStartTime.isSome();
 }
 
 int64_t
 OggDemuxer::StartTime(TrackInfo::TrackType aType)
 {
-  return OggState(aType).mStartTime.refOr(TimeUnit::FromMicroseconds(0)).ToMicroseconds();
+  return OggState(aType).mStartTime.refOr(TimeUnit::Zero()).ToMicroseconds();
 }
 
 RefPtr<OggDemuxer::InitPromise>
 OggDemuxer::Init()
 {
   int ret = ogg_sync_init(OggSyncState(TrackInfo::kAudioTrack));
   if (ret != 0) {
     return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -24,16 +24,18 @@
 #include "nsThreadUtils.h"
 
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
 namespace mozilla {
 
+using media::TimeUnit;
+
 /**
  * FFmpeg calls back to this function with a list of pixel formats it supports.
  * We choose a pixel format that we support and return it.
  * For now, we just look for YUV420P, YUVJ420P and YUV444 as those are the only
  * only non-HW accelerated format supported by FFmpeg's H264 and VP9 decoder.
  */
 static AVPixelFormat
 ChoosePixelFormat(AVCodecContext* aCodecContext, const AVPixelFormat* aFormats)
@@ -337,17 +339,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
         break;
     }
   }
   RefPtr<VideoData> v =
     VideoData::CreateAndCopyData(mInfo,
                                   mImageContainer,
                                   aSample->mOffset,
                                   pts,
-                                  media::TimeUnit::FromMicroseconds(duration),
+                                  TimeUnit::FromMicroseconds(duration),
                                   b,
                                   !!mFrame->key_frame,
                                   -1,
                                   mInfo.ScaledImageRect(mFrame->width,
                                                         mFrame->height));
 
   if (!v) {
     return MediaResult(NS_ERROR_OUT_OF_MEMORY,
@@ -359,17 +361,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
   }
   return NS_OK;
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 FFmpegVideoDecoder<LIBAV_VER>::ProcessDrain()
 {
   RefPtr<MediaRawData> empty(new MediaRawData());
-  empty->mTimecode = media::TimeUnit::FromMicroseconds(mLastInputDts);
+  empty->mTimecode = TimeUnit::FromMicroseconds(mLastInputDts);
   bool gotFrame = false;
   DecodedData results;
   while (NS_SUCCEEDED(DoDecode(empty, &gotFrame, results)) && gotFrame) {
   }
   return DecodePromise::CreateAndResolve(Move(results), __func__);
 }
 
 RefPtr<MediaDataDecoder::FlushPromise>
--- a/dom/media/platforms/wmf/WMFUtils.cpp
+++ b/dom/media/platforms/wmf/WMFUtils.cpp
@@ -19,16 +19,18 @@
 #ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
 // Some SDK versions don't define the AAC decoder CLSID.
 // {32D186A7-218F-4C75-8876-DD77273A8999}
 DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
 #endif
 
 namespace mozilla {
 
+using media::TimeUnit;
+
 HRESULT
 HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames)
 {
   MOZ_ASSERT(aOutFrames);
   const int64_t HNS_PER_S = USECS_PER_S * 10;
   CheckedInt<int64_t> i = aHNs;
   i *= aRate;
   i /= HNS_PER_S;
@@ -60,33 +62,33 @@ GetDefaultStride(IMFMediaType *aType, ui
 }
 
 int32_t
 MFOffsetToInt32(const MFOffset& aOffset)
 {
   return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
 }
 
-media::TimeUnit
+TimeUnit
 GetSampleDuration(IMFSample* aSample)
 {
-  NS_ENSURE_TRUE(aSample, media::TimeUnit::Invalid());
+  NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
   int64_t duration = 0;
   aSample->GetSampleDuration(&duration);
-  return media::TimeUnit::FromMicroseconds(HNsToUsecs(duration));
+  return TimeUnit::FromMicroseconds(HNsToUsecs(duration));
 }
 
-media::TimeUnit
+TimeUnit
 GetSampleTime(IMFSample* aSample)
 {
-  NS_ENSURE_TRUE(aSample, media::TimeUnit::Invalid());
+  NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
   LONGLONG timestampHns = 0;
   HRESULT hr = aSample->GetSampleTime(&timestampHns);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), media::TimeUnit::Invalid());
-  return media::TimeUnit::FromMicroseconds(HNsToUsecs(timestampHns));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), TimeUnit::Invalid());
+  return TimeUnit::FromMicroseconds(HNsToUsecs(timestampHns));
 }
 
 // Gets the sub-region of the video frame that should be displayed.
 // See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
 HRESULT
 GetPictureRegion(IMFMediaType* aMediaType, nsIntRect& aOutPictureRegion)
 {
   // Determine if "pan and scan" is enabled for this media. If it is, we
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -36,16 +36,17 @@
 #include "nsWindowsHelpers.h"
 
 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 using mozilla::layers::Image;
 using mozilla::layers::IMFYCbCrImage;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
+using mozilla::media::TimeUnit;
 
 #if WINVER_MAXVER < 0x0A00
 // Windows 10+ SDK has VP80 and VP90 defines
 const GUID MFVideoFormat_VP80 =
 {
   0x30385056,
   0x0000,
   0x0010,
@@ -821,19 +822,19 @@ WMFVideoMFTManager::CreateBasicVideoFram
   // V plane (Cr)
   b.mPlanes[2].mData = data + y_size;
   b.mPlanes[2].mStride = halfStride;
   b.mPlanes[2].mHeight = halfHeight;
   b.mPlanes[2].mWidth = halfWidth;
   b.mPlanes[2].mOffset = 0;
   b.mPlanes[2].mSkip = 0;
 
-  media::TimeUnit pts = GetSampleTime(aSample);
+  TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
-  media::TimeUnit duration = GetSampleDuration(aSample);
+  TimeUnit duration = GetSampleDuration(aSample);
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
   nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
 
   LayersBackend backend = GetCompositorBackendType(mKnowsCompositor);
   if (backend != LayersBackend::LAYERS_D3D11) {
     RefPtr<VideoData> v =
       VideoData::CreateAndCopyData(mVideoInfo,
                                    mImageContainer,
@@ -892,19 +893,19 @@ WMFVideoMFTManager::CreateD3DVideoFrame(
     mVideoInfo.ScaledImageRect(mImageSize.width, mImageSize.height);
   RefPtr<Image> image;
   hr = mDXVA2Manager->CopyToImage(aSample,
                                   pictureRegion,
                                   getter_AddRefs(image));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(image, E_FAIL);
 
-  media::TimeUnit pts = GetSampleTime(aSample);
+  TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
-  media::TimeUnit duration = GetSampleDuration(aSample);
+  TimeUnit duration = GetSampleDuration(aSample);
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
   RefPtr<VideoData> v = VideoData::CreateFromImage(mVideoInfo.mDisplay,
                                                    aStreamOffset,
                                                    pts.ToMicroseconds(),
                                                    duration,
                                                    image.forget(),
                                                    false,
                                                    -1);
@@ -926,18 +927,18 @@ WMFVideoMFTManager::Output(int64_t aStre
   int typeChangeCount = 0;
   bool wasDraining = mDraining;
   int64_t sampleCount = mSamplesCount;
   if (wasDraining) {
     mSamplesCount = 0;
     mDraining = false;
   }
 
-  media::TimeUnit pts;
-  media::TimeUnit duration;
+  TimeUnit pts;
+  TimeUnit duration;
 
   // Loop until we decode a sample, or an unexpected error that we can't
   // handle occurs.
   while (true) {
     hr = mDecoder->Output(&sample);
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       return MF_E_TRANSFORM_NEED_MORE_INPUT;
     }
@@ -988,24 +989,24 @@ WMFVideoMFTManager::Output(int64_t aStre
         }
         continue;
       }
       pts = GetSampleTime(sample);
       duration = GetSampleDuration(sample);
       if (!pts.IsValid() || !duration.IsValid()) {
         return E_FAIL;
       }
-      if (wasDraining && sampleCount == 1 && pts == media::TimeUnit()) {
+      if (wasDraining && sampleCount == 1 && pts == TimeUnit::Zero()) {
         // WMF is unable to calculate a duration if only a single sample
         // was parsed. Additionally, the pts always comes out at 0 under those
         // circumstances.
         // Seeing that we've only fed the decoder a single frame, the pts
         // and duration are known, it's of the last sample.
-        pts = media::TimeUnit::FromMicroseconds(mLastTime);
-        duration = media::TimeUnit::FromMicroseconds(mLastDuration);
+        pts = TimeUnit::FromMicroseconds(mLastTime);
+        duration = TimeUnit::FromMicroseconds(mLastDuration);
       }
       if (mSeekTargetThreshold.isSome()) {
         if ((pts + duration) < mSeekTargetThreshold.ref()) {
           LOG("Dropping video frame which pts is smaller than seek target.");
           // It is necessary to clear the pointer to release the previous output
           // buffer.
           sample = nullptr;
           continue;
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -184,22 +184,24 @@ skip-if = toolkit == 'android'
 skip-if = toolkit == 'android'
 [test_peerConnection_restartIceNoRtcpMux.html]
 skip-if = toolkit == 'android'
 [test_peerConnection_restartIceLocalRollback.html]
 skip-if = toolkit == 'android'
 [test_peerConnection_restartIceLocalAndRemoteRollback.html]
 skip-if = toolkit == 'android'
 [test_peerConnection_scaleResolution.html]
-skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1264343
-[test_peerConnection_simulcastOffer.html]
-skip-if = android_version # no simulcast support on android
-[test_peerConnection_simulcastAnswer.html]
-skip-if = android_version # no simulcast support on android
-#[test_peerConnection_relayOnly.html] Bug 1222983
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
+# disable test_peerConnection_simulcastOffer.html for Bug 1351590
+#[test_peerConnection_simulcastOffer.html]
+#skip-if = android_version # no simulcast support on android
+# disable test_peerConnection_simulcastAnswer.html for Bug 1351531
+#[test_peerConnection_simulcastAnswer.html]
+#skip-if = android_version # no simulcast support on android
+#[test_peerConnection_relayOnly.html]
 [test_peerConnection_callbacks.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_replaceTrack.html]
 skip-if = toolkit == 'android'  # Bug 1189784
 [test_peerConnection_syncSetDescription.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
 [test_peerConnection_setLocalAnswerInStable.html]
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -34,16 +34,17 @@
 #include <numeric>
 
 #define WEBM_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("WebMDemuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 
 namespace mozilla {
 
 using namespace gfx;
+using media::TimeUnit;
 
 LazyLogModule gNesteggLog("Nestegg");
 
 // How far ahead will we look when searching future keyframe. In microseconds.
 // This value is based on what appears to be a reasonable value as most webm
 // files encountered appear to have keyframes located < 4s.
 #define MAX_LOOK_AHEAD 10000000
 
@@ -385,17 +386,17 @@ WebMDemuxer::ReadMetadata()
           break;
         case NESTEGG_VIDEO_STEREO_RIGHT_LEFT:
           mInfo.mVideo.mStereoMode = StereoMode::RIGHT_LEFT;
           break;
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
-        mInfo.mVideo.mDuration = media::TimeUnit::FromNanoseconds(duration);
+        mInfo.mVideo.mDuration = TimeUnit::FromNanoseconds(duration);
       }
       mInfo.mVideo.mCrypto = GetTrackCrypto(TrackInfo::kVideoTrack, track);
       if (mInfo.mVideo.mCrypto.mValid) {
         mCrypto.AddInitData(NS_LITERAL_STRING("webm"),
                             mInfo.mVideo.mCrypto.mKeyId);
       }
     } else if (type == NESTEGG_TRACK_AUDIO && !mHasAudio) {
       nestegg_audio_params params;
@@ -408,18 +409,17 @@ WebMDemuxer::ReadMetadata()
       mHasAudio = true;
       mAudioCodec = nestegg_track_codec_id(context, track);
       if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
         mInfo.mAudio.mMimeType = "audio/vorbis";
       } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
         mInfo.mAudio.mMimeType = "audio/opus";
         OpusDataDecoder::AppendCodecDelay(
           mInfo.mAudio.mCodecSpecificConfig,
-          media::TimeUnit::FromNanoseconds(params.codec_delay)
-            .ToMicroseconds());
+          TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds());
       }
       mSeekPreroll = params.seek_preroll;
       mInfo.mAudio.mRate = params.rate;
       mInfo.mAudio.mChannels = params.channels;
 
       unsigned int nheaders = 0;
       r = nestegg_track_codec_data_count(context, track, &nheaders);
       if (r == -1) {
@@ -452,17 +452,17 @@ WebMDemuxer::ReadMetadata()
       }
       else {
         mInfo.mAudio.mCodecSpecificConfig->AppendElements(headers[0],
                                                           headerLens[0]);
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
-        mInfo.mAudio.mDuration = media::TimeUnit::FromNanoseconds(duration);
+        mInfo.mAudio.mDuration = TimeUnit::FromNanoseconds(duration);
       }
       mInfo.mAudio.mCrypto = GetTrackCrypto(TrackInfo::kAudioTrack, track);
       if (mInfo.mAudio.mCrypto.mValid) {
         mCrypto.AddInitData(NS_LITERAL_STRING("webm"),
                             mInfo.mAudio.mCrypto.mKeyId);
       }
     }
   }
@@ -717,32 +717,32 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
       }
     } else {
       sample = new MediaRawData(data, length);
       if (length && !sample->Data()) {
         // OOM.
         return NS_ERROR_OUT_OF_MEMORY;
       }
     }
-    sample->mTimecode = media::TimeUnit::FromMicroseconds(tstamp);
-    sample->mTime = media::TimeUnit::FromMicroseconds(tstamp);
-    sample->mDuration = media::TimeUnit::FromMicroseconds(next_tstamp - tstamp);
+    sample->mTimecode = TimeUnit::FromMicroseconds(tstamp);
+    sample->mTime = TimeUnit::FromMicroseconds(tstamp);
+    sample->mDuration = TimeUnit::FromMicroseconds(next_tstamp - tstamp);
     sample->mOffset = holder->Offset();
     sample->mKeyframe = isKeyframe;
     if (discardPadding && i == count - 1) {
       CheckedInt64 discardFrames;
       if (discardPadding < 0) {
         // This is an invalid value as discard padding should never be negative.
         // Set to maximum value so that the decoder will reject it as it's
         // greater than the number of frames available.
         discardFrames = INT32_MAX;
         WEBM_DEBUG("Invalid negative discard padding");
       } else {
         discardFrames = TimeUnitToFrames(
-          media::TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
+          TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
       }
       if (discardFrames.isValid()) {
         sample->mDiscardPadding = discardFrames.value();
       }
     }
 
     if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED
         || packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED
@@ -921,47 +921,47 @@ WebMDemuxer::PushAudioPacket(NesteggPack
 void
 WebMDemuxer::PushVideoPacket(NesteggPacketHolder* aItem)
 {
   mVideoPackets.PushFront(aItem);
 }
 
 nsresult
 WebMDemuxer::SeekInternal(TrackInfo::TrackType aType,
-                          const media::TimeUnit& aTarget)
+                          const TimeUnit& aTarget)
 {
   EnsureUpToDateIndex();
   uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
   uint64_t target = aTarget.ToNanoseconds();
 
   if (NS_FAILED(Reset(aType))) {
     return NS_ERROR_FAILURE;
   }
 
   if (mSeekPreroll) {
     uint64_t startTime = 0;
     if (!mBufferedState->GetStartTime(&startTime)) {
       startTime = 0;
     }
     WEBM_DEBUG("Seek Target: %f",
-               media::TimeUnit::FromNanoseconds(target).ToSeconds());
+               TimeUnit::FromNanoseconds(target).ToSeconds());
     if (target < mSeekPreroll || target - mSeekPreroll < startTime) {
       target = startTime;
     } else {
       target -= mSeekPreroll;
     }
     WEBM_DEBUG("SeekPreroll: %f StartTime: %f Adjusted Target: %f",
-               media::TimeUnit::FromNanoseconds(mSeekPreroll).ToSeconds(),
-               media::TimeUnit::FromNanoseconds(startTime).ToSeconds(),
-               media::TimeUnit::FromNanoseconds(target).ToSeconds());
+               TimeUnit::FromNanoseconds(mSeekPreroll).ToSeconds(),
+               TimeUnit::FromNanoseconds(startTime).ToSeconds(),
+               TimeUnit::FromNanoseconds(target).ToSeconds());
   }
   int r = nestegg_track_seek(Context(aType), trackToSeek, target);
   if (r == -1) {
     WEBM_DEBUG("track_seek for track %u to %f failed, r=%d", trackToSeek,
-               media::TimeUnit::FromNanoseconds(target).ToSeconds(), r);
+               TimeUnit::FromNanoseconds(target).ToSeconds(), r);
     // Try seeking directly based on cluster information in memory.
     int64_t offset = 0;
     bool rv = mBufferedState->GetOffsetForTime(target, &offset);
     if (!rv) {
       WEBM_DEBUG("mBufferedState->GetOffsetForTime failed too");
       return NS_ERROR_FAILURE;
     }
 
@@ -998,36 +998,36 @@ WebMDemuxer::GetBuffered()
   }
   uint64_t duration = 0;
   uint64_t startOffset = 0;
   if (!nestegg_duration(Context(TrackInfo::kVideoTrack), &duration)) {
     if(mBufferedState->GetStartTime(&startOffset)) {
       duration += startOffset;
     }
     WEBM_DEBUG("Duration: %f StartTime: %f",
-               media::TimeUnit::FromNanoseconds(duration).ToSeconds(),
-               media::TimeUnit::FromNanoseconds(startOffset).ToSeconds());
+               TimeUnit::FromNanoseconds(duration).ToSeconds(),
+               TimeUnit::FromNanoseconds(startOffset).ToSeconds());
   }
   for (uint32_t index = 0; index < ranges.Length(); index++) {
     uint64_t start, end;
     bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart,
                                                         ranges[index].mEnd,
                                                         &start, &end);
     if (rv) {
       NS_ASSERTION(startOffset <= start,
           "startOffset negative or larger than start time");
 
       if (duration && end > duration) {
         WEBM_DEBUG("limit range to duration, end: %f duration: %f",
-                   media::TimeUnit::FromNanoseconds(end).ToSeconds(),
-                   media::TimeUnit::FromNanoseconds(duration).ToSeconds());
+                   TimeUnit::FromNanoseconds(end).ToSeconds(),
+                   TimeUnit::FromNanoseconds(duration).ToSeconds());
         end = duration;
       }
-      media::TimeUnit startTime = media::TimeUnit::FromNanoseconds(start);
-      media::TimeUnit endTime = media::TimeUnit::FromNanoseconds(end);
+      auto startTime = TimeUnit::FromNanoseconds(start);
+      auto endTime = TimeUnit::FromNanoseconds(end);
       WEBM_DEBUG("add range %f-%f", startTime.ToSeconds(), endTime.ToSeconds());
       buffered += media::TimeInterval(startTime, endTime);
     }
   }
   return buffered;
 }
 
 bool WebMDemuxer::GetOffsetForTime(uint64_t aTime, int64_t* aOffset)
@@ -1056,29 +1056,29 @@ WebMTrackDemuxer::~WebMTrackDemuxer()
 
 UniquePtr<TrackInfo>
 WebMTrackDemuxer::GetInfo() const
 {
   return mInfo->Clone();
 }
 
 RefPtr<WebMTrackDemuxer::SeekPromise>
-WebMTrackDemuxer::Seek(const media::TimeUnit& aTime)
+WebMTrackDemuxer::Seek(const TimeUnit& aTime)
 {
   // Seeks to aTime. Upon success, SeekPromise will be resolved with the
   // actual time seeked to. Typically the random access point time
 
-  media::TimeUnit seekTime = aTime;
+  auto seekTime = aTime;
   mSamples.Reset();
   mParent->SeekInternal(mType, aTime);
   nsresult rv = mParent->GetNextPacket(mType, &mSamples);
   if (NS_FAILED(rv)) {
     if (rv == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
       // Ignore the error for now, the next GetSample will be rejected with EOS.
-      return SeekPromise::CreateAndResolve(media::TimeUnit(), __func__);
+      return SeekPromise::CreateAndResolve(TimeUnit::Zero(), __func__);
     }
     return SeekPromise::CreateAndReject(rv, __func__);
   }
   mNeedKeyframe = true;
 
   // Check what time we actually seeked to.
   if (mSamples.GetSize() > 0) {
     const RefPtr<MediaRawData>& sample = mSamples.First();
@@ -1135,17 +1135,17 @@ WebMTrackDemuxer::GetSamples(int32_t aNu
 
 void
 WebMTrackDemuxer::SetNextKeyFrameTime()
 {
   if (mType != TrackInfo::kVideoTrack || mParent->IsMediaSource()) {
     return;
   }
 
-  auto frameTime = media::TimeUnit::Invalid();
+  auto frameTime = TimeUnit::Invalid();
 
   mNextKeyframeTime.reset();
 
   MediaRawDataQueue skipSamplesQueue;
   bool foundKeyframe = false;
   while (!foundKeyframe && mSamples.GetSize()) {
     RefPtr<MediaRawData> sample = mSamples.PopFront();
     if (sample->mKeyframe) {
@@ -1221,31 +1221,29 @@ WebMTrackDemuxer::UpdateSamples(nsTArray
   }
   if (mNextKeyframeTime.isNothing()
       || aSamples.LastElement()->mTime >= mNextKeyframeTime.value()) {
     SetNextKeyFrameTime();
   }
 }
 
 nsresult
-WebMTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
+WebMTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime)
 {
   if (mNextKeyframeTime.isNothing()) {
     // There's no next key frame.
-    *aTime =
-      media::TimeUnit::FromMicroseconds(std::numeric_limits<int64_t>::max());
+    *aTime = TimeUnit::FromInfinity();
   } else {
     *aTime = mNextKeyframeTime.ref();
   }
   return NS_OK;
 }
 
 RefPtr<WebMTrackDemuxer::SkipAccessPointPromise>
-WebMTrackDemuxer::SkipToNextRandomAccessPoint(
-  const media::TimeUnit& aTimeThreshold)
+WebMTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
 {
   uint32_t parsed = 0;
   bool found = false;
   RefPtr<MediaRawData> sample;
   nsresult rv = NS_OK;
 
   WEBM_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds());
   while (!found && NS_SUCCEEDED((rv = NextSample(sample)))) {
@@ -1277,17 +1275,17 @@ WebMTrackDemuxer::GetBuffered()
 
 void
 WebMTrackDemuxer::BreakCycles()
 {
   mParent = nullptr;
 }
 
 int64_t
-WebMTrackDemuxer::GetEvictionOffset(const media::TimeUnit& aTime)
+WebMTrackDemuxer::GetEvictionOffset(const TimeUnit& aTime)
 {
   int64_t offset;
   if (!mParent->GetOffsetForTime(aTime.ToNanoseconds(), &offset)) {
     return 0;
   }
 
   return offset;
 }
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_bad_origin_directory.js
@@ -0,0 +1,38 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const invalidOrigin = {
+    url: "ftp://ftp.invalid.origin",
+    path: "storage/default/ftp+++ftp.invalid.origin"
+  };
+
+  info("Persisting an invalid origin");
+
+  let invalidPrincipal = getPrincipal(invalidOrigin.url);
+
+  let request = persist(invalidPrincipal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_ERROR_FAILURE,
+     "Persist() failed because of the invalid origin");
+  ok(request.result === null, "The request result is null");
+
+  let originDir = getRelativeFile(invalidOrigin.path);
+  let exists = originDir.exists();
+  ok(!exists, "Directory for invalid origin doesn't exist");
+
+  request = persisted(invalidPrincipal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(!request.result,
+     "The origin isn't persisted since the operation failed");
+
+  finishTest();
+}
--- a/dom/quota/test/unit/test_persist.js
+++ b/dom/quota/test/unit/test_persist.js
@@ -2,33 +2,25 @@
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var testGenerator = testSteps();
 
 function* testSteps()
 {
-  const origins = [
-    {
-      url: "http://default.test.persist",
-      path: "storage/default/http+++default.test.persist",
-      persistence: "default"
-    },
-
-    {
-      url: "ftp://ftp.invalid.origin",
-      path: "storage/default/ftp+++ftp.invalid.origin",
-      persistence: "default"
-    },
-  ];
+  const origin = {
+    url: "http://default.test.persist",
+    path: "storage/default/http+++default.test.persist",
+    persistence: "default"
+  };
 
   const metadataFileName = ".metadata-v2";
 
-  let principal = getPrincipal(origins[0].url);
+  let principal = getPrincipal(origin.url);
 
   info("Persisting an uninitialized origin");
 
   // Origin directory doesn't exist yet, so only check the result for
   // persisted().
   let request = persisted(principal, continueToNextStepSync);
   yield undefined;
 
@@ -37,17 +29,17 @@ function* testSteps()
 
   info("Verifying persist() does update the metadata");
 
   request = persist(principal, continueToNextStepSync);
   yield undefined;
 
   ok(request.resultCode === NS_OK, "Persist() succeeded");
 
-  let originDir = getRelativeFile(origins[0].path);
+  let originDir = getRelativeFile(origin.path);
   let exists = originDir.exists();
   ok(exists, "Origin directory does exist");
 
   info("Reading out contents of metadata file");
 
   let metadataFile = originDir.clone();
   metadataFile.append(metadataFileName);
 
@@ -69,22 +61,22 @@ function* testSteps()
 
   ok(request.resultCode === NS_OK, "Persisted() succeeded");
   ok(request.result === originPersisted, "Persisted() concurs with metadata");
 
   info("Clearing the origin");
 
   // Clear the origin since we'll test the same directory again under different
   // circumstances.
-  clearOrigin(principal, origins[0].persistence, continueToNextStepSync);
+  clearOrigin(principal, origin.persistence, continueToNextStepSync);
   yield undefined;
 
   info("Persisting an already initialized origin");
 
-  initOrigin(principal, origins[0].persistence, continueToNextStepSync);
+  initOrigin(principal, origin.persistence, continueToNextStepSync);
   yield undefined;
 
   info("Reading out contents of metadata file");
 
   fileReader = new FileReader();
   fileReader.onload = continueToNextStepSync;
   fileReader.readAsArrayBuffer(file);
   yield undefined;
@@ -120,32 +112,10 @@ function* testSteps()
   info("Verifying persisted()");
 
   request = persisted(principal, continueToNextStepSync);
   yield undefined;
 
   ok(request.resultCode === NS_OK, "Persisted() succeeded");
   ok(request.result === originPersisted, "Persisted() concurs with metadata");
 
-  info("Persisting an invalid origin");
-
-  let invalidPrincipal = getPrincipal(origins[1].url);
-
-  request = persist(invalidPrincipal, continueToNextStepSync);
-  yield undefined;
-
-  ok(request.resultCode === NS_ERROR_FAILURE,
-     "Persist() failed because of the invalid origin");
-  ok(request.result === null, "The request result is null");
-
-  originDir = getRelativeFile(origins[1].path);
-  exists = originDir.exists();
-  ok(!exists, "Directory for invalid origin doesn't exist");
-
-  request = persisted(invalidPrincipal, continueToNextStepSync);
-  yield undefined;
-
-  ok(request.resultCode === NS_OK, "Persisted() succeeded");
-  ok(!request.result,
-     "The origin isn't persisted since the operation failed");
-
   finishTest();
 }
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -13,16 +13,18 @@ support-files =
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
   removeAppsUpgrade_profile.zip
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
 
 [test_basics.js]
+[test_bad_origin_directory.js]
+skip-if = release_or_beta
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
 [test_idbSubdirUpgrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_removeAppsUpgrade.js]
--- a/dom/vr/test/mochitest.ini
+++ b/dom/vr/test/mochitest.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   VRSimulationDriver.js
   requestPresent.js
   runVRTest.js
   WebVRHelpers.js
 
 [test_vrDisplay_exitPresent.html]
+skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
 [test_vrDisplay_getFrameData.html]
+skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
 [test_vrDisplay_onvrdisplaydeactivate_crosscontent.html]
 skip-if = true
 [test_vrDisplay_requestPresent.html]
 skip-if = true
--- a/dom/vr/test/runVRTest.js
+++ b/dom/vr/test/runVRTest.js
@@ -1,10 +1,9 @@
 function runVRTest(callback) {
-  SpecialPowers.pushPrefEnv({"set" : [["dom.vr.enabled", true],
-                                      ["dom.vr.puppet.enabled", true],
+  SpecialPowers.pushPrefEnv({"set" : [["dom.vr.puppet.enabled", true],
                                       ["dom.vr.require-gesture", false],
                                       ["dom.vr.test.enabled", true]]},
   () => {
     VRServiceTest = navigator.requestVRServiceTest();
     callback();
   });
 }
\ No newline at end of file
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -344,16 +344,17 @@ private:
   DECL_GFX_PREF(Live, "browser.ui.scroll-toolbar-threshold",   ToolbarScrollThreshold, int32_t, 10);
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.autoactivate.enabled",           VRAutoActivateEnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.controller_trigger_threshold",   VRControllerTriggerThreshold, float, 0.1f);
   DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.require-gesture",                VRRequireGesture, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -1194,36 +1194,34 @@ VRSystemManagerOculus::HandleInput()
     const uint32_t handIdx = static_cast<uint32_t>(hand) - 1;
     uint32_t buttonIdx = 0;
 
     switch (hand) {
       case dom::GamepadHand::Left:
         HandleButtonPress(i, buttonIdx, ovrButton_LThumb, inputState.Buttons,
                           inputState.Touches);
         ++buttonIdx;
-        HandleIndexTriggerPress(i, buttonIdx, ovrTouch_LIndexTrigger,
-                                inputState.IndexTrigger[handIdx], inputState.Touches);
+        HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
         ++buttonIdx;
         HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
         ++buttonIdx;
         HandleButtonPress(i, buttonIdx, ovrButton_X, inputState.Buttons,
                           inputState.Touches);
         ++buttonIdx;
         HandleButtonPress(i, buttonIdx, ovrButton_Y, inputState.Buttons,
                           inputState.Touches);
         ++buttonIdx;
         HandleTouchEvent(i, buttonIdx, ovrTouch_LThumbRest, inputState.Touches);
         ++buttonIdx;
         break;
       case dom::GamepadHand::Right:
         HandleButtonPress(i, buttonIdx, ovrButton_RThumb, inputState.Buttons,
                           inputState.Touches);
         ++buttonIdx;
-        HandleIndexTriggerPress(i, buttonIdx, ovrTouch_RIndexTrigger,
-                                inputState.IndexTrigger[handIdx], inputState.Touches);
+        HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
         ++buttonIdx;
         HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
         ++buttonIdx;
         HandleButtonPress(i, buttonIdx, ovrButton_A, inputState.Buttons,
                           inputState.Touches);
         ++buttonIdx;
         HandleButtonPress(i, buttonIdx, ovrButton_B, inputState.Buttons,
                           inputState.Touches);
@@ -1313,45 +1311,51 @@ VRSystemManagerOculus::HandleButtonPress
                    aButtonMask & aButtonTouched,
                    (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
   }
 }
 
 void
 VRSystemManagerOculus::HandleIndexTriggerPress(uint32_t aControllerIdx,
                                                uint32_t aButton,
-                                               uint64_t aTouchMask,
-                                               float aValue,
-                                               uint64_t aButtonTouched)
+                                               float aValue)
 {
   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
   MOZ_ASSERT(controller);
-  const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
   const float oldValue = controller->GetIndexTrigger();
+  // We prefer to let developers to set their own threshold for the adjustment.
+  // Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask here.
+  // we just check the button value is larger than the threshold value or not.
+  const float threshold = gfxPrefs::VRControllerTriggerThreshold();
 
   // Avoid sending duplicated events in IPC channels.
-  if ((oldValue != aValue) ||
-      (touchedDiff & aTouchMask)) {
-    NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aTouchMask & aButtonTouched, aValue);
+  if (oldValue != aValue) {
+    NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
+                   aValue > threshold, aValue);
     controller->SetIndexTrigger(aValue);
   }
 }
 
 void
 VRSystemManagerOculus::HandleHandTriggerPress(uint32_t aControllerIdx,
                                               uint32_t aButton,
                                               float aValue)
 {
   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
   MOZ_ASSERT(controller);
   const float oldValue = controller->GetHandTrigger();
+  // We prefer to let developers to set their own threshold for the adjustment.
+  // Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask here.
+  // we just check the button value is larger than the threshold value or not.
+  const float threshold = gfxPrefs::VRControllerTriggerThreshold();
 
   // Avoid sending duplicated events in IPC channels.
   if (oldValue != aValue) {
-    NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aValue > 0.1f, aValue);
+    NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
+                   aValue > threshold, aValue);
     controller->SetHandTrigger(aValue);
   }
 }
 
 void
 VRSystemManagerOculus::HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
                                         uint64_t aTouchMask, uint64_t aButtonTouched)
 {
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -164,20 +164,18 @@ private:
                          uint64_t aButtonMask,
                          uint64_t aButtonPressed,
                          uint64_t aButtonTouched);
   void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                       float aValue);
   void HandlePoseTracking(uint32_t aControllerIdx,
                           const dom::GamepadPoseState& aPose,
                           VRControllerHost* aController);
-  void HandleIndexTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
-                               uint64_t aTouchMask, float aValue, uint64_t aButtonTouched);
-  void HandleHandTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
-                              float aValue);
+  void HandleIndexTriggerPress(uint32_t aControllerIdx, uint32_t aButton, float aValue);
+  void HandleHandTriggerPress(uint32_t aControllerIdx, uint32_t aButton, float aValue);
   void HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
                         uint64_t aTouchMask, uint64_t aTouched);
   PRLibrary* mOvrLib;
   RefPtr<impl::VRDisplayOculus> mHMDInfo;
   nsTArray<RefPtr<impl::VRControllerOculus>> mOculusController;
   RefPtr<nsIThread> mOculusThread;
   ovrSession mSession;
   bool mStarted;
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -642,21 +642,18 @@ VRSystemManagerOpenVR::HandleInput()
                            state.rAxis[j].y);
             ++axisIdx;
             HandleButtonPress(i, buttonIdx,
                               ::vr::ButtonMaskFromId(
                                  static_cast<::vr::EVRButtonId>(::vr::k_EButton_Axis0 + j)),
                                  state.ulButtonPressed, state.ulButtonTouched);
             ++buttonIdx;
             break;
-          case ::vr::EVRControllerAxisType::k_eControllerAxis_Trigger:
-            HandleTriggerPress(i, buttonIdx,
-                               ::vr::ButtonMaskFromId(
-                                static_cast<::vr::EVRButtonId>(::vr::k_EButton_Axis0 + j)),
-                                state.rAxis[j].x, state.ulButtonPressed, state.ulButtonTouched);
+          case vr::EVRControllerAxisType::k_eControllerAxis_Trigger:
+            HandleTriggerPress(i, buttonIdx, state.rAxis[j].x);
             ++buttonIdx;
             break;
         }
       }
       MOZ_ASSERT(axisIdx ==
                  controller->GetControllerInfo().GetNumAxes());
 
       const uint64_t supportedButtons = mVRSystem->GetUint64TrackedDeviceProperty(
@@ -781,33 +778,31 @@ VRSystemManagerOpenVR::HandleButtonPress
                    aButtonMask & aButtonTouched,
                    (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
   }
 }
 
 void
 VRSystemManagerOpenVR::HandleTriggerPress(uint32_t aControllerIdx,
                                           uint32_t aButton,
-                                          uint64_t aButtonMask,
-                                          float aValue,
-                                          uint64_t aButtonPressed,
-                                          uint64_t aButtonTouched)
+                                          float aValue)
 {
   RefPtr<impl::VRControllerOpenVR> controller(mOpenVRController[aControllerIdx]);
   MOZ_ASSERT(controller);
-  const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed);
-  const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
   const float oldValue = controller->GetTrigger();
+  // For OpenVR, the threshold value of ButtonPressed and ButtonTouched is 0.55.
+  // We prefer to let developers to set their own threshold for the adjustment.
+  // Therefore, we don't check ButtonPressed and ButtonTouched with ButtonMask here.
+  // we just check the button value is larger than the threshold value or not.
+  const float threshold = gfxPrefs::VRControllerTriggerThreshold();
 
   // Avoid sending duplicated events in IPC channels.
-  if ((oldValue != aValue) ||
-      (pressedDiff & aButtonMask) ||
-      (touchedDiff & aButtonMask)) {
-    NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
-                   aButtonMask & aButtonTouched, aValue);
+  if (oldValue != aValue) {
+    NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
+                   aValue > threshold, aValue);
     controller->SetTrigger(aValue);
   }
 }
 
 void
 VRSystemManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                                       float aValue)
 {
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -127,20 +127,17 @@ protected:
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
                          uint32_t aButton,
                          uint64_t aButtonMask,
                          uint64_t aButtonPressed,
                          uint64_t aButtonTouched);
   void HandleTriggerPress(uint32_t aControllerIdx,
                           uint32_t aButton,
-                          uint64_t aButtonMask,
-                          float aValue,
-                          uint64_t aButtonPressed,
-                          uint64_t aButtonTouched);
+                          float aValue);
   void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                       float aValue);
   void HandlePoseTracking(uint32_t aControllerIdx,
                           const dom::GamepadPoseState& aPose,
                           VRControllerHost* aController);
   dom::GamepadHand GetGamepadHandFromControllerRole(
                           ::vr::ETrackedControllerRole aRole);
 
--- a/intl/locale/moz.build
+++ b/intl/locale/moz.build
@@ -73,17 +73,16 @@ EXTRA_JS_MODULES += [
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/intl/uconv',
 ]
 
 RESOURCE_FILES += [
-    'langGroups.properties',
     'language.properties',
 ]
 
 GENERATED_FILES += [
     'langGroups.properties.h',
 ]
 langgroups = GENERATED_FILES['langGroups.properties.h']
 langgroups.script = 'props2arrays.py'
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -202,16 +202,20 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_nsStyleSVG_SetDashArrayLength", "aSvg", null],
         ["Gecko_nsStyleSVG_CopyDashArray", "aDst", null],
         ["Gecko_nsStyleFont_SetLang", "aFont", null],
         ["Gecko_nsStyleFont_CopyLangFrom", "aFont", null],
         ["Gecko_MatchStringArgPseudo", "aSetSlowSelectorFlag", null],
         ["Gecko_ClearWillChange", "aDisplay", null],
         ["Gecko_AppendWillChange", "aDisplay", null],
         ["Gecko_CopyWillChangeFrom", "aDest", null],
+        ["Gecko_InitializeImageCropRect", "aImage", null],
+        ["Gecko_CopyShapeSourceFrom", "aDst", null],
+        ["Gecko_DestroyShapeSource", "aShape", null],
+        ["Gecko_StyleShapeSource_SetURLValue", "aShape", null],
     ];
     for (var [entryMatch, varMatch, csuMatch] of whitelist) {
         assert(entryMatch || varMatch || csuMatch);
         if (entryMatch && !nameMatches(entry.name, entryMatch))
             continue;
         if (varMatch && !nameMatches(varName, varMatch))
             continue;
         if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
--- a/layout/reftests/invalidation/reftest-stylo.list
+++ b/layout/reftests/invalidation/reftest-stylo.list
@@ -43,17 +43,17 @@ skip-if(stylo) == test-animated-image-la
 fails == scroll-inactive-layers.html scroll-inactive-layers.html
 fails == scroll-inactive-layers-2.html scroll-inactive-layers-2.html
 fails == inactive-layertree-visible-region-1.html inactive-layertree-visible-region-1.html
 fails == inactive-layertree-visible-region-2.html inactive-layertree-visible-region-2.html
 fails == transform-floating-point-invalidation.html transform-floating-point-invalidation.html
 fails == transform-floating-point-invalidation.html?reverse transform-floating-point-invalidation.html?reverse
 fails == nudge-to-integer-invalidation.html nudge-to-integer-invalidation.html
 fails == nudge-to-integer-invalidation.html?reverse nudge-to-integer-invalidation.html?reverse
-== clipped-animated-transform-1.html clipped-animated-transform-1.html
+skip-if(stylo) == clipped-animated-transform-1.html clipped-animated-transform-1.html # bug 1334036
 fails == paintedlayer-recycling-1.html paintedlayer-recycling-1.html
 fails == paintedlayer-recycling-2.html paintedlayer-recycling-2.html
 fails pref(layers.single-tile.enabled,false) == paintedlayer-recycling-3.html paintedlayer-recycling-3.html
 fails == paintedlayer-recycling-4.html paintedlayer-recycling-4.html
 fails == paintedlayer-recycling-5.html paintedlayer-recycling-5.html
 fails == paintedlayer-recycling-6.html paintedlayer-recycling-6.html
 fails == paintedlayer-recycling-7.html paintedlayer-recycling-7.html
 == masklayer-1.html masklayer-1.html
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -645,17 +645,17 @@ bool
 Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed aElement,
                            CSSPseudoClassType aType,
                            const char16_t* aIdent,
                            bool* aSetSlowSelectorFlag)
 {
   EventStates dummyMask; // mask is never read because we pass aDependence=nullptr
   return nsCSSRuleProcessor::StringPseudoMatches(aElement, aType, aIdent,
                                                  aElement->OwnerDoc(), true,
-                                                 dummyMask, false, aSetSlowSelectorFlag, nullptr);
+                                                 dummyMask, aSetSlowSelectorFlag, nullptr);
 }
 
 nsIAtom*
 Gecko_GetXMLLangValue(RawGeckoElementBorrowed aElement)
 {
   nsString string;
   if (aElement->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang, string)) {
     return NS_Atomize(string).take();
@@ -1804,16 +1804,22 @@ Gecko_CSSFontFaceRule_Create()
   RefPtr<nsCSSFontFaceRule> rule = new nsCSSFontFaceRule(0, 0);
   return rule.forget().take();
 }
 
 void
 Gecko_CSSFontFaceRule_GetCssText(const nsCSSFontFaceRule* aRule,
                                  nsAString* aResult)
 {
+  // GetCSSText serializes nsCSSValues, which have a heap write
+  // hazard when dealing with color values (nsCSSKeywords::AddRefTable)
+  // We only serialize on the main thread; assert to convince the analysis
+  // and prevent accidentally calling this elsewhere
+  MOZ_ASSERT(NS_IsMainThread());
+
   aRule->GetCssText(*aResult);
 }
 
 NS_IMPL_FFI_REFCOUNTING(nsCSSFontFaceRule, CSSFontFaceRule);
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
 
 #define STYLE_STRUCT(name, checkdata_cb)                                      \
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/ServoRestyleManager.h"
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSPseudoElements.h"
+#include "nsCSSRuleProcessor.h"
 #include "nsDeviceContext.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsIDocumentInlines.h"
 #include "nsPrintfCString.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
 
 using namespace mozilla;
@@ -240,16 +241,18 @@ ServoStyleSet::ResolveMappedAttrDeclarat
   mPresContext->Document()->ResolveScheduledSVGPresAttrs();
 }
 
 void
 ServoStyleSet::PreTraverseSync()
 {
   ResolveMappedAttrDeclarationBlocks();
 
+  nsCSSRuleProcessor::InitSystemMetrics();
+
   // This is lazily computed and pseudo matching needs to access
   // it so force computation early.
   mPresContext->Document()->GetDocumentState();
 
   // Ensure that the @font-face data is not stale
   mPresContext->Document()->GetUserFontSet();
 }
 
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -203,16 +203,20 @@ CSS_STATE_PSEUDO_CLASS(mozHandlerBlocked
 CSS_STATE_PSEUDO_CLASS(mozHandlerCrashed, ":-moz-handler-crashed",
                        CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
                        NS_EVENT_STATE_HANDLER_CRASHED)
 
 CSS_STATE_PSEUDO_CLASS(mozMathIncrementScriptLevel,
                        ":-moz-math-increment-script-level", 0, "",
                        NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL)
 
+CSS_STATE_PSEUDO_CLASS(mozAutofill, ":-moz-autofill",
+                       CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+                       NS_EVENT_STATE_AUTOFILL)
+
 // CSS 3 UI
 // http://www.w3.org/TR/2004/CR-css3-ui-20040511/#pseudo-classes
 CSS_STATE_PSEUDO_CLASS(required, ":required", 0, "", NS_EVENT_STATE_REQUIRED)
 CSS_STATE_PSEUDO_CLASS(optional, ":optional", 0, "", NS_EVENT_STATE_OPTIONAL)
 CSS_STATE_PSEUDO_CLASS(valid, ":valid", 0, "", NS_EVENT_STATE_VALID)
 CSS_STATE_PSEUDO_CLASS(invalid, ":invalid", 0, "", NS_EVENT_STATE_INVALID)
 CSS_STATE_PSEUDO_CLASS(inRange, ":in-range", 0, "", NS_EVENT_STATE_INRANGE)
 CSS_STATE_PSEUDO_CLASS(outOfRange, ":out-of-range", 0, "", NS_EVENT_STATE_OUTOFRANGE)
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -42,16 +42,17 @@
 #include "nsCSSRules.h"
 #include "nsCSSFontFaceRule.h"
 #include "nsStyleSet.h"
 #include "mozilla/dom/Element.h"
 #include "nsNthIndexCache.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ServoStyleSet.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Likely.h"
 #include "mozilla/OperatorNewExtensions.h"
 #include "mozilla/TypedEnumBits.h"
 #include "RuleProcessorCache.h"
 #include "nsIDOMMutationEvent.h"
 
 using namespace mozilla;
@@ -1056,23 +1057,25 @@ nsCSSRuleProcessor::ClearSheets()
 
 /* static */ void
 nsCSSRuleProcessor::Startup()
 {
   Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF,
                                true);
 }
 
-static bool
-InitSystemMetrics()
+/* static */ void
+nsCSSRuleProcessor::InitSystemMetrics()
 {
-  NS_ASSERTION(!sSystemMetrics, "already initialized");
+  if (sSystemMetrics)
+    return;
+
+  MOZ_ASSERT(NS_IsMainThread());
 
   sSystemMetrics = new nsTArray< nsCOMPtr<nsIAtom> >;
-  NS_ENSURE_TRUE(sSystemMetrics, false);
 
   /***************************************************************************
    * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN         *
    * nsMediaFeatures.cpp                                                     *
    ***************************************************************************/
 
   int32_t metricResult =
     LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle);
@@ -1188,18 +1191,16 @@ InitSystemMetrics()
         sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune);
         break;
       case LookAndFeel::eWindowsTheme_Generic:
         sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic);
         break;
     }
   }
 #endif
-
-  return true;
 }
 
 /* static */ void
 nsCSSRuleProcessor::FreeSystemMetrics()
 {
   delete sSystemMetrics;
   sSystemMetrics = nullptr;
 }
@@ -1208,28 +1209,25 @@ nsCSSRuleProcessor::FreeSystemMetrics()
 nsCSSRuleProcessor::Shutdown()
 {
   FreeSystemMetrics();
 }
 
 /* static */ bool
 nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric)
 {
-  if (!sSystemMetrics && !InitSystemMetrics()) {
-    return false;
-  }
+  nsCSSRuleProcessor::InitSystemMetrics();
   return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex;
 }
 
 #ifdef XP_WIN
 /* static */ uint8_t
 nsCSSRuleProcessor::GetWindowsThemeIdentifier()
 {
-  if (!sSystemMetrics)
-    InitSystemMetrics();
+  nsCSSRuleProcessor::InitSystemMetrics();
   return sWinThemeId;
 }
 #endif
 
 /* static */
 EventStates
 nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext)
 {
@@ -1634,54 +1632,52 @@ StateSelectorMatches(Element* aElement,
 }
 
 // Chooses the thread safe version in Servo mode, and
 // the non-thread safe one in Gecko mode. The non thread safe one does
 // some extra caching, and is preferred when possible.
 static inline bool
 IsSignificantChildMaybeThreadSafe(const nsIContent* aContent,
                                   bool aTextIsSignificant,
-                                  bool aWhitespaceIsSignificant,
-                                  bool aIsGecko)
+                                  bool aWhitespaceIsSignificant)
 {
-  if (aIsGecko) {
-    auto content = const_cast<nsIContent*>(aContent);
-    return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
-  } else {
+  if (ServoStyleSet::IsInServoTraversal()) {
     // See bug 1349100 for optimizing this
     return nsStyleUtil::ThreadSafeIsSignificantChild(aContent,
                                                      aTextIsSignificant,
                                                      aWhitespaceIsSignificant);
+  } else {
+    auto content = const_cast<nsIContent*>(aContent);
+    return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
   }
 }
 
 /* static */ bool
 nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
                                         CSSPseudoClassType aPseudo,
                                         const char16_t* aString,
                                         const nsIDocument* aDocument,
                                         bool aForStyling,
                                         EventStates aStateMask,
-                                        bool aIsGecko,
                                         bool* aSetSlowSelectorFlag,
                                         bool* const aDependence)
 {
   MOZ_ASSERT(aSetSlowSelectorFlag);
 
   switch (aPseudo) {
     case CSSPseudoClassType::mozLocaleDir:
       {
         bool docIsRTL;
-        if (aIsGecko) {
+        if (ServoStyleSet::IsInServoTraversal()) {
+          docIsRTL = aDocument->ThreadSafeGetDocumentState()
+                              .HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
+        } else {
           auto doc = const_cast<nsIDocument*>(aDocument);
           docIsRTL = doc->GetDocumentState()
                         .HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
-        } else {
-          docIsRTL = aDocument->ThreadSafeGetDocumentState()
-                              .HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
         }
 
         nsDependentString dirString(aString);
 
         if (dirString.EqualsLiteral("rtl")) {
           if (!docIsRTL) {
             return false;
           }
@@ -1717,17 +1713,17 @@ nsCSSRuleProcessor::StringPseudoMatches(
           //   :-moz-empty-except-children-with-localname() ~ E
           // because we don't know to restyle the grandparent of the
           // inserted/removed element (as in bug 534804 for :empty).
           *aSetSlowSelectorFlag = true;
         }
         do {
           child = aElement->GetChildAt(++index);
         } while (child &&
-                  (!IsSignificantChildMaybeThreadSafe(child, true, false, aIsGecko) ||
+                  (!IsSignificantChildMaybeThreadSafe(child, true, false) ||
                   (child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
                     child->NodeInfo()->NameAtom()->Equals(nsDependentString(aString)))));
         if (child) {
           return false;
         }
       }
       break;
 
@@ -2163,17 +2159,16 @@ static bool SelectorMatches(Element* aEl
         MOZ_ASSERT(nsCSSPseudoClasses::HasStringArg(pseudoClass->mType));
         bool setSlowSelectorFlag = false;
         bool matched = nsCSSRuleProcessor::StringPseudoMatches(aElement,
                                                                pseudoClass->mType,
                                                                pseudoClass->u.mString,
                                                                aTreeMatchContext.mDocument,
                                                                aTreeMatchContext.mForStyling,
                                                                aNodeMatchContext.mStateMask,
-                                                               true,
                                                                &setSlowSelectorFlag,
                                                                aDependence);
         if (setSlowSelectorFlag) {
           aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
         }
 
         if (!matched) {
           return false;
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -77,16 +77,17 @@ public:
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
 
 public:
   nsresult ClearRuleCascades();
 
   static void Startup();
+  static void InitSystemMetrics();
   static void Shutdown();
   static void FreeSystemMetrics();
   static bool HasSystemMetric(nsIAtom* aMetric);
 
   /*
    * Returns true if the given aElement matches one of the
    * selectors in aSelectorList.  Note that this method will assume
    * the given aElement is not a relevant link.  aSelectorList must not
@@ -147,29 +148,27 @@ public:
    * @param aElement The element we are trying to match
    * @param aPseudo The name of the pseudoselector
    * @param aString The identifier inside the pseudoselector (cannot be null)
    * @param aDocument The document
    * @param aForStyling Is this matching operation for the creation of a style context?
    *                    (For setting the slow selector flag)
    * @param aStateMask Mask containing states which we should exclude.
    *                   Ignored if aDependence is null
-   * @param aIsGecko Set if Gecko.
    * @param aSetSlowSelectorFlag Outparam, set if the caller is
    *                             supposed to set the slow selector flag.
    * @param aDependence Pointer to be set to true if we ignored a state due to
    *                    aStateMask. Can be null.
    */
   static bool StringPseudoMatches(const mozilla::dom::Element* aElement,
                                   mozilla::CSSPseudoClassType aPseudo,
                                   const char16_t* aString,
                                   const nsIDocument* aDocument,
                                   bool aForStyling,
                                   mozilla::EventStates aStateMask,
-                                  bool aIsGecko,
                                   bool* aSetSlowSelectorFlag,
                                   bool* const aDependence = nullptr);
 
   // nsIStyleRuleProcessor
   virtual void RulesMatching(ElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
 
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -1230,8 +1230,12 @@ input[type=number]::-moz-number-spin-dow
   border-bottom-left-radius: 4px;
   border-bottom-right-radius: 4px;
 }
 
 input[type="number"] > div > div > div:hover {
   /* give some indication of hover state for the up/down buttons */
   background-color: lightblue;
 }
+
+:-moz-autofill {
+  filter: grayscale(21%) brightness(88%) contrast(161%) invert(10%) sepia(40%) saturate(206%);
+}
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -390,48 +390,48 @@ bool MP4Metadata::ShouldPreferRust() con
   return false;
 }
 
 static const char*
 GetDifferentField(const mozilla::TrackInfo& info,
                   const mozilla::TrackInfo& infoRust)
 {
   if (infoRust.mId != info.mId) { return "Id"; }
-  if (infoRust.mKind == info.mKind) { return "Kind"; }
-  if (infoRust.mLabel == info.mLabel) { return "Label"; }
-  if (infoRust.mLanguage == info.mLanguage) { return "Language"; }
-  if (infoRust.mEnabled == info.mEnabled) { return "Enabled"; }
-  if (infoRust.mTrackId == info.mTrackId) { return "TrackId"; }
-  if (infoRust.mMimeType == info.mMimeType) { return "MimeType"; }
-  if (infoRust.mDuration == info.mDuration) { return "Duration"; }
-  if (infoRust.mMediaTime == info.mMediaTime) { return "MediaTime"; }
-  if (infoRust.mCrypto.mValid == info.mCrypto.mValid) { return "Crypto-Valid"; }
-  if (infoRust.mCrypto.mMode == info.mCrypto.mMode) { return "Crypto-Mode"; }
-  if (infoRust.mCrypto.mIVSize == info.mCrypto.mIVSize) { return "Crypto-IVSize"; }
-  if (infoRust.mCrypto.mKeyId == info.mCrypto.mKeyId) { return "Crypto-KeyId"; }
+  if (infoRust.mKind != info.mKind) { return "Kind"; }
+  if (infoRust.mLabel != info.mLabel) { return "Label"; }
+  if (infoRust.mLanguage != info.mLanguage) { return "Language"; }
+  if (infoRust.mEnabled != info.mEnabled) { return "Enabled"; }
+  if (infoRust.mTrackId != info.mTrackId) { return "TrackId"; }
+  if (infoRust.mMimeType != info.mMimeType) { return "MimeType"; }
+  if (infoRust.mDuration != info.mDuration) { return "Duration"; }
+  if (infoRust.mMediaTime != info.mMediaTime) { return "MediaTime"; }
+  if (infoRust.mCrypto.mValid != info.mCrypto.mValid) { return "Crypto-Valid"; }
+  if (infoRust.mCrypto.mMode != info.mCrypto.mMode) { return "Crypto-Mode"; }
+  if (infoRust.mCrypto.mIVSize != info.mCrypto.mIVSize) { return "Crypto-IVSize"; }
+  if (infoRust.mCrypto.mKeyId != info.mCrypto.mKeyId) { return "Crypto-KeyId"; }
   switch (info.GetType()) {
   case mozilla::TrackInfo::kAudioTrack: {
     const AudioInfo *audioRust = infoRust.GetAsAudioInfo();
     const AudioInfo *audio = info.GetAsAudioInfo();
-    if (audioRust->mRate == audio->mRate) { return "Rate"; }
-    if (audioRust->mChannels == audio->mChannels) { return "Channels"; }
-    if (audioRust->mBitDepth == audio->mBitDepth) { return "BitDepth"; }
-    if (audioRust->mProfile == audio->mProfile) { return "Profile"; }
-    if (audioRust->mExtendedProfile == audio->mExtendedProfile) { return "ExtendedProfile"; }
+    if (audioRust->mRate != audio->mRate) { return "Rate"; }
+    if (audioRust->mChannels != audio->mChannels) { return "Channels"; }
+    if (audioRust->mBitDepth != audio->mBitDepth) { return "BitDepth"; }
+    if (audioRust->mProfile != audio->mProfile) { return "Profile"; }
+    if (audioRust->mExtendedProfile != audio->mExtendedProfile) { return "ExtendedProfile"; }
     break;
   }
   case mozilla::TrackInfo::kVideoTrack: {
     const VideoInfo *videoRust = infoRust.GetAsVideoInfo();
     const VideoInfo *video = info.GetAsVideoInfo();
-    if (videoRust->mDisplay == video->mDisplay) { return "Display"; }
-    if (videoRust->mImage == video->mImage) { return "Image"; }
-    if (*videoRust->mExtraData == *video->mExtraData) { return "ExtraData"; }
+    if (videoRust->mDisplay != video->mDisplay) { return "Display"; }
+    if (videoRust->mImage != video->mImage) { return "Image"; }
+    if (*videoRust->mExtraData != *video->mExtraData) { return "ExtraData"; }
     // mCodecSpecificConfig is for video/mp4-es, not video/avc. Since video/mp4-es
     // is supported on b2g only, it could be removed from TrackInfo.
-    if (*videoRust->mCodecSpecificConfig == *video->mCodecSpecificConfig) { return "CodecSpecificConfig"; }
+    if (*videoRust->mCodecSpecificConfig != *video->mCodecSpecificConfig) { return "CodecSpecificConfig"; }
     break;
   }
   default:
     break;
   }
 
   return nullptr;
 }
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -259,17 +259,17 @@
             </intent-filter>
         </receiver>
 
 #include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
 #ifdef MOZ_ANDROID_SEARCH_ACTIVITY
 #include ../search/manifests/SearchAndroidManifest_activities.xml.in
 #endif
 
-#if MOZ_CRASHREPORTER
+#ifdef MOZ_CRASHREPORTER
   <activity android:name="org.mozilla.gecko.CrashReporter"
             android:process="@ANDROID_PACKAGE_NAME@.CrashReporter"
             android:label="@string/crash_reporter_title"
             android:icon="@drawable/crash_reporter"
             android:theme="@style/Gecko"
             android:exported="false"
             android:excludeFromRecents="true">
           <intent-filter>
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -186,17 +186,17 @@ public class AppConstants {
     public static final boolean MOZ_TELEMETRY_REPORTING =
 //#ifdef MOZ_TELEMETRY_REPORTING
     true;
 //#else
     false;
 //#endif
 
     public static final boolean MOZ_CRASHREPORTER =
-//#if MOZ_CRASHREPORTER
+//#ifdef MOZ_CRASHREPORTER
     true;
 //#else
     false;
 //#endif
 
     public static final boolean MOZ_DATA_REPORTING =
 //#ifdef MOZ_DATA_REPORTING
       true;
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -3,38 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # We call mach -> Make -> gradle -> mach, which races to find and
 # create .mozconfig files and to generate targets.
 ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
 .NOTPARALLEL:
 endif
 
-MOZ_BUILDID := $(shell awk '{print $$3}' $(DEPTH)/buildid.h)
-
-# Set the appropriate version code, based on the existance of the
-# MOZ_APP_ANDROID_VERSION_CODE variable.
-ifdef MOZ_APP_ANDROID_VERSION_CODE
-    ANDROID_VERSION_CODE:=$(MOZ_APP_ANDROID_VERSION_CODE)
-else
-    ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
-      $(topsrcdir)/python/mozbuild/mozbuild/android_version_code.py \
-        --verbose \
-        --with-android-cpu-arch=$(ANDROID_CPU_ARCH) \
-        $(if $(MOZ_ANDROID_MIN_SDK_VERSION),--with-android-min-sdk=$(MOZ_ANDROID_MIN_SDK_VERSION)) \
-        $(if $(MOZ_ANDROID_MAX_SDK_VERSION),--with-android-max-sdk=$(MOZ_ANDROID_MAX_SDK_VERSION)) \
-        $(MOZ_BUILDID))
-endif
-
-DEFINES += \
-  -DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
-  -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
-  -DMOZ_BUILDID=$(MOZ_BUILDID) \
-  $(NULL)
-
 GARBAGE += \
   classes.dex  \
   gecko.ap_  \
   res/values/strings.xml \
   res/raw/browsersearch.json \
   res/raw/suggestedsites.json \
   .aapt.deps \
   GeneratedJNINatives.h \
--- a/mobile/android/base/generate_build_config.py
+++ b/mobile/android/base/generate_build_config.py
@@ -23,16 +23,17 @@ from __future__ import (
 
 from collections import defaultdict
 import os
 import sys
 
 import buildconfig
 
 from mozbuild import preprocessor
+from mozbuild.android_version_code import android_version_code
 
 
 def _defines():
     CONFIG = defaultdict(lambda: None)
     CONFIG.update(buildconfig.substs)
     DEFINES = dict(buildconfig.defines)
 
     for var in ('MOZ_ANDROID_ACTIVITY_STREAM'
@@ -40,16 +41,17 @@ def _defines():
                 'MOZ_ANDROID_BEAM',
                 'MOZ_ANDROID_CUSTOM_TABS',
                 'MOZ_ANDROID_DOWNLOADS_INTEGRATION',
                 'MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE',
                 'MOZ_ANDROID_EXCLUDE_FONTS',
                 'MOZ_ANDROID_GCM',
                 'MOZ_ANDROID_MLS_STUMBLER',
                 'MOZ_ANDROID_SEARCH_ACTIVITY',
+                'MOZ_CRASHREPORTER',
                 'MOZ_DEBUG',
                 'MOZ_INSTALL_TRACKING',
                 'MOZ_LOCALE_SWITCHER',
                 'MOZ_NATIVE_DEVICES',
                 'MOZ_SWITCHBOARD'):
         if CONFIG[var]:
             DEFINES[var] = 1
 
@@ -57,28 +59,28 @@ def _defines():
                 'MOZ_PKG_SPECIAL',
                 'MOZ_UPDATER'):
         if CONFIG[var]:
             DEFINES[var] = CONFIG[var]
 
     for var in ('ANDROID_CPU_ARCH',
                 'ANDROID_PACKAGE_NAME',
                 'GRE_MILESTONE',
+                'MOZ_ANDROID_SHARED_ID',
                 'MOZ_ANDROID_APPLICATION_CLASS',
                 'MOZ_ANDROID_BROWSER_INTENT_CLASS',
                 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
                 'MOZ_APP_BASENAME',
                 'MOZ_APP_DISPLAYNAME',
                 'MOZ_APP_ID',
                 'MOZ_APP_NAME',
                 'MOZ_APP_UA_NAME',
                 'MOZ_APP_VENDOR',
                 'MOZ_APP_VERSION',
                 'MOZ_CHILD_PROCESS_NAME',
-                'MOZ_CRASHREPORTER',
                 'MOZ_MOZILLA_API_KEY',
                 'MOZ_UPDATE_CHANNEL',
                 'OMNIJAR_NAME',
                 'OS_TARGET',
                 'TARGET_XPCOM_ABI'):
         DEFINES[var] = CONFIG[var]
 
     # Mangle our package name to avoid Bug 750548.
@@ -96,18 +98,40 @@ def _defines():
 
     # It's okay to use MOZ_ADJUST_SDK_KEY here because this doesn't
     # leak the value to build logs.
     if CONFIG['MOZ_INSTALL_TRACKING']:
         DEFINES['MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN'] = CONFIG['MOZ_ADJUST_SDK_KEY']
 
     DEFINES['MOZ_BUILDID'] = open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
 
+    # Set the appropriate version code if not set by MOZ_APP_ANDROID_VERSION_CODE.
+    if CONFIG.get('MOZ_APP_ANDROID_VERSION_CODE'):
+        DEFINES['ANDROID_VERSION_CODE'] = \
+            CONFIG.get('MOZ_APP_ANDROID_VERSION_CODE')
+    else:
+        min_sdk = int(CONFIG.get('MOZ_ANDROID_MIN_SDK_VERSION') or '0') or None
+        max_sdk = int(CONFIG.get('MOZ_ANDROID_MAX_SDK_VERSION') or '0') or None
+        DEFINES['ANDROID_VERSION_CODE'] = android_version_code(
+            buildid=DEFINES['MOZ_BUILDID'],
+            cpu_arch=CONFIG['ANDROID_CPU_ARCH'],
+            min_sdk=min_sdk,
+            max_sdk=max_sdk)
+
     return DEFINES
 
 
 def generate_java(output_file, input_filename):
     includes = preprocessor.preprocess(includes=[input_filename],
                                    defines=_defines(),
                                    output=output_file,
-                                   marker="//#")
+                                   marker='//#')
     includes.add(os.path.join(buildconfig.topobjdir, 'buildid.h'))
     return includes
+
+
+def generate_android_manifest(output_file, input_filename):
+    includes = preprocessor.preprocess(includes=[input_filename],
+                                       defines=_defines(),
+                                       output=output_file,
+                                       marker='#')
+    includes.add(os.path.join(buildconfig.topobjdir, 'buildid.h'))
+    return includes
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -17,16 +17,19 @@ import android.support.annotation.UiThre
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
+import org.mozilla.gecko.bookmarks.BookmarkUtils;
+import org.mozilla.gecko.bookmarks.EditBookmarkTask;
 import org.mozilla.gecko.cleanup.FileCleanupController;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.delegates.BrowserAppDelegate;
 import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
 import org.mozilla.gecko.delegates.ScreenshotDelegate;
 import org.mozilla.gecko.distribution.Distribution;
@@ -161,18 +164,16 @@ import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.ViewFlipper;
 import org.mozilla.gecko.switchboard.AsyncConfigLoader;
 import org.mozilla.gecko.switchboard.SwitchBoard;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.Collections;
@@ -190,17 +191,18 @@ public class BrowserApp extends GeckoApp
                                    DynamicToolbarAnimator.MetricsListener,
                                    DynamicToolbarAnimator.ToolbarChromeProxy,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
                                    OnUrlOpenListener,
                                    OnUrlOpenInBackgroundListener,
                                    AnchoredPopup.OnVisibilityChangeListener,
                                    ActionModePresenter,
-                                   LayoutInflater.Factory {
+                                   LayoutInflater.Factory,
+                                   BookmarkEditFragment.Callbacks {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
     // Intent String extras used to specify custom Switchboard configurations.
     private static final String INTENT_KEY_SWITCHBOARD_SERVER = "switchboard-server";
 
     // TODO: Replace with kinto endpoint.
@@ -4111,9 +4113,27 @@ public class BrowserApp extends GeckoApp
         }
 
         if (GeckoProfile.get(this).inGuestMode()) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "guest");
         } else if (Restrictions.isRestrictedProfile(this)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "restricted");
         }
     }
+
+    /**
+     * Launch edit bookmark dialog. The {@link BookmarkEditFragment} needs to be started by an activity
+     * that implements the interface({@link BookmarkEditFragment.Callbacks}) for handling callback method.
+     */
+    public void showEditBookmarkDialog(String pageUrl) {
+        if (BookmarkUtils.isEnabled(this)) {
+            BookmarkEditFragment dialog = BookmarkEditFragment.newInstance(pageUrl);
+            dialog.show(getSupportFragmentManager(), "edit-bookmark");
+        } else {
+            new EditBookmarkDialog(this).show(pageUrl);
+        }
+    }
+
+    @Override
+    public void onEditBookmark(@NonNull Bundle bundle) {
+        new EditBookmarkTask(this, bundle).execute();
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/Experiments.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Experiments.java
@@ -63,16 +63,19 @@ public class Experiments {
     public static final String ACTIVITY_STREAM_SETTING = "activity-stream-setting";
 
     // Enable Activity stream by default for users in the "opt out" group.
     public static final String ACTIVITY_STREAM_OPT_OUT = "activity-stream-opt-out";
 
     // Tabs tray: Arrange tabs in two columns in portrait mode
     public static final String COMPACT_TABS = "compact-tabs";
 
+    // Enable full bookmark management(full-page dialog, bookmark/folder modification, etc.)
+    public static final String FULL_BOOKMARK_MANAGEMENT = "full-bookmark-management";
+
     /**
      * Returns if a user is in certain local experiment.
      * @param experiment Name of experiment to look up
      * @return returns value for experiment or false if experiment does not exist.
      */
     public static boolean isInExperimentLocal(Context context, String experiment) {
         if (SwitchBoard.isInBucket(context, 0, 20)) {
             return Experiments.ONBOARDING3_A.equals(experiment);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1878,24 +1878,28 @@ public abstract class GeckoApp
         }
 
         final GeckoBundle restoreData = new GeckoBundle(1);
         restoreData.putString("sessionString", sessionString);
         return restoreData;
     }
 
     @RobocopTarget
-    public static EventDispatcher getEventDispatcher() {
+    public static @NonNull EventDispatcher getEventDispatcher() {
         final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
         return geckoApp.getAppEventDispatcher();
     }
 
     @Override
-    public EventDispatcher getAppEventDispatcher() {
-        return mLayerView != null ? mLayerView.getEventDispatcher() : null;
+    public @NonNull EventDispatcher getAppEventDispatcher() {
+        if (mLayerView == null) {
+            throw new IllegalStateException("Must not call getAppEventDispatcher() until after onCreate()");
+        }
+
+        return mLayerView.getEventDispatcher();
     }
 
     @Override
     public GeckoProfile getProfile() {
         return GeckoThread.getActiveProfile();
     }
 
     /**
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
@@ -0,0 +1,498 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.bookmarks;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
+import org.mozilla.gecko.db.BrowserDB;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A dialog fragment that allows editing bookmark's url, title and changing the parent."
+ */
+public class BookmarkEditFragment extends DialogFragment {
+
+    private static final String ARG_ID = "id";
+    private static final String ARG_URL = "url";
+    private static final String ARG_BOOKMARK = "bookmark";
+
+    private long bookmarkId;
+    private String url;
+    private Bookmark bookmark;
+
+    private Toolbar toolbar;
+    private EditText nameText;
+    private TextInputLayout locationLayout;
+    private EditText locationText;
+    private EditText folderText;
+
+    public interface Callbacks {
+        /**
+         * A callback method to tell caller that bookmark has been modified.
+         * Caller takes charge for the change(e.g. update database).
+         */
+        void onEditBookmark(Bundle bundle);
+    }
+    private Callbacks callbacks;
+
+    public static BookmarkEditFragment newInstance(long id) {
+        final Bundle args = new Bundle();
+        args.putLong(ARG_ID, id);
+
+        return newInstance(args);
+    }
+
+    public static BookmarkEditFragment newInstance(String url) {
+        final Bundle args = new Bundle();
+        args.putString(ARG_URL, url);
+
+        return newInstance(args);
+    }
+
+    private static BookmarkEditFragment newInstance(Bundle args) {
+        final BookmarkEditFragment fragment = new BookmarkEditFragment();
+        fragment.setArguments(args);
+
+        return fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        Fragment fragment = getTargetFragment();
+        if (fragment != null && fragment instanceof Callbacks) {
+            callbacks = (Callbacks) fragment;
+        } else if (context instanceof Callbacks) {
+            callbacks = (Callbacks) context;
+        }
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Apply DialogWhenLarge theme
+        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.Bookmark_Gecko);
+
+        Bundle args = getArguments();
+        bookmarkId = args.getLong(ARG_ID);
+        url = args.getString(ARG_URL);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.bookmark_edit_with_full_page, container);
+        toolbar = (Toolbar) view.findViewById(R.id.toolbar);
+        nameText = (EditText) view.findViewById(R.id.edit_bookmark_name);
+        locationLayout = (TextInputLayout) view.findViewById(R.id.edit_bookmark_location_layout);
+        locationText = (EditText) view.findViewById(R.id.edit_bookmark_location);
+        folderText = (EditText) view.findViewById(R.id.edit_parent_folder);
+
+        toolbar.inflateMenu(R.menu.bookmark_edit_menu);
+        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
+            @Override
+            public boolean onMenuItemClick(MenuItem item) {
+                switch (item.getItemId()) {
+                    case R.id.done:
+                        final String newUrl = locationText.getText().toString().trim();
+                        final String newTitle = nameText.getText().toString();
+                        if (callbacks != null) {
+                            if (TextUtils.equals(newTitle, bookmark.originalTitle) &&
+                                TextUtils.equals(newUrl, bookmark.originalUrl) &&
+                                bookmark.parentId == bookmark.originalParentId) {
+                                // Nothing changed, skip callback.
+                                break;
+                            }
+
+                            final Bundle bundle = new Bundle();
+                            bundle.putLong(Bookmarks._ID, bookmark.id);
+                            bundle.putString(Bookmarks.TITLE, newTitle);
+                            bundle.putString(Bookmarks.URL, newUrl);
+                            bundle.putString(Bookmarks.KEYWORD, bookmark.keyword);
+                            if (bookmark.parentId != bookmark.originalParentId) {
+                                bundle.putLong(Bookmarks.PARENT, bookmark.parentId);
+                                bundle.putLong(BrowserContract.PARAM_OLD_BOOKMARK_PARENT, bookmark.originalParentId);
+                            }
+
+                            callbacks.onEditBookmark(bundle);
+                        }
+                        break;
+                }
+
+                dismiss();
+                return true;
+            }
+        });
+        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+
+        return view;
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            final Bookmark bookmark = savedInstanceState.getParcelable(ARG_BOOKMARK);
+            if (bookmark != null) {
+                invalidateView(bookmark);
+                return;
+            }
+        }
+
+        getLoaderManager().initLoader(0, null, new BookmarkLoaderCallbacks());
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+
+        getLoaderManager().destroyLoader(0);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        if (bookmark != null) {
+            bookmark.url = locationText.getText().toString().trim();
+            bookmark.title = nameText.getText().toString();
+            bookmark.folder = folderText.getText().toString();
+            outState.putParcelable(ARG_BOOKMARK, bookmark);
+        }
+
+        super.onSaveInstanceState(outState);
+    }
+
+    private void invalidateView(Bookmark bookmark) {
+        this.bookmark = bookmark;
+
+        nameText.setText(bookmark.title);
+
+        if (bookmark.type == Bookmarks.TYPE_FOLDER) {
+            locationLayout.setVisibility(View.GONE);
+        } else {
+            locationLayout.setVisibility(View.VISIBLE);
+        }
+        locationText.setText(bookmark.url);
+
+        if (Bookmarks.MOBILE_FOLDER_GUID.equals(bookmark.guid)) {
+            folderText.setText(R.string.bookmarks_folder_mobile);
+        } else {
+            folderText.setText(bookmark.folder);
+        }
+
+        // Enable menu item after bookmark is set to view
+        final MenuItem doneItem = toolbar.getMenu().findItem(R.id.done);
+        doneItem.setEnabled(true);
+
+        // Add a TextWatcher to prevent invalid input(e.g. empty string).
+        if (bookmark.type == Bookmarks.TYPE_FOLDER) {
+            BookmarkTextWatcher nameTextWatcher = new BookmarkTextWatcher(doneItem);
+            nameText.addTextChangedListener(nameTextWatcher);
+        } else {
+            BookmarkTextWatcher locationTextWatcher = new BookmarkTextWatcher(doneItem);
+            locationText.addTextChangedListener(locationTextWatcher);
+        }
+    }
+
+    /**
+     * A private struct to make it easier to pass bookmark data across threads
+     */
+    private static class Bookmark implements Parcelable {
+        // Cannot be modified in this fragment.
+        final long id;
+        final String keyword;
+        final int type; // folder or bookmark
+        final String guid;
+        final String originalTitle;
+        final String originalUrl;
+        final long originalParentId;
+        final String originalFolder;
+
+        // Can be modified in this fragment.
+        String title;
+        String url;
+        long parentId;
+        String folder;
+
+        public Bookmark(long id, String url, String title, String keyword, long parentId,
+                        String folder, int type, String guid) {
+            this(id, url, title, keyword, parentId, folder, type, guid, url, title, parentId, folder);
+        }
+
+        private Bookmark(long id, String originalUrl, String originalTitle, String keyword,
+                         long originalParentId, String originalFolder, int type, String guid,
+                         String modifiedUrl, String modifiedTitle, long modifiedParentId, String modifiedFolder) {
+            this.id = id;
+            this.originalUrl = originalUrl;
+            this.originalTitle = originalTitle;
+            this.keyword = keyword;
+            this.originalParentId = originalParentId;
+            this.originalFolder = originalFolder;
+            this.type = type;
+            this.guid = guid;
+
+            this.url = modifiedUrl;
+            this.title = modifiedTitle;
+            this.parentId = modifiedParentId;
+            this.folder = modifiedFolder;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeLong(id);
+            parcel.writeString(url);
+            parcel.writeString(title);
+            parcel.writeString(keyword);
+            parcel.writeLong(parentId);
+            parcel.writeString(folder);
+            parcel.writeInt(type);
+            parcel.writeString(guid);
+            parcel.writeString(originalUrl);
+            parcel.writeString(originalTitle);
+            parcel.writeLong(originalParentId);
+            parcel.writeString(originalFolder);
+        }
+
+        public static final Creator<Bookmark> CREATOR = new Creator<Bookmark>() {
+            @Override
+            public Bookmark createFromParcel(final Parcel source) {
+                final long id = source.readLong();
+                final String modifiedUrl = source.readString();
+                final String modifiedTitle = source.readString();
+                final String keyword = source.readString();
+                final long modifiedParentId = source.readLong();
+                final String modifiedFolder = source.readString();
+                final int type = source.readInt();
+                final String guid = source.readString();
+
+                final String originalUrl = source.readString();
+                final String originalTitle = source.readString();
+                final long originalParentId = source.readLong();
+                final String originalFolder = source.readString();
+
+                return new Bookmark(id, originalUrl, originalTitle, keyword, originalParentId, originalFolder,
+                                    type, guid, modifiedUrl, modifiedTitle, modifiedParentId, modifiedFolder);
+            }
+
+            @Override
+            public Bookmark[] newArray(final int size) {
+                return new Bookmark[size];
+            }
+        };
+    }
+
+    private class BookmarkLoaderCallbacks implements LoaderManager.LoaderCallbacks<Bookmark> {
+        @Override
+        public Loader<Bookmark> onCreateLoader(int id, Bundle args) {
+            return new BookmarkLoader(getContext(), bookmarkId, url);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Bookmark> loader, final Bookmark bookmark) {
+            if (bookmark == null) {
+                return;
+            }
+
+            invalidateView(bookmark);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Bookmark> loader) {
+        }
+    }
+
+    /**
+     * An AsyncTaskLoader to load {@link Bookmark} from a cursor.
+     */
+    private static class BookmarkLoader extends AsyncTaskLoader<Bookmark> {
+        private final long bookmarkId;
+        private final String url;
+        private final ContentResolver contentResolver;
+        private final BrowserDB db;
+        private Bookmark bookmark;
+
+        private BookmarkLoader(Context context, long id, String url) {
+            super(context);
+
+            this.bookmarkId = id;
+            this.url = url;
+            this.contentResolver = context.getContentResolver();
+            this.db = BrowserDB.from(context);
+        }
+
+        @Override
+        public Bookmark loadInBackground() {
+            final Cursor cursor;
+
+            if (url != null) {
+                cursor = db.getBookmarkForUrl(contentResolver, url);
+            } else {
+                cursor = db.getBookmarkById(contentResolver, bookmarkId);
+            }
+            if (cursor == null) {
+                return null;
+            }
+
+            Bookmark bookmark = null;
+            try {
+                if (cursor.moveToFirst()) {
+                    final long id = cursor.getLong(cursor.getColumnIndexOrThrow(Bookmarks._ID));
+                    final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
+                    final String title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
+                    final String keyword = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.KEYWORD));
+
+                    final long parentId = cursor.getLong(cursor.getColumnIndexOrThrow(Bookmarks.PARENT));
+                    final String parentName = queryParentName(parentId);
+
+                    final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
+                    final String guid = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.GUID));
+                    bookmark = new Bookmark(id, url, title, keyword, parentId, parentName, type, guid);
+                }
+            } finally {
+                cursor.close();
+            }
+            return bookmark;
+        }
+
+        private String queryParentName(long folderId) {
+            Cursor cursor = db.getBookmarkById(contentResolver, folderId);
+            if (cursor == null) {
+                return "";
+            }
+
+            String folderName = "";
+            try {
+                if (cursor.moveToFirst()) {
+                    final String guid = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.GUID));
+                    if (Bookmarks.MOBILE_FOLDER_GUID.equals(guid)) {
+                        folderName = getContext().getString(R.string.bookmarks_folder_mobile);
+                    } else {
+                        folderName = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
+                    }
+                }
+            } finally {
+                cursor.close();
+            }
+            return folderName;
+        }
+
+        @Override
+        public void deliverResult(Bookmark bookmark) {
+            if (isReset()) {
+                this.bookmark = null;
+                return;
+            }
+
+            this.bookmark = bookmark;
+
+            if (isStarted()) {
+                super.deliverResult(bookmark);
+            }
+        }
+
+        @Override
+        protected void onStartLoading() {
+            if (bookmark != null) {
+                deliverResult(bookmark);
+            }
+
+            if (takeContentChanged() || bookmark == null) {
+                forceLoad();
+            }
+        }
+
+        @Override
+        protected void onStopLoading() {
+            cancelLoad();
+        }
+
+        @Override
+        public void onCanceled(Bookmark bookmark) {
+            this.bookmark = null;
+        }
+
+        @Override
+        protected void onReset() {
+            super.onReset();
+
+            // Ensure the loader is stopped.
+            onStopLoading();
+
+            bookmark = null;
+        }
+    }
+
+    /**
+     * This text watcher enables the menu item if the dialog contains valid information, or disables otherwise.
+     */
+    private class BookmarkTextWatcher implements TextWatcher {
+        // A stored reference to the dialog containing the text field being watched.
+        private final WeakReference<MenuItem> doneItemWeakReference;
+
+        // Whether or not the menu item should be enabled.
+        private boolean enabled = true;
+
+        private BookmarkTextWatcher(MenuItem doneItem) {
+            doneItemWeakReference = new WeakReference<>(doneItem);
+        }
+
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // Disables the menu item if the input field is empty.
+            final boolean enabled = (s.toString().trim().length() > 0);
+
+            final MenuItem doneItem = doneItemWeakReference.get();
+            if (doneItem != null) {
+                doneItem.setEnabled(enabled);
+            }
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {}
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkUtils.java
@@ -0,0 +1,24 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.bookmarks;
+
+import android.content.Context;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.Experiments;
+import org.mozilla.gecko.switchboard.SwitchBoard;
+
+public class BookmarkUtils {
+
+    /**
+     * Get the switch from {@link SwitchBoard}. It's used to enable/disable
+     * full bookmark management features(full-page dialog, bookmark/folder modification, etc.)
+     */
+    public static boolean isEnabled(Context context) {
+        return AppConstants.NIGHTLY_BUILD &&
+                       SwitchBoard.isInExperiment(context, Experiments.FULL_BOOKMARK_MANAGEMENT);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/EditBookmarkTask.java
@@ -0,0 +1,68 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.bookmarks;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.SnackbarBuilder;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UIAsyncTask;
+
+import java.lang.ref.WeakReference;
+
+public class EditBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
+    private final WeakReference<Activity> activityWeakReference;
+    private final BrowserDB db;
+    private final ContentResolver contentResolver;
+    private final Bundle bundle;
+
+    public EditBookmarkTask(Activity activity, @NonNull Bundle bundle) {
+        super(ThreadUtils.getBackgroundHandler());
+
+        this.activityWeakReference = new WeakReference<>(activity);
+        this.db = BrowserDB.from(activity);
+        this.contentResolver = activity.getContentResolver();
+        this.bundle = bundle;
+    }
+
+    @Override
+    public Void doInBackground() {
+        final long bookmarkId = bundle.getLong(BrowserContract.Bookmarks._ID);
+        final String url = bundle.getString(BrowserContract.Bookmarks.URL);
+        final String title = bundle.getString(BrowserContract.Bookmarks.TITLE);
+        final String keyword = bundle.getString(BrowserContract.Bookmarks.KEYWORD);
+
+        if (bundle.containsKey(BrowserContract.Bookmarks.PARENT) &&
+            bundle.containsKey(BrowserContract.PARAM_OLD_BOOKMARK_PARENT)) {
+            final long newParentId = bundle.getLong(BrowserContract.Bookmarks.PARENT);
+            final long oldParentId = bundle.getLong(BrowserContract.PARAM_OLD_BOOKMARK_PARENT);
+            db.updateBookmark(contentResolver, bookmarkId, url, title, keyword, newParentId, oldParentId);
+        } else {
+            db.updateBookmark(contentResolver, bookmarkId, url, title, keyword);
+        }
+        return null;
+    }
+
+    @Override
+    public void onPostExecute(Void result) {
+        final Activity activity = activityWeakReference.get();
+        if (activity == null || activity.isFinishing()) {
+            return;
+        }
+
+        SnackbarBuilder.builder(activity)
+                .message(R.string.bookmark_updated)
+                .duration(Snackbar.LENGTH_LONG)
+                .buildAndShow();
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -45,16 +45,17 @@ public class BrowserContract {
     public static final String PARAM_PROFILE = "profile";
     public static final String PARAM_PROFILE_PATH = "profilePath";
     public static final String PARAM_LIMIT = "limit";
     public static final String PARAM_SUGGESTEDSITES_LIMIT = "suggestedsites_limit";
     public static final String PARAM_TOPSITES_EXCLUDE_REMOTE_ONLY = "topsites_exclude_remote_only";
     public static final String PARAM_IS_SYNC = "sync";
     public static final String PARAM_SHOW_DELETED = "show_deleted";
     public static final String PARAM_IS_TEST = "test";
+    public static final String PARAM_OLD_BOOKMARK_PARENT = "old_bookmark_parent";
     public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
     public static final String PARAM_INCREMENT_VISITS = "increment_visits";
     public static final String PARAM_INCREMENT_REMOTE_AGGREGATES = "increment_remote_aggregates";
     public static final String PARAM_NON_POSITIONED_PINS = "non_positioned_pins";
     public static final String PARAM_EXPIRE_PRIORITY = "priority";
     public static final String PARAM_DATASET_ID = "dataset_id";
     public static final String PARAM_GROUP_BY = "group_by";
 
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
@@ -17,16 +17,17 @@ import org.mozilla.gecko.icons.decoders.
 
 import android.content.ContentProviderClient;
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
 import android.support.v4.content.CursorLoader;
 
 /**
  * Interface for interactions with all databases. If you want an instance
  * that implements this, you should go through GeckoProfile. E.g.,
  * <code>BrowserDB.from(context)</code>.
  */
 public abstract class BrowserDB {
@@ -101,21 +102,25 @@ public abstract class BrowserDB {
 
     public abstract void clearHistory(ContentResolver cr, boolean clearSearchHistory);
 
 
     public abstract String getUrlForKeyword(ContentResolver cr, String keyword);
 
     public abstract boolean isBookmark(ContentResolver cr, String uri);
     public abstract boolean addBookmark(ContentResolver cr, String title, String uri);
+    public abstract Uri addBookmarkFolder(ContentResolver cr, String title, long parentId);
     public abstract Cursor getBookmarkForUrl(ContentResolver cr, String url);
     public abstract Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl);
+    public abstract Cursor getBookmarkById(ContentResolver cr, long id);
     public abstract void removeBookmarksWithURL(ContentResolver cr, String uri);
+    public abstract void removeBookmarkWithId(ContentResolver cr, long id);
     public abstract void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
-    public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
+    public abstract void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword);
+    public abstract void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword, long newParentId, long oldParentId);
     public abstract boolean hasBookmarkWithGuid(ContentResolver cr, String guid);
 
     public abstract boolean insertPageMetadata(ContentProviderClient contentProviderClient, String pageUrl, boolean hasImage, String metadataJSON);
     public abstract int deletePageMetadata(ContentProviderClient contentProviderClient, String pageUrl);
     /**
      * Can return <code>null</code>.
      */
     public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -10,33 +10,31 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ranking.HighlightsRanking;
 import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
 import org.mozilla.gecko.db.BrowserContract.Highlights;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Visits;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.db.DBUtils.UpdateOperation;
-import org.mozilla.gecko.home.activitystream.model.Highlight;
 import org.mozilla.gecko.icons.IconsHelper;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
@@ -45,17 +43,16 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.OperationApplicationException;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.MatrixCursor;
-import android.database.MergeCursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteConstraintException;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.database.sqlite.SQLiteStatement;
 import android.net.Uri;
 import android.os.Bundle;
@@ -1500,17 +1497,22 @@ public class BrowserProvider extends Sha
         return processCount;
     }
 
     /**
      * Construct an update expression that will modify the parents of any records
      * that match.
      */
     private int updateBookmarkParents(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs) {
-        trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
+        if (selectionArgs != null) {
+            trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
+        } else {
+            trace("Updating bookmark parents of " + selection);
+        }
+
         String where = Bookmarks._ID + " IN (" +
                        " SELECT DISTINCT " + Bookmarks.PARENT +
                        " FROM " + TABLE_BOOKMARKS +
                        " WHERE " + selection + " )";
         return db.update(TABLE_BOOKMARKS, values, where, selectionArgs);
     }
 
     private long insertBookmark(Uri uri, ContentValues values) {
@@ -1541,17 +1543,42 @@ public class BrowserProvider extends Sha
             values.put(Bookmarks.TITLE, "");
         }
 
         String url = values.getAsString(Bookmarks.URL);
 
         debug("Inserting bookmark in database with URL: " + url);
         final SQLiteDatabase db = getWritableDatabase(uri);
         beginWrite(db);
-        return db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
+        final long insertedId = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
+
+        if (insertedId == -1) {
+            Log.e(LOGTAG, "Unable to insert bookmark in database with URL: " + url);
+            return insertedId;
+        }
+
+        if (isCallerSync(uri)) {
+            // Sync will handle timestamps on its own, so we don't perform the update here.
+            return insertedId;
+        }
+
+        // Bump parent's lastModified timestamp.
+        final long lastModified = values.getAsLong(Bookmarks.DATE_MODIFIED);
+        final ContentValues parentValues = new ContentValues();
+        parentValues.put(Bookmarks.DATE_MODIFIED, lastModified);
+
+        // The ContentValues should have parentId, or the insertion above would fail because of
+        // database schema foreign key constraint.
+        final long parentId = values.getAsLong(Bookmarks.PARENT);
+        db.update(TABLE_BOOKMARKS,
+                  parentValues,
+                  Bookmarks._ID + " = ?",
+                  new String[] { String.valueOf(parentId) });
+
+        return insertedId;
     }
 
 
     private int updateOrInsertBookmark(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
         int updated = updateBookmarks(uri, values, selection, selectionArgs);
         if (updated > 0) {
             return updated;
@@ -1590,17 +1617,63 @@ public class BrowserProvider extends Sha
         final String inClause;
         try {
             inClause = DBUtils.computeSQLInClauseFromLongs(cursor, Bookmarks._ID);
         } finally {
             cursor.close();
         }
 
         beginWrite(db);
-        return db.update(TABLE_BOOKMARKS, values, inClause, null);
+
+        final int updated = db.update(TABLE_BOOKMARKS, values, inClause, null);
+        if (updated == 0) {
+            trace("No update on URI: " + uri);
+            return updated;
+        }
+
+        if (isCallerSync(uri)) {
+            // Sync will handle timestamps on its own, so we don't perform the update here.
+            return updated;
+        }
+
+        final long oldParentId = getOldParentIdIfParentChanged(uri);
+        if (oldParentId == -1) {
+            // Parent isn't changed, don't bump its timestamps.
+            return updated;
+        }
+
+        final long newParentId = values.getAsLong(Bookmarks.PARENT);
+        final long lastModified = values.getAsLong(Bookmarks.DATE_MODIFIED);
+        final ContentValues parentValues = new ContentValues();
+        parentValues.put(Bookmarks.DATE_MODIFIED, lastModified);
+
+        // Bump old/new parent's lastModified timestamps.
+        db.update(TABLE_BOOKMARKS, parentValues,
+                  Bookmarks._ID + " in (?, ?)",
+                  new String[] { String.valueOf(oldParentId), String.valueOf(newParentId) });
+
+        return updated;
+    }
+
+    /**
+     * Use the query key {@link BrowserContract#PARAM_OLD_BOOKMARK_PARENT} to check if parent is changed or not.
+     *
+     * @return old parent id if uri has the key, or -1 otherwise.
+     */
+    private long getOldParentIdIfParentChanged(Uri uri) {
+        final String oldParentId = uri.getQueryParameter(BrowserContract.PARAM_OLD_BOOKMARK_PARENT);
+        if (TextUtils.isEmpty(oldParentId)) {
+            return -1;
+        }
+
+        try {
+            return Long.parseLong(oldParentId);
+        } catch (NumberFormatException ignored) {
+            return -1;
+        }
     }
 
     private long insertHistory(Uri uri, ContentValues values) {
         final long now = System.currentTimeMillis();
         values.put(History.DATE_CREATED, now);
         values.put(History.DATE_MODIFIED, now);
 
         // Generate GUID for new history entry. Don't override specified GUIDs.
@@ -2093,25 +2166,30 @@ public class BrowserProvider extends Sha
         beginWrite(db);
         return db.delete(TABLE_VISITS, selection, selectionArgs);
     }
 
     private int deleteBookmarks(Uri uri, String selection, String[] selectionArgs) {
         debug("Deleting bookmarks for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
+        beginWrite(db);
 
         if (isCallerSync(uri)) {
-            beginWrite(db);
             return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
         }
 
         debug("Marking bookmarks as deleted for URI: " + uri);
 
-        ContentValues values = new ContentValues();
+        // Bump parent's lastModified timestamp before record deleted.
+        final ContentValues parentValues = new ContentValues();
+        parentValues.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+        updateBookmarkParents(db, parentValues, selection, selectionArgs);
+
+        final ContentValues values = new ContentValues();
         values.put(Bookmarks.IS_DELETED, 1);
         values.put(Bookmarks.POSITION, 0);
         values.putNull(Bookmarks.PARENT);
         values.putNull(Bookmarks.URL);
         values.putNull(Bookmarks.TITLE);
         values.putNull(Bookmarks.DESCRIPTION);
         values.putNull(Bookmarks.KEYWORD);
         values.putNull(Bookmarks.TAGS);
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -31,17 +31,16 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.db.BrowserContract.Highlights;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.GeckoJarReader;
@@ -1116,30 +1115,16 @@ public class LocalBrowserDB extends Brow
             final long id = c.getLong(col);
             mFolderIdMap.put(guid, id);
             return id;
         } finally {
             c.close();
         }
     }
 
-    /**
-     * Find parents of records that match the provided criteria, and bump their
-     * modified timestamp.
-     */
-    protected void bumpParents(ContentResolver cr, String param, String value) {
-        ContentValues values = new ContentValues();
-        values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
-
-        String where  = param + " = ?";
-        String[] args = new String[] { value };
-        int updated  = cr.update(mParentsUriWithProfile, values, where, args);
-        debug("Updated " + updated + " rows to new modified time.");
-    }
-
     private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) {
         final long now = System.currentTimeMillis();
         ContentValues values = new ContentValues();
         if (title != null) {
             values.put(Bookmarks.TITLE, title);
         }
 
         values.put(Bookmarks.URL, uri);
@@ -1170,42 +1155,32 @@ public class LocalBrowserDB extends Brow
                                           .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
                                           .build();
         cr.update(bookmarksWithInsert,
                   values,
                   Bookmarks.URL + " = ? AND " +
                   Bookmarks.PARENT + " = " + folderId,
                   new String[] { uri });
 
-        // Bump parent modified time using its ID.
-        debug("Bumping parent modified time for addition to: " + folderId);
-        final String where  = Bookmarks._ID + " = ?";
-        final String[] args = new String[] { String.valueOf(folderId) };
-
-        ContentValues bumped = new ContentValues();
-        bumped.put(Bookmarks.DATE_MODIFIED, now);
-
-        final int updated = cr.update(mBookmarksUriWithProfile, bumped, where, args);
-        debug("Updated " + updated + " rows to new modified time.");
+        // BrowserProvider will handle updating parent's lastModified timestamp, nothing else to do.
     }
 
     @Override
     @RobocopTarget
     public boolean addBookmark(ContentResolver cr, String title, String uri) {
         long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
         if (isBookmarkForUrlInFolder(cr, uri, folderId)) {
             // Bookmark added already.
             return false;
         }
 
         // Add a new bookmark.
         addBookmarkItem(cr, title, uri, folderId);
         return true;
     }
-
     private boolean isBookmarkForUrlInFolder(ContentResolver cr, String uri, long folderId) {
         final Cursor c = cr.query(bookmarksUriWithLimit(1),
                                   new String[] { Bookmarks._ID },
                                   Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " == 0",
                                   new String[] { uri, String.valueOf(folderId) },
                                   Bookmarks.URL);
 
         if (c == null) {
@@ -1215,50 +1190,88 @@ public class LocalBrowserDB extends Brow
         try {
             return c.getCount() > 0;
         } finally {
             c.close();
         }
     }
 
     @Override
+    public Uri addBookmarkFolder(ContentResolver cr, String title, long parentId) {
+        final ContentValues values = new ContentValues();
+        final long now = System.currentTimeMillis();
+        values.put(Bookmarks.DATE_CREATED, now);
+        values.put(Bookmarks.DATE_MODIFIED, now);
+        values.put(Bookmarks.GUID, Utils.generateGuid());
+        values.put(Bookmarks.PARENT, parentId);
+        values.put(Bookmarks.TITLE, title);
+        values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
+
+        // BrowserProvider will bump parent's lastModified timestamp after successful insertion.
+        return cr.insert(mBookmarksUriWithProfile, values);
+    }
+
+    @Override
     @RobocopTarget
     public void removeBookmarksWithURL(ContentResolver cr, String uri) {
-        Uri contentUri = mBookmarksUriWithProfile;
-
-        // Do this now so that the items still exist!
-        bumpParents(cr, Bookmarks.URL, uri);
+        // BrowserProvider will bump parent's lastModified timestamp after successful deletion.
+        cr.delete(mBookmarksUriWithProfile,
+                  Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? ",
+                  new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
+    }
 
-        final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) };
-        final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? ";
-
-        cr.delete(contentUri, urlEquals, urlArgs);
+    @Override
+    public void removeBookmarkWithId(ContentResolver cr, long id) {
+        // BrowserProvider will bump parent's lastModified timestamp after successful deletion.
+        cr.delete(mBookmarksUriWithProfile,
+                  Bookmarks._ID + " = ? AND " + Bookmarks.PARENT + " != ? ",
+                  new String[] { String.valueOf(id), String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
     }
 
     @Override
     public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
         cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
     }
 
     @Override
     @RobocopTarget
-    public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
+    public void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword) {
         ContentValues values = new ContentValues();
         values.put(Bookmarks.TITLE, title);
         values.put(Bookmarks.URL, uri);
         values.put(Bookmarks.KEYWORD, keyword);
         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
 
         cr.update(mBookmarksUriWithProfile,
                   values,
                   Bookmarks._ID + " = ?",
                   new String[] { String.valueOf(id) });
     }
 
     @Override
+    public void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword,
+                               long newParentId, long oldParentId) {
+        final ContentValues values = new ContentValues();
+        values.put(Bookmarks.TITLE, title);
+        values.put(Bookmarks.URL, uri);
+        values.put(Bookmarks.KEYWORD, keyword);
+        values.put(Bookmarks.PARENT, newParentId);
+        values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+
+        final Uri contentUri = mBookmarksUriWithProfile.buildUpon()
+                                .appendQueryParameter(BrowserContract.PARAM_OLD_BOOKMARK_PARENT,
+                                                      String.valueOf(oldParentId))
+                                .build();
+        cr.update(contentUri,
+                  values,
+                  Bookmarks._ID + " = ?",
+                  new String[] { String.valueOf(id) });
+    }
+
+    @Override
     public boolean hasBookmarkWithGuid(ContentResolver cr, String guid) {
         Cursor c = cr.query(bookmarksUriWithLimit(1),
                 new String[] { Bookmarks.GUID },
                 Bookmarks.GUID + " = ?",
                 new String[] { guid },
                 null);
 
         try {
@@ -1780,30 +1793,55 @@ public class LocalBrowserDB extends Brow
 
     @Override
     @RobocopTarget
     public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
         Cursor c = cr.query(bookmarksUriWithLimit(1),
                             new String[] { Bookmarks._ID,
                                            Bookmarks.URL,
                                            Bookmarks.TITLE,
+                                           Bookmarks.TYPE,
+                                           Bookmarks.PARENT,
+                                           Bookmarks.GUID,
                                            Bookmarks.KEYWORD },
                             Bookmarks.URL + " = ?",
                             new String[] { url },
                             null);
 
         if (c != null && c.getCount() == 0) {
             c.close();
             c = null;
         }
 
         return c;
     }
 
     @Override
+    public Cursor getBookmarkById(ContentResolver cr, long id) {
+        final Cursor c = cr.query(mBookmarksUriWithProfile,
+                                  new String[] { Bookmarks._ID,
+                                                 Bookmarks.URL,
+                                                 Bookmarks.TITLE,
+                                                 Bookmarks.TYPE,
+                                                 Bookmarks.PARENT,
+                                                 Bookmarks.GUID,
+                                                 Bookmarks.KEYWORD },
+                                  Bookmarks._ID + " = ?",
+                                  new String[] { String.valueOf(id) },
+                                  null);
+
+        if (c != null && c.getCount() == 0) {
+            c.close();
+            return null;
+        }
+
+        return c;
+    }
+
+    @Override
     public Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl) {
         Cursor c = cr.query(mBookmarksUriWithProfile,
                 new String[] { Bookmarks.GUID, Bookmarks._ID, Bookmarks.URL },
                 Bookmarks.URL + " LIKE '%" + partialUrl + "%'", // TODO: Escaping!
                 null,
                 null);
 
         if (c != null && c.getCount() == 0) {
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
@@ -5,46 +5,39 @@
 
 package org.mozilla.gecko.delegates;
 
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.support.design.widget.Snackbar;
 import android.support.v4.content.ContextCompat;
-import android.util.Log;
 import android.view.View;
 import android.widget.ListView;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.promotion.SimpleHelperUI;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
 import org.mozilla.gecko.util.DrawableUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import java.lang.ref.WeakReference;
-
 /**
  * Delegate to watch for bookmark state changes.
  *
  * This is responsible for showing snackbars and helper UIs related to the addition/removal
  * of bookmarks, or reader view bookmarks.
  */
 public class BookmarkStateChangeDelegate extends BrowserAppDelegateWithReference implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "BookmarkDelegate";
@@ -172,17 +165,17 @@ public class BookmarkStateChangeDelegate
             public void onPromptFinished(final GeckoBundle result) {
                 final int itemId = result.getInt("button", -1);
 
                 if (itemId == 0) {
                     final String extrasId = res.getResourceEntryName(R.string.contextmenu_edit_bookmark);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
                             TelemetryContract.Method.DIALOG, extrasId);
 
-                    new EditBookmarkDialog(browserApp).show(tab.getURL());
+                    browserApp.showEditBookmarkDialog(tab.getURL());
 
                 } else if (itemId == 1) {
                     final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
                             TelemetryContract.Method.DIALOG, extrasId);
 
                     final String url = tab.getURL();
                     final String title = tab.getDisplayTitle();
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
@@ -58,29 +58,31 @@ public class BookmarkFolderView extends 
 
         FolderState(final int image) { this.image = image; }
     }
 
     private final TextView mTitle;
     private final TextView mSubtitle;
 
     private final ImageView mIcon;
+    private final ImageView mIndicator;
 
     public BookmarkFolderView(Context context) {
         this(context, null);
     }
 
     public BookmarkFolderView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         LayoutInflater.from(context).inflate(R.layout.two_line_folder_row, this);
 
         mTitle = (TextView) findViewById(R.id.title);
         mSubtitle = (TextView) findViewById(R.id.subtitle);
         mIcon =  (ImageView) findViewById(R.id.icon);
+        mIndicator = (ImageView) findViewById(R.id.indicator);
     }
 
     public void update(String title, int folderID) {
         setTitle(title);
         setID(folderID);
     }
 
     private void setTitle(String title) {
@@ -138,10 +140,16 @@ public class BookmarkFolderView extends 
             new ItemCountUpdateTask(subTitleReference, folderID).execute();
         } else {
             mSubtitle.setVisibility(View.GONE);
         }
     }
 
     public void setState(@NonNull FolderState state) {
         mIcon.setImageResource(state.image);
+
+        if (state == FolderState.PARENT) {
+            mIndicator.setVisibility(View.GONE);
+        } else {
+            mIndicator.setVisibility(View.VISIBLE);
+        }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
@@ -4,75 +4,70 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
+import org.mozilla.gecko.bookmarks.BookmarkUtils;
+import org.mozilla.gecko.bookmarks.EditBookmarkTask;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
 import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
 import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
 import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.media.MediaControlService;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.database.MergeCursor;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
-public class BookmarksPanel extends HomeFragment {
+public class BookmarksPanel extends HomeFragment implements BookmarkEditFragment.Callbacks {
     public static final String LOGTAG = "GeckoBookmarksPanel";
 
-    public static final String BOOKMARK_MOBILE_FOLDER_PREF = "ui.bookmark.mobilefolder.enabled";
-
     // Cursor loader ID for list of bookmarks.
     private static final int LOADER_ID_BOOKMARKS_LIST = 0;
 
     // Information about the target bookmarks folder.
     private static final String BOOKMARKS_FOLDER_INFO = "folder_info";
 
     // Refresh type for folder refreshing loader.
     private static final String BOOKMARKS_REFRESH_TYPE = "refresh_type";
 
     // Position that the list view should be scrolled to after loading has finished.
     private static final String BOOKMARKS_SCROLL_POSITION = "listview_position";
 
-    private final String[] mPrefs = { BOOKMARK_MOBILE_FOLDER_PREF };
-
-    private PrefsHelper.PrefHandler mPrefsObserver;
-
-    private boolean mIfMobileFolderPrefOn = true;
-
     // List of bookmarks.
     private BookmarksListView mList;
 
     // Adapter for list of bookmarks.
     private BookmarksListAdapter mListAdapter;
 
     // Adapter's parent stack.
     private List<FolderInfo> mSavedParentStack;
@@ -97,19 +92,16 @@ public class BookmarksPanel extends Home
             mSavedParentStack = new LinkedList<FolderInfo>(stack);
         } else {
             mListAdapter.restoreData(stack);
         }
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
-        setupPrefHandler();
-
         final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
 
         mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
 
         mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
@@ -124,28 +116,16 @@ public class BookmarksPanel extends Home
                 info.itemType = RemoveItemType.BOOKMARKS;
                 return info;
             }
         });
 
         return view;
     }
 
-    private void setupPrefHandler() {
-        mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
-            @Override
-            public void prefValue(String pref, boolean value) {
-                if (pref.equals(BOOKMARK_MOBILE_FOLDER_PREF)) {
-                    mIfMobileFolderPrefOn = value;
-                }
-            }
-        };
-        PrefsHelper.addObserver(mPrefs, mPrefsObserver);
-    }
-
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
         OnUrlOpenListener listener = null;
         try {
             listener = (OnUrlOpenListener) getActivity();
         } catch (ClassCastException e) {
@@ -183,16 +163,48 @@ public class BookmarksPanel extends Home
         mList.setAdapter(mListAdapter);
 
         // Create callbacks before the initial loader is started.
         mLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (super.onContextItemSelected(item)) {
+            // HomeFragment was able to handle to selected item.
+            return true;
+        }
+
+        final ContextMenuInfo menuInfo = item.getMenuInfo();
+        if (!(menuInfo instanceof HomeContextMenuInfo)) {
+            return false;
+        }
+
+        final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
+
+        final int itemId = item.getItemId();
+        final Context context = getContext();
+
+        if (itemId == R.id.home_edit_bookmark) {
+            if (BookmarkUtils.isEnabled(getContext())) {
+                final BookmarkEditFragment dialog = BookmarkEditFragment.newInstance(info.bookmarkId);
+                dialog.setTargetFragment(this, 0);
+                dialog.show(getFragmentManager(), "edit-bookmark");
+            } else {
+                // UI Dialog associates to the activity context, not the applications'.
+                new EditBookmarkDialog(context).show(info.url);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
     public void onDestroyView() {
         mList = null;
         mListAdapter = null;
         mEmptyView = null;
         super.onDestroyView();
     }
 
     @Override
@@ -215,16 +227,21 @@ public class BookmarksPanel extends Home
             bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, RefreshType.CHILD);
         } else {
             bundle = null;
         }
 
         getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
     }
 
+    @Override
+    public void onEditBookmark(@NonNull Bundle bundle) {
+        new EditBookmarkTask(getActivity(), bundle).execute();
+    }
+
     private void updateUiFromCursor(Cursor c) {
         if ((c == null || c.getCount() == 0) && mEmptyView == null) {
             // Set empty page view. We delay this so that the empty view won't flash.
             final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
             mEmptyView = emptyViewStub.inflate();
 
             final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
             emptyIcon.setImageResource(R.drawable.icon_bookmarks_empty);
@@ -326,17 +343,17 @@ public class BookmarksPanel extends Home
                 RefreshType refreshType = (RefreshType) args.getParcelable(BOOKMARKS_REFRESH_TYPE);
                 final int targetPosition = args.getInt(BOOKMARKS_SCROLL_POSITION);
                 return new BookmarksLoader(getActivity(), folderInfo, refreshType, targetPosition);
             }
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            BookmarksLoader bl = (BookmarksLoader) loader;
+            final BookmarksLoader bl = (BookmarksLoader) loader;
             mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType());
 
             if (mPanelStateChangeListener != null) {
                 final List<FolderInfo> parentStack = mListAdapter.getParentStack();
                 final Bundle bundle = new Bundle();
 
                 // Bundle likes to store ArrayLists or Arrays, but we've got a generic List (which
                 // is actually an unmodifiable wrapper around a LinkedList). We'll need to do a
@@ -345,17 +362,17 @@ public class BookmarksPanel extends Home
                 bundle.putParcelableArrayList("parentStack", new ArrayList<FolderInfo>(parentStack));
 
                 mPanelStateChangeListener.onStateChanged(bundle);
             }
 
             // BrowserDB updates (e.g. through sync, or when opening a new tab) will trigger
             // a refresh which reuses the same loader - in that case we don't want to reset
             // the scroll position again.
-            int currentLoaderHash = bl.hashCode();
+            final int currentLoaderHash = bl.hashCode();
             if (mList != null && currentLoaderHash != mLastLoaderHash) {
                 mList.setSelection(bl.getTargetPosition());
                 mLastLoaderHash = currentLoaderHash;
             }
             updateUiFromCursor(c);
         }
 
         @Override
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
@@ -437,17 +437,17 @@ public class BrowserSearch extends HomeF
         final Context context = getActivity();
 
         final int itemId = item.getItemId();
 
         if (itemId == R.id.browsersearch_remove) {
             // Position for Top Sites grid items, but will always be -1 since this is only for BrowserSearch result
             final int position = -1;
 
-            new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
+            new RemoveItemTask(getActivity(), info, position).execute();
             return true;
         }
 
         return false;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -1,28 +1,27 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.home;
 
+import java.lang.ref.WeakReference;
 import java.util.EnumSet;
 
-import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
 import org.mozilla.gecko.preferences.GeckoPreferences;
@@ -291,30 +290,24 @@ public abstract class HomeFragment exten
 
             mUrlOpenInBackgroundListener.onUrlOpenInBackground(url, flags);
 
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
 
             return true;
         }
 
-        if (itemId == R.id.home_edit_bookmark) {
-            // UI Dialog associates to the activity context, not the applications'.
-            new EditBookmarkDialog(context).show(info.url);
-            return true;
-        }
-
         if (itemId == R.id.home_remove) {
             // For Top Sites grid items, position is required in case item is Pinned.
             final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
 
             if (info.hasPartnerBookmarkId()) {
-                new RemovePartnerBookmarkTask(context, info.bookmarkId).execute();
+                new RemovePartnerBookmarkTask(getActivity(), info.bookmarkId).execute();
             } else {
-                new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
+                new RemoveItemTask(getActivity(), info, position).execute();
             }
             return true;
         }
 
         if (itemId == R.id.home_set_as_homepage) {
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
             final SharedPreferences.Editor editor = prefs.edit();
             editor.putString(GeckoPreferences.PREFS_HOMEPAGE, info.url);
@@ -390,124 +383,137 @@ public abstract class HomeFragment exten
         if (!canLoad() || mIsLoaded) {
             return;
         }
 
         load();
         mIsLoaded = true;
     }
 
-    protected static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
-        private final Context mContext;
-        private final String mUrl;
-        private final RemoveItemType mType;
-        private final int mPosition;
-        private final BrowserDB mDB;
+    static class RemoveItemTask extends UIAsyncTask.WithoutParams<Void> {
+        private final WeakReference<Activity> activityWeakReference;
+        private final Context context;
+        private final HomeContextMenuInfo info;
+        private final int position;
+        private final BrowserDB db;
 
         /**
-         * Remove bookmark/history/reading list type item by url, and also unpin the
+         * Remove bookmark/history/reading list type item, and also unpin the
          * Top Sites grid item at index <code>position</code>.
          */
-        public RemoveItemByUrlTask(Context context, String url, RemoveItemType type, int position) {
+        RemoveItemTask(Activity activity, HomeContextMenuInfo info, int position) {
             super(ThreadUtils.getBackgroundHandler());
 
-            mContext = context;
-            mUrl = url;
-            mType = type;
-            mPosition = position;
-            mDB = BrowserDB.from(context);
+            this.activityWeakReference = new WeakReference<>(activity);
+            this.context = activity.getApplicationContext();
+            this.info = info;
+            this.position = position;
+            this.db = BrowserDB.from(context);
         }
 
         @Override
         public Void doInBackground() {
-            ContentResolver cr = mContext.getContentResolver();
+            ContentResolver cr = context.getContentResolver();
 
-            if (mPosition > -1) {
-                mDB.unpinSite(cr, mPosition);
-                if (mDB.hideSuggestedSite(mUrl)) {
+            if (position > -1) {
+                db.unpinSite(cr, position);
+                if (db.hideSuggestedSite(info.url)) {
                     cr.notifyChange(SuggestedSites.CONTENT_URI, null);
                 }
             }
 
-            switch (mType) {
+            final RemoveItemType type = info.itemType;
+            switch (type) {
                 case BOOKMARKS:
                     removeBookmark(cr);
                     break;
 
                 case HISTORY:
                     removeHistory(cr);
                     break;
 
                 case COMBINED:
                     removeBookmark(cr);
                     removeHistory(cr);
                     break;
 
                 default:
-                    Log.e(LOGTAG, "Can't remove item type " + mType.toString());
+                    Log.e(LOGTAG, "Can't remove item type " + type.toString());
                     break;
             }
             return null;
         }
 
         @Override
         public void onPostExecute(Void result) {
-            SnackbarBuilder.builder((Activity) mContext)
+            final Activity activity = activityWeakReference.get();
+            if (activity == null || activity.isFinishing()) {
+                return;
+            }
+
+            SnackbarBuilder.builder(activity)
                     .message(R.string.page_removed)
                     .duration(Snackbar.LENGTH_LONG)
                     .buildAndShow();
         }
 
         private void removeBookmark(ContentResolver cr) {
-            SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
-            final boolean isReaderViewPage = rch.isURLCached(mUrl);
+            SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
+            final boolean isReaderViewPage = rch.isURLCached(info.url);
 
             final String extra;
             if (isReaderViewPage) {
                 extra = "bookmark_reader";
             } else {
                 extra = "bookmark";
             }
 
             Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, extra);
-            mDB.removeBookmarksWithURL(cr, mUrl);
+            db.removeBookmarkWithId(cr, info.bookmarkId);
 
             if (isReaderViewPage) {
-                ReadingListHelper.removeCachedReaderItem(mUrl, mContext);
+                ReadingListHelper.removeCachedReaderItem(info.url, context);
             }
         }
 
         private void removeHistory(ContentResolver cr) {
-            mDB.removeHistoryEntry(cr, mUrl);
+            db.removeHistoryEntry(cr, info.url);
         }
     }
 
     private static class RemovePartnerBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
+        private final WeakReference<Activity> activityWeakReference;
         private Context context;
         private long bookmarkId;
 
-        public RemovePartnerBookmarkTask(Context context, long bookmarkId) {
+        private RemovePartnerBookmarkTask(Activity activity, long bookmarkId) {
             super(ThreadUtils.getBackgroundHandler());
 
-            this.context = context;
+            this.activityWeakReference = new WeakReference<>(activity);
+            this.context = activity.getApplicationContext();
             this.bookmarkId = bookmarkId;
         }
 
         @Override
         protected Void doInBackground() {
             context.getContentResolver().delete(
                     PartnerBookmarksProviderProxy.getUriForBookmark(context, bookmarkId),
                     null,
                     null
             );
 
             return null;
         }
 
         @Override
         protected void onPostExecute(Void aVoid) {
-            SnackbarBuilder.builder((Activity) context)
+            final Activity activity = activityWeakReference.get();
+            if (activity == null || activity.isFinishing()) {
+                return;
+            }
+
+            SnackbarBuilder.builder(activity)
                     .message(R.string.page_removed)
                     .duration(Snackbar.LENGTH_LONG)
                     .buildAndShow();
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
@@ -26,27 +26,24 @@ import org.mozilla.gecko.util.StringUtil
 /**
  * This loader will generate an icon in case no icon could be loaded. In order to do so this needs
  * to be the last loader that will be tried.
  */
 public class IconGenerator implements IconLoader {
     // Mozilla's Visual Design Colour Palette
     // http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
     private static final int[] COLORS = {
-            0xFFc33c32,
-            0xFFf25820,
-            0xFFff9216,
-            0xFFffcb00,
-            0xFF57bd35,
-            0xFF01bdad,
-            0xFF0996f8,
-            0xFF02538b,
-            0xFF1f386e,
-            0xFF7a2f7a,
-            0xFFea385e,
+            0xFF9A4C00,
+            0xFFAB008D,
+            0xFF4C009C,
+            0xFF002E9C,
+            0xFF009EC2,
+            0xFF009D02,
+            0xFF51AB00,
+            0xFF36385A,
     };
 
     private static final int TEXT_SIZE_DP = 12;
     @Override
     public IconResponse load(IconRequest request) {
         if (request.getIconCount() > 1) {
             // There are still other icons to try. We will only generate an icon if there's only one
             // icon left and all previous loaders have failed (assuming this is the last one).
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
@@ -5,21 +5,24 @@
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.icons.IconCallback;
 import org.mozilla.gecko.icons.IconResponse;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.RectF;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.widget.ImageView;
 
 import java.lang.ref.WeakReference;
 
 /**
@@ -28,17 +31,17 @@ import java.lang.ref.WeakReference;
  * selected is the dominant colour of the provided Favicon.
  */
 public class FaviconView extends ImageView {
     private static final String LOGTAG = "GeckoFaviconView";
 
     private static String DEFAULT_FAVICON_KEY = FaviconView.class.getSimpleName() + "DefaultFavicon";
 
     // Default x/y-radius of the oval used to round the corners of the background (dp)
-    private static final int DEFAULT_CORNER_RADIUS_DP = 4;
+    private static final int DEFAULT_CORNER_RADIUS_DP = 2;
 
     private Bitmap mIconBitmap;
 
     // Reference to the unscaled bitmap, if any, to prevent repeated assignments of the same bitmap
     // to the view from causing repeated rescalings (Some of the callers do this)
     private Bitmap mUnscaledBitmap;
 
     private int mActualWidth;
@@ -63,16 +66,18 @@ public class FaviconView extends ImageVi
     private final boolean isDominantBorderEnabled;
 
     // boolean switch for overriding scaletype, whose value is defined in attrs.xml .
     private final boolean isOverrideScaleTypeEnabled;
 
     // boolean switch for disabling rounded corners, value defined in attrs.xml .
     private final boolean areRoundCornersEnabled;
 
+    private final Resources mResources;
+
     // Initializing the static paints.
     static {
         sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         sBackgroundPaint.setStyle(Paint.Style.FILL);
     }
 
     public FaviconView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -89,16 +94,18 @@ public class FaviconView extends ImageVi
         if (isOverrideScaleTypeEnabled) {
             setScaleType(ImageView.ScaleType.CENTER);
         }
 
         final DisplayMetrics metrics = getResources().getDisplayMetrics();
 
         mBackgroundRect = new RectF(0, 0, 0, 0);
         mBackgroundCornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_CORNER_RADIUS_DP, metrics);
+
+        mResources = getResources();
     }
 
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 
         // No point rechecking the image if there hasn't really been any change.
         if (w == mActualWidth && h == mActualHeight) {
@@ -142,17 +149,30 @@ public class FaviconView extends ImageVi
         }
 
         if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) {
             scaleBitmap();
             // Don't scale the image every time something changes.
             mScalingExpected = false;
         }
 
-        setImageBitmap(mIconBitmap);
+        // In original, there is no round corners if FaviconView has bitmap icon. But the new design
+        // needs round corners all the time, so we use RoundedBitmapDrawableFactory to create round corners.
+        if (areRoundCornersEnabled) {
+            // mIconBitmap's size must bew small or equal to mActualWidth, or we cannot see the round corners.
+            if (mActualWidth < mIconBitmap.getWidth()) {
+                scaleBitmap();
+            }
+            RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(mResources, mIconBitmap);
+            roundedBitmapDrawable.setCornerRadius(mBackgroundCornerRadius);
+            roundedBitmapDrawable.setAntiAlias(true);
+            setImageDrawable(roundedBitmapDrawable);
+        } else {
+            setImageBitmap(mIconBitmap);
+        }
 
         // After scaling, determine if we have empty space around the scaled image which we need to
         // fill with the coloured background. If applicable, show it.
         // We assume Favicons are still squares and only bother with the background if more than 3px
         // of it would be displayed.
         if (Math.abs(mIconBitmap.getWidth() - mActualWidth) < 3) {
             mDominantColor = 0;
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -70,27 +70,29 @@
 <!ENTITY bookmark_added "Bookmark added">
 <!-- Localization note (bookmark_already_added) : This string is
      used as a label in a toast. It is the verb "to bookmark", not
      the noun "a bookmark". -->
 <!ENTITY bookmark_already_added "Already bookmarked">
 <!ENTITY bookmark_removed "Bookmark removed">
 <!ENTITY bookmark_updated "Bookmark updated">
 <!ENTITY bookmark_options "Options">
+<!ENTITY bookmark_save "Save">
 <!ENTITY screenshot_added_to_bookmarks "Screenshot added to bookmarks">
 <!-- Localization note (screenshot_folder_label_in_bookmarks): We save links to screenshots
      the user takes. The folder we store these links in is located in the bookmarks list
      and is labeled by this String. -->
 <!ENTITY screenshot_folder_label_in_bookmarks "Screenshots">
 <!ENTITY readinglist_smartfolder_label_in_bookmarks "Reading List">
 
 <!-- Localization note (bookmark_folder_items): The variable is replaced by the number of items
      in the folder. -->
 <!ENTITY bookmark_folder_items "&formatD; items">
 <!ENTITY bookmark_folder_one_item "1 item">
+<!ENTITY bookmark_parent_folder "Parent Folder">
 
 <!ENTITY reader_saved_offline "Saved offline">
 <!-- Localization note (reader_switch_to_bookmarks) : This
      string is used as an action in a snackbar - it lets you
      "switch" to the bookmarks (saved items) panel. -->
 <!ENTITY reader_switch_to_bookmarks "Switch">
 
 <!ENTITY history_today_section "Today">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -114,28 +114,32 @@ with Files('resources/menu/*activitystre
 
 with Files('resources/menu/browsersearch_contextmenu.xml'):
     BUG_COMPONENT = ('Firefox for Android', 'Awesomescreen')
 
 DIRS += ['locales']
 
 GENERATED_FILES += [
     '../geckoview/generated/preprocessed/org/mozilla/geckoview/BuildConfig.java',
+    'AndroidManifest.xml',
     'generated/preprocessed/org/mozilla/gecko/AdjustConstants.java',
     'generated/preprocessed/org/mozilla/gecko/AppConstants.java',
 ]
 w = GENERATED_FILES['../geckoview/generated/preprocessed/org/mozilla/geckoview/BuildConfig.java']
 w.script = 'generate_build_config.py:generate_java'
 w.inputs += ['../geckoview/BuildConfig.java.in']
 x = GENERATED_FILES['generated/preprocessed/org/mozilla/gecko/AdjustConstants.java']
 x.script = 'generate_build_config.py:generate_java'
 x.inputs += ['AdjustConstants.java.in']
 y = GENERATED_FILES['generated/preprocessed/org/mozilla/gecko/AppConstants.java']
 y.script = 'generate_build_config.py:generate_java'
 y.inputs += ['AppConstants.java.in']
+z = GENERATED_FILES['AndroidManifest.xml']
+z.script = 'generate_build_config.py:generate_android_manifest'
+z.inputs += ['AndroidManifest.xml.in']
 
 include('android-services.mozbuild')
 
 geckoview_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/main/'
 geckoview_thirdparty_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/thirdparty/'
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
@@ -471,16 +475,19 @@ gbjar.sources += ['java/org/mozilla/geck
     'activitystream/Utils.java',
     'adjust/AdjustBrowserAppDelegate.java',
     'animation/AnimationUtils.java',
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
+    'bookmarks/BookmarkEditFragment.java',
+    'bookmarks/BookmarkUtils.java',
+    'bookmarks/EditBookmarkTask.java',
     'BootReceiver.java',
     'BrowserApp.java',
     'BrowserLocaleManager.java',
     'cleanup/FileCleanupController.java',
     'cleanup/FileCleanupService.java',
     'CustomEditText.java',
     'customtabs/ActionBarPresenter.java',
     'customtabs/CustomTabsActivity.java',
@@ -1221,55 +1228,16 @@ if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRE
     # If you change this, also change its equivalent in mobile/android/bouncer.
     if not CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
         # If we are packaging the bouncer, it will have the distribution, so don't put
         # it in the main APK as well.
         ANDROID_ASSETS_DIRS += [
             '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
         ]
 
-# We do not expose MOZ_ADJUST_SDK_KEY here because that # would leak the value
-# to build logs.  Instead we expose the token quietly where appropriate in
-# Makefile.in.
-for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_DEBUG',
-            'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
-            'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
-            'MOZ_ANDROID_GCM', 'MOZ_ANDROID_EXCLUDE_FONTS', 'MOZ_LOCALE_SWITCHER',
-            'MOZ_ANDROID_BEAM', 'MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE',
-            'MOZ_SWITCHBOARD', 'MOZ_ANDROID_CUSTOM_TABS',
-            'MOZ_ANDROID_ACTIVITY_STREAM'):
-    if CONFIG[var]:
-        DEFINES[var] = 1
-
-for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL', 'MOZ_ANDROID_GCM_SENDERID'):
-    if CONFIG[var]:
-        DEFINES[var] = CONFIG[var]
-
-for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH',
-            'GRE_MILESTONE', 'MOZ_APP_BASENAME', 'MOZ_MOZILLA_API_KEY',
-            'MOZ_APP_DISPLAYNAME', 'MOZ_APP_UA_NAME', 'MOZ_APP_ID', 'MOZ_APP_NAME',
-            'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'MOZ_CHILD_PROCESS_NAME',
-            'MOZ_ANDROID_APPLICATION_CLASS', 'MOZ_ANDROID_BROWSER_INTENT_CLASS', 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
-            'MOZ_CRASHREPORTER', 'MOZ_UPDATE_CHANNEL', 'OMNIJAR_NAME',
-            'OS_TARGET', 'TARGET_XPCOM_ABI'):
-    DEFINES[var] = CONFIG[var]
-
-# Mangle our package name to avoid Bug 750548.
-DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
-DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
-if not CONFIG['COMPILE_ENVIRONMENT']:
-    # These should really come from the included binaries, but that's not easy.
-    DEFINES['MOZ_APP_ABI'] = 'arm-eabi-gcc3' # Observe quote differences here ...
-    DEFINES['TARGET_XPCOM_ABI'] = '"arm-eabi-gcc3"' # ... and here.
-
-if '-march=armv7' in CONFIG['OS_CFLAGS']:
-    DEFINES['MOZ_MIN_CPU_VERSION'] = 7
-else:
-    DEFINES['MOZ_MIN_CPU_VERSION'] = 5
-
 if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
     # The Search Activity is mostly independent of Fennec proper, but
     # it does depend on Geckoview.  Therefore, we build it as a jar
     # that depends on the Geckoview jars.
     search_source_dir = SRCDIR + '/../search'
     include('../search/search_activity_sources.mozbuild')
 
     search_activity = add_java_jar('search-activity')
@@ -1283,25 +1251,19 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']
         'gecko-R.jar',
         'gecko-browser.jar',
         'gecko-mozglue.jar',
         'gecko-thirdparty.jar',
         'gecko-util.jar',
         'gecko-view.jar',
     ]
 
+DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
 FINAL_TARGET_PP_FILES += ['package-name.txt.in']
 
-DEFINES['OBJDIR'] = OBJDIR
-DEFINES['TOPOBJDIR'] = TOPOBJDIR
-
-OBJDIR_PP_FILES.mobile.android.base += [
-    'AndroidManifest.xml.in',
-]
-
 gvjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
     'IGeckoEditableChild.java',
     'IGeckoEditableParent.java',
     'media/ICodec.java',
     'media/ICodecCallbacks.java',
     'media/IMediaDrmBridge.java',
     'media/IMediaDrmBridgeCallbacks.java',
     'media/IMediaManager.java',
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..795377c664bde31182f64a0e25dfae29eb03138e
GIT binary patch
literal 20527
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6269637bf43d568be6e660646a4e1d64883e67cd
GIT binary patch
literal 20519
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e1c61d2bfb79594b7897c747179caebe6071c0ec
GIT binary patch
literal 20702
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
S*NT4><6yVY_L%j`jsFAfiw;Hr
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0e01eabde5b6db975445a197bbc57babeb4da940
GIT binary patch
literal 20769
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
E0Ts(L)&Kwi
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7b1a41b03a60477d9a9bbbdf80ab2ced902910ed
GIT binary patch
literal 20704
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
W@1S|7K=H3)Y^`QaJ#68*@_zt$D;x&^
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3834ad18e25ff5de90c2f3353cb4777b5d5a974e
GIT binary patch
literal 20761
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
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/bookmark_edit_with_full_page.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/white"
+    android:orientation="vertical">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="56dp"
+        android:background="@color/text_and_tabs_tray_grey"
+        android:minHeight="?actionBarSize"
+        app:navigationIcon="@drawable/abc_ic_clear_mtrl_alpha"
+        app:subtitleTextColor="@android:color/white"
+        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        app:title="@string/bookmark_edit_title"
+        app:titleTextColor="@android:color/white" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingEnd="16dp"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp"
+        android:paddingStart="16dp">
+
+        <android.support.design.widget.TextInputLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp"
+            android:layout_marginTop="16dp">
+
+            <EditText
+                android:id="@+id/edit_bookmark_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:gravity="start"
+                android:hint="@string/bookmark_edit_name"
+                android:maxLines="1"
+                android:singleLine="true"
+                android:textColor="@color/text_and_tabs_tray_grey"
+                android:textSize="18sp"
+                android:focusable="true"
+                tools:text="Firefox: About your browser" />
+        </android.support.design.widget.TextInputLayout>
+
+        <android.support.design.widget.TextInputLayout
+            android:id="@+id/edit_bookmark_location_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp"
+            android:layout_marginTop="16dp">
+
+            <EditText
+                android:id="@+id/edit_bookmark_location"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:gravity="start"
+                android:hint="@string/bookmark_edit_location"
+                android:inputType="textNoSuggestions"
+                android:maxLines="1"
+                android:singleLine="true"
+                android:textColor="@color/text_and_tabs_tray_grey"
+                android:textSize="18sp" />
+        </android.support.design.widget.TextInputLayout>
+
+        <android.support.design.widget.TextInputLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp"
+            android:layout_marginTop="16dp">
+
+            <EditText
+                android:id="@+id/edit_parent_folder"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:cursorVisible="false"
+                android:drawableEnd="@drawable/arrow"
+                android:drawableRight="@drawable/arrow"
+                android:drawablePadding="8dp"
+                android:ellipsize="end"
+                android:focusable="false"
+                android:focusableInTouchMode="false"
+                android:gravity="start"
+                android:hint="@string/bookmark_parent_folder"
+                android:inputType="none"
+                android:maxLines="1"
+                android:singleLine="true"
+                android:textColor="@color/text_and_tabs_tray_grey"
+                android:textSize="18sp" />
+        </android.support.design.widget.TextInputLayout>
+    </LinearLayout>
+</LinearLayout>
--- a/mobile/android/base/resources/layout/bookmark_folder_row.xml
+++ b/mobile/android/base/resources/layout/bookmark_folder_row.xml
@@ -1,15 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <org.mozilla.gecko.home.BookmarkFolderView xmlns:android="http://schemas.android.com/apk/res/android"
                                            style="@style/Widget.FolderView"
                                            android:layout_width="match_parent"
-                                           android:paddingLeft="0dp"
-                                           android:paddingStart="0dp"
+                                           android:layout_height="wrap_content"
+                                           android:minHeight="@dimen/page_row_height"
+                                           android:paddingLeft="16dp"
+                                           android:paddingStart="16dp"
                                            android:paddingRight="16dp"
                                            android:paddingEnd="16dp"
-                                           android:paddingTop="0dp"
-                                           android:paddingBottom="0dp"
-                                           android:gravity="center_vertical"/>
+                                           android:gravity="center_vertical" />
--- a/mobile/android/base/resources/layout/bookmark_item_row.xml
+++ b/mobile/android/base/resources/layout/bookmark_item_row.xml
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
                                        style="@style/Widget.BookmarkItemView"
                                        android:layout_width="match_parent"
-                                       android:layout_height="@dimen/page_row_height"
-                                       android:minHeight="@dimen/page_row_height"/>
+                                       android:layout_height="wrap_content"
+                                       android:minHeight="@dimen/page_row_height"
+                                       android:gravity="center_vertical" />
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ b/mobile/android/base/resources/layout/home_pager.xml
@@ -15,11 +15,12 @@
 
     <org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
                                          android:layout_height="@dimen/tabs_strip_height"
                                          android:background="@color/about_page_header_grey"
                                          android:layout_gravity="top"
                                          gecko:strip="@drawable/home_tab_menu_strip"
                                          gecko:activeTextColor="@color/placeholder_grey"
                                          gecko:inactiveTextColor="@color/tab_text_color"
-                                         gecko:tabsMarginLeft="@dimen/tab_strip_content_start" />
+                                         gecko:tabsMarginLeft="@dimen/tab_strip_content_start"
+                                         gecko:titlebarFill="true" />
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/two_line_folder_row.xml
+++ b/mobile/android/base/resources/layout/two_line_folder_row.xml
@@ -5,27 +5,28 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:gecko="http://schemas.android.com/apk/res-auto"
        tools:context=".BrowserApp">
 
     <ImageView android:id="@+id/icon"
                android:src="@drawable/folder_closed"
-               android:layout_width="24dp"
-               android:layout_height="18dp"
-               android:scaleType="fitXY"
-               android:layout_margin="20dp"/>
+               android:layout_width="@dimen/favicon_small_size"
+               android:layout_height="@dimen/favicon_small_size"
+               android:scaleType="fitCenter" />
 
     <LinearLayout android:layout_width="0dp"
                   android:layout_height="wrap_content"
                   android:layout_weight="1"
                   android:layout_gravity="center_vertical"
-                  android:paddingRight="10dp"
-                  android:paddingEnd="10dp"
+                  android:paddingLeft="16dp"
+                  android:paddingStart="16dp"
+                  android:paddingRight="16dp"
+                  android:paddingEnd="16dp"
                   android:orientation="vertical">
 
         <org.mozilla.gecko.widget.FadedSingleColorTextView
                 android:id="@+id/title"
                 style="@style/Widget.TwoLinePageRow.Title"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 gecko:fadeWidth="90dp"
@@ -36,9 +37,15 @@
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:visibility="gone"
                   gecko:fadeWidth="90dp"
                   tools:text="1 items"/>
 
     </LinearLayout>
 
+    <ImageView android:id="@+id/indicator"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:layout_gravity="center"
+               android:src="@drawable/arrow" />
+
 </merge>
--- a/mobile/android/base/resources/layout/two_line_page_row.xml
+++ b/mobile/android/base/resources/layout/two_line_page_row.xml
@@ -4,18 +4,18 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:gecko="http://schemas.android.com/apk/res-auto"
        tools:context=".BrowserApp">
 
     <org.mozilla.gecko.widget.FaviconView android:id="@+id/icon"
-                                          android:layout_width="@dimen/favicon_bg"
-                                          android:layout_height="@dimen/favicon_bg"
+                                          android:layout_width="@dimen/favicon_small_size"
+                                          android:layout_height="@dimen/favicon_small_size"
                                           android:layout_margin="16dp"
                                           tools:background="@drawable/favicon_globe"/>
 
     <LinearLayout android:layout_width="0dp"
                   android:layout_height="wrap_content"
                   android:layout_weight="1"
                   android:layout_gravity="center_vertical"
                   android:paddingRight="10dp"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/bookmark_edit_menu.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item android:id="@+id/done"
+        android:title="@string/bookmark_save"
+        android:enabled="false"
+        app:showAsAction="always"/>
+</menu>
\ No newline at end of file
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -51,24 +51,26 @@
     <!-- Padding combines with an 18dp image to form the menu item width and height. -->
     <dimen name="tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
     <dimen name="tablet_tab_strip_button_inset">5dp</dimen>
 
     <!-- Dimensions used by Favicons and FaviconView -->
     <dimen name="favicon_bg">32dp</dimen>
-    <dimen name="favicon_corner_radius">4dp</dimen>
+    <dimen name="favicon_corner_radius">2dp</dimen>
     <!-- Set the upper limit on the size of favicon that will be processed. Favicons larger than
          this will be downscaled to this value. If you need to use larger Favicons (Due to a UI
          redesign sometime after this is written) you should increase this value to the largest
          commonly-used size of favicon and, performance permitting, fetch the remainder from the
          database. The largest available size is always stored in the database, regardless of this
          value.-->
     <dimen name="favicon_largest_interesting_size">32dp</dimen>
+    <!-- Small favicon used in about:home(TopsitesPanel, BookmarksPanel and CombinedHistoryPanel) -->
+    <dimen name="favicon_small_size">25dp</dimen>
 
     <dimen name="firstrun_content_width">300dp</dimen>
     <dimen name="firstrun_min_height">120dp</dimen>
     <dimen name="firstrun_background_height">120dp</dimen>
 
     <dimen name="overlay_prompt_content_width">260dp</dimen>
     <dimen name="overlay_prompt_button_width">148dp</dimen>
     <dimen name="overlay_prompt_container_width">@dimen/match_parent</dimen>
@@ -87,17 +89,17 @@
 
     <dimen name="home_folder_title_oneline_textsize">16sp</dimen>
     <dimen name="home_folder_title_twoline_textsize">14sp</dimen>
     <dimen name="home_twolinepagerow_title_textsize">16sp</dimen>
 
     <dimen name="page_row_edge_padding">16dp</dimen>
 
     <!-- Regular page row on about:home -->
-    <dimen name="page_row_height">64dp</dimen>
+    <dimen name="page_row_height">56dp</dimen>
 
     <!-- Group/heading page row on about:home -->
     <dimen name="page_group_height">56dp</dimen>
     <dimen name="home_header_item_height">56dp</dimen>
     <dimen name="page_row_divider_height">1dp</dimen>
 
     <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
     <dimen name="home_remote_tabs_top_padding">48dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -391,17 +391,17 @@
     <style name="TextAppearance.Widget.Home" />
 
     <style name="TextAppearance.Widget.Home.Header" parent="TextAppearance.Small">
         <item name="android:textColor">@color/disabled_grey</item>
         <item name="android:textSize">12sp</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance">
-        <item name="android:textSize">16dp</item>
+        <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro">
         <item name="android:textColor">@color/tabs_tray_icon_grey</item>
     </style>
 
     <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorHint</item>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -132,9 +132,23 @@
     </style>
 
     <style name="GeckoCustomTabs.Light" parent="Theme.AppCompat.Light.NoActionBar">
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemCustomTabs</item>
         <item name="windowActionModeOverlay">true</item>
     </style>
 
+    <!-- Bookmark full-page dialog theme -->
+    <style name="Bookmark" parent="Theme.AppCompat.Light.DialogWhenLarge"/>
+    <style name="Bookmark.Gecko" parent="Gecko">
+        <item name="toolbarStyle">@style/BookmarkToolbarStyle</item>
+        <item name="colorAccent">@color/fennec_ui_orange</item>
+        <item name="colorControlNormal">@color/disabled_grey</item>
+
+        <item name="android:textColorHint">@color/tabs_tray_icon_grey</item>
+    </style>
+    <style name="BookmarkToolbarStyle" parent="@style/Widget.AppCompat.Toolbar">
+        <item name="android:paddingRight">5dp</item>
+        <item name="android:paddingEnd">5dp</item>
+    </style>
+
 </resources>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -92,21 +92,23 @@
   <string name="quit">&quit;</string>
   <string name="bookmark">&bookmark;</string>
   <string name="bookmark_remove">&bookmark_remove;</string>
   <string name="bookmark_added">&bookmark_added;</string>
   <string name="bookmark_already_added">&bookmark_already_added;</string>
   <string name="bookmark_removed">&bookmark_removed;</string>
   <string name="bookmark_updated">&bookmark_updated;</string>
   <string name="bookmark_options">&bookmark_options;</string>
+  <string name="bookmark_save">&bookmark_save;</string>
   <string name="screenshot_added_to_bookmarks">&screenshot_added_to_bookmarks;</string>
   <string name="screenshot_folder_label_in_bookmarks">&screenshot_folder_label_in_bookmarks;</string>
   <string name="readinglist_smartfolder_label_in_bookmarks">&readinglist_smartfolder_label_in_bookmarks;</string>
   <string name="bookmark_folder_items">&bookmark_folder_items;</string>
   <string name="bookmark_folder_one_item">&bookmark_folder_one_item;</string>
+  <string name="bookmark_parent_folder">&bookmark_parent_folder;</string>
 
   <string name="reader_saved_offline">&reader_saved_offline;</string>
   <string name="reader_switch_to_bookmarks">&reader_switch_to_bookmarks;</string>
 
   <string name="history_today_section">&history_today_section;</string>
   <string name="history_yesterday_section">&history_yesterday_section;</string>
   <string name="history_week_section">&history_week_section3;</string>
   <string name="history_older_section">&history_older_section3;</string>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -79,16 +79,17 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Settings;
+import android.support.annotation.NonNull;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.Surface;
@@ -1655,17 +1656,17 @@ public class GeckoAppShell
 
     public interface AppStateListener {
         public void onPause();
         public void onResume();
         public void onOrientationChanged();
     }
 
     public interface GeckoInterface {
-        public EventDispatcher getAppEventDispatcher();
+        public @NonNull EventDispatcher getAppEventDispatcher();
         public GeckoProfile getProfile();
         public Activity getActivity();
         public String getDefaultUAString();
         public void doRestart();
 
         /**
          * This API doesn't make sense for arbitrary GeckoView consumers. In future, consider an
          * API like Android WebView's, which provides a View to the consumer to display fullscreen.
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/LocalBrowserDBTest.java
@@ -0,0 +1,345 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.gecko.db;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
+public class LocalBrowserDBTest {
+    private static final long INVALID_ID = -1;
+    private final String BOOKMARK_URL = "https://www.mozilla.org";
+    private final String BOOKMARK_TITLE = "mozilla";
+
+    private final String UPDATE_URL = "https://bugzilla.mozilla.org";
+    private final String UPDATE_TITLE = "bugzilla";
+
+    private final String FOLDER_NAME = "folder1";
+
+    private Context context;
+    private BrowserProvider provider;
+    private ContentProviderClient bookmarkClient;
+
+    @Before
+    public void setUp() throws Exception {
+        context = RuntimeEnvironment.application;
+        provider = new BrowserProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
+        ShadowContentResolver contentResolver = new ShadowContentResolver();
+        bookmarkClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
+    }
+
+    @After
+    public void tearDown() {
+        bookmarkClient.release();
+        provider.shutdown();
+    }
+
+    @Test
+    public void testRemoveBookmarkWithURL() {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        db.addBookmark(cr, BOOKMARK_TITLE, BOOKMARK_URL);
+        Cursor cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNotNull(cursor);
+
+        long parentId = INVALID_ID;
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(title, BOOKMARK_TITLE);
+
+            final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(url, BOOKMARK_URL);
+
+            parentId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.PARENT));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(parentId, INVALID_ID);
+
+        final long lastModifiedBeforeRemove = getModifiedDate(parentId);
+
+        // Remove bookmark record
+        db.removeBookmarksWithURL(cr, BOOKMARK_URL);
+
+        // Check the record has been removed
+        cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNull(cursor);
+
+        // Check parent's lastModified timestamp is updated
+        final long lastModifiedAfterRemove = getModifiedDate(parentId);
+        assertTrue(lastModifiedAfterRemove > lastModifiedBeforeRemove);
+    }
+
+    @Test
+    public void testRemoveBookmarkWithId() {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        db.addBookmark(cr, BOOKMARK_TITLE, BOOKMARK_URL);
+        Cursor cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNotNull(cursor);
+
+        long bookmarkId = INVALID_ID;
+        long parentId = INVALID_ID;
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            bookmarkId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
+            parentId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.PARENT));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(bookmarkId, INVALID_ID);
+        assertNotEquals(parentId, INVALID_ID);
+
+        final long lastModifiedBeforeRemove = getModifiedDate(parentId);
+
+        // Remove bookmark record
+        db.removeBookmarkWithId(cr, bookmarkId);
+
+        cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNull(cursor);
+
+        // Check parent's lastModified timestamp is updated
+        final long lastModifiedAfterRemove = getModifiedDate(parentId);
+        assertTrue(lastModifiedAfterRemove > lastModifiedBeforeRemove);
+    }
+
+    @Test
+    public void testUpdateBookmark() throws Exception {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        db.addBookmark(cr, BOOKMARK_TITLE, BOOKMARK_URL);
+        Cursor cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNotNull(cursor);
+
+        long bookmarkId = INVALID_ID;
+        long parentId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String insertedUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(insertedUrl, BOOKMARK_URL);
+
+            final String insertedTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(insertedTitle, BOOKMARK_TITLE);
+
+            bookmarkId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(bookmarkId, INVALID_ID);
+
+        final long parentLastModifiedBeforeUpdate = getModifiedDate(parentId);
+
+        // Update bookmark record
+        db.updateBookmark(cr, bookmarkId, UPDATE_URL, UPDATE_TITLE, "");
+        cursor = db.getBookmarkById(cr, bookmarkId);
+        assertNotNull(cursor);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String updatedUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(updatedUrl, UPDATE_URL);
+
+            final String updatedTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(updatedTitle, UPDATE_TITLE);
+        } finally {
+            cursor.close();
+        }
+
+        // Check parent's lastModified timestamp isn't changed
+        final long parentLastModifiedAfterUpdate = getModifiedDate(parentId);
+        assertTrue(parentLastModifiedAfterUpdate == parentLastModifiedBeforeUpdate);
+    }
+
+    @Test
+    public void testUpdateBookmarkWithParentChange() throws Exception {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        db.addBookmark(cr, BOOKMARK_TITLE, BOOKMARK_URL);
+        Cursor cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNotNull(cursor);
+
+        long bookmarkId = INVALID_ID;
+        long originalParentId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String insertedUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(insertedUrl, BOOKMARK_URL);
+
+            final String insertedTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(insertedTitle, BOOKMARK_TITLE);
+
+            bookmarkId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(bookmarkId, INVALID_ID);
+
+        // Create a folder
+        final Uri newFolderUri = db.addBookmarkFolder(cr, FOLDER_NAME, originalParentId);
+        // Get id from Uri
+        final long newParentId = Long.valueOf(newFolderUri.getLastPathSegment());
+
+        final long originalParentLastModifiedBeforeUpdate = getModifiedDate(originalParentId);
+        final long newParentLastModifiedBeforeUpdate = getModifiedDate(newParentId);
+
+        // Update bookmark record
+        db.updateBookmark(cr, bookmarkId, UPDATE_URL, UPDATE_TITLE, "", newParentId, originalParentId);
+        cursor = db.getBookmarkById(cr, bookmarkId);
+        assertNotNull(cursor);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String updatedUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(updatedUrl, UPDATE_URL);
+
+            final String updatedTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(updatedTitle, UPDATE_TITLE);
+
+            final long parentId = cursor.getLong(cursor.getColumnIndex(BrowserContract.Bookmarks.PARENT));
+            assertEquals(parentId, newParentId);
+        } finally {
+            cursor.close();
+        }
+
+        // Check parent's lastModified timestamp
+        final long originalParentLastModifiedAfterUpdate = getModifiedDate(originalParentId);
+        assertTrue(originalParentLastModifiedAfterUpdate > originalParentLastModifiedBeforeUpdate);
+
+        final long newParentLastModifiedAfterUpdate = getModifiedDate(newParentId);
+        assertTrue(newParentLastModifiedAfterUpdate > newParentLastModifiedBeforeUpdate);
+    }
+
+    @Test
+    public void testAddBookmarkFolder() throws Exception {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        // Add a bookmark folder record
+        final long rootFolderId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
+        final long lastModifiedBeforeAdd = getModifiedDate(rootFolderId);
+        final Uri folderUri = db.addBookmarkFolder(cr, FOLDER_NAME, rootFolderId);
+        assertNotNull(folderUri);
+
+        // Get id from Uri
+        long folderId = Long.valueOf(folderUri.getLastPathSegment());
+
+        final Cursor cursor = db.getBookmarkById(cr, folderId);
+        assertNotNull(cursor);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String name = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(name, FOLDER_NAME);
+
+            final long parent = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.PARENT));
+            assertEquals(parent, rootFolderId);
+        } finally {
+            cursor.close();
+        }
+
+        // Check parent's lastModified timestamp is updated
+        final long lastModifiedAfterAdd = getModifiedDate(rootFolderId);
+        assertTrue(lastModifiedAfterAdd > lastModifiedBeforeAdd);
+    }
+
+    @Test
+    public void testAddBookmark() throws Exception {
+        BrowserDB db = new LocalBrowserDB("default");
+        ContentResolver cr = context.getContentResolver();
+
+        final long rootFolderId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
+        final long lastModifiedBeforeAdd = getModifiedDate(rootFolderId);
+
+        // Add a bookmark
+        db.addBookmark(cr, BOOKMARK_TITLE, BOOKMARK_URL);
+
+        final Cursor cursor = db.getBookmarkForUrl(cr, BOOKMARK_URL);
+        assertNotNull(cursor);
+        try {
+            assertTrue(cursor.moveToFirst());
+
+            final String name = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+            assertEquals(name, BOOKMARK_TITLE);
+
+            final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL));
+            assertEquals(url, BOOKMARK_URL);
+        } finally {
+            cursor.close();
+        }
+
+        // Check parent's lastModified timestamp is updated
+        final long lastModifiedAfterAdd = getModifiedDate(rootFolderId);
+        assertTrue(lastModifiedAfterAdd > lastModifiedBeforeAdd);
+    }
+
+    private long getBookmarkIdFromGuid(String guid) throws RemoteException {
+        Cursor cursor = bookmarkClient.query(BrowserContract.Bookmarks.CONTENT_URI,
+                                             new String[] { BrowserContract.Bookmarks._ID },
+                                             BrowserContract.Bookmarks.GUID + " = ?",
+                                             new String[] { guid },
+                                             null);
+        assertNotNull(cursor);
+
+        long id = INVALID_ID;
+        try {
+            assertTrue(cursor.moveToFirst());
+            id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(id, INVALID_ID);
+        return id;
+    }
+
+    private long getModifiedDate(long id) {
+        Cursor cursor = provider.query(BrowserContract.Bookmarks.CONTENT_URI,
+                                       new String[] { BrowserContract.Bookmarks.DATE_MODIFIED },
+                                       BrowserContract.Bookmarks._ID + " = ?",
+                                       new String[] { String.valueOf(id) },
+                                       null);
+        assertNotNull(cursor);
+
+        long modified = -1;
+        try {
+            assertTrue(cursor.moveToFirst());
+            modified = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.DATE_MODIFIED));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(modified, -1);
+        return modified;
+    }
+}
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5051,16 +5051,18 @@ pref("dom.vr.enabled", false);
 // It is often desirable to automatically start vr presentation when
 // a user puts on the VR headset.  This is done by emitting the
 // Window.vrdisplayactivate event when the headset's sensors detect it
 // being worn.  This can result in WebVR content taking over the headset
 // when the user is using it outside the browser or inadvertent start of
 // presentation due to the high sensitivity of the proximity sensor in some
 // headsets, so it is off by default.
 pref("dom.vr.autoactivate.enabled", false);
+// The threshold value of trigger inputs for VR controllers
+pref("dom.vr.controller_trigger_threshold", "0.1");
 // Maximum number of milliseconds the browser will wait for content to call
 // VRDisplay.requestPresent after emitting vrdisplayactivate during VR
 // link traversal.  This prevents a long running event handler for
 // vrdisplayactivate from later calling VRDisplay.requestPresent, which would
 // result in a non-responsive browser in the VR headset.
 pref("dom.vr.navigation.timeout", 5000);
 // Oculus device
 pref("dom.vr.oculus.enabled", true);
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -423,16 +423,17 @@ nsProtocolProxyService::nsProtocolProxyS
     , mHTTPSProxyPort(-1)
     , mSOCKSProxyPort(-1)
     , mSOCKSProxyVersion(4)
     , mSOCKSProxyRemoteDNS(false)
     , mProxyOverTLS(true)
     , mPACMan(nullptr)
     , mSessionStart(PR_Now())
     , mFailedProxyTimeout(30 * 60) // 30 minute default
+    , mIsShutdown(false)
 {
 }
 
 nsProtocolProxyService::~nsProtocolProxyService()
 {
     // These should have been cleaned up in our Observe method.
     NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr &&
                  mPACMan == nullptr, "what happened to xpcom-shutdown?");
@@ -513,16 +514,17 @@ nsProtocolProxyService::ReloadNetworkPAC
 
 
 NS_IMETHODIMP
 nsProtocolProxyService::Observe(nsISupports     *aSubject,
                                 const char      *aTopic,
                                 const char16_t *aData)
 {
     if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+        mIsShutdown = true;
         // cleanup
         if (mHostFiltersArray.Length() > 0) {
             mHostFiltersArray.Clear();
         }
         if (mFilters) {
             delete mFilters;
             mFilters = nullptr;
         }
@@ -1012,16 +1014,20 @@ nsProtocolProxyService::IsProxyDisabled(
     }
 
     return true;
 }
 
 nsresult
 nsProtocolProxyService::SetupPACThread()
 {
+    if (mIsShutdown) {
+        return NS_ERROR_FAILURE;
+    }
+
     if (mPACMan)
         return NS_OK;
 
     mPACMan = new nsPACMan();
 
     bool mainThreadOnly;
     nsresult rv;
     if (mSystemProxySettings &&
@@ -1048,17 +1054,18 @@ nsProtocolProxyService::ResetPACThread()
     mPACMan = nullptr;
     return SetupPACThread();
 }
 
 nsresult
 nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec,
                                          bool forceReload)
 {
-    SetupPACThread();
+    nsresult rv = SetupPACThread();
+    NS_ENSURE_SUCCESS(rv, rv);
 
     if (mPACMan->IsPACURI(spec) && !forceReload)
         return NS_OK;
 
     mFailedProxies.Clear();
 
     return mPACMan->LoadPACFromURI(spec);
 }
@@ -1369,16 +1376,20 @@ nsProtocolProxyService::GetFailoverForPr
 
     NS_ADDREF(*aResult = pi->mNext);
     return NS_OK;
 }
 
 nsresult
 nsProtocolProxyService::InsertFilterLink(FilterLink *link, uint32_t position)
 {
+    if (mIsShutdown) {
+        return NS_ERROR_FAILURE;
+    }
+
     if (!mFilters) {
         mFilters = link;
         return NS_OK;
     }
 
     // insert into mFilters in sorted order
     FilterLink *last = nullptr;
     for (FilterLink *iter = mFilters; iter; iter = iter->next) {
@@ -1405,30 +1416,38 @@ nsProtocolProxyService::RegisterFilter(n
                                        uint32_t position)
 {
     UnregisterFilter(filter); // remove this filter if we already have it
 
     FilterLink *link = new FilterLink(position, filter);
     if (!link) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
-    return InsertFilterLink(link, position);
+    nsresult rv = InsertFilterLink(link, position);
+    if (NS_FAILED(rv)) {
+        delete link;
+    }
+    return rv;
 }
 
 NS_IMETHODIMP
 nsProtocolProxyService::RegisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter,
                                               uint32_t position)
 {
     UnregisterChannelFilter(channelFilter);  // remove this filter if we already have it
 
     FilterLink *link = new FilterLink(position, channelFilter);
     if (!link) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
-    return InsertFilterLink(link, position);
+    nsresult rv = InsertFilterLink(link, position);
+    if (NS_FAILED(rv)) {
+        delete link;
+    }
+    return rv;
 }
 
 nsresult
 nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject)
 {
     FilterLink *last = nullptr;
     for (FilterLink *iter = mFilters; iter; iter = iter->next) {
         nsCOMPtr<nsISupports> object = do_QueryInterface(iter->filter);
@@ -1468,16 +1487,20 @@ nsProtocolProxyService::GetProxyConfigTy
 {
   *aProxyConfigType = mProxyConfig;
   return NS_OK;
 }
 
 void
 nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters)
 {
+    if (mIsShutdown) {
+        return;
+    }
+
     // check to see the owners flag? /!?/ TODO
     if (mHostFiltersArray.Length() > 0) {
         mHostFiltersArray.Clear();
     }
 
     if (aFilters.IsEmpty()) {
         return;
     }
--- a/netwerk/base/nsProtocolProxyService.h
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -395,17 +395,17 @@ protected:
     nsFailedProxyTable           mFailedProxies;
     int32_t                      mFailedProxyTimeout;
 
 private:
     nsresult AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
                                   nsIProtocolProxyCallback *callback,
                                   nsICancelable **result,
                                   bool isSyncOK);
-
+    bool                          mIsShutdown;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService, NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // !nsProtocolProxyService_h__
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -270,16 +270,23 @@ class BuildOutputManager(LoggingMixin):
                 self.handler.acquire()
             try:
                 self.refresh()
             finally:
                 if have_handler:
                     self.handler.release()
 
 
+class StoreDebugParamsAndWarnAction(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+        sys.stderr.write('The --debugparams argument is deprecated. Please ' +
+                         'use --debugger-args instead.\n\n')
+        setattr(namespace, self.dest, values)
+
+
 @CommandProvider
 class Build(MachCommandBase):
     """Interface to build the tree."""
 
     @Command('build', category='build', description='Build the tree.')
     @CommandArgument('--jobs', '-j', default='0', metavar='jobs', type=int,
         help='Number of concurrent jobs to run. Default is the number of CPUs.')
     @CommandArgument('-C', '--directory', default=None,
@@ -1118,40 +1125,43 @@ class RunProgram(MachCommandBase):
     @CommandArgument('--enable-crash-reporter', action='store_true', group=prog_group,
         help='Run the program with the crash reporter enabled.')
 
     @CommandArgumentGroup('debugging')
     @CommandArgument('--debug', action='store_true', group='debugging',
         help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used.')
     @CommandArgument('--debugger', default=None, type=str, group='debugging',
         help='Name of debugger to use.')
-    @CommandArgument('--debugparams', default=None, metavar='params', type=str,
+    @CommandArgument('--debugger-args', default=None, metavar='params', type=str,
         group='debugging',
         help='Command-line arguments to pass to the debugger itself; split as the Bourne shell would.')
+    @CommandArgument('--debugparams', action=StoreDebugParamsAndWarnAction,
+        default=None, type=str, dest='debugger_args', group='debugging',
+        help=argparse.SUPPRESS)
 
     @CommandArgumentGroup('DMD')
     @CommandArgument('--dmd', action='store_true', group='DMD',
         help='Enable DMD. The following arguments have no effect without this.')
     @CommandArgument('--mode', choices=['live', 'dark-matter', 'cumulative', 'scan'], group='DMD',
          help='Profiling mode. The default is \'dark-matter\'.')
     @CommandArgument('--stacks', choices=['partial', 'full'], group='DMD',
         help='Allocation stack trace coverage. The default is \'partial\'.')
     @CommandArgument('--show-dump-stats', action='store_true', group='DMD',
         help='Show stats when doing dumps.')
     def run(self, params, remote, background, noprofile, disable_e10s,
-        enable_crash_reporter, debug, debugger, debugparams,
+        enable_crash_reporter, debug, debugger, debugger_args,
         dmd, mode, stacks, show_dump_stats):
 
         if conditions.is_android(self):
             # Running Firefox for Android is completely different
             if dmd:
                 print("DMD is not supported for Firefox for Android")
                 return 1
             from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
-            if not (debug or debugger or debugparams):
+            if not (debug or debugger or debugger_args):
                 verify_android_device(self, install=True)
                 return run_firefox_for_android(self, params)
             verify_android_device(self, install=True, debugger=True)
             args = ['']
 
         else:
 
             try:
@@ -1187,40 +1197,40 @@ class RunProgram(MachCommandBase):
         }
 
         if not enable_crash_reporter:
             extra_env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
         if disable_e10s:
             extra_env['MOZ_FORCE_DISABLE_E10S'] = '1'
 
-        if debug or debugger or debugparams:
+        if debug or debugger or debugger_args:
             if 'INSIDE_EMACS' in os.environ:
                 self.log_manager.terminal_handler.setLevel(logging.WARNING)
 
             import mozdebug
             if not debugger:
                 # No debugger name was provided. Look for the default ones on
                 # current OS.
                 debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
 
             if debugger:
-                self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugparams)
+                self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
                 if not self.debuggerInfo:
                     print("Could not find a suitable debugger in your PATH.")
                     return 1
 
             # Parameters come from the CLI. We need to convert them before
             # their use.
-            if debugparams:
+            if debugger_args:
                 from mozbuild import shellutil
                 try:
-                    debugparams = shellutil.split(debugparams)
+                    debugger_args = shellutil.split(debugger_args)
                 except shellutil.MetaCharacterException as e:
-                    print("The --debugparams you passed require a real shell to parse them.")
+                    print("The --debugger-args you passed require a real shell to parse them.")
                     print("(We can't handle the %r character.)" % e.char)
                     return 1
 
             # Prepend the debugger args.
             args = [self.debuggerInfo.path] + self.debuggerInfo.args + args
 
         if dmd:
             dmd_params = []
--- a/security/certverifier/CTLogVerifier.cpp
+++ b/security/certverifier/CTLogVerifier.cpp
@@ -157,16 +157,44 @@ CTLogVerifier::Init(Input subjectPublicK
   }
   mSignatureAlgorithm = trustDomain.mSignatureAlgorithm;
 
   rv = InputToBuffer(subjectPublicKeyInfo, mSubjectPublicKeyInfo);
   if (rv != Success) {
     return rv;
   }
 
+  if (mSignatureAlgorithm == DigitallySigned::SignatureAlgorithm::ECDSA) {
+    SECItem spkiSECItem = {
+      siBuffer,
+      mSubjectPublicKeyInfo.begin(),
+      static_cast<unsigned int>(mSubjectPublicKeyInfo.length())
+    };
+    UniqueCERTSubjectPublicKeyInfo spki(
+      SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiSECItem));
+    if (!spki) {
+      return MapPRErrorCodeToResult(PR_GetError());
+    }
+    mPublicECKey.reset(SECKEY_ExtractPublicKey(spki.get()));
+    if (!mPublicECKey) {
+      return MapPRErrorCodeToResult(PR_GetError());
+    }
+    UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+    if (!slot) {
+      return MapPRErrorCodeToResult(PR_GetError());
+    }
+    CK_OBJECT_HANDLE handle = PK11_ImportPublicKey(slot.get(),
+                                                   mPublicECKey.get(), false);
+    if (handle == CK_INVALID_HANDLE) {
+      return MapPRErrorCodeToResult(PR_GetError());
+    }
+  } else {
+    mPublicECKey.reset(nullptr);
+  }
+
   if (!mKeyId.resizeUninitialized(SHA256_LENGTH)) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   rv = DigestBufNSS(subjectPublicKeyInfo, DigestAlgorithm::sha256,
                     mKeyId.begin(), mKeyId.length());
   if (rv != Success) {
     return rv;
   }
@@ -235,16 +263,48 @@ CTLogVerifier::VerifySignedTreeHead(cons
 
 bool
 CTLogVerifier::SignatureParametersMatch(const DigitallySigned& signature)
 {
   return signature.SignatureParametersMatch(
     DigitallySigned::HashAlgorithm::SHA256, mSignatureAlgorithm);
 }
 
+static Result
+FasterVerifyECDSASignedDigestNSS(const SignedDigest& sd,
+                                 UniqueSECKEYPublicKey& pubkey)
+{
+  MOZ_ASSERT(pubkey);
+  if (!pubkey) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  // The signature is encoded as a DER SEQUENCE of two INTEGERs. PK11_Verify
+  // expects the signature as only the two integers r and s (so no encoding -
+  // just two series of bytes each half as long as SECKEY_SignatureLen(pubkey)).
+  // DSAU_DecodeDerSigToLen converts from the former format to the latter.
+  SECItem derSignatureSECItem(UnsafeMapInputToSECItem(sd.signature));
+  size_t signatureLen = SECKEY_SignatureLen(pubkey.get());
+  if (signatureLen == 0) {
+    return MapPRErrorCodeToResult(PR_GetError());
+  }
+  UniqueSECItem signatureSECItem(DSAU_DecodeDerSigToLen(&derSignatureSECItem,
+                                                        signatureLen));
+  if (!signatureSECItem) {
+    return MapPRErrorCodeToResult(PR_GetError());
+  }
+  SECItem digestSECItem(UnsafeMapInputToSECItem(sd.digest));
+  SECStatus srv = PK11_Verify(pubkey.get(), signatureSECItem.get(),
+                              &digestSECItem, nullptr);
+  if (srv != SECSuccess) {
+    return MapPRErrorCodeToResult(PR_GetError());
+  }
+
+  return Success;
+}
+
 Result
 CTLogVerifier::VerifySignature(Input data, Input signature)
 {
   uint8_t digest[SHA256_LENGTH];
   Result rv = DigestBufNSS(data, DigestAlgorithm::sha256, digest,
                            ArrayLength(digest));
   if (rv != Success) {
     return rv;
@@ -267,17 +327,17 @@ CTLogVerifier::VerifySignature(Input dat
     return rv;
   }
 
   switch (mSignatureAlgorithm) {
     case DigitallySigned::SignatureAlgorithm::RSA:
       rv = VerifyRSAPKCS1SignedDigestNSS(signedDigest, spki, nullptr);
       break;
     case DigitallySigned::SignatureAlgorithm::ECDSA:
-      rv = VerifyECDSASignedDigestNSS(signedDigest, spki, nullptr);
+      rv = FasterVerifyECDSASignedDigestNSS(signedDigest, mPublicECKey);
       break;
     // We do not expect new values added to this enum any time soon,
     // so just listing all the available ones seems to be the easiest way
     // to suppress warning C4061 on MSVC (which expects all values of the
     // enum to be explicitly handled).
     case DigitallySigned::SignatureAlgorithm::Anonymous:
     case DigitallySigned::SignatureAlgorithm::DSA:
     default:
--- a/security/certverifier/CTLogVerifier.h
+++ b/security/certverifier/CTLogVerifier.h
@@ -6,16 +6,17 @@
 
 #ifndef CTLogVerifier_h
 #define CTLogVerifier_h
 
 #include "CTLog.h"
 #include "pkix/Input.h"
 #include "pkix/pkix.h"
 #include "pkix/Result.h"
+#include "ScopedNSSTypes.h"
 #include "SignedCertificateTimestamp.h"
 #include "SignedTreeHead.h"
 
 namespace mozilla { namespace ct {
 
 // Verifies Signed Certificate Timestamps (SCTs) provided by a specific log
 // using the public key of that log. Assumes the SCT being verified
 // matches the log by log key ID and signature parameters (an error is returned
@@ -67,16 +68,21 @@ private:
   // Performs the underlying verification using the log's public key. Note
   // that |signature| contains the raw signature data (i.e. without any
   // DigitallySigned struct encoding).
   // Returns Success if passed verification, ERROR_BAD_SIGNATURE if failed
   // verification, or other result on error.
   pkix::Result VerifySignature(pkix::Input data, pkix::Input signature);
   pkix::Result VerifySignature(const Buffer& data, const Buffer& signature);
 
+  // mPublicECKey works around an architectural deficiency in NSS. In the case
+  // of EC, if we don't create, import, and cache this key, NSS will import and
+  // verify it every signature verification, which is slow. For RSA, this is
+  // unused and will be null.
+  UniqueSECKEYPublicKey mPublicECKey;
   Buffer mSubjectPublicKeyInfo;
   Buffer mKeyId;
   DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
   CTLogOperatorId mOperatorId;
   bool mDisqualified;
   uint64_t mDisqualificationTime;
 };
 
--- a/security/certverifier/tests/gtest/CTLogVerifierTest.cpp
+++ b/security/certverifier/tests/gtest/CTLogVerifierTest.cpp
@@ -61,67 +61,70 @@ TEST_F(CTLogVerifierTest, FailsInvalidTi
   GetX509CertLogEntry(certEntry);
 
   SignedCertificateTimestamp certSct;
   GetX509CertSCT(certSct);
 
   // Mangle the timestamp, so that it should fail signature validation.
   certSct.timestamp = 0;
 
-  EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
+  EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
 }
 
 TEST_F(CTLogVerifierTest, FailsInvalidSignature)
 {
   LogEntry certEntry;
   GetX509CertLogEntry(certEntry);
 
-  // Mangle the signature, making VerifyECDSASignedDigestNSS (used by
-  // CTLogVerifier) return ERROR_BAD_SIGNATURE.
+  // Mangle the value of the signature, making the underlying signature
+  // verification code return ERROR_BAD_SIGNATURE.
   SignedCertificateTimestamp certSct;
   GetX509CertSCT(certSct);
   certSct.signature.signatureData[20] ^= '\xFF';
-  EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
+  EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
 
-  // Make VerifyECDSASignedDigestNSS return ERROR_BAD_DER. We still expect
-  // the verifier to return ERROR_BAD_SIGNATURE.
+  // Mangle the encoding of the signature, making the underlying implementation
+  // return ERROR_BAD_DER. We still expect the verifier to return
+  // ERROR_BAD_SIGNATURE.
   SignedCertificateTimestamp certSct2;
   GetX509CertSCT(certSct2);
   certSct2.signature.signatureData[0] ^= '\xFF';
-  EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct2));
+  EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry,
+                                                           certSct2));
 }
 
 TEST_F(CTLogVerifierTest, FailsInvalidLogID)
 {
   LogEntry certEntry;
   GetX509CertLogEntry(certEntry);
 
   SignedCertificateTimestamp certSct;
   GetX509CertSCT(certSct);
 
   // Mangle the log ID, which should cause it to match a different log before
   // attempting signature validation.
   MOZ_RELEASE_ASSERT(certSct.logId.append('\x0'));
 
-  EXPECT_EQ(Result::FATAL_ERROR_INVALID_ARGS, mLog.Verify(certEntry, certSct));
+  EXPECT_EQ(pkix::Result::FATAL_ERROR_INVALID_ARGS, mLog.Verify(certEntry,
+                                                                certSct));
 }
 
 TEST_F(CTLogVerifierTest, VerifiesValidSTH)
 {
   SignedTreeHead sth;
   GetSampleSignedTreeHead(sth);
   EXPECT_EQ(Success, mLog.VerifySignedTreeHead(sth));
 }
 
 TEST_F(CTLogVerifierTest, DoesNotVerifyInvalidSTH)
 {
   SignedTreeHead sth;
   GetSampleSignedTreeHead(sth);
   sth.sha256RootHash[0] ^= '\xFF';
-  EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.VerifySignedTreeHead(sth));
+  EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.VerifySignedTreeHead(sth));
 }
 
 // Test that excess data after the public key is rejected.
 TEST_F(CTLogVerifierTest, ExcessDataInPublicKey)
 {
   Buffer key = GetTestPublicKey();
   MOZ_RELEASE_ASSERT(key.append("extra", 5));
 
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1834,16 +1834,20 @@ nsNSSComponent::ShutdownNSS()
     // those resources are cleaned up.
     Unused << SSL_ShutdownServerSessionIDCache();
 
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("evaporating psm resources"));
     if (NS_FAILED(nsNSSShutDownList::evaporateAllNSSResourcesAndShutDown())) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to evaporate resources"));
       return;
     }
+    // Release the default CertVerifier. This will cause any held NSS resources
+    // to be released (it's not an nsNSSShutDownObject, so we have to do this
+    // manually).
+    mDefaultCertVerifier = nullptr;
     UnloadLoadableRoots();
     if (SECSuccess != ::NSS_Shutdown()) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("NSS SHUTDOWN FAILURE"));
     } else {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS shutdown =====>> OK <<====="));
     }
   }
 }
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -434,16 +434,17 @@ PK11_Sign
 PK11_SignatureLen
 PK11_SignWithMechanism
 PK11_TokenKeyGenWithFlags
 PK11_UnwrapPrivKey
 PK11_UnwrapSymKey
 PK11_UpdateSlotAttribute
 PK11_UserDisableSlot
 PK11_UserEnableSlot
+PK11_Verify
 PK11_VerifyWithMechanism
 PK11_WrapPrivKey
 PK11_WrapSymKey
 PORT_Alloc
 PORT_Alloc_Util
 PORT_ArenaAlloc
 PORT_ArenaAlloc_Util
 PORT_ArenaGrow_Util
new file mode 100644
--- /dev/null
+++ b/servo/components/selectors/arcslice.rs
@@ -0,0 +1,326 @@
+/* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+ * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+ * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+ * option. This file may not be copied, modified, or distributed
+ * except according to those terms.
+ *
+ * See the COPYRIGHT file at the top-level directory of this distribution */
+//! A thread-safe reference-counted slice type.
+//!
+//! Forked from https://github.com/huonw/shared_slice , which doesn't work on
+//! rust stable.
+
+use std::{cmp, fmt, ops};
+use std::hash::{Hash, Hasher};
+use std::sync::{Arc, Weak};
+
+
+/// A reference-counted slice type.
+pub struct ArcSlice<T> {
+    data: *const [T],
+    counts: Arc<Box<[T]>>,
+}
+
+unsafe impl<T: Send + Sync> Send for ArcSlice<T> {}
+unsafe impl<T: Send + Sync> Sync for ArcSlice<T> {}
+
+/// A non-owning reference-counted slice type.
+///
+/// This is to `ArcSlice` as `std::sync::Weak` is to `std::sync::Arc`, and
+/// allows one to have cyclic references without stopping memory from
+/// being deallocated.
+pub struct WeakSlice<T> {
+    data: *const [T],
+    counts: Weak<Box<[T]>>,
+}
+unsafe impl<T: Send + Sync> Send for WeakSlice<T> {}
+unsafe impl<T: Send + Sync> Sync for WeakSlice<T> {}
+
+impl<T> ArcSlice<T> {
+    /// Construct a new `ArcSlice` containing the elements of `slice`.
+    ///
+    /// This reuses the allocation of `slice`.
+    pub fn new(slice: Box<[T]>) -> ArcSlice<T> {
+        ArcSlice {
+            data: &*slice,
+            counts: Arc::new(slice),
+        }
+    }
+
+    /// Downgrade self into a weak slice.
+    pub fn downgrade(&self) -> WeakSlice<T> {
+        WeakSlice {
+            data: self.data,
+            counts: Arc::downgrade(&self.counts)
+        }
+    }
+
+    /// Construct a new `ArcSlice` that only points to elements at
+    /// indices `lo` (inclusive) through `hi` (exclusive).
+    ///
+    /// This consumes `self` to avoid unnecessary reference-count
+    /// modifications. Use `.clone()` if it is necessary to refer to
+    /// `self` after calling this.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `lo > hi` or if either are strictly greater than
+    /// `self.len()`.
+    pub fn slice(mut self, lo: usize, hi: usize) -> ArcSlice<T> {
+        self.data = &self[lo..hi];
+        self
+    }
+    /// Construct a new `ArcSlice` that only points to elements at
+    /// indices up to `hi` (exclusive).
+    ///
+    /// This consumes `self` to avoid unnecessary reference-count
+    /// modifications. Use `.clone()` if it is necessary to refer to
+    /// `self` after calling this.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `hi > self.len()`.
+    pub fn slice_to(self, hi: usize) -> ArcSlice<T> {
+        self.slice(0, hi)
+    }
+    /// Construct a new `ArcSlice` that only points to elements at
+    /// indices starting at  `lo` (inclusive).
+    ///
+    /// This consumes `self` to avoid unnecessary reference-count
+    /// modifications. Use `.clone()` if it is necessary to refer to
+    /// `self` after calling this.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `lo > self.len()`.
+    pub fn slice_from(self, lo: usize) -> ArcSlice<T> {
+        let hi = self.len();
+        self.slice(lo, hi)
+    }
+}
+
+impl<T> Clone for ArcSlice<T> {
+    fn clone(&self) -> ArcSlice<T> {
+        ArcSlice {
+            data: self.data,
+            counts: self.counts.clone()
+        }
+    }
+}
+
+impl<T> ops::Deref for ArcSlice<T> {
+    type Target = [T];
+    fn deref<'a>(&'a self) -> &'a [T] {
+        unsafe { &*self.data }
+    }
+}
+
+impl<T> AsRef<[T]> for ArcSlice<T> {
+    fn as_ref(&self) -> &[T] { &**self }
+}
+
+impl<T: PartialEq> PartialEq for ArcSlice<T> {
+    fn eq(&self, other: &ArcSlice<T>) -> bool { **self == **other }
+    fn ne(&self, other: &ArcSlice<T>) -> bool { **self != **other }
+}
+impl<T: Eq> Eq for ArcSlice<T> {}
+
+impl<T: PartialOrd> PartialOrd for ArcSlice<T> {
+    fn partial_cmp(&self, other: &ArcSlice<T>) -> Option<cmp::Ordering> {
+        (**self).partial_cmp(&**other)
+    }
+    fn lt(&self, other: &ArcSlice<T>) -> bool { **self < **other }
+    fn le(&self, other: &ArcSlice<T>) -> bool { **self <= **other }
+    fn gt(&self, other: &ArcSlice<T>) -> bool { **self > **other }
+    fn ge(&self, other: &ArcSlice<T>) -> bool { **self >= **other }
+}
+impl<T: Ord> Ord for ArcSlice<T> {
+    fn cmp(&self, other: &ArcSlice<T>) -> cmp::Ordering { (**self).cmp(&**other) }
+}
+
+impl<T: Hash> Hash for ArcSlice<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        Hash::hash(&**self, state)
+    }
+}
+
+impl<T: fmt::Debug> fmt::Debug for ArcSlice<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Debug::fmt(&**self, f)
+    }
+}
+
+impl<T> WeakSlice<T> {
+    /// Attempt to upgrade `self` to a strongly-counted `ArcSlice`.
+    ///
+    /// Returns `None` if this is not possible (the data has already
+    /// been freed).
+    pub fn upgrade(&self) -> Option<ArcSlice<T>> {
+        self.counts.upgrade().map(|counts| {
+            ArcSlice {
+                data: self.data,
+                counts: counts
+            }
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::cell::Cell;
+    use std::cmp::Ordering;
+    use std::sync::{Arc, Mutex};
+    use super::{ArcSlice, WeakSlice};
+    #[test]
+    fn clone() {
+        let x = ArcSlice::new(Box::new([Cell::new(false)]));
+        let y = x.clone();
+
+        assert_eq!(x[0].get(), false);
+        assert_eq!(y[0].get(), false);
+
+        x[0].set(true);
+        assert_eq!(x[0].get(), true);
+        assert_eq!(y[0].get(), true);
+    }
+
+    #[test]
+    fn test_upgrade_downgrade() {
+        let x = ArcSlice::new(Box::new([1]));
+        let y: WeakSlice<_> = x.downgrade();
+
+        assert_eq!(y.upgrade(), Some(x.clone()));
+
+        drop(x);
+
+        assert!(y.upgrade().is_none())
+    }
+
+    #[test]
+    fn test_total_cmp() {
+        let x = ArcSlice::new(Box::new([1, 2, 3]));
+        let y = ArcSlice::new(Box::new([1, 2, 3]));
+        let z = ArcSlice::new(Box::new([1, 2, 4]));
+
+        assert_eq!(x, x);
+        assert_eq!(x, y);
+        assert!(x != z);
+        assert!(y != z);
+
+        assert!(x < z);
+        assert!(x <= z);
+        assert!(!(x > z));
+        assert!(!(x >= z));
+
+        assert!(!(z < x));
+        assert!(!(z <= x));
+        assert!(z > x);
+        assert!(z >= x);
+
+        assert_eq!(x.partial_cmp(&x), Some(Ordering::Equal));
+        assert_eq!(x.partial_cmp(&y), Some(Ordering::Equal));
+        assert_eq!(x.partial_cmp(&z), Some(Ordering::Less));
+        assert_eq!(z.partial_cmp(&y), Some(Ordering::Greater));
+
+        assert_eq!(x.cmp(&x), Ordering::Equal);
+        assert_eq!(x.cmp(&y), Ordering::Equal);
+        assert_eq!(x.cmp(&z), Ordering::Less);
+        assert_eq!(z.cmp(&y), Ordering::Greater);
+    }
+
+    #[test]
+    fn test_partial_cmp() {
+        use std::f64;
+        let x = ArcSlice::new(Box::new([1.0, f64::NAN]));
+        let y = ArcSlice::new(Box::new([1.0, f64::NAN]));
+        let z = ArcSlice::new(Box::new([2.0, f64::NAN]));
+        let w = ArcSlice::new(Box::new([f64::NAN, 1.0]));
+        assert!(!(x == y));
+        assert!(x != y);
+
+        assert!(!(x < y));
+        assert!(!(x <= y));
+        assert!(!(x > y));
+        assert!(!(x >= y));
+
+        assert!(x < z);
+        assert!(x <= z);
+        assert!(!(x > z));
+        assert!(!(x >= z));
+
+        assert!(!(z < w));
+        assert!(!(z <= w));
+        assert!(!(z > w));
+        assert!(!(z >= w));
+
+        assert_eq!(x.partial_cmp(&x), None);
+        assert_eq!(x.partial_cmp(&y), None);
+        assert_eq!(x.partial_cmp(&z), Some(Ordering::Less));
+        assert_eq!(z.partial_cmp(&x), Some(Ordering::Greater));
+
+        assert_eq!(x.partial_cmp(&w), None);
+        assert_eq!(y.partial_cmp(&w), None);
+        assert_eq!(z.partial_cmp(&w), None);
+        assert_eq!(w.partial_cmp(&w), None);
+    }
+
+    #[test]
+    fn test_show() {
+        let x = ArcSlice::new(Box::new([1, 2]));
+        assert_eq!(format!("{:?}", x), "[1, 2]");
+
+        let y: ArcSlice<i32> = ArcSlice::new(Box::new([]));
+        assert_eq!(format!("{:?}", y), "[]");
+    }
+
+    #[test]
+    fn test_slice() {
+        let x = ArcSlice::new(Box::new([1, 2, 3]));
+        let real = [1, 2, 3];
+        for i in 0..3 + 1 {
+            for j in i..3 + 1 {
+                let slice: ArcSlice<_> = x.clone().slice(i, j);
+                assert_eq!(&*slice, &real[i..j]);
+            }
+            assert_eq!(&*x.clone().slice_to(i), &real[..i]);
+            assert_eq!(&*x.clone().slice_from(i), &real[i..]);
+        }
+    }
+
+
+    #[test]
+    fn test_send_sync() {
+        fn assert_send<T: Send>() {}
+        fn assert_sync<T: Send>() {}
+
+        assert_send::<ArcSlice<u8>>();
+        assert_sync::<ArcSlice<u8>>();
+        assert_send::<WeakSlice<u8>>();
+        assert_sync::<WeakSlice<u8>>();
+    }
+
+    #[test]
+    fn test_drop() {
+        let drop_flag = Arc::new(Mutex::new(0));
+        struct Foo(Arc<Mutex<i32>>);
+
+        impl Drop for Foo {
+            fn drop(&mut self) {
+                let mut n = self.0.lock().unwrap();
+                *n += 1;
+            }
+        }
+
+        let whole = ArcSlice::new(Box::new([Foo(drop_flag.clone()), Foo(drop_flag.clone())]));
+
+        drop(whole);
+        assert_eq!(*drop_flag.lock().unwrap(), 2);
+
+        *drop_flag.lock().unwrap() = 0;
+
+        let whole = ArcSlice::new(Box::new([Foo(drop_flag.clone()), Foo(drop_flag.clone())]));
+        let part = whole.slice(1, 2);
+        drop(part);
+        assert_eq!(*drop_flag.lock().unwrap(), 2);
+    }
+}
--- a/servo/components/selectors/lib.rs
+++ b/servo/components/selectors/lib.rs
@@ -4,16 +4,17 @@
 
 #[macro_use] extern crate bitflags;
 #[macro_use] extern crate cssparser;
 #[macro_use] extern crate matches;
 extern crate fnv;
 extern crate precomputed_hash;
 extern crate smallvec;
 
+pub mod arcslice;
 pub mod bloom;
 pub mod matching;
 pub mod parser;
 mod tree;
 pub mod visitor;
 
 pub use parser::{SelectorImpl, Parser, SelectorList};
 pub use tree::Element;
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -1,14 +1,14 @@
 /* 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 bloom::BloomFilter;
-use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName};
-use parser::{SimpleSelector, Selector, SelectorInner};
+use parser::{CaseSensitivity, Combinator, ComplexSelector, Component, LocalName};
+use parser::{Selector, SelectorInner, SelectorIter};
 use std::borrow::Borrow;
 use tree::Element;
 
 // The bloom filter for descendant CSS selectors will have a <1% false
 // positive rate until it has this many selectors in it, then it will
 // rapidly increase.
 pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
 
@@ -202,70 +202,68 @@ enum SelectorMatchingResult {
 pub fn matches_complex_selector<E, F>(selector: &ComplexSelector<E::Impl>,
                                       element: &E,
                                       relations: &mut StyleRelations,
                                       flags_setter: &mut F)
                                       -> bool
      where E: Element,
            F: FnMut(&E, ElementSelectorFlags),
 {
-    match matches_complex_selector_internal(selector,
+    match matches_complex_selector_internal(selector.iter(),
                                             element,
                                             relations,
                                             flags_setter) {
         SelectorMatchingResult::Matched => true,
         _ => false
     }
 }
 
-fn matches_complex_selector_internal<E, F>(selector: &ComplexSelector<E::Impl>,
+fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Impl>,
                                            element: &E,
                                            relations: &mut StyleRelations,
                                            flags_setter: &mut F)
                                            -> SelectorMatchingResult
      where E: Element,
            F: FnMut(&E, ElementSelectorFlags),
 {
-    let matches_all_simple_selectors = selector.compound_selector.iter().all(|simple| {
+    let matches_all_simple_selectors = selector_iter.all(|simple| {
         matches_simple_selector(simple, element, relations, flags_setter)
     });
 
-    let siblings = selector.next.as_ref().map_or(false, |&(_, combinator)| {
-        matches!(combinator, Combinator::NextSibling | Combinator::LaterSibling)
-    });
-
+    let combinator = selector_iter.next_sequence();
+    let siblings = combinator.map_or(false, |c| c.is_sibling());
     if siblings {
         flags_setter(element, HAS_SLOW_SELECTOR_LATER_SIBLINGS);
     }
 
     if !matches_all_simple_selectors {
         return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
     }
 
-    match selector.next {
+    match combinator {
         None => SelectorMatchingResult::Matched,
-        Some((ref next_selector, combinator)) => {
+        Some(c) => {
             let (mut next_element, candidate_not_found) = if siblings {
                 (element.prev_sibling_element(),
                  SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant)
             } else {
                 (element.parent_element(),
                  SelectorMatchingResult::NotMatchedGlobally)
             };
 
             loop {
                 let element = match next_element {
                     None => return candidate_not_found,
                     Some(next_element) => next_element,
                 };
-                let result = matches_complex_selector_internal(&**next_selector,
+                let result = matches_complex_selector_internal(selector_iter.clone(),
                                                                &element,
                                                                relations,
                                                                flags_setter);
-                match (result, combinator) {
+                match (result, c) {
                     // Return the status immediately.
                     (SelectorMatchingResult::Matched, _) => return result,
                     (SelectorMatchingResult::NotMatchedGlobally, _) => return result,
 
                     // Upgrade the failure status to
                     // NotMatchedAndRestartFromClosestDescendant.
                     (_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
 
@@ -294,17 +292,17 @@ fn matches_complex_selector_internal<E, 
             }
         }
     }
 }
 
 /// Determines whether the given element matches the given single selector.
 #[inline]
 fn matches_simple_selector<E, F>(
-        selector: &SimpleSelector<E::Impl>,
+        selector: &Component<E::Impl>,
         element: &E,
         relations: &mut StyleRelations,
         flags_setter: &mut F)
         -> bool
     where E: Element,
           F: FnMut(&E, ElementSelectorFlags),
 {
     macro_rules! relation_if {
@@ -314,119 +312,120 @@ fn matches_simple_selector<E, F>(
                 true
             } else {
                 false
             }
         }
     }
 
     match *selector {
-        SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
+        Component::Combinator(_) => unreachable!(),
+        Component::LocalName(LocalName { ref name, ref lower_name }) => {
             let name = if element.is_html_element_in_html_document() { lower_name } else { name };
             element.get_local_name() == name.borrow()
         }
-        SimpleSelector::Namespace(ref namespace) => {
+        Component::Namespace(ref namespace) => {
             element.get_namespace() == namespace.url.borrow()
         }
         // TODO: case-sensitivity depends on the document type and quirks mode
-        SimpleSelector::ID(ref id) => {
+        Component::ID(ref id) => {
             relation_if!(element.get_id().map_or(false, |attr| attr == *id),
                          AFFECTED_BY_ID_SELECTOR)
         }
-        SimpleSelector::Class(ref class) => {
+        Component::Class(ref class) => {
             element.has_class(class)
         }
-        SimpleSelector::AttrExists(ref attr) => {
+        Component::AttrExists(ref attr) => {
             element.match_attr_has(attr)
         }
-        SimpleSelector::AttrEqual(ref attr, ref value, case_sensitivity) => {
+        Component::AttrEqual(ref attr, ref value, case_sensitivity) => {
             match case_sensitivity {
                 CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value),
                 CaseSensitivity::CaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value),
             }
         }
-        SimpleSelector::AttrIncludes(ref attr, ref value) => {
+        Component::AttrIncludes(ref attr, ref value) => {
             element.match_attr_includes(attr, value)
         }
-        SimpleSelector::AttrDashMatch(ref attr, ref value) => {
+        Component::AttrDashMatch(ref attr, ref value) => {
             element.match_attr_dash(attr, value)
         }
-        SimpleSelector::AttrPrefixMatch(ref attr, ref value) => {
+        Component::AttrPrefixMatch(ref attr, ref value) => {
             element.match_attr_prefix(attr, value)
         }
-        SimpleSelector::AttrSubstringMatch(ref attr, ref value) => {
+        Component::AttrSubstringMatch(ref attr, ref value) => {
             element.match_attr_substring(attr, value)
         }
-        SimpleSelector::AttrSuffixMatch(ref attr, ref value) => {
+        Component::AttrSuffixMatch(ref attr, ref value) => {
             element.match_attr_suffix(attr, value)
         }
-        SimpleSelector::AttrIncludesNeverMatch(..) |
-        SimpleSelector::AttrPrefixNeverMatch(..) |
-        SimpleSelector::AttrSubstringNeverMatch(..) |
-        SimpleSelector::AttrSuffixNeverMatch(..) => {
+        Component::AttrIncludesNeverMatch(..) |
+        Component::AttrPrefixNeverMatch(..) |
+        Component::AttrSubstringNeverMatch(..) |
+        Component::AttrSuffixNeverMatch(..) => {
             false
         }
-        SimpleSelector::NonTSPseudoClass(ref pc) => {
+        Component::NonTSPseudoClass(ref pc) => {
             relation_if!(element.match_non_ts_pseudo_class(pc, relations, flags_setter),
                          AFFECTED_BY_STATE)
         }
-        SimpleSelector::FirstChild => {
+        Component::FirstChild => {
             relation_if!(matches_first_child(element, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::LastChild => {
+        Component::LastChild => {
             relation_if!(matches_last_child(element, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::OnlyChild => {
+        Component::OnlyChild => {
             relation_if!(matches_first_child(element, flags_setter) &&
                          matches_last_child(element, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::Root => {
+        Component::Root => {
             // We never share styles with an element with no parent, so no point
             // in creating a new StyleRelation.
             element.is_root()
         }
-        SimpleSelector::Empty => {
+        Component::Empty => {
             flags_setter(element, HAS_EMPTY_SELECTOR);
             relation_if!(element.is_empty(), AFFECTED_BY_EMPTY)
         }
-        SimpleSelector::NthChild(a, b) => {
+        Component::NthChild(a, b) => {
             relation_if!(matches_generic_nth_child(element, a, b, false, false, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::NthLastChild(a, b) => {
+        Component::NthLastChild(a, b) => {
             relation_if!(matches_generic_nth_child(element, a, b, false, true, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::NthOfType(a, b) => {
+        Component::NthOfType(a, b) => {
             relation_if!(matches_generic_nth_child(element, a, b, true, false, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::NthLastOfType(a, b) => {
+        Component::NthLastOfType(a, b) => {
             relation_if!(matches_generic_nth_child(element, a, b, true, true, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::FirstOfType => {
+        Component::FirstOfType => {
             relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::LastOfType => {
+        Component::LastOfType => {
             relation_if!(matches_generic_nth_child(element, 0, 1, true, true, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::OnlyOfType => {
+        Component::OnlyOfType => {
             relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags_setter) &&
                          matches_generic_nth_child(element, 0, 1, true, true, flags_setter),
                          AFFECTED_BY_CHILD_INDEX)
         }
-        SimpleSelector::Negation(ref negated) => {
+        Component::Negation(ref negated) => {
             !negated.iter().all(|s| {
-                match matches_complex_selector_internal(s,
+                match matches_complex_selector_internal(s.iter(),
                                                         element,
                                                         relations,
                                                         flags_setter) {
                     SelectorMatchingResult::Matched => true,
                     _ => false,
                 }
             })
         }
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1,21 +1,24 @@
 /* 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 arcslice::ArcSlice;
 use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
 use precomputed_hash::PrecomputedHash;
+use smallvec::SmallVec;
 use std::ascii::AsciiExt;
 use std::borrow::{Borrow, Cow};
 use std::cmp;
 use std::fmt::{self, Display, Debug, Write};
 use std::hash::Hash;
+use std::iter::Rev;
 use std::ops::Add;
-use std::sync::Arc;
+use std::slice;
 use tree::SELECTOR_WHITESPACE;
 use visitor::SelectorVisitor;
 
 macro_rules! with_all_bounds {
     (
         [ $( $InSelector: tt )* ]
         [ $( $CommonBounds: tt )* ]
         [ $( $FromStr: tt )* ]
@@ -127,46 +130,51 @@ const NUM_ANCESTOR_HASHES: usize = 4;
 /// The cores parts of a selector used for matching. This exists to make that
 /// information accessibly separately from the specificity and pseudo-element
 /// information that lives on |Selector| proper. We may want to refactor things
 /// and move that information elsewhere, at which point we could rename this
 /// to |Selector|.
 #[derive(PartialEq, Eq, Hash, Clone)]
 pub struct SelectorInner<Impl: SelectorImpl> {
     /// The selector data.
-    pub complex: Arc<ComplexSelector<Impl>>,
+    pub complex: ComplexSelector<Impl>,
     /// Ancestor hashes for the bloom filter. We precompute these and store
     /// them inline to optimize cache performance during selector matching.
     /// This matters a lot.
     pub ancestor_hashes: [u32; NUM_ANCESTOR_HASHES],
 }
 
 impl<Impl: SelectorImpl> SelectorInner<Impl> {
-    pub fn new(c: Arc<ComplexSelector<Impl>>) -> Self {
+    pub fn new(c: ComplexSelector<Impl>) -> Self {
         let mut hashes = [0; NUM_ANCESTOR_HASHES];
         {
             // Compute ancestor hashes for the bloom filter.
-            let mut hash_iter =
-                iter_ancestors(&c).flat_map(|x| x.compound_selector.iter())
-                                  .map(|x| x.ancestor_hash())
-                                  .filter(|x| x.is_some())
-                                  .map(|x| x.unwrap());
+            let mut hash_iter = c.iter_ancestors()
+                                 .map(|x| x.ancestor_hash())
+                                 .filter(|x| x.is_some())
+                                 .map(|x| x.unwrap());
             for i in 0..NUM_ANCESTOR_HASHES {
                 hashes[i] = match hash_iter.next() {
                     Some(x) => x,
                     None => break,
                 }
             }
         }
 
         SelectorInner {
             complex: c,
             ancestor_hashes: hashes,
         }
     }
+
+    /// Creates a SelectorInner from a Vec of Components. Used in tests.
+    pub fn from_vec(vec: Vec<Component<Impl>>) -> Self {
+        let complex = ComplexSelector::from_vec(vec);
+        Self::new(complex)
+    }
 }
 
 #[derive(PartialEq, Eq, Hash, Clone)]
 pub struct Selector<Impl: SelectorImpl> {
     pub inner: SelectorInner<Impl>,
     pub pseudo_element: Option<Impl::PseudoElement>,
     pub specificity: u32,
 }
@@ -189,56 +197,53 @@ impl<Impl: SelectorImpl> SelectorMethods
 }
 
 impl<Impl: SelectorImpl> SelectorMethods for ComplexSelector<Impl> {
     type Impl = Impl;
 
     fn visit<V>(&self, visitor: &mut V) -> bool
         where V: SelectorVisitor<Impl = Impl>,
     {
-        let mut current = self;
+        let mut current = self.iter();
         let mut combinator = None;
         loop {
-            if !visitor.visit_complex_selector(current, combinator) {
+            if !visitor.visit_complex_selector(current.clone(), combinator) {
                 return false;
             }
 
-            for selector in &current.compound_selector {
+            for selector in &mut current {
                 if !selector.visit(visitor) {
                     return false;
                 }
             }
 
-            match current.next {
-                Some((ref next, next_combinator)) => {
-                    current = next;
-                    combinator = Some(next_combinator);
-                }
-                None => break,
+            combinator = current.next_sequence();
+            if combinator.is_none() {
+                break;
             }
         }
 
         true
     }
 }
 
-impl<Impl: SelectorImpl> SelectorMethods for SimpleSelector<Impl> {
+impl<Impl: SelectorImpl> SelectorMethods for Component<Impl> {
     type Impl = Impl;
 
     fn visit<V>(&self, visitor: &mut V) -> bool
         where V: SelectorVisitor<Impl = Impl>,
     {
-        use self::SimpleSelector::*;
+        use self::Component::*;
         if !visitor.visit_simple_selector(self) {
             return false;
         }
 
         match *self {
             Negation(ref negated) => {
-                for selector in negated {
+                for selector in negated.iter() {
                     if !selector.visit(visitor) {
                         return false;
                     }
                 }
             }
             AttrExists(ref selector) |
             AttrEqual(ref selector, _, _) |
             AttrIncludes(ref selector, _) |
@@ -257,61 +262,179 @@ impl<Impl: SelectorImpl> SelectorMethods
             },
             _ => {}
         }
 
         true
     }
 }
 
+/// A ComplexSelectors stores a sequence of simple selectors and combinators. The
+/// iterator classes allow callers to iterate at either the raw sequence level or
+/// at the level of sequences of simple selectors separated by combinators. Most
+/// callers want the higher-level iterator.
+///
+/// We store selectors internally left-to-right (in parsing order), but the
+/// canonical iteration order is right-to-left (selector matching order). The
+/// iterators abstract over these details.
 #[derive(Clone, Eq, Hash, PartialEq)]
-pub struct ComplexSelector<Impl: SelectorImpl> {
-    pub compound_selector: Vec<SimpleSelector<Impl>>,
-    pub next: Option<(Arc<ComplexSelector<Impl>>, Combinator)>,  // c.next is left of c
-}
+pub struct ComplexSelector<Impl: SelectorImpl>(ArcSlice<Component<Impl>>);
+
+impl<Impl: SelectorImpl> ComplexSelector<Impl> {
+    /// Returns an iterator over the next sequence of simple selectors. When
+    /// a combinator is reached, the iterator will return None, and
+    /// next_sequence() may be called to continue to the next sequence.
+    pub fn iter(&self) -> SelectorIter<Impl> {
+        SelectorIter {
+            iter: self.iter_raw(),
+            next_combinator: None,
+        }
+    }
+
+    /// Returns an iterator over the entire sequence of simple selectors and combinators,