Merge f-t to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 09 Jul 2016 08:53:47 -0700
changeset 304396 679118259e91f40d4a8f968f03ec4cff066cdb5b
parent 304379 d55b1b1d51cf148f1b509740be963e86da7e697e (current diff)
parent 304395 fe0ea08656ec28e9d33e381e0653ba65713a7db4 (diff)
child 304397 489a2db1b1657f31916d33cac2862544a1c80de0
child 304445 f48f9ddf2961e8e776d58566a85907c1419e5582
child 304487 972c11c59a0c3485ac6a208db543d1aa8bcd51c8
push id79304
push userphilringnalda@gmail.com
push dateSat, 09 Jul 2016 16:01:45 +0000
treeherdermozilla-inbound@489a2db1b165 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
679118259e91 / 50.0a1 / 20160710030217 / files
nightly linux64
679118259e91 / 50.0a1 / 20160710030217 / files
nightly mac
679118259e91 / 50.0a1 / 20160710030201 / files
nightly win32
679118259e91 / 50.0a1 / 20160710030217 / files
nightly win64
679118259e91 / 50.0a1 / 20160710030217 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge f-t to m-c, a=merge
accessible/tests/mochitest/dumbfile.xpi
browser/base/content/browser-fullScreen.js
browser/themes/shared/glyphs.svg
dom/base/nsDocument.cpp
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
modules/libpref/init/all.js
--- a/accessible/tests/mochitest/a11y.ini
+++ b/accessible/tests/mochitest/a11y.ini
@@ -1,12 +1,12 @@
 [DEFAULT]
 support-files =
   ../../../dom/media/test/bug461281.ogg
-  dumbfile.xpi
+  dumbfile.zip
   formimage.png
   letters.gif
   moz.png
   longdesc_src.html
   *.js
   treeview.css
 
 [test_aria_token_attrs.html]
rename from accessible/tests/mochitest/dumbfile.xpi
rename to accessible/tests/mochitest/dumbfile.zip
--- a/accessible/tests/mochitest/states/a11y.ini
+++ b/accessible/tests/mochitest/states/a11y.ini
@@ -1,17 +1,17 @@
 [DEFAULT]
 support-files =
   z_frames.html
   z_frames_article.html
   z_frames_checkbox.html
   z_frames_textbox.html
   z_frames_update.html
   !/accessible/tests/mochitest/*.js
-  !/accessible/tests/mochitest/dumbfile.xpi
+  !/accessible/tests/mochitest/dumbfile.zip
   !/accessible/tests/mochitest/formimage.png
   !/accessible/tests/mochitest/treeview.css
 
 [test_aria.html]
 [test_aria.xul]
 [test_aria_imgmap.html]
 [test_aria_widgetitems.html]
 [test_buttons.html]
--- a/accessible/tests/mochitest/states/test_doc_busy.html
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -69,11 +69,11 @@
      title="Missing busy state change event when downloading files"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=446469">Bug 446469</a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <a id="link" href="http://example.com/a11y/accessible/tests/mochitest/dumbfile.xpi">a file</a>
+  <a id="link" href="http://example.com/a11y/accessible/tests/mochitest/dumbfile.zip">a file</a>
 </body>
 </html>
rename from browser/base/content/browser-fullScreen.js
rename to browser/base/content/browser-fullScreenAndPointerLock.js
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -1,13 +1,255 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * 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/. */
 
+var PointerlockFsWarning = {
+
+  _element: null,
+  _origin: null,
+
+  init: function() {
+    this.Timeout.prototype = {
+      start: function() {
+        this.cancel();
+        this._id = setTimeout(() => this._handle(), this._delay);
+      },
+      cancel: function() {
+        if (this._id) {
+          clearTimeout(this._id);
+          this._id = 0;
+        }
+      },
+      _handle: function() {
+        this._id = 0;
+        this._func();
+      },
+      get delay() {
+        return this._delay;
+      }
+    };
+  },
+
+  /**
+   * Timeout object for managing timeout request. If it is started when
+   * the previous call hasn't finished, it would automatically cancelled
+   * the previous one.
+   */
+  Timeout: function(func, delay) {
+    this._id = 0;
+    this._func = func;
+    this._delay = delay;
+  },
+
+  showPointerLock: function(aOrigin) {
+    if (!document.fullscreen) {
+      let timeout = gPrefService.getIntPref("pointer-lock-api.warning.timeout");
+      this.show(aOrigin, "pointerlock-warning", timeout, 0);
+    }
+  },
+
+  showFullScreen: function(aOrigin) {
+    let timeout = gPrefService.getIntPref("full-screen-api.warning.timeout");
+    let delay = gPrefService.getIntPref("full-screen-api.warning.delay");
+    this.show(aOrigin, "fullscreen-warning", timeout, delay);
+  },
+
+  // Shows a warning that the site has entered fullscreen or
+  // pointer lock for a short duration.
+  show: function(aOrigin, elementId, timeout, delay) {
+
+    if (!this._element) {
+      this._element = document.getElementById(elementId);
+      // Setup event listeners
+      this._element.addEventListener("transitionend", this);
+      window.addEventListener("mousemove", this, true);
+      // The timeout to hide the warning box after a while.
+      this._timeoutHide = new this.Timeout(() => {
+        this._state = "hidden";
+      }, timeout);
+      // The timeout to show the warning box when the pointer is at the top
+      this._timeoutShow = new this.Timeout(() => {
+        this._state = "ontop";
+        this._timeoutHide.start();
+      }, delay);
+    }
+
+    // Set the strings on the warning UI.
+    if (aOrigin) {
+      this._origin = aOrigin;
+    }
+    let uri = BrowserUtils.makeURI(this._origin);
+    let host = null;
+    try {
+      host = uri.host;
+    } catch (e) { }
+    let textElem = this._element.querySelector(".pointerlockfswarning-domain-text");
+    if (!host) {
+      textElem.setAttribute("hidden", true);
+    } else {
+      textElem.removeAttribute("hidden");
+      let hostElem = this._element.querySelector(".pointerlockfswarning-domain");
+      // Document's principal's URI has a host. Display a warning including it.
+      let utils = {};
+      Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
+      hostElem.textContent = utils.DownloadUtils.getURIHost(uri.spec)[0];
+    }
+
+    this._element.dataset.identity =
+      gIdentityHandler.pointerlockFsWarningClassName;
+
+    // User should be allowed to explicitly disable
+    // the prompt if they really want.
+    if (this._timeoutHide.delay <= 0) {
+      return;
+    }
+
+    // Explicitly set the last state to hidden to avoid the warning
+    // box being hidden immediately because of mousemove.
+    this._state = "onscreen";
+    this._lastState = "hidden";
+    this._timeoutHide.start();
+  },
+
+  close: function() {
+    if (!this._element) {
+      return;
+    }
+    // Cancel any pending timeout
+    this._timeoutHide.cancel();
+    this._timeoutShow.cancel();
+    // Reset state of the warning box
+    this._state = "hidden";
+    this._element.setAttribute("hidden", true);
+    // Remove all event listeners
+    this._element.removeEventListener("transitionend", this);
+    window.removeEventListener("mousemove", this, true);
+    // Clear fields
+    this._element = null;
+    this._timeoutHide = null;
+    this._timeoutShow = null;
+
+    // Ensure focus switches away from the (now hidden) warning box.
+    // If the user clicked buttons in the warning box, it would have
+    // been focused, and any key events would be directed at the (now
+    // hidden) chrome document instead of the target document.
+    gBrowser.selectedBrowser.focus();
+  },
+
+  // State could be one of "onscreen", "ontop", "hiding", and
+  // "hidden". Setting the state to "onscreen" and "ontop" takes
+  // effect immediately, while setting it to "hidden" actually
+  // turns the state to "hiding" before the transition finishes.
+  _lastState: null,
+  _STATES: ["hidden", "ontop", "onscreen"],
+  get _state() {
+    for (let state of this._STATES) {
+      if (this._element.hasAttribute(state)) {
+        return state;
+      }
+    }
+    return "hiding";
+  },
+  set _state(newState) {
+    let currentState = this._state;
+    if (currentState == newState) {
+      return;
+    }
+    if (currentState != "hiding") {
+      this._lastState = currentState;
+      this._element.removeAttribute(currentState);
+    }
+    if (newState != "hidden") {
+      if (currentState != "hidden") {
+        this._element.setAttribute(newState, true);
+      } else {
+        // When the previous state is hidden, the display was none,
+        // thus no box was constructed. We need to wait for the new
+        // display value taking effect first, otherwise, there won't
+        // be any transition. Since requestAnimationFrame callback is
+        // generally triggered before any style flush and layout, we
+        // should wait for the second animation frame.
+        requestAnimationFrame(() => {
+          requestAnimationFrame(() => {
+            if (this._element) {
+              this._element.setAttribute(newState, true);
+            }
+          });
+        });
+      }
+    }
+  },
+
+  handleEvent: function(event) {
+    switch (event.type) {
+    case "mousemove": {
+      let state = this._state;
+      if (state == "hidden") {
+        // If the warning box is currently hidden, show it after
+        // a short delay if the pointer is at the top.
+        if (event.clientY != 0) {
+          this._timeoutShow.cancel();
+        } else if (this._timeoutShow.delay >= 0) {
+          this._timeoutShow.start();
+        }
+      } else {
+        let elemRect = this._element.getBoundingClientRect();
+        if (state == "hiding" && this._lastState != "hidden") {
+          // If we are on the hiding transition, and the pointer
+          // moved near the box, restore to the previous state.
+          if (event.clientY <= elemRect.bottom + 50) {
+            this._state = this._lastState;
+            this._timeoutHide.start();
+          }
+        } else if (state == "ontop" || this._lastState != "hidden") {
+          // State being "ontop" or the previous state not being
+          // "hidden" indicates this current warning box is shown
+          // in response to user's action. Hide it immediately when
+          // the pointer leaves that area.
+          if (event.clientY > elemRect.bottom + 50) {
+            this._state = "hidden";
+            this._timeoutHide.cancel();
+          }
+        }
+      }
+      break;
+    }
+    case "transitionend": {
+      if (this._state == "hiding") {
+        this._element.setAttribute("hidden", true);
+      }
+      break;
+    }
+    }
+  }
+};
+
+var PointerLock = {
+
+  init: function() {
+    window.messageManager.addMessageListener("PointerLock:Entered", this);
+    window.messageManager.addMessageListener("PointerLock:Exited", this);
+  },
+
+  receiveMessage: function(aMessage) {
+    switch (aMessage.name) {
+      case "PointerLock:Entered": {
+        PointerlockFsWarning.showPointerLock(aMessage.data.originNoSuffix);
+        break;
+      }
+      case "PointerLock:Exited": {
+        PointerlockFsWarning.close();
+        break;
+      }
+    }
+  }
+};
+
 var FullScreen = {
   _MESSAGES: [
     "DOMFullscreen:Request",
     "DOMFullscreen:NewOrigin",
     "DOMFullscreen:Exit",
     "DOMFullscreen:Painted",
   ],
 
@@ -19,18 +261,16 @@ var FullScreen = {
                             /* wantsUntrusted */ false);
     window.addEventListener("MozDOMFullscreen:Exited", this,
                             /* useCapture */ true,
                             /* wantsUntrusted */ false);
     for (let type of this._MESSAGES) {
       window.messageManager.addMessageListener(type, this);
     }
 
-    this._WarningBox.init();
-
     if (window.fullScreen)
       this.toggle();
   },
 
   uninit: function() {
     for (let type of this._MESSAGES) {
       window.messageManager.removeMessageListener(type, this);
     }
@@ -103,21 +343,16 @@ var FullScreen = {
   },
 
   exitDomFullScreen : function() {
     document.exitFullscreen();
   },
 
   handleEvent: function (event) {
     switch (event.type) {
-      case "activate":
-        if (document.fullscreenElement) {
-          this._WarningBox.show();
-        }
-        break;
       case "fullscreen":
         this.toggle();
         break;
       case "MozDOMFullscreen:Entered": {
         // The event target is the element which requested the DOM
         // fullscreen. If we were entering DOM fullscreen for a remote
         // browser, the target would be `gBrowser` and the original
         // target would be the browser which was the parameter of
@@ -145,36 +380,41 @@ var FullScreen = {
   receiveMessage: function(aMessage) {
     let browser = aMessage.target;
     switch (aMessage.name) {
       case "DOMFullscreen:Request": {
         this._windowUtils.remoteFrameFullscreenChanged(browser);
         break;
       }
       case "DOMFullscreen:NewOrigin": {
-        this._WarningBox.show(aMessage.data.originNoSuffix);
+        PointerlockFsWarning.showFullScreen(aMessage.data.originNoSuffix);
         break;
       }
       case "DOMFullscreen:Exit": {
         this._windowUtils.remoteFrameFullscreenReverted();
         break;
       }
       case "DOMFullscreen:Painted": {
         Services.obs.notifyObservers(window, "fullscreen-painted", "");
         TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
         break;
       }
     }
   },
 
   enterDomFullscreen : function(aBrowser) {
+
     if (!document.fullscreenElement) {
       return;
     }
 
+    // If we have a current pointerlock warning shown then hide it
+    // before transition.
+    PointerlockFsWarning.close();
+
     // If we've received a fullscreen notification, we have to ensure that the
     // element that's requesting fullscreen belongs to the browser that's currently
     // active. If not, we exit fullscreen since the "full-screen document" isn't
     // actually visible now.
     if (!aBrowser || gBrowser.selectedBrowser != aBrowser ||
         // The top-level window has lost focus since the request to enter
         // full-screen was made. Cancel full-screen.
         Services.focus.activeWindow != window) {
@@ -217,17 +457,17 @@ var FullScreen = {
       document.removeEventListener("popuphidden", this._setPopupOpen, false);
     }
   },
 
   cleanupDomFullscreen: function () {
     window.messageManager
           .broadcastAsyncMessage("DOMFullscreen:CleanUp");
 
-    this._WarningBox.close();
+    PointerlockFsWarning.close();
     gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
     window.removeEventListener("activate", this);
 
     document.documentElement.removeAttribute("inDOMFullscreen");
   },
 
@@ -315,223 +555,16 @@ var FullScreen = {
   },
   setAutohide: function()
   {
     gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
     // Try again to hide toolbar when we change the pref.
     FullScreen.hideNavToolbox(true);
   },
 
-  _WarningBox: {
-    _element: null,
-    _origin: null,
-
-    /**
-     * Timeout object for managing timeout request. If it is started when
-     * the previous call hasn't finished, it would automatically cancelled
-     * the previous one.
-     */
-    Timeout: function(func, delay) {
-      this._id = 0;
-      this._func = func;
-      this._delay = delay;
-    },
-
-    init: function() {
-      this.Timeout.prototype = {
-        start: function() {
-          this.cancel();
-          this._id = setTimeout(() => this._handle(), this._delay);
-        },
-        cancel: function() {
-          if (this._id) {
-            clearTimeout(this._id);
-            this._id = 0;
-          }
-        },
-        _handle: function() {
-          this._id = 0;
-          this._func();
-        },
-        get delay() {
-          return this._delay;
-        }
-      };
-    },
-
-    // Shows a warning that the site has entered fullscreen for a short duration.
-    show: function(aOrigin) {
-      if (!document.fullscreenElement) {
-        return;
-      }
-
-      if (!this._element) {
-        this._element = document.getElementById("fullscreen-warning");
-        // Setup event listeners
-        this._element.addEventListener("transitionend", this);
-        window.addEventListener("mousemove", this, true);
-        // The timeout to hide the warning box after a while.
-        this._timeoutHide = new this.Timeout(() => {
-          this._state = "hidden";
-        }, gPrefService.getIntPref("full-screen-api.warning.timeout"));
-        // The timeout to show the warning box when the pointer is at the top
-        this._timeoutShow = new this.Timeout(() => {
-          this._state = "ontop";
-          this._timeoutHide.start();
-        }, gPrefService.getIntPref("full-screen-api.warning.delay"));
-      }
-
-      // Set the strings on the fullscreen warning UI.
-      if (aOrigin) {
-        this._origin = aOrigin;
-      }
-      let uri = BrowserUtils.makeURI(this._origin);
-      let host = null;
-      try {
-        host = uri.host;
-      } catch (e) { }
-      let textElem = document.getElementById("fullscreen-domain-text");
-      if (!host) {
-        textElem.setAttribute("hidden", true);
-      } else {
-        textElem.removeAttribute("hidden");
-        let hostElem = document.getElementById("fullscreen-domain");
-        // Document's principal's URI has a host. Display a warning including it.
-        let utils = {};
-        Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
-        hostElem.textContent = utils.DownloadUtils.getURIHost(uri.spec)[0];
-      }
-      this._element.className = gIdentityHandler.fullscreenWarningClassName;
-
-      // User should be allowed to explicitly disable
-      // the prompt if they really want.
-      if (this._timeoutHide.delay <= 0) {
-        return;
-      }
-
-      // Explicitly set the last state to hidden to avoid the warning
-      // box being hidden immediately because of mousemove.
-      this._state = "onscreen";
-      this._lastState = "hidden";
-      this._timeoutHide.start();
-    },
-
-    close: function() {
-      if (!this._element) {
-        return;
-      }
-      // Cancel any pending timeout
-      this._timeoutHide.cancel();
-      this._timeoutShow.cancel();
-      // Reset state of the warning box
-      this._state = "hidden";
-      this._element.setAttribute("hidden", true);
-      // Remove all event listeners
-      this._element.removeEventListener("transitionend", this);
-      window.removeEventListener("mousemove", this, true);
-      // Clear fields
-      this._element = null;
-      this._timeoutHide = null;
-      this._timeoutShow = null;
-
-      // Ensure focus switches away from the (now hidden) warning box.
-      // If the user clicked buttons in the warning box, it would have
-      // been focused, and any key events would be directed at the (now
-      // hidden) chrome document instead of the target document.
-      gBrowser.selectedBrowser.focus();
-    },
-
-    // State could be one of "onscreen", "ontop", "hiding", and
-    // "hidden". Setting the state to "onscreen" and "ontop" takes
-    // effect immediately, while setting it to "hidden" actually
-    // turns the state to "hiding" before the transition finishes.
-    _lastState: null,
-    _STATES: ["hidden", "ontop", "onscreen"],
-    get _state() {
-      for (let state of this._STATES) {
-        if (this._element.hasAttribute(state)) {
-          return state;
-        }
-      }
-      return "hiding";
-    },
-    set _state(newState) {
-      let currentState = this._state;
-      if (currentState == newState) {
-        return;
-      }
-      if (currentState != "hiding") {
-        this._lastState = currentState;
-        this._element.removeAttribute(currentState);
-      }
-      if (newState != "hidden") {
-        if (currentState != "hidden") {
-          this._element.setAttribute(newState, true);
-        } else {
-          // When the previous state is hidden, the display was none,
-          // thus no box was constructed. We need to wait for the new
-          // display value taking effect first, otherwise, there won't
-          // be any transition. Since requestAnimationFrame callback is
-          // generally triggered before any style flush and layout, we
-          // should wait for the second animation frame.
-          requestAnimationFrame(() => {
-            requestAnimationFrame(() => {
-              if (this._element) {
-                this._element.setAttribute(newState, true);
-              }
-            });
-          });
-        }
-      }
-    },
-
-    handleEvent: function(event) {
-      switch (event.type) {
-        case "mousemove": {
-          let state = this._state;
-          if (state == "hidden") {
-            // If the warning box is currently hidden, show it after
-            // a short delay if the pointer is at the top.
-            if (event.clientY != 0) {
-              this._timeoutShow.cancel();
-            } else if (this._timeoutShow.delay >= 0) {
-              this._timeoutShow.start();
-            }
-          } else {
-            let elemRect = this._element.getBoundingClientRect();
-            if (state == "hiding" && this._lastState != "hidden") {
-              // If we are on the hiding transition, and the pointer
-              // moved near the box, restore to the previous state.
-              if (event.clientY <= elemRect.bottom + 50) {
-                this._state = this._lastState;
-                this._timeoutHide.start();
-              }
-            } else if (state == "ontop" || this._lastState != "hidden") {
-              // State being "ontop" or the previous state not being
-              // "hidden" indicates this current warning box is shown
-              // in response to user's action. Hide it immediately when
-              // the pointer leaves that area.
-              if (event.clientY > elemRect.bottom + 50) {
-                this._state = "hidden";
-                this._timeoutHide.cancel();
-              }
-            }
-          }
-          break;
-        }
-        case "transitionend": {
-          if (this._state == "hiding") {
-            this._element.setAttribute("hidden", true);
-          }
-          break;
-        }
-      }
-    }
-  },
-
   showNavToolbox: function(trackMouse = true) {
     this._fullScrToggler.hidden = true;
     gNavToolbox.removeAttribute("fullscreenShouldAnimate");
     gNavToolbox.style.marginTop = "";
 
     if (!this._isChromeCollapsed) {
       return;
     }
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -655,52 +655,52 @@ window[chromehidden~="toolbar"] toolbar:
 
 /*  Full Screen UI */
 
 #fullscr-toggler {
   height: 1px;
   background: black;
 }
 
-html|*#fullscreen-warning {
+html|*.pointerlockfswarning {
   position: fixed;
   z-index: 2147483647 !important;
   visibility: visible;
   transition: transform 300ms ease-in;
   /* To center the warning box horizontally,
      we use left: 50% with translateX(-50%). */
   top: 0; left: 50%;
   transform: translate(-50%, -100%);
   box-sizing: border-box;
   width: -moz-max-content;
   max-width: 95%;
   pointer-events: none;
 }
-html|*#fullscreen-warning:not([hidden]) {
+html|*.pointerlockfswarning:not([hidden]) {
   display: flex;
   will-change: transform;
 }
-html|*#fullscreen-warning[onscreen] {
+html|*.pointerlockfswarning[onscreen] {
   transform: translate(-50%, 50px);
 }
-html|*#fullscreen-warning[ontop] {
+html|*.pointerlockfswarning[ontop] {
   /* Use -10px to hide the border and border-radius on the top */
   transform: translate(-50%, -10px);
 }
-#main-window[OSXLionFullscreen] html|*#fullscreen-warning[ontop] {
+#main-window[OSXLionFullscreen] html|*.pointerlockfswarning[ontop] {
   transform: translate(-50%, 80px);
 }
 
-html|*#fullscreen-domain-text,
-html|*#fullscreen-generic-text {
+html|*.pointerlockfswarning-domain-text,
+html|*.pointerlockfswarning-generic-text {
   word-wrap: break-word;
   /* We must specify a min-width, otherwise word-wrap:break-word doesn't work. Bug 630864. */
   min-width: 1px
 }
-html|*#fullscreen-domain-text:not([hidden]) + html|*#fullscreen-generic-text {
+html|*.pointerlockfswarning-domain-text:not([hidden]) + html|*.pointerlockfswarning-generic-text {
   display: none;
 }
 
 html|*#fullscreen-exit-button {
   pointer-events: auto;
 }
 
 /* ::::: Ctrl-Tab Panel ::::: */
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1292,17 +1292,19 @@ var gBrowserInit = {
       placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
     }
 
     LightWeightThemeWebInstaller.init();
 
     if (Win7Features)
       Win7Features.onOpenWindow();
 
+    PointerlockFsWarning.init();
     FullScreen.init();
+    PointerLock.init();
 
     // initialize the sync UI
     gSyncUI.init();
     gFxAccounts.init();
 
     if (AppConstants.MOZ_DATA_REPORTING)
       gDataNotificationInfoBar.init();
 
@@ -6815,17 +6817,17 @@ var gIdentityHandler = {
     }
   },
 
   /**
    * Return the CSS class name to set on the "fullscreen-warning" element to
    * display information about connection security in the notification shown
    * when a site enters the fullscreen mode.
    */
-  get fullscreenWarningClassName() {
+  get pointerlockFsWarningClassName() {
     // Note that the fullscreen warning does not handle _isSecureInternalUI.
     if (this._uriHasHost && this._isEV) {
       return "verifiedIdentity";
     }
     if (this._uriHasHost && this._isSecure) {
       return "verifiedDomain";
     }
     return "unknownIdentity";
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1137,35 +1137,46 @@
                  flex="1"
                  style="min-width: 14em; width: 18em; max-width: 36em;"/>
       </vbox>
       <vbox id="browser-border-end" hidden="true" layer="true"/>
     </hbox>
 #include ../../components/customizableui/content/customizeMode.inc.xul
   </deck>
 
-  <html:div id="fullscreen-warning" hidden="true">
-    <html:div id="fullscreen-domain-text">
+  <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
+    <html:div class="pointerlockfswarning-domain-text">
       &fullscreenWarning.beforeDomain.label;
-      <html:span id="fullscreen-domain"/>
+      <html:span class="pointerlockfswarning-domain"/>
       &fullscreenWarning.afterDomain.label;
     </html:div>
-    <html:div id="fullscreen-generic-text">
+    <html:div class="pointerlockfswarning-generic-text">
       &fullscreenWarning.generic.label;
     </html:div>
     <html:button id="fullscreen-exit-button"
                  onclick="FullScreen.exitDomFullScreen();">
 #ifdef XP_MACOSX
             &exitDOMFullscreenMac.button;
 #else
             &exitDOMFullscreen.button;
 #endif
     </html:button>
   </html:div>
 
+  <html:div id="pointerlock-warning" class="pointerlockfswarning" hidden="true">
+    <html:div class="pointerlockfswarning-domain-text">
+      &pointerlockWarning.beforeDomain.label;
+      <html:span class="pointerlockfswarning-domain"/>
+      &pointerlockWarning.afterDomain.label;
+    </html:div>
+    <html:div class="pointerlockfswarning-generic-text">
+      &pointerlockWarning.generic.label;
+    </html:div>
+  </html:div>
+
   <vbox id="browser-bottombox" layer="true">
     <notificationbox id="global-notificationbox" notificationside="bottom"/>
   </vbox>
 
   <svg:svg height="0">
 #include tab-shape.inc.svg
     <svg:clipPath id="urlbar-back-button-clip-path">
 #ifndef XP_MACOSX
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -11,17 +11,17 @@
 <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
 <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
 
 <script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-devedition.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-feeds.js"/>
-<script type="application/javascript" src="chrome://browser/content/browser-fullScreen.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-fullScreenAndPointerLock.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullZoom.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-media.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
 #ifdef MOZ_SAFE_BROWSING
 <script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -36,16 +36,27 @@ XPCOMUtils.defineLazyGetter(this, "Simpl
   });
   return ssdp;
 });
 
 // TabChildGlobal
 var global = this;
 
 
+addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
+  sendAsyncMessage("PointerLock:Entered", {
+    originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
+  });
+});
+
+addEventListener("MozDOMPointerLock:Exited", function(aEvent) {
+  sendAsyncMessage("PointerLock:Exited");
+});
+
+
 addMessageListener("Browser:HideSessionRestoreButton", function (message) {
   // Hide session restore button on about:home
   let doc = content.document;
   let container;
   if (doc.documentURI.toLowerCase() == "about:home" &&
       (container = doc.getElementById("sessionRestoreContainer"))) {
     container.hidden = true;
   }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2725,16 +2725,23 @@
             if (remoteBrowser) {
               remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, aOtherBrowser);
             }
 
             // Swap permanentKey properties.
             let ourPermanentKey = ourBrowser.permanentKey;
             ourBrowser.permanentKey = aOtherBrowser.permanentKey;
             aOtherBrowser.permanentKey = ourPermanentKey;
+            aOurTab.permanentKey = ourBrowser.permanentKey;
+            if (remoteBrowser) {
+              let otherTab = remoteBrowser.getTabForBrowser(aOtherBrowser);
+              if (otherTab) {
+                otherTab.permanentKey = aOtherBrowser.permanentKey;
+              }
+            }
 
             // Restore the progress listener
             tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false);
             this._tabListeners.set(aOurTab, tabListener);
 
             const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
             filter.addProgressListener(tabListener, notifyAll);
             ourBrowser.webProgress.addProgressListener(filter, notifyAll);
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -74,17 +74,17 @@ browser.jar:
         content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
         content/browser/browser-addons.js             (content/browser-addons.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
         content/browser/browser-devedition.js         (content/browser-devedition.js)
         content/browser/browser-feeds.js              (content/browser-feeds.js)
-        content/browser/browser-fullScreen.js         (content/browser-fullScreen.js)
+        content/browser/browser-fullScreenAndPointerLock.js  (content/browser-fullScreenAndPointerLock.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
         content/browser/browser-fxaccounts.js         (content/browser-fxaccounts.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
         content/browser/browser-media.js              (content/browser-media.js)
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-refreshblocker.js     (content/browser-refreshblocker.js)
 #ifdef MOZ_SAFE_BROWSING
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2735,84 +2735,27 @@ ContentPermissionPrompt.prototype = {
       },
     };
 
     this._showPrompt(aRequest, message, "desktop-notification", actions,
                      "web-notifications",
                      "web-notifications-notification-icon", options);
   },
 
-  _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) {
-    let message = gBrowserBundle.GetStringFromName(autoAllow ?
-                                  "pointerLock.autoLock.title3" : "pointerLock.title3");
-
-    // If this is an autoAllow info prompt, offer no actions.
-    // _showPrompt() will allow the request when it's dismissed.
-    let actions = [];
-    if (!autoAllow) {
-      actions = [
-        {
-          stringId: "pointerLock.allow2",
-          action: null,
-          expireType: null,
-          callback: function() {},
-        },
-        {
-          stringId: "pointerLock.alwaysAllow",
-          action: Ci.nsIPermissionManager.ALLOW_ACTION,
-          expireType: null,
-          callback: function() {},
-        },
-        {
-          stringId: "pointerLock.neverAllow",
-          action: Ci.nsIPermissionManager.DENY_ACTION,
-          expireType: null,
-          callback: function() {},
-        },
-      ];
-    }
-
-    function onFullScreen() {
-      notification.remove();
-    }
-
-    let options = {};
-    options.removeOnDismissal = autoAllow;
-    options.eventCallback = type => {
-      if (type == "removed") {
-        notification.browser.removeEventListener("fullscreenchange", onFullScreen, true);
-        if (autoAllow) {
-          aRequest.allow();
-        }
-      }
-    }
-
-    let notification =
-      this._showPrompt(aRequest, message, "pointerLock", actions, "pointerLock",
-                       "pointerLock-notification-icon", options);
-
-    // pointerLock is automatically allowed in fullscreen mode (and revoked
-    // upon exit), so if the page enters fullscreen mode after requesting
-    // pointerLock (but before the user has granted permission), we should
-    // remove the now-impotent notification.
-    notification.browser.addEventListener("fullscreenchange", onFullScreen, true);
-  },
-
   prompt: function CPP_prompt(request) {
     // Only allow exactly one permission request here.
     let types = request.types.QueryInterface(Ci.nsIArray);
     if (types.length != 1) {
       request.cancel();
       return;
     }
     let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
 
     const kFeatureKeys = { "geolocation" : "geo",
-                           "desktop-notification" : "desktop-notification",
-                           "pointerLock" : "pointerLock",
+                           "desktop-notification" : "desktop-notification"
                          };
 
     // Make sure that we support the request.
     if (!(perm.type in kFeatureKeys)) {
       return;
     }
 
     var requestingPrincipal = request.principal;
@@ -2828,21 +2771,16 @@ ContentPermissionPrompt.prototype = {
 
     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
       request.cancel();
       return;
     }
 
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       autoAllow = true;
-      // For pointerLock, we still want to show a warning prompt.
-      if (perm.type != "pointerLock") {
-        request.allow();
-        return;
-      }
     }
 
     var browser = this._getBrowserForRequest(request);
     var chromeWin = browser.ownerDocument.defaultView;
     if (!chromeWin.PopupNotifications)
       // Ignore requests from browsers hosted in windows that don't support
       // PopupNotifications.
       return;
@@ -2850,19 +2788,16 @@ ContentPermissionPrompt.prototype = {
     // Show the prompt.
     switch (perm.type) {
     case "geolocation":
       this._promptGeo(request);
       break;
     case "desktop-notification":
       this._promptWebNotifications(request);
       break;
-    case "pointerLock":
-      this._promptPointerLock(request, autoAllow);
-      break;
     }
   },
 
 };
 
 var DefaultBrowserCheck = {
   get OPTIONPOPUP() { return "defaultBrowserNotificationPopup" },
   _setAsDefaultTimer: null,
--- a/browser/components/sessionstore/test/browser_456342_sample.xhtml
+++ b/browser/components/sessionstore/test/browser_456342_sample.xhtml
@@ -17,12 +17,20 @@
 <input type="HIDDEN" name="hideme2"/>
 <input type="submit" name="submit"/>
 <input type="reset"  name="reset"/>
 <input type="image"  name="image"/>
 <input type="button" name="button"/>
 <input type="password" name="password"/>
 <input type="PassWord" name="password2"/>
 <input type="PASSWORD" name="password3"/>
+<input autocomplete="off" name="auto1"/>
+<input type="text" autocomplete="OFF" name="auto2"/>
+<textarea autocomplete="off" name="auto3"/>
+<select autocomplete="off" name="auto4">
+  <option value="1" selected="true"/>
+  <option value="2"/>
+  <option value="3"/>
+</select>
 </form>
 
 </body>
 </html>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -123,16 +123,21 @@ These should match what Safari and other
 <!-- LOCALIZATION NOTE (fullscreenWarning.beforeDomain.label,
      fullscreenWarning.afterDomain.label): these two strings are used
      respectively before and after the domain requiring fullscreen.
      Localizers can use one of them, or both, to better adapt this
      sentence to their language. -->
 <!ENTITY fullscreenWarning.beforeDomain.label "">
 <!ENTITY fullscreenWarning.afterDomain.label "is now full screen">
 <!ENTITY fullscreenWarning.generic.label "This document is now full screen">
+
+<!ENTITY pointerlockWarning.beforeDomain.label "">
+<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
+<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control">
+
 <!-- LOCALIZATION NOTE (exitDOMFullscreen.button,
      exitDOMFullscreenMac.button): the "escape" button on PC keyboards
      is uppercase, while on Mac keyboards it is lowercase -->
 <!ENTITY exitDOMFullscreen.button "Exit Full Screen (Esc)">
 <!ENTITY exitDOMFullscreenMac.button "Exit Full Screen (esc)">
 <!ENTITY leaveDOMFullScreen.label "Exit Full Screen">
 <!ENTITY leaveDOMFullScreen.accesskey "u">
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -383,27 +383,16 @@ webNotifications.alwaysReceive.accesskey
 webNotifications.neverShow=Always Block Notifications
 webNotifications.neverShow.accesskey=N
 webNotifications.receiveFromSite=Would you like to receive notifications from this site?
 # LOCALIZATION NOTE (webNotifications.upgradeTitle): When using native notifications on OS X, the title may be truncated around 32 characters.
 webNotifications.upgradeTitle=Upgraded notifications
 # LOCALIZATION NOTE (webNotifications.upgradeBody): When using native notifications on OS X, the body may be truncated around 100 characters in some views.
 webNotifications.upgradeBody=You can now receive notifications from sites that are not currently loaded. Click to learn more.
 
-# Pointer lock UI
-
-pointerLock.allow2=Hide pointer
-pointerLock.allow2.accesskey=H
-pointerLock.alwaysAllow=Always allow hiding
-pointerLock.alwaysAllow.accesskey=A
-pointerLock.neverAllow=Never allow hiding
-pointerLock.neverAllow.accesskey=N
-pointerLock.title3=Would you like to allow the pointer to be hidden on this site?
-pointerLock.autoLock.title3=This site will hide the pointer.
-
 # Phishing/Malware Notification Bar.
 # LOCALIZATION NOTE (notADeceptiveSite, notAnAttack)
 # The two button strings will never be shown at the same time, so
 # it's okay for them to have the same access key
 safebrowsing.getMeOutOfHereButton.label=Get me out of here!
 safebrowsing.getMeOutOfHereButton.accessKey=G
 safebrowsing.deceptiveSite=Deceptive Site!
 safebrowsing.notADeceptiveSiteButton.label=This isn’t a deceptive site…
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -11,9 +11,8 @@ permission.cookie.label = Set Cookies
 permission.desktop-notification2.label = Receive Notifications
 permission.image.label = Load Images
 permission.camera.label = Use the Camera
 permission.microphone.label = Use the Microphone
 permission.install.label = Install Add-ons
 permission.popup.label = Open Pop-up Windows
 permission.geo.label = Access Your Location
 permission.indexedDB.label = Maintain Offline Storage
-permission.pointerLock.label = Hide the Mouse Pointer
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -240,16 +240,12 @@ var gPermissionObject = {
                SitePermissions.BLOCK : SitePermissions.ALLOW;
     }
   },
 
   "geo": {
     exactHostMatch: true
   },
 
-  "indexedDB": {},
-
-  "pointerLock": {
-    exactHostMatch: true
-  }
+  "indexedDB": {}
 };
 
 const kPermissionIDs = Object.keys(gPermissionObject);
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -4,17 +4,17 @@
 "use strict";
 
 Components.utils.import("resource:///modules/SitePermissions.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 add_task(function* testPermissionsListing() {
   Assert.deepEqual(SitePermissions.listPermissions().sort(),
     ["camera","cookie","desktop-notification","geo","image",
-     "indexedDB","install","microphone","pointerLock","popup"],
+     "indexedDB","install","microphone","popup"],
     "Correct list of all permissions");
 });
 
 add_task(function* testHasGrantedPermissions() {
   // check that it returns false on an invalid URI
   // like a file URI, which doesn't support site permissions
   let wrongURI = Services.io.newURI("file:///example.js", null, null)
   Assert.equal(SitePermissions.hasGrantedPermissions(wrongURI), false);
@@ -33,31 +33,28 @@ add_task(function* testHasGrantedPermiss
   // check that SESSION states return true
   SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
   Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
 
   // removing the SESSION state should revert to false
   SitePermissions.remove(uri, "microphone");
   Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
 
-  SitePermissions.set(uri, "pointerLock", SitePermissions.BLOCK);
-
   // check that a combination of ALLOW and BLOCK states returns true
   SitePermissions.set(uri, "geo", SitePermissions.ALLOW);
   Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
 
   // check that a combination of SESSION and BLOCK states returns true
   SitePermissions.set(uri, "geo", SitePermissions.SESSION);
   Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
 
   // check that only BLOCK states will not return true
   SitePermissions.remove(uri, "geo");
   Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
 
-  SitePermissions.remove(uri, "pointerLock");
 });
 
 add_task(function* testGetPermissionsByURI() {
   // check that it returns an empty array on an invalid URI
   // like a file URI, which doesn't support site permissions
   let wrongURI = Services.io.newURI("file:///example.js", null, null)
   Assert.deepEqual(SitePermissions.getPermissionsByURI(wrongURI), []);
 
--- a/browser/themes/shared/fullscreen/warning.inc.css
+++ b/browser/themes/shared/fullscreen/warning.inc.css
@@ -1,51 +1,51 @@
 %if 0
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
-html|*#fullscreen-warning {
+html|*.pointerlockfswarning {
   align-items: center;
   background: rgba(45, 62, 72, 0.9);
   border: 2px solid #fafafa;
   box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.5);
   border-radius: 8px;
   padding: 24px 16px;
   font: message-box;
 }
 
-html|*#fullscreen-warning::before {
+html|*.pointerlockfswarning::before {
   margin: 0;
   width: 24px; height: 24px;
 }
 
-html|*#fullscreen-warning.verifiedIdentity::before,
-html|*#fullscreen-warning.verifiedDomain::before {
+html|*.pointerlockfswarning[data-identity="verifiedIdentity"]::before,
+html|*.pointerlockfswarning[data-identity="verifiedDomain"]::before {
   content: url("chrome://browser/skin/fullscreen/secure.svg");
 }
 
-html|*#fullscreen-warning.unknownIdentity::before {
+html|*.pointerlockfswarning[data-identity="unknownIdentity"]::before {
   content: url("chrome://browser/skin/fullscreen/insecure.svg");
 }
 
-html|*#fullscreen-domain-text,
-html|*#fullscreen-generic-text {
+html|*.pointerlockfswarning-domain-text,
+html|*.pointerlockfswarning-generic-text {
   font-size: 21px;
   font-weight: lighter;
   color: #fafafa;
   margin: 0 16px;
 }
 
-html|*#fullscreen-domain {
+html|*.pointerlockfswarning-domain {
   font-weight: bold;
   margin: 0;
 }
 
-html|*#fullscreen-exit-button {
+html|*.pointerlockfswarning-exit-button {
   padding: 5px 30px;
   font: message-box;
   font-size: 14px;
   font-weight: lighter;
   margin: 0;
   box-sizing: content-box;
-}
+}
\ No newline at end of file
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -52,26 +52,26 @@
   skin/classic/browser/customizableui/whimsy.png               (../shared/customizableui/whimsy.png)
   skin/classic/browser/customizableui/whimsy@2x.png            (../shared/customizableui/whimsy@2x.png)
   skin/classic/browser/downloads/contentAreaDownloadsView.css  (../shared/downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-blocked.svg          (../shared/downloads/download-blocked.svg)
   skin/classic/browser/drm-icon.svg                            (../shared/drm-icon.svg)
   skin/classic/browser/filters.svg                             (../shared/filters.svg)
   skin/classic/browser/fullscreen/insecure.svg                 (../shared/fullscreen/insecure.svg)
   skin/classic/browser/fullscreen/secure.svg                   (../shared/fullscreen/secure.svg)
-  skin/classic/browser/glyphs.svg                              (../shared/glyphs.svg)
   skin/classic/browser/heartbeat-icon.svg                      (../shared/heartbeat-icon.svg)
   skin/classic/browser/heartbeat-star-lit.svg                  (../shared/heartbeat-star-lit.svg)
   skin/classic/browser/heartbeat-star-off.svg                  (../shared/heartbeat-star-off.svg)
   skin/classic/browser/identity-icon.svg                       (../shared/identity-block/identity-icon.svg)
   skin/classic/browser/identity-not-secure.svg                 (../shared/identity-block/identity-not-secure.svg)
   skin/classic/browser/identity-secure.svg                     (../shared/identity-block/identity-secure.svg)
   skin/classic/browser/identity-mixed-passive-loaded.svg       (../shared/identity-block/identity-mixed-passive-loaded.svg)
   skin/classic/browser/identity-mixed-active-loaded.svg        (../shared/identity-block/identity-mixed-active-loaded.svg)
   skin/classic/browser/info.svg                                (../shared/info.svg)
+  skin/classic/browser/permissions.svg                         (../shared/permissions.svg)
   skin/classic/browser/tracking-protection-16.svg              (../shared/identity-block/tracking-protection-16.svg)
   skin/classic/browser/tracking-protection-disabled-16.svg     (../shared/identity-block/tracking-protection-disabled-16.svg)
   skin/classic/browser/newtab/close.png                        (../shared/newtab/close.png)
   skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
   skin/classic/browser/newtab/whimsycorn.png                   (../shared/newtab/whimsycorn.png)
   skin/classic/browser/preferences/in-content/favicon.ico      (../shared/incontentprefs/favicon.ico)
   skin/classic/browser/preferences/in-content/icons.svg        (../shared/incontentprefs/icons.svg)
   skin/classic/browser/preferences/in-content/search.css       (../shared/incontentprefs/search.css)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -76,124 +76,124 @@
 .webRTC-sharingDevices-notification-icon,
 .webRTC-sharingMicrophone-notification-icon,
 .in-use {
   fill: #fea01b;
 }
 
 .popup-notification-icon[popupid="web-notifications"],
 .desktop-notification-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#desktop-notification);
+  list-style-image: url(chrome://browser/skin/permissions.svg#desktop-notification);
 }
 
 .desktop-notification-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#desktop-notification-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#desktop-notification-blocked);
 }
 
 .geo-icon {
 %ifdef XP_MACOSX
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-osx);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx);
 %elif defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-linux);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux);
 %else
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-windows);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows);
 %endif
 }
 
 .geo-icon.blocked {
 %ifdef XP_MACOSX
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-osx-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx-blocked);
 %elif defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-linux-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux-blocked);
 %else
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-windows-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows-blocked);
 %endif
 }
 
 .popup-notification-icon[popupid="geolocation"] {
 %ifdef XP_MACOSX
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-osx);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx);
 %elif defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-linux-detailed);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux-detailed);
 %else
-  list-style-image: url(chrome://browser/skin/glyphs.svg#geo-windows-detailed);
+  list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows-detailed);
 %endif
 }
 
 .popup-notification-icon[popupid="indexedDB-permissions-prompt"],
 .indexedDB-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#indexedDB);
+  list-style-image: url(chrome://browser/skin/permissions.svg#indexedDB);
 }
 
 .indexedDB-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#indexedDB-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#indexedDB-blocked);
 }
 
 .login-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#login);
+  list-style-image: url(chrome://browser/skin/permissions.svg#login);
 }
 
 .popup-notification-icon[popupid="password"] {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#login-detailed);
+  list-style-image: url(chrome://browser/skin/permissions.svg#login-detailed);
 }
 
 #login-fill-notification-icon {
   /* Temporary solution until the capture and fill doorhangers are unified. */
   transform: scaleX(-1);
 }
 
 /* The first selector is used by socialchat.xml (bug 1275558). */
 .webRTC-sharingDevices-notification-icon,
 .camera-icon,
 .popup-notification-icon[popupid="webRTC-shareDevices"],
 .popup-notification-icon[popupid="webRTC-sharingDevices"] {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#camera);
+  list-style-image: url(chrome://browser/skin/permissions.svg#camera);
 }
 
 .camera-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#camera-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#camera-blocked);
 }
 
 /* The first selector is used by socialchat.xml (bug 1275558). */
 .webRTC-sharingMicrophone-notification-icon,
 .microphone-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#microphone);
+  list-style-image: url(chrome://browser/skin/permissions.svg#microphone);
 }
 
 .microphone-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#microphone-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#microphone-blocked);
 }
 
 .popup-notification-icon[popupid="webRTC-shareMicrophone"],
 .popup-notification-icon[popupid="webRTC-sharingMicrophone"] {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#microphone-detailed);
+  list-style-image: url(chrome://browser/skin/permissions.svg#microphone-detailed);
 }
 
 .popup-notification-icon[popupid="webRTC-shareScreen"],
 .popup-notification-icon[popupid="webRTC-sharingScreen"],
 .screen-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#screen);
+  list-style-image: url(chrome://browser/skin/permissions.svg#screen);
 }
 
 .screen-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#screen-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#screen-blocked);
 }
 
 .popup-notification-icon[popupid="pointerLock"],
 .pointerLock-icon {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#pointerLock);
+  list-style-image: url(chrome://browser/skin/permissions.svg#pointerLock);
 }
 
 .pointerLock-icon.blocked {
-  list-style-image: url(chrome://browser/skin/glyphs.svg#pointerLock-blocked);
+  list-style-image: url(chrome://browser/skin/permissions.svg#pointerLock-blocked);
 }
 
 /* This icon has a block sign in it, so we don't need a blocked version. */
 .popup-icon {
-  list-style-image: url("chrome://browser/skin/glyphs.svg#popup");
+  list-style-image: url("chrome://browser/skin/permissions.svg#popup");
 }
 
 /* EME */
 
 .popup-notification-icon[popupid="drmContentPlaying"],
 .drm-icon {
   list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
 }
rename from browser/themes/shared/glyphs.svg
rename to browser/themes/shared/permissions.svg
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -109,25 +109,25 @@
 
 .tab-sharing-icon-overlay {
   /* 16px of the icon + 6px of margin-inline-end of .tab-icon-image */
   margin-inline-start: -22px;
   position: relative;
 }
 
 .tab-sharing-icon-overlay[sharing="camera"] {
-  list-style-image: url("chrome://browser/skin/glyphs.svg#camera");
+  list-style-image: url("chrome://browser/skin/permissions.svg#camera");
 }
 
 .tab-sharing-icon-overlay[sharing="microphone"] {
-  list-style-image: url("chrome://browser/skin/glyphs.svg#microphone");
+  list-style-image: url("chrome://browser/skin/permissions.svg#microphone");
 }
 
 .tab-sharing-icon-overlay[sharing="screen"] {
-  list-style-image: url("chrome://browser/skin/glyphs.svg#screen");
+  list-style-image: url("chrome://browser/skin/permissions.svg#screen");
 }
 
 .tab-sharing-icon-overlay[sharing] {
   filter: url("chrome://browser/skin/filters.svg#fill");
   fill: rgb(224, 41, 29);
 }
 
 .tab-icon-overlay {
--- a/devtools/client/dom/content/components/search-box.css
+++ b/devtools/client/dom/content/components/search-box.css
@@ -1,46 +1,10 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 /******************************************************************************/
 /* Search Box */
-
-.searchBox {
-  height: 18px;
-  font-size: 12px;
-  margin-top: 0;
-  border: 1px solid rgb(170, 188, 207);
-  width: 200px;
+.dom-searchbox {
   float: right;
-  background-image: url("./search.svg");
-  background-repeat: no-repeat;
-  background-position: 2px center;
-  padding-left: 20px;
-  margin-right: 5px;
-}
-
-/******************************************************************************/
-/* Light Theme & Dark Theme*/
-
-.theme-dark .searchBox,
-.theme-light .searchBox {
-  border: 1px solid rgb(170, 170, 170);
-  background-image: url("chrome://devtools/skin/images/magnifying-glass-light.png");
-  background-position: 8px center;
-  border-radius: 2px;
-  padding-left: 25px;
-  margin-top: 1px;
-  height: 16px;
-  font-style: italic;
-}
-
-/******************************************************************************/
-/* Dark Theme */
-
-.theme-dark .searchBox {
-  background-color: rgba(24, 29, 32, 1);
-  color: rgba(184, 200, 217, 1);
-  border-color: var(--theme-splitter-color);
-  background-image: url("chrome://devtools/skin/images/magnifying-glass.png");
-}
+}
\ No newline at end of file
--- a/devtools/client/dom/content/components/search-box.js
+++ b/devtools/client/dom/content/components/search-box.js
@@ -48,17 +48,17 @@ var SearchBox = React.createClass({
       this.searchTimeout = null;
       this.props.onSearch(searchBox.value);
     }, searchDelay);
   },
 
   render: function () {
     return (
       input({
-        className: "searchBox",
+        className: "dom-searchbox devtools-filterinput",
         placeholder: l10n.getStr("dom.filterDOMPanel"),
         onChange: this.onSearch
       })
     );
   }
 });
 
 // Exports from this module
--- a/devtools/client/framework/menu.js
+++ b/devtools/client/framework/menu.js
@@ -99,16 +99,25 @@ Menu.prototype._createMenuItems = functi
 
     if (item.submenu) {
       let menupopup = doc.createElement("menupopup");
       item.submenu._createMenuItems(menupopup);
 
       let menu = doc.createElement("menu");
       menu.appendChild(menupopup);
       menu.setAttribute("label", item.label);
+      if (item.disabled) {
+        menu.setAttribute("disabled", "true");
+      }
+      if (item.accesskey) {
+        menu.setAttribute("accesskey", item.accesskey);
+      }
+      if (item.id) {
+        menu.id = item.id;
+      }
       parent.appendChild(menu);
     } else if (item.type === "separator") {
       let menusep = doc.createElement("menuseparator");
       parent.appendChild(menusep);
     } else {
       let menuitem = doc.createElement("menuitem");
       menuitem.setAttribute("label", item.label);
       menuitem.addEventListener("command", () => {
--- a/devtools/client/framework/test/browser_menu_api.js
+++ b/devtools/client/framework/test/browser_menu_api.js
@@ -125,25 +125,37 @@ function* testSubmenu(toolbox) {
       info("Click callback has fired for submenu item");
       clickFired = true;
     },
   }));
   menu.append(new MenuItem({
     label: "Submenu parent",
     submenu: submenu,
   }));
+  menu.append(new MenuItem({
+    label: "Submenu parent with attributes",
+    id: "submenu-parent-with-attrs",
+    submenu: submenu,
+    accesskey: "A",
+    disabled: true,
+  }));
 
   menu.popup(0, 0, toolbox);
   ok(toolbox.doc.querySelector("#menu-popup"), "A popup is in the DOM");
   is(toolbox.doc.querySelectorAll("#menu-popup > menuitem").length, 0,
     "No menuitem children");
 
   let menus = toolbox.doc.querySelectorAll("#menu-popup > menu");
-  is(menus.length, 1, "Correct number of menus");
+  is(menus.length, 2, "Correct number of menus");
   is(menus[0].getAttribute("label"), "Submenu parent", "Correct label");
+  ok(!menus[0].hasAttribute("disabled"), "Correct disabled state");
+
+  is(menus[1].getAttribute("accesskey"), "A", "Correct accesskey");
+  ok(menus[1].hasAttribute("disabled"), "Correct disabled state");
+  ok(menus[1].id, "submenu-parent-with-attrs", "Correct id");
 
   let subMenuItems = menus[0].querySelectorAll("menupopup > menuitem");
   is(subMenuItems.length, 1, "Correct number of submenu items");
   is(subMenuItems[0].getAttribute("label"), "Submenu item", "Correct label");
 
   yield once(menu, "open");
   let closed = once(menu, "close");
 
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -64,38 +64,38 @@ InspectorSearch.prototype = {
         .catch(e => console.error(e));
   },
 
   doFullTextSearch: Task.async(function* (query, reverse) {
     let lastSearched = this._lastSearched;
     this._lastSearched = query;
 
     if (query.length === 0) {
-      this.searchBox.classList.remove("devtools-no-search-result");
+      this.searchBox.classList.remove("devtools-style-searchbox-no-match");
       if (!lastSearched || lastSearched.length > 0) {
         this.emit("search-cleared");
       }
       return;
     }
 
     let res = yield this.walker.search(query, { reverse });
 
     // Value has changed since we started this request, we're done.
     if (query != this.searchBox.value) {
       return;
     }
 
     if (res) {
       this.inspector.selection.setNodeFront(res.node, "inspectorsearch");
-      this.searchBox.classList.remove("devtools-no-search-result");
+      this.searchBox.classList.remove("devtools-style-searchbox-no-match");
 
       res.query = query;
       this.emit("search-result", res);
     } else {
-      this.searchBox.classList.add("devtools-no-search-result");
+      this.searchBox.classList.add("devtools-style-searchbox-no-match");
       this.emit("search-result");
     }
   }),
 
   _onCommand: function () {
     if (this.searchBox.value.length === 0) {
       this._onSearch();
     }
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -67,17 +67,17 @@
              hidden="true"/>
       </tabs>
       <tabpanels flex="1">
         <tabpanel id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
           <html:div id="ruleview-toolbar-container" class="devtools-toolbar">
             <html:div id="ruleview-toolbar">
               <html:div class="devtools-searchbox">
                 <html:input id="ruleview-searchbox"
-                            class="devtools-searchinput devtools-rule-searchbox"
+                            class="devtools-filterinput devtools-rule-searchbox"
                             type="search"
                             placeholder="&filterStylesPlaceholder;"/>
                 <html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
               </html:div>
               <html:div id="ruleview-command-toolbar">
                 <html:button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></html:button>
                 <html:button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></html:button>
               </html:div>
@@ -92,17 +92,17 @@
           <html:div id="ruleview-container" class="ruleview">
           </html:div>
         </tabpanel>
 
         <tabpanel id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
           <html:div class="devtools-toolbar">
             <html:div class="devtools-searchbox">
               <html:input id="computedview-searchbox"
-                          class="devtools-searchinput devtools-rule-searchbox"
+                          class="devtools-filterinput devtools-rule-searchbox"
                           type="search"
                           placeholder="&filterStylesPlaceholder;"/>
               <html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
             </html:div>
             <checkbox id="browser-style-checkbox"
                       class="includebrowserstyles"
                       checked="false"
                       label="&browserStylesLabel;"/>
--- a/devtools/client/inspector/test/browser_inspector_search-01.js
+++ b/devtools/client/inspector/test/browser_inspector_search-01.js
@@ -83,14 +83,14 @@ add_task(function* () {
     yield inspector.searchSuggestions._lastQuery;
 
     info(inspector.selection.nodeFront.id + " is selected with text " +
          searchBox.value);
     let nodeFront = yield getNodeFront("#" + id, inspector);
     is(inspector.selection.nodeFront, nodeFront,
        "Correct node is selected for state " + index);
 
-    is(!searchBox.classList.contains("devtools-no-search-result"), isValid,
+    is(!searchBox.classList.contains("devtools-style-searchbox-no-match"), isValid,
        "Correct searchbox result state for state " + index);
 
     index++;
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_search-06.js
+++ b/devtools/client/inspector/test/browser_inspector_search-06.js
@@ -70,17 +70,17 @@ function* synthesizeKeys(keys, inspector
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     yield eventHandled;
     info("Waiting for the search query to complete");
     yield inspector.searchSuggestions._lastQuery;
   }
 }
 
 function assertHasResult(inspector, expectResult) {
-  is(inspector.searchBox.classList.contains("devtools-no-search-result"),
+  is(inspector.searchBox.classList.contains("devtools-style-searchbox-no-match"),
      !expectResult,
      "There are" + (expectResult ? "" : " no") + " search results");
 }
 
 function* mutatePage(inspector, testActor, expression) {
   let onMutation = inspector.once("markupmutation");
   yield testActor.eval(expression);
   yield onMutation;
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -213,16 +213,18 @@ devtools.jar:
     skin/webaudioeditor.css (themes/webaudioeditor.css)
     skin/components-frame.css (themes/components-frame.css)
     skin/components-h-split-box.css (themes/components-h-split-box.css)
     skin/jit-optimizations.css (themes/jit-optimizations.css)
     skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
     skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
     skin/images/magnifying-glass-light.png (themes/images/magnifying-glass-light.png)
     skin/images/magnifying-glass-light@2x.png (themes/images/magnifying-glass-light@2x.png)
+    skin/images/filter.svg (themes/images/filter.svg)
+    skin/images/search.svg (themes/images/search.svg)
     skin/images/itemToggle.png (themes/images/itemToggle.png)
     skin/images/itemToggle@2x.png (themes/images/itemToggle@2x.png)
     skin/images/itemArrow-dark-rtl.svg (themes/images/itemArrow-dark-rtl.svg)
     skin/images/itemArrow-dark-ltr.svg (themes/images/itemArrow-dark-ltr.svg)
     skin/images/itemArrow-rtl.svg (themes/images/itemArrow-rtl.svg)
     skin/images/itemArrow-ltr.svg (themes/images/itemArrow-ltr.svg)
     skin/images/noise.png (themes/images/noise.png)
     skin/images/dropmarker.svg (themes/images/dropmarker.svg)
--- a/devtools/client/jsonview/css/toolbar.css
+++ b/devtools/client/jsonview/css/toolbar.css
@@ -3,19 +3,18 @@
  * 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/. */
 
 /******************************************************************************/
 /* Toolbar */
 
 .toolbar {
   line-height: 20px;
-  font-size: 12px;
   height: 22px;
-  font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+  font: message-box;
   padding: 4px 0 3px 0;
 }
 
 .toolbar .btn {
   margin-left: 5px;
   background-color: #E6E6E6;
   border: 1px solid rgb(204, 204, 204);
   text-decoration: none;
--- a/devtools/client/memory/components/toolbar.js
+++ b/devtools/client/memory/components/toolbar.js
@@ -104,17 +104,17 @@ module.exports = createClass({
           )
         ),
 
         dom.div({ id: "toolbar-spacer", className: "spacer" }),
 
         dom.input({
           id: "filter",
           type: "search",
-          className: "devtools-searchinput",
+          className: "devtools-filterinput",
           placeholder: L10N.getStr("filter.placeholder"),
           title: L10N.getStr("filter.tooltip"),
           onChange: event => setFilterString(event.target.value),
           value: filterString || undefined,
         })
       );
     } else if (view.state == viewState.TREE_MAP) {
       assert(treeMapDisplays.length >= 1,
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -155,17 +155,17 @@
           </button>
         </hbox>
         <spacer id="requests-menu-spacer"
                 flex="1"/>
         <toolbarbutton id="requests-menu-network-summary-button"
                        class="devtools-toolbarbutton icon-and-text"
                        tooltiptext="&netmonitorUI.footer.perf;"/>
         <textbox id="requests-menu-filter-freetext-text"
-                 class="devtools-searchinput"
+                 class="devtools-filterinput"
                  type="search"
                  required="true"
                  placeholder="&netmonitorUI.footer.filterFreetext.label;"/>
         <toolbarbutton id="details-pane-toggle"
                        class="devtools-toolbarbutton"
                        tooltiptext="&netmonitorUI.panesButton.tooltip;"
                        disabled="true"
                        tabindex="0"/>
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -453,17 +453,17 @@ VariablesView.prototype = {
     let container = this._searchboxContainer = document.createElement("hbox");
     container.className = "devtools-toolbar";
 
     // Hide the variables searchbox container if there are no variables or
     // properties to display.
     container.hidden = !this._store.length;
 
     let searchbox = this._searchboxNode = document.createElement("textbox");
-    searchbox.className = "variables-view-searchinput devtools-searchinput";
+    searchbox.className = "variables-view-searchinput devtools-filterinput";
     searchbox.setAttribute("placeholder", this._searchboxPlaceholder);
     searchbox.setAttribute("type", "search");
     searchbox.setAttribute("flex", "1");
     searchbox.addEventListener("command", this._onSearchboxInput, false);
     searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
 
     container.appendChild(searchbox);
     ownerNode.insertBefore(container, this._parent);
--- a/devtools/client/storage/storage.xul
+++ b/devtools/client/storage/storage.xul
@@ -37,17 +37,17 @@
   </popupset>
 
   <box flex="1" class="devtools-responsive-container theme-body">
     <vbox id="storage-tree"/>
     <splitter class="devtools-side-splitter"/>
     <vbox flex="1">
       <hbox id="storage-toolbar" class="devtools-toolbar">
         <textbox id="storage-searchbox"
-                 class="devtools-searchinput"
+                 class="devtools-filterinput"
                  type="search"
                  timeout="200"
                  placeholder="&searchBox.placeholder;"/>
       </hbox>
       <vbox id="storage-table" class="theme-sidebar" flex="1"/>
     </vbox>
     <splitter class="devtools-side-splitter"/>
     <vbox id="storage-sidebar" class="devtools-sidebar-tabs" hidden="true">
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -369,17 +369,18 @@ div.CodeMirror span.eval-text {
   border-bottom: 1px solid #434850;
 }
 
 .theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
   border-bottom: 0;
 }
 
 .devtools-textinput,
-.devtools-searchinput {
+.devtools-searchinput,
+.devtools-filterinput {
   background-color: rgba(24, 29, 32, 1);
   color: rgba(184, 200, 217, 1);
 }
 
 .CodeMirror-Tern-fname {
   color: #f7f7f7;
 }
 
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -30,17 +30,17 @@
 
 #font-showall {
   border-radius: 0;
   border: 1px solid black;
   margin: 3px;
   cursor: pointer;
   position: absolute;
   bottom: 0;
-  right: 0;
+  offset-inline-end: 0;
 }
 
 .dim > #font-container,
 .font:not(.has-code) .font-css-code,
 .font-is-local,
 .font-is-remote,
 .font.is-local .font-format-url,
 #font-template {
@@ -65,17 +65,16 @@
   width: 100%;
 }
 
 .font-preview-container {
   overflow-x: auto;
 }
 
 #font-preview-text-input {
-  font: inherit;
   margin-top: 1px;
   margin-bottom: 1px;
   padding-top: 0;
   padding-bottom: 0;
   flex: 1;
 }
 
 .font {
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/filter.svg
@@ -0,0 +1,7 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#aaa">
+  <path fill-opacity="0.3" d="M6.6 8.4c0-.6-1.7.3-1.7-.3C4.9 7.7 2.4 4 2.4 4h11.3s-2.5 3.4-2.5 4.1c0 .3-2.1-.1-2.1.3v5.9H7s-.4-3.9-.4-5.9z"/>
+  <path d="M2 2v2.3l2.7 4.5h1.6v5.5s1.1.6 1.8.6c.5 0 1.8-.6 1.8-.6V8.8h1.6L14 4.3V2H2zm10.8 2l-2.1 3.6H8.5v5.9c-.1 0-.1.1-.2.1-.2.1-.3.1-.3.1s-.1 0-.2-.1c-.1 0-.2-.1-.3-.1V7.6H5.4L3.2 4v-.8h9.5V4z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/search.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#aaa">
+  <path d="M10.716 10.032C11.516 9.077 12 7.845 12 6.5 12 3.462 9.538 1 6.5 1S1 3.462 1 6.5 3.462 12 6.5 12c1.345 0 2.577-.483 3.532-1.284l4.143 4.142c.19.19.495.19.683 0 .19-.188.19-.494 0-.683l-4.142-4.143zM6.5 11C8.985 11 11 8.985 11 6.5S8.985 2 6.5 2 2 4.015 2 6.5 4.015 11 6.5 11z" fill-rule="evenodd"/>
+</svg>
--- a/devtools/client/themes/styleeditor.css
+++ b/devtools/client/themes/styleeditor.css
@@ -141,17 +141,18 @@ li.linked-file-error .stylesheet-linked-
   font-size: 110%;
 }
 
 .stylesheet-more > h3 {
   font-size: 11px;
   margin-inline-end: 2px;
 }
 
-.devtools-searchinput {
+.devtools-searchinput,
+.devtools-filterinput {
   max-width: 25ex;
   font-size: 11px;
 }
 
 .placeholder a {
   text-decoration: underline;
 }
 
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -6,18 +6,18 @@
 /* CSS Variables specific to the devtools toolbar that aren't defined by the themes */
 .theme-light {
   --toolbar-tab-hover: rgba(170, 170, 170, .2);
   --toolbar-tab-hover-active: rgba(170, 170, 170, .4);
   --searchbox-background-color: #ffee99;
   --searchbox-border-color: #ffbf00;
   --searcbox-no-match-background-color: #ffe5e5;
   --searcbox-no-match-border-color: #e52e2e;
-  --magnifying-glass-image: url(images/magnifying-glass-light.png);
-  --magnifying-glass-image-2x: url(images/magnifying-glass-light@2x.png);
+  --magnifying-glass-image: url(images/search.svg);
+  --filter-image: url(images/filter.svg);
   --tool-options-image: url(images/tool-options.svg);
   --close-button-image: url(chrome://devtools/skin/images/close.svg);
   --icon-filter: invert(1);
   --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
   --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
   --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
   --toolbar-button-border-color: rgba(170, 170, 170, .5);
 
@@ -37,18 +37,18 @@
 
 .theme-dark {
   --toolbar-tab-hover: hsla(206, 37%, 4%, .2);
   --toolbar-tab-hover-active: hsla(206, 37%, 4%, .4);
   --searchbox-background-color: #4d4222;
   --searchbox-border-color: #d99f2b;
   --searcbox-no-match-background-color: #402325;
   --searcbox-no-match-border-color: #cc3d3d;
-  --magnifying-glass-image: url(images/magnifying-glass.png);
-  --magnifying-glass-image-2x: url(images/magnifying-glass@2x.png);
+  --magnifying-glass-image: url(images/search.svg);
+  --filter-image: url(images/filter.svg);
   --tool-options-image: url(images/tool-options.svg);
   --close-button-image: url(chrome://devtools/skin/images/close.svg);
   --icon-filter: none;
   --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
   --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
   --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
   --toolbar-button-border-color: rgba(0, 0, 0, .4);
 
@@ -371,111 +371,129 @@
 
 .devtools-separator + .devtools-toolbarbutton {
   margin-inline-start: 1px;
 }
 
 /* Text input */
 
 .devtools-textinput,
-.devtools-searchinput {
+.devtools-searchinput,
+.devtools-filterinput {
   -moz-appearance: none;
   margin: 1px 3px;
   border: 1px solid;
   border-radius: 2px;
   padding: 4px 6px;
   border-color: var(--theme-splitter-color);
+  font: message-box;
 }
 
 :root[platform="mac"] .devtools-textinput,
-:root[platform="mac"] .devtools-searchinput {
+:root[platform="mac"] .devtools-searchinput,
+:root[platform="mac"] .devtools-filterinput {
   border-radius: 20px;
 }
 
-.devtools-searchinput {
+.devtools-searchinput,
+.devtools-filterinput {
   padding: 0;
   padding-inline-start: 22px;
   padding-inline-end: 4px;
-  background-image: var(--magnifying-glass-image);
   background-position: 8px center;
   background-size: 11px 11px;
   background-repeat: no-repeat;
   font-size: inherit;
 }
 
+.devtools-searchinput {
+  background-image: var(--magnifying-glass-image);
+}
+
+.devtools-filterinput {
+  background-image: var(--filter-image);
+}
+
 .devtools-searchinput:-moz-locale-dir(rtl),
-.devtools-searchinput:dir(rtl) {
+.devtools-searchinput:dir(rtl),
+.devtools-filterinput:-moz-locale-dir(rtl),
+.devtools-filterinput:dir(rtl) {
   background-position: calc(100% - 8px) center;
 }
 
-.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
+.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon,
+.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
   visibility: hidden;
 }
 
+.devtools-searchinput .textbox-input::-moz-placeholder,
+.devtools-filterinput .textbox-input::-moz-placeholder {
+  font-style: normal;
+}
+
 /* Searchbox is a div container element for a search input element */
 .devtools-searchbox {
   display: flex;
   flex: 1;
   position: relative;
   padding: 0 3px;
 }
 
 /* The spacing is accomplished with a padding on the searchbox */
 .devtools-searchbox > .devtools-textinput,
-.devtools-searchbox > .devtools-searchinput {
+.devtools-searchbox > .devtools-searchinput,
+.devtools-searchbox > .devtools-filterinput {
   margin-left: 0;
   margin-right: 0;
 }
 
 .devtools-searchbox > .devtools-textinput:-moz-focusring,
-.devtools-searchbox > .devtools-searchinput:-moz-focusring {
+.devtools-searchbox > .devtools-searchinput:-moz-focusring,
+.devtools-searchbox > .devtools-filterinput:-moz-focusring {
   border-color: var(--theme-focus-border-color-textbox);
   box-shadow: var(--theme-focus-box-shadow-textbox);
   transition: all 0.2s ease-in-out;
   outline: none;
 }
 
 /* Don't add 'double spacing' for inputs that are at beginning / end
    of a toolbar (since the toolbar has it's own spacing). */
 .devtools-toolbar > .devtools-textinput:first-child,
-.devtools-toolbar > .devtools-searchinput:first-child {
+.devtools-toolbar > .devtools-searchinput:first-child,
+.devtools-toolbar > .devtools-filterinput:first-child {
   margin-inline-start: 0;
 }
 .devtools-toolbar > .devtools-textinput:last-child,
-.devtools-toolbar > .devtools-searchinput:last-child {
+.devtools-toolbar > .devtools-searchinput:last-child,
+.devtools-toolbar > .devtools-filterinput:last-child {
   margin-inline-end: 0;
 }
 .devtools-toolbar > .devtools-searchbox:first-child {
   padding-inline-start: 0;
 }
 .devtools-toolbar > .devtools-searchbox:last-child {
   padding-inline-end: 0;
 }
 
 .devtools-rule-searchbox {
   -moz-box-flex: 1;
   width: 100%;
-  font: inherit;
 }
 
 .devtools-rule-searchbox[filled] {
   background-color: var(--searchbox-background-color);
   border-color: var(--searchbox-border-color);
   padding-inline-end: 23px;
 }
 
 .devtools-style-searchbox-no-match {
   background-color: var(--searcbox-no-match-background-color) !important;
   border-color: var(--searcbox-no-match-border-color) !important;
 }
 
-.devtools-no-search-result {
-  border-color: var(--theme-highlight-red) !important;
-}
-
 .devtools-searchinput-clear {
   position: absolute;
   top: 3.5px;
   right: 7px;
   padding: 0;
   border: 0;
   width: 16px;
   height: 16px;
@@ -500,40 +518,38 @@
 .devtools-style-searchbox-no-match + .devtools-searchinput-clear {
   background-image: url("chrome://devtools/skin/images/search-clear-failed.svg") !important;
 }
 
 .devtools-searchinput-clear:hover {
   background-position: -16px 0;
 }
 
-.theme-dark .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
+.theme-dark .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
+.theme-dark .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
   list-style-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-.theme-light .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
+.theme-light .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
+.theme-light .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
   list-style-image: url("chrome://devtools/skin/images/search-clear-light.svg");
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
+.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
+.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
   margin-bottom: 0;
 }
 
-.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
+.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover,
+.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-@media (min-resolution: 1.1dppx) {
-  .devtools-searchinput {
-    background-image: var(--magnifying-glass-image-2x);
-  }
-}
-
 /* Close button */
 
 #toolbox-close::before {
   background-image: var(--close-button-image);
 }
 
 /* In-tools sidebar */
 .devtools-sidebar-tabs {
--- a/devtools/client/webconsole/webconsole.xul
+++ b/devtools/client/webconsole/webconsole.xul
@@ -174,17 +174,17 @@ function goUpdateConsoleCommands() {
               <menuitem label="&btnServerLog;" type="checkbox" autocheck="false"
                         prefKey="serverlog"/>
             </menupopup>
           </toolbarbutton>
         </hbox>
 
         <spacer flex="1"/>
 
-        <textbox class="compact hud-filter-box devtools-searchinput" type="search"
+        <textbox class="compact hud-filter-box devtools-filterinput" type="search"
                  placeholder="&filterOutput.placeholder;" tabindex="2"/>
       </toolbar>
 
       <hbox id="output-wrapper" flex="1" context="output-contextmenu" tooltip="aHTMLTooltip">
         <!-- Wrapper element to make scrolling in output-container much faster.
              See Bug 1237368 -->
         <div xmlns="http://www.w3.org/1999/xhtml">
           <div xmlns="http://www.w3.org/1999/xhtml" id="output-container"
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12365,20 +12365,17 @@ public:
     // Also, don't let the page to try to get the permission too many times.
     if (!mUserInputOrChromeCaller ||
         doc->mCancelledPointerLockRequests > kPointerLockRequestLimit) {
       Handled();
       DispatchPointerLockError(d);
       return NS_OK;
     }
 
-    // Handling a request from user input in non-fullscreen mode.
-    // Do a normal permission check.
-    nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow();
-    nsContentPermissionUtils::AskPermission(this, window);
+    Allow(JS::UndefinedHandleValue);
     return NS_OK;
   }
 
   void Handled()
   {
     mElement = nullptr;
     mDocument = nullptr;
     if (gPendingPointerLockRequest == this) {
@@ -12491,16 +12488,20 @@ nsPointerLockPermissionRequest::Allow(JS
   d->mCancelledPointerLockRequests = 0;
   e->SetPointerLock();
   EventStateManager::sPointerLockedElement = do_GetWeakReference(e);
   EventStateManager::sPointerLockedDoc = do_GetWeakReference(doc);
   NS_ASSERTION(EventStateManager::sPointerLockedElement &&
                EventStateManager::sPointerLockedDoc,
                "aElement and this should support weak references!");
 
+  nsContentUtils::DispatchEventOnlyToChrome(
+    doc, ToSupports(e), NS_LITERAL_STRING("MozDOMPointerLock:Entered"),
+    /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+
   DispatchPointerLockChange(d);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
 {
   NS_ENSURE_ARG_POINTER(aRequester);
@@ -12765,16 +12766,22 @@ nsDocument::UnlockPointer(nsIDocument* a
   if (pointerLockedElement) {
     pointerLockedElement->ClearPointerLock();
   }
 
   EventStateManager::sPointerLockedElement = nullptr;
   EventStateManager::sPointerLockedDoc = nullptr;
   static_cast<nsDocument*>(pointerLockedDoc.get())->mAllowRelocking = !!aDoc;
   gPendingPointerLockRequest = nullptr;
+
+  nsContentUtils::DispatchEventOnlyToChrome(
+    doc, ToSupports(pointerLockedElement),
+    NS_LITERAL_STRING("MozDOMPointerLock:Exited"),
+    /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+
   DispatchPointerLockChange(pointerLockedDoc);
 }
 
 void
 nsIDocument::UnlockPointer(nsIDocument* aDoc)
 {
   nsDocument::UnlockPointer(aDoc);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1667,26 +1667,56 @@ public abstract class GeckoApp
 
             // If we are doing an OOM restore, parse the session data and
             // stub the restored tabs immediately. This allows the UI to be
             // updated before Gecko has restored.
             if (mShouldRestore) {
                 final JSONArray tabs = new JSONArray();
                 final JSONObject windowObject = new JSONObject();
                 SessionParser parser = new SessionParser() {
+                    private boolean selectNextTab;
+
                     @Override
                     public void onTabRead(final SessionTab sessionTab) {
+                        if (sessionTab.isAboutHomeWithoutHistory()) {
+                            // This is a tab pointing to about:home with no history. We won't restore
+                            // this tab. If we end up restoring no tabs then the browser will decide
+                            // whether it needs to open about:home or a different 'homepage'. If we'd
+                            // always restore about:home only tabs then we'd never open the homepage.
+                            // See bug 1261008.
+
+                            if (sessionTab.isSelected()) {
+                                // Unfortunately this tab is the selected tab. Let's just try to select
+                                // the first tab. If we haven't restored any tabs so far then remember
+                                // to select the next tab that gets restored.
+
+                                if (!Tabs.getInstance().selectLastTab()) {
+                                    selectNextTab = true;
+                                }
+                            }
+
+                            // Do not restore this tab.
+                            return;
+                        }
+
                         JSONObject tabObject = sessionTab.getTabObject();
 
                         int flags = Tabs.LOADURL_NEW_TAB;
                         flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
                         flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
                         flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
 
                         final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
+
+                        if (selectNextTab) {
+                            // We did not restore the selected tab previously. Now let's select this tab.
+                            Tabs.getInstance().selectTab(tab.getId());
+                            selectNextTab = false;
+                        }
+
                         ThreadUtils.postToUiThread(new Runnable() {
                             @Override
                             public void run() {
                                 tab.updateTitle(sessionTab.getTitle());
                             }
                         });
 
                         try {
--- a/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
@@ -43,16 +43,24 @@ public abstract class SessionParser {
 
         public boolean isSelected() {
             return mIsSelected;
         }
 
         public JSONObject getTabObject() {
             return mTabObject;
         }
+
+        /**
+         * Is this tab pointing to about:home and does not contain any other history?
+         */
+        public boolean isAboutHomeWithoutHistory() {
+            JSONArray entries = mTabObject.optJSONArray("entries");
+            return entries != null && entries.length() == 1 && AboutPages.isAboutHome(mUrl);
+        }
     };
 
     abstract public void onTabRead(SessionTab tab);
 
     /**
      * Placeholder method that must be overloaded to handle closedTabs while parsing session data.
      *
      * @param closedTabs, JSONArray of recently closed tab entries.
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -255,16 +255,25 @@ public class Tabs implements GeckoEventL
             notifyListeners(oldTab, TabEvents.UNSELECTED);
         }
 
         // Pass a message to Gecko to update tab state in BrowserApp.
         GeckoAppShell.notifyObservers("Tab:Selected", String.valueOf(tab.getId()));
         return tab;
     }
 
+    public synchronized boolean selectLastTab() {
+        if (mOrder.isEmpty()) {
+            return false;
+        }
+
+        selectTab(mOrder.get(mOrder.size() - 1).getId());
+        return true;
+    }
+
     private int getIndexOf(Tab tab) {
         return mOrder.lastIndexOf(tab);
     }
 
     private Tab getNextTabFrom(Tab tab, boolean getPrivate) {
         int numTabs = mOrder.size();
         int index = getIndexOf(tab);
         for (int i = index + 1; i < numTabs; i++) {
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -47,28 +47,35 @@ fi
 ac_add_options --with-android-version=9
 ac_add_options --with-system-zlib
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-fennec-geoloc-api.key
+
+# MOZ_INSTALL_TRACKING does not guarantee MOZ_UPDATE_CHANNEL will be set so we
+# provide a default state. Currently, the default state provides a default
+# keyfile because an assertion will be thrown if MOZ_INSTALL_TRACKING is
+# specified but a keyfile is not. This assertion can catch if we misconfigure a
+# release or beta build and it does not have a valid keyfile.
+#
+# However, by providing a default keyfile, if we misconfigure beta or release,
+# the default keyfile may be used instead and the assertion won't catch the
+# error.  Therefore, it would be ideal to have MOZ_INSTALL_TRACKING guarantee
+# MOZ_UPDATE_CHANNEL was set so we can remove the default case. This may occur
+# when release promotion is implemented on Android.
+#
+# In all cases, we don't upload Adjust pings in automation.
 if test "$MOZ_UPDATE_CHANNEL" = "release" ; then
     ac_add_options --with-adjust-sdk-keyfile=/builds/adjust-sdk.token
 elif test "$MOZ_UPDATE_CHANNEL" = "beta" ; then
     ac_add_options --with-adjust-sdk-keyfile=/builds/adjust-sdk-beta.token
 else
-    # (bug 1277553) In Aurora -> Beta simulation builds, no update channel is
-    # specified, causing an assertion to throw that MOZ_INSTALL_TRACKING is
-    # specified but the keyfile is not. In this case, we add a default keyfile.
-    # This has the disadvantage that if our beta/release checks above ever
-    # fail, we'll come to this default case and the compile-time check to
-    # specify a valid keyfile will be broken. I don't have any better
-    # alternatives.
     ac_add_options --with-adjust-sdk-keyfile="$topsrcdir/mobile/android/base/adjust-sdk-sandbox.token"
 fi
 export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 # Use ccache
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
@@ -6,20 +6,46 @@ package org.mozilla.gecko.sync.setup.act
 
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Matcher;
-
-import android.util.Patterns;
+import java.util.regex.Pattern;
 
 public class WebURLFinder {
+  /**
+   * These regular expressions are taken from Android's Patterns.java.
+   * We brought them in to standardize URL matching across Android versions, instead of relying
+   * on Android version-dependent built-ins that can vary across Android versions.
+   * The original code can be found here:
+   * http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/util/Patterns.java
+   *
+   */
+  public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+  public static final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+  public static final String IRI = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
+  public static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
+  public static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
+  public static final Pattern IP_ADDRESS = Pattern.compile("((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+          + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+          + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+          + "|[1-9][0-9]|[0-9]))");
+  public static final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
+  public static final Pattern WEB_URL = Pattern.compile("((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+          + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+          + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+          + "(?:" + DOMAIN_NAME + ")"
+          + "(?:\\:\\d{1,5})?)"
+          + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~"
+          + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+          + "(?:\\b|$)");
+
   public final List<String> candidates;
 
   public WebURLFinder(String string) {
     if (string == null) {
       throw new IllegalArgumentException("string must not be null");
     }
 
     this.candidates = candidateWebURLs(string);
@@ -85,17 +111,17 @@ public class WebURLFinder {
 
       candidates.addAll(candidateWebURLs(string));
     }
 
     return candidates;
   }
 
   protected static List<String> candidateWebURLs(String string) {
-    Matcher matcher = Patterns.WEB_URL.matcher(string);
+    Matcher matcher = WEB_URL.matcher(string);
     List<String> matches = new LinkedList<String>();
 
     while (matcher.find()) {
       // Remove URLs with bad schemes.
       if (!isWebURL(matcher.group())) {
         continue;
       }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4669,16 +4669,20 @@ pref("full-screen-api.transition-duratio
 pref("full-screen-api.transition-duration.leave", "200 200");
 // timeout for black screen in fullscreen transition, unit: ms
 pref("full-screen-api.transition.timeout", 1000);
 // time for the warning box stays on the screen before sliding out, unit: ms
 pref("full-screen-api.warning.timeout", 3000);
 // delay for the warning box to show when pointer stays on the top, unit: ms
 pref("full-screen-api.warning.delay", 500);
 
+// DOM pointerlock API
+// time for the warning box stays on the screen before sliding out, unit: ms
+pref("pointer-lock-api.warning.timeout", 3000);
+
 // DOM idle observers API
 pref("dom.idle-observers-api.enabled", true);
 
 // Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
 // Used to detect long running handlers of user-generated events.
 pref("dom.event.handling-user-input-time-limit", 1000);
 
 // Whether we should layerize all animated images (if otherwise possible).