Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 07 Aug 2015 15:45:41 -0400
changeset 288462 e1925e00d2b85fe904d852059d02a3a3b349211a
parent 288443 be100e46b1ba9196ba6900a2c86a62c7285004ce (current diff)
parent 288461 3ccebe8a8e612e96054f56d4b10e55a054fc1b65 (diff)
child 288498 461fc0a6a130b5cee4c18481db4aa51b818c65f5
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c. a=merge
mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6673,22 +6673,20 @@ var gIdentityHandler = {
   IDENTITY_MODE_UNKNOWN                                : "unknownIdentity",  // No trusted identity information
   IDENTITY_MODE_USES_WEAK_CIPHER                       : "unknownIdentity weakCipher",  // SSL with RC4 cipher suite or SSL3
   IDENTITY_MODE_MIXED_DISPLAY_LOADED                   : "unknownIdentity mixedContent mixedDisplayContent",  // SSL with unauthenticated display content
   IDENTITY_MODE_MIXED_ACTIVE_LOADED                    : "unknownIdentity mixedContent mixedActiveContent",  // SSL with unauthenticated active (and perhaps also display) content
   IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED    : "unknownIdentity mixedContent mixedDisplayContentLoadedActiveBlocked",  // SSL with unauthenticated display content; unauthenticated active content is blocked.
   IDENTITY_MODE_MIXED_ACTIVE_BLOCKED                   : "verifiedDomain mixedContent mixedActiveBlocked",  // SSL with unauthenticated active content blocked; no unauthenticated display content
   IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED        : "verifiedIdentity mixedContent mixedActiveBlocked",  // SSL with unauthenticated active content blocked; no unauthenticated display content
   IDENTITY_MODE_CHROMEUI                               : "chromeUI",         // Part of the product's UI
-  IDENTITY_MODE_FILE_URI                               : "fileURI",  // File path
-
-  // Cache the most recent SSLStatus and Location seen in checkIdentity
-  _lastStatus : null,
-  _lastUri : null,
-  _mode : "unknownIdentity",
+
+  _isChromeUI: false,
+  _sslStatus: null,
+  _uri: null,
 
   // smart getters
   get _identityPopup () {
     delete this._identityPopup;
     return this._identityPopup = document.getElementById("identity-popup");
   },
   get _identityBox () {
     delete this._identityBox;
@@ -6709,30 +6707,20 @@ var gIdentityHandler = {
     return this._identityPopupContentSupp =
       document.getElementById("identity-popup-content-supplemental");
   },
   get _identityPopupContentVerif () {
     delete this._identityPopupContentVerif;
     return this._identityPopupContentVerif =
       document.getElementById("identity-popup-content-verifier");
   },
-  get _identityPopupSecurityContent () {
-    delete this._identityPopupSecurityContent;
-    return this._identityPopupSecurityContent =
-      document.getElementById("identity-popup-security-content");
-  },
-  get _identityPopupSecurityView () {
-    delete this._identityPopupSecurityView;
-    return this._identityPopupSecurityView =
-      document.getElementById("identity-popup-securityView");
-  },
-  get _identityPopupMainView () {
-    delete this._identityPopupMainView;
-    return this._identityPopupMainView =
-      document.getElementById("identity-popup-mainView");
+  get _identityPopupMixedContentLearnMore () {
+    delete this._identityPopupMixedContentLearnMore;
+    return this._identityPopupMixedContentLearnMore =
+      document.getElementById("identity-popup-mcb-learn-more");
   },
   get _identityIconLabel () {
     delete this._identityIconLabel;
     return this._identityIconLabel = document.getElementById("identity-icon-label");
   },
   get _overrideService () {
     delete this._overrideService;
     return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
@@ -6801,23 +6789,43 @@ var gIdentityHandler = {
     // If an element is focused that's not the anchor, clear the focus.
     // Elements of hidden views have -moz-user-focus:ignore but setting that
     // per CSS selector doesn't blur a focused element in those hidden views.
     if (Services.focus.focusedElement != anchor) {
       Services.focus.clearFocus(window);
     }
   },
 
+  disableMixedContentProtection() {
+    // Use telemetry to measure how often unblocking happens
+    const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
+    let histogram =
+      Services.telemetry.getHistogramById(
+        "MIXED_CONTENT_UNBLOCK_COUNTER");
+    histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
+    // Reload the page with the content unblocked
+    BrowserReloadWithFlags(
+      Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+    this._identityPopup.hidePopup();
+  },
+
+  enableMixedContentProtection() {
+    gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
+      "MixedContent:ReenableProtection", {});
+    BrowserReload();
+    this._identityPopup.hidePopup();
+  },
+
   /**
-   * Helper to parse out the important parts of _lastStatus (of the SSL cert in
+   * Helper to parse out the important parts of _sslStatus (of the SSL cert in
    * particular) for use in constructing identity UI strings
   */
   getIdentityData : function() {
     var result = {};
-    var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+    var status = this._sslStatus.QueryInterface(Ci.nsISSLStatus);
     var cert = status.serverCert;
 
     // Human readable name of Subject
     result.subjectOrg = cert.organization;
 
     // SubjectName fields, broken up for individual access
     if (cert.subjectName) {
       result.subjectNameFields = {};
@@ -6843,77 +6851,77 @@ var gIdentityHandler = {
    * Determine the identity of the page being displayed by examining its SSL cert
    * (if available) and, if necessary, update the UI to reflect this.  Intended to
    * be called by onSecurityChange
    *
    * @param PRUint32 state
    * @param nsIURI uri The address for which the UI should be updated.
    */
   checkIdentity : function(state, uri) {
-    var currentStatus = gBrowser.securityUI
-                                .QueryInterface(Components.interfaces.nsISSLStatusProvider)
-                                .SSLStatus;
-    this._lastStatus = currentStatus;
-    this._lastUri = uri;
-
     let nsIWebProgressListener = Ci.nsIWebProgressListener;
 
-    // For some URIs like data: we can't get a host and so can't do
-    // anything useful here.
+    // For some URIs like data: we can't get a host. URIs without a host will
+    // usually be treated as a non-secure connection if they're not on the
+    // whitelist below and don't resolve to file:// URIs internally.
     let unknown = false;
     try {
       uri.host;
     } catch (e) { unknown = true; }
 
     // Chrome URIs however get special treatment. Some chrome URIs are
     // whitelisted to provide a positive security signal to the user.
     let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|sessionrestore|support|welcomeback)/i;
     let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.spec);
+    let mode = this.IDENTITY_MODE_UNKNOWN;
+
     if (isChromeUI) {
-      this.setMode(this.IDENTITY_MODE_CHROMEUI);
+      mode = this.IDENTITY_MODE_CHROMEUI;
     } else if (unknown) {
-      this.setMode(this.IDENTITY_MODE_UNKNOWN);
+      // Use default mode.
     } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
       if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
-        this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED);
+        mode = this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED;
       } else {
-        this.setMode(this.IDENTITY_MODE_IDENTIFIED);
+        mode = this.IDENTITY_MODE_IDENTIFIED;
       }
     } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
       if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
-        this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED);
+        mode = this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED;
       } else {
-        this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
+        mode = this.IDENTITY_MODE_DOMAIN_VERIFIED;
       }
     } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
       if (state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
-        this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_LOADED);
+        mode = this.IDENTITY_MODE_MIXED_ACTIVE_LOADED;
       } else if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
-        this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED);
+        mode = this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED;
       } else if (state & nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) {
-        this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED);
+        mode = this.IDENTITY_MODE_MIXED_DISPLAY_LOADED;
       } else {
-        this.setMode(this.IDENTITY_MODE_USES_WEAK_CIPHER);
+        mode = this.IDENTITY_MODE_USES_WEAK_CIPHER;
       }
-    } else {
-      // Create a channel for the sole purpose of getting the resolved URI
-      // of the request to determine if it's loaded from the file system.
-      let resolvedURI = NetUtil.newChannel({uri,loadUsingSystemPrincipal:true}).URI;
-      if (resolvedURI.schemeIs("jar")) {
-        // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
-        // create a new URI using <jar-file-uri>!/<jar-entry>
-        resolvedURI = NetUtil.newURI(resolvedURI.path);
-      }
-
-      if (resolvedURI.schemeIs("file")) {
-        this.setMode(this.IDENTITY_MODE_FILE_URI);
-      } else {
-        this.setMode(this.IDENTITY_MODE_UNKNOWN);
-      }
-    }
+    }
+
+    // We need those values later when populating the control center.
+    this._uri = uri;
+    this._state = state;
+    this._isChromeUI = isChromeUI;
+    this._sslStatus =
+      gBrowser.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+
+    // Update the identity block.
+    if (this._identityBox) {
+      this._identityBox.className = mode;
+      this.refreshIdentityBlock(mode);
+    }
+
+    // NOTE: We do NOT update the identity popup (the control center) when
+    // we receive a new security state. If the user opened the popup and looks
+    // at the provided information we don't want to suddenly change the panel
+    // contents.
 
     // Show the doorhanger when:
     // - mixed active content is blocked
     // - mixed active content is loaded (detected but not blocked)
     // - tracking content is blocked
     // - tracking content is not blocked
     if (state &
         (nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT |
@@ -6956,77 +6964,53 @@ var gIdentityHandler = {
    * Return the eTLD+1 version of the current hostname
    */
   getEffectiveHost : function() {
     if (!this._IDNService)
       this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
                          .getService(Ci.nsIIDNService);
     try {
       let baseDomain =
-        Services.eTLD.getBaseDomainFromHost(this._lastUri.host);
+        Services.eTLD.getBaseDomainFromHost(this._uri.host);
       return this._IDNService.convertToDisplayIDN(baseDomain, {});
     } catch (e) {
       // If something goes wrong (e.g. host is an IP address) just fail back
       // to the full domain.
-      return this._lastUri.host;
-    }
-  },
-
-  /**
-   * Update the UI to reflect the specified mode, which should be one of the
-   * IDENTITY_MODE_* constants.
-   */
-  setMode : function(newMode) {
-    if (!this._identityBox) {
-      // No identity box means the identity box is not visible, in which
-      // case there's nothing to do.
-      return;
-    }
-
-    this._identityPopup.className = newMode;
-    this._identityBox.className = newMode;
-    this.setIdentityMessages(newMode);
-
-    // Update the popup too, if it's open
-    if (this._identityPopup.state == "open") {
-      this.setPopupMessages(newMode);
-      this.updateSitePermissions();
-    }
-
-    this._mode = newMode;
+      return this._uri.host;
+    }
   },
 
   /**
    * Set up the messages for the primary identity UI based on the specified mode,
    * and the details of the SSL cert, where applicable
    *
    * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
    */
-  setIdentityMessages : function(newMode) {
+  refreshIdentityBlock(newMode) {
     let icon_label = "";
     let tooltip = "";
     let icon_country_label = "";
     let icon_labels_dir = "ltr";
 
     switch (newMode) {
     case this.IDENTITY_MODE_DOMAIN_VERIFIED:
     case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED: {
       let iData = this.getIdentityData();
 
       // Verifier is either the CA Org, for a normal cert, or a special string
       // for certs that are trusted because of a security exception.
       tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
                                                     [iData.caOrg]);
 
       // This can't throw, because URI's with a host that throw don't end up in this case.
-      let host = this._lastUri.host;
+      let host = this._uri.host;
       let port = 443;
       try {
-        if (this._lastUri.port > 0)
-          port = this._lastUri.port;
+        if (this._uri.port > 0)
+          port = this._uri.port;
       } catch (e) {}
 
       if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {}))
         tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
 
       break; }
     case this.IDENTITY_MODE_IDENTIFIED:
     case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED: {
@@ -7065,92 +7049,154 @@ var gIdentityHandler = {
     // Hide completely if the organization label is empty
     this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
   },
 
   /**
    * Set up the title and content messages for the identity message popup,
    * based on the specified mode, and the details of the SSL cert, where
    * applicable
-   *
-   * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
    */
-  setPopupMessages : function(newMode) {
-
-    this._identityPopup.className = newMode;
-    this._identityPopupMainView.className = newMode;
-    this._identityPopupSecurityView.className = newMode;
-    this._identityPopupSecurityContent.className = newMode;
+  refreshIdentityPopup() {
+    // Update the "Learn More" hrefs for Mixed Content Blocking.
+    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+    let learnMoreHref = `${baseURL}mixed-content`;
+    this._identityPopupMixedContentLearnMore.setAttribute("href", learnMoreHref);
+
+    // Basic connection properties.
+    let isBroken = this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+    let isSecure = this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
+    let isEV = this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+
+    // Determine connection security information.
+    let connection = "not-secure";
+    if (this._isChromeUI) {
+      connection = "chrome";
+    } else if (this._isURILoadedFromFile(this._uri)) {
+      connection = "file";
+    } else if (isEV) {
+      connection = "secure-ev";
+    } else if (isSecure) {
+      connection = "secure";
+    }
+
+    // Mixed content flags.
+    let isMixedActiveContentLoaded =
+      this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
+    let isMixedActiveContentBlocked =
+      this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+    let isMixedPassiveContentLoaded =
+      this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
+
+    // Determine the mixed content state.
+    let mixedcontent = [];
+    if (isMixedPassiveContentLoaded) {
+      mixedcontent.push("passive-loaded");
+    }
+    if (isMixedActiveContentLoaded) {
+      mixedcontent.push("active-loaded");
+    } else if (isMixedActiveContentBlocked) {
+      mixedcontent.push("active-blocked");
+    }
+    mixedcontent = mixedcontent.join(" ");
+
+    // We have no specific flags for weak ciphers (yet). If a connection is
+    // broken and we can't detect any mixed active content loaded then it's
+    // a weak cipher.
+    let ciphers = "";
+    if (isBroken && !isMixedActiveContentLoaded) {
+      ciphers = "weak";
+    }
+
+    // Update all elements.
+    let elementIDs = [
+      "identity-popup",
+      "identity-popup-securityView-body",
+    ];
+
+    function updateAttribute(elem, attr, value) {
+      if (value) {
+        elem.setAttribute(attr, value);
+      } else {
+        elem.removeAttribute(attr);
+      }
+    }
+
+    for (let id of elementIDs) {
+      let element = document.getElementById(id);
+      updateAttribute(element, "connection", connection);
+      updateAttribute(element, "ciphers", ciphers);
+      updateAttribute(element, "mixedcontent", mixedcontent);
+    }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
     let host = "";
     let owner = "";
 
     try {
       host = this.getEffectiveHost();
     } catch (e) {
       // Some URIs might have no hosts.
     }
 
+    // Fallback for special protocols.
     if (!host) {
-      // Fallback for special protocols.
-      host = this._lastUri.specIgnoringRef;
-    }
-
-    switch (newMode) {
-    case this.IDENTITY_MODE_DOMAIN_VERIFIED:
-    case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED:
+      host = this._uri.specIgnoringRef;
+    }
+
+    // Fill in the CA name if we have a valid TLS certificate.
+    if (isSecure) {
       verifier = this._identityBox.tooltipText;
-      break;
-    case this.IDENTITY_MODE_IDENTIFIED:
-    case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED: {
-      // If it's identified, then we can populate the dialog with credentials
+    }
+
+    // Fill in organization information if we have a valid EV certificate.
+    if (isEV) {
       let iData = this.getIdentityData();
       host = owner = iData.subjectOrg;
       verifier = this._identityBox.tooltipText;
 
       // Build an appropriate supplemental block out of whatever location data we have
       if (iData.city)
         supplemental += iData.city + "\n";
       if (iData.state && iData.country)
         supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
                                                             [iData.state, iData.country]);
       else if (iData.state) // State only
         supplemental += iData.state;
       else if (iData.country) // Country only
         supplemental += iData.country;
-      break;
-    }
-    case this.IDENTITY_MODE_UNKNOWN:
-      supplemental = gNavigatorBundle.getString("identity.not_secure");
-      break;
-    case this.IDENTITY_MODE_USES_WEAK_CIPHER:
-      supplemental = gNavigatorBundle.getString("identity.uses_weak_cipher");
-      break;
-    case this.IDENTITY_MODE_MIXED_DISPLAY_LOADED:
-    case this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED:
-      supplemental = gNavigatorBundle.getString("identity.mixed_display_loaded");
-      break;
-    case this.IDENTITY_MODE_MIXED_ACTIVE_LOADED:
-      supplemental = gNavigatorBundle.getString("identity.mixed_active_loaded2");
-      break;
     }
 
     // Push the appropriate strings out to the UI. Need to use |value| for the
     // host as it's a <label> that will be cropped if too long. Using
     // |textContent| would simply wrap the value.
     this._identityPopupContentHost.setAttribute("value", host);
     this._identityPopupContentOwner.textContent = owner;
     this._identityPopupContentSupp.textContent = supplemental;
     this._identityPopupContentVerif.textContent = verifier;
 
-    // Hide subviews when updating panel information.
-    document.getElementById("identity-popup-multiView").showMainView();
+    // Update per-site permissions section.
+    this.updateSitePermissions();
+  },
+
+  _isURILoadedFromFile(uri) {
+    // Create a channel for the sole purpose of getting the resolved URI
+    // of the request to determine if it's loaded from the file system.
+    let chanOptions = {uri, loadUsingSystemPrincipal: true};
+    let resolvedURI = NetUtil.newChannel(chanOptions).URI;
+    if (resolvedURI.schemeIs("jar")) {
+      // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
+      // create a new URI using <jar-file-uri>!/<jar-entry>
+      resolvedURI = NetUtil.newURI(resolvedURI.path);
+    }
+
+    // Check the URI again after resolving.
+    return resolvedURI.schemeIs("file");
   },
 
   /**
    * Click handler for the identity-box element in primary chrome.
    */
   handleIdentityButtonEvent : function(event) {
     event.stopPropagation();
 
@@ -7165,19 +7211,17 @@ var gIdentityHandler = {
       return;
     }
 
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
     // Update the popup strings
-    this.setPopupMessages(this._identityBox.className);
-
-    this.updateSitePermissions();
+    this.refreshIdentityPopup();
 
     // Add the "open" attribute to the identity box for styling
     this._identityBox.setAttribute("open", "true");
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityIcons, "bottomcenter topleft");
   },
 
--- a/browser/base/content/test/general/browser_bug590206.js
+++ b/browser/base/content/test/general/browser_bug590206.js
@@ -1,22 +1,29 @@
 /*
  * Test the identity mode UI for a variety of page types
  */
 
+"use strict";
+
 const DUMMY = "browser/browser/base/content/test/general/dummy_page.html";
 
 function loadNewTab(url) {
   return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 }
 
 function getIdentityMode() {
   return document.getElementById("identity-box").className;
 }
 
+function getConnectionState() {
+  gIdentityHandler.refreshIdentityPopup();
+  return document.getElementById("identity-popup").getAttribute("connection");
+}
+
 // This test is slow on Linux debug e10s
 requestLongerTimeout(2);
 
 add_task(function* test_webpage() {
   let oldTab = gBrowser.selectedTab;
 
   let newTab = yield loadNewTab("http://example.com/" + DUMMY);
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
@@ -44,23 +51,23 @@ add_task(function* test_blank() {
 
   gBrowser.removeTab(newTab);
 });
 
 add_task(function* test_chrome() {
   let oldTab = gBrowser.selectedTab;
 
   let newTab = yield loadNewTab("chrome://mozapps/content/extensions/extensions.xul");
-  is(getIdentityMode(), "fileURI", "Identity should be file");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.selectedTab = oldTab;
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.selectedTab = newTab;
-  is(getIdentityMode(), "fileURI", "Identity should be file");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.removeTab(newTab);
 });
 
 add_task(function* test_https() {
   let oldTab = gBrowser.selectedTab;
 
   let newTab = yield loadNewTab("https://example.com/" + DUMMY);
@@ -90,40 +97,40 @@ add_task(function* test_addons() {
   gBrowser.removeTab(newTab);
 });
 
 add_task(function* test_file() {
   let oldTab = gBrowser.selectedTab;
   let fileURI = getTestFilePath("");
 
   let newTab = yield loadNewTab(fileURI);
-  is(getIdentityMode(), "fileURI", "Identity should be file");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.selectedTab = oldTab;
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.selectedTab = newTab;
-  is(getIdentityMode(), "fileURI", "Identity should be file");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.removeTab(newTab);
 });
 
 add_task(function test_resource_uri() {
   let oldTab = gBrowser.selectedTab;
-  let dataURI = "resource://gre/modules/Services.jsm"
+  let dataURI = "resource://gre/modules/Services.jsm";
 
   let newTab = yield loadNewTab(dataURI);
 
-  is(getIdentityMode(), "fileURI", "Identity should be unknown");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.selectedTab = oldTab;
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.selectedTab = newTab;
-  is(getIdentityMode(), "fileURI", "Identity should be unknown");
+  is(getConnectionState(), "file", "Connection should be file");
 
   gBrowser.removeTab(newTab);
 });
 
 add_task(function test_data_uri() {
   let oldTab = gBrowser.selectedTab;
   let dataURI = "data:text/html,hi"
 
@@ -133,8 +140,24 @@ add_task(function test_data_uri() {
   gBrowser.selectedTab = oldTab;
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.selectedTab = newTab;
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.removeTab(newTab);
 });
+
+add_task(function test_about_uri() {
+  let oldTab = gBrowser.selectedTab;
+  let aboutURI = "about:robots"
+
+  let newTab = yield loadNewTab(aboutURI);
+  is(getConnectionState(), "file", "Connection should be file");
+
+  gBrowser.selectedTab = oldTab;
+  is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+  gBrowser.selectedTab = newTab;
+  is(getConnectionState(), "file", "Connection should be file");
+
+  gBrowser.removeTab(newTab);
+});
--- a/browser/base/content/test/general/browser_identity_UI.js
+++ b/browser/base/content/test/general/browser_identity_UI.js
@@ -38,17 +38,16 @@ var tests = [
   {
     name: "subdomain with port",
     location: "http://sub1.test1.example.org:8000/",
     effectiveHost: "example.org"
   },
   {
     name: "subdomain HTTPS",
     location: "https://test1.example.com/",
-
     effectiveHost: "example.com",
     isHTTPS: true
   },
   {
     name: "view-source HTTPS",
     location: "view-source:https://example.com/",
     effectiveHost: null,
     isHTTPS: true
@@ -99,17 +98,19 @@ function nextTest() {
     if (!gForward)
       gTestDesc += " (second time)";
     content.location.reload(true);
   }
 }
 
 function checkResult() {
   // Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
-  is(gIdentityHandler._lastUri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
+  is(gIdentityHandler._uri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
   // getEffectiveHost can't be called for all modes
-  if (gCurrentTest.effectiveHost === null)
-    is(gIdentityHandler._mode == gIdentityHandler.IDENTITY_MODE_UNKNOWN || gIdentityHandler._mode == gIdentityHandler.IDENTITY_MODE_CHROMEUI, true, "mode matched");
-  else
+  if (gCurrentTest.effectiveHost === null) {
+    let identityBox = document.getElementById("identity-box");
+    is(identityBox.className == gIdentityHandler.IDENTITY_MODE_UNKNOWN || identityBox.className == gIdentityHandler.IDENTITY_MODE_CHROMEUI, true, "mode matched");
+  } else {
     is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
+  }
 
   executeSoon(nextTest);
 }
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -5,57 +5,69 @@
 <panel id="identity-popup"
        type="arrow"
        hidden="true"
        onpopupshown="gIdentityHandler.onPopupShown(event);"
        onpopuphidden="gIdentityHandler.onPopupHidden(event);"
        orient="vertical">
 
   <broadcasterset>
-    <broadcaster id="identity-popup-content-host" value=""/>
+    <broadcaster id="identity-popup-content-host" class="identity-popup-headline" crop="end"/>
+    <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
   </broadcasterset>
 
   <panelmultiview id="identity-popup-multiView"
                   mainViewId="identity-popup-mainView">
     <panelview id="identity-popup-mainView" flex="1">
 
       <!-- Security Section -->
-      <hbox class="identity-popup-section">
+      <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
-          <label class="identity-popup-headline" crop="end">
-            <observes element="identity-popup-content-host" attribute="value"/>
-          </label>
-          <label class="identity-popup-connection-secure identity-popup-text"
-                 value="&identity.connectionSecure;"/>
-          <label class="identity-popup-connection-not-secure identity-popup-text"
-                 value="&identity.connectionNotSecure;"/>
-          <label class="identity-popup-connection-file-uri identity-popup-text"
-                 value="&identity.connectionFile;"/>
-          <label class="identity-popup-connection-internal identity-popup-text"
-                 value="&identity.connectionInternal;"/>
+          <label observes="identity-popup-content-host"/>
+          <description class="identity-popup-connection-not-secure"
+                       value="&identity.connectionNotSecure;"
+                       when-connection="not-secure"/>
+          <description class="identity-popup-connection-secure"
+                       value="&identity.connectionSecure;"
+                       when-connection="secure secure-ev"/>
+          <description value="&identity.connectionInternal;"
+                       when-connection="chrome"/>
+          <description value="&identity.connectionFile;"
+                       when-connection="file"/>
+
+          <vbox id="identity-popup-security-descriptions">
+            <description class="identity-popup-warning-gray"
+                         when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
+            <description class="identity-popup-warning-yellow"
+                         when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
+            <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
+            <description class="identity-popup-warning-yellow"
+                         when-ciphers="weak">&identity.weakEncryption;</description>
+          </vbox>
         </vbox>
-        <button class="identity-popup-expander"
+        <button id="identity-popup-security-expander"
+                class="identity-popup-expander"
+                when-connection="not-secure secure secure-ev"
                 oncommand="gIdentityHandler.toggleSubView('security', this)"/>
       </hbox>
 
       <!-- Tracking Protection Section -->
-      <hbox id="tracking-protection-container" class="identity-popup-section">
+      <hbox id="tracking-protection-container"
+            class="identity-popup-section"
+            when-connection="not-secure secure secure-ev file">
         <vbox id="tracking-protection-content" flex="1">
-          <description class="identity-popup-text identity-popup-headline"
+          <description class="identity-popup-headline"
                        crop="end"
                        value="&trackingProtection.title;" />
 
           <label id="tracking-blocked"
-                 class="identity-popup-text"
                  crop="end">&trackingProtection.detectedBlocked2;</label>
           <label id="tracking-loaded"
-                 class="identity-popup-text"
                  crop="end">&trackingProtection.detectedNotBlocked2;</label>
           <label id="tracking-not-detected"
-                 class="identity-popup-text"
                  crop="end">&trackingProtection.notDetected2;</label>
 
           <button id="tracking-action-unblock"
                   label="&trackingProtection.unblock.label;"
                   accesskey="&trackingProtection.unblock.accesskey;"
                   oncommand="TrackingProtection.disableForCurrentPage();" />
           <button id="tracking-action-unblock-private"
                   label="&trackingProtection.unblockPrivate.label;"
@@ -66,17 +78,17 @@
                   accesskey="&trackingProtection.block2.accesskey;"
                   oncommand="TrackingProtection.enableForCurrentPage();" />
         </vbox>
       </hbox>
 
       <!-- Permissions Section -->
       <hbox id="identity-popup-permissions" class="identity-popup-section">
         <vbox id="identity-popup-permissions-content" flex="1">
-          <label class="identity-popup-text identity-popup-headline"
+          <label class="identity-popup-headline"
                  value="&identity.permissions;"/>
           <vbox id="identity-popup-permission-list"/>
         </vbox>
       </hbox>
 
       <spacer flex="1"/>
 
       <!-- More Information Button -->
@@ -85,34 +97,68 @@
                 label="&identity.moreInfoLinkText2;"
                 oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
       </hbox>
     </panelview>
 
     <!-- Security SubView -->
     <panelview id="identity-popup-securityView" flex="1">
       <vbox id="identity-popup-securityView-header">
-        <label class="identity-popup-headline" crop="end">
-          <observes element="identity-popup-content-host" attribute="value"/>
-        </label>
-        <label class="identity-popup-connection-secure identity-popup-text"
-               value="&identity.connectionSecure;"/>
-        <label class="identity-popup-connection-not-secure identity-popup-text"
-               value="&identity.connectionNotSecure;"/>
-        <label class="identity-popup-connection-file-uri identity-popup-text"
-               value="&identity.connectionFile;"/>
-        <label class="identity-popup-connection-internal identity-popup-text"
-               value="&identity.connectionInternal;"/>
+        <label observes="identity-popup-content-host"/>
+        <description class="identity-popup-connection-not-secure"
+                     value="&identity.connectionNotSecure;"
+                     when-connection="not-secure"/>
+        <description class="identity-popup-connection-secure"
+                     value="&identity.connectionSecure;"
+                     when-connection="secure secure-ev"/>
       </vbox>
 
-      <description id="identity-popup-content-verifier"
-                   class="identity-popup-text"/>
+      <vbox id="identity-popup-securityView-body">
+        <!-- (EV) Certificate Information -->
+        <description id="identity-popup-content-verified-by"
+                     when-connection="secure-ev">&identity.connectionVerified;</description>
+        <description id="identity-popup-content-owner"
+                     when-connection="secure-ev"
+                     class="header"/>
+        <description id="identity-popup-content-supplemental"
+                     when-connection="secure-ev"/>
+        <description id="identity-popup-content-verifier"
+                     when-connection="secure secure-ev"/>
+
+        <!-- Connection is Not Secure -->
+        <description when-connection="not-secure">&identity.description.insecure;</description>
+
+        <!-- Weak Cipher -->
+        <description when-ciphers="weak">&identity.description.weakCipher;</description>
+        <description class="identity-popup-warning-yellow"
+                     when-ciphers="weak">&identity.description.weakCipher2;</description>
+
+        <!-- Active Mixed Content Blocked -->
+        <description class="identity-popup-warning-gray"
+                     when-mixedcontent="active-blocked">&identity.description.activeBlocked; <label observes="identity-popup-mcb-learn-more"/></description>
 
-      <description id="identity-popup-securityView-connection"
-                   class="identity-popup-text">&identity.connectionVerified;</description>
+        <!-- Passive Mixed Content Loaded -->
+        <description when-mixedcontent="passive-loaded">&identity.description.passiveLoaded;</description>
+        <description class="identity-popup-warning-yellow"
+                     when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+
+        <!-- Passive Mixed Content Loaded, Active Mixed Content Blocked -->
+        <description when-mixedcontent="passive-loaded active-blocked">&identity.description.passiveLoaded;</description>
+        <description when-mixedcontent="passive-loaded active-blocked"
+                     class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
 
-      <description id="identity-popup-content-owner"
-                   class="identity-popup-text"/>
-      <description id="identity-popup-content-supplemental"
-                   class="identity-popup-text"/>
+        <!-- Active Mixed Content Blocking Disabled -->
+        <description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
+        <description when-mixedcontent="active-loaded">&identity.description.activeLoaded2;</description>
+
+        <!-- Buttons to enable/disable mixed content blocking. -->
+        <button when-mixedcontent="active-blocked"
+                label="&identity.disableMixedContentBlocking.label;"
+                accesskey="&identity.disableMixedContentBlocking.accesskey;"
+                oncommand="gIdentityHandler.disableMixedContentProtection()"/>
+        <button when-mixedcontent="active-loaded"
+                label="&identity.enableMixedContentBlocking.label;"
+                accesskey="&identity.enableMixedContentBlocking.accesskey;"
+                oncommand="gIdentityHandler.enableMixedContentProtection()"/>
+      </vbox>
     </panelview>
   </panelmultiview>
 </panel>
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -288,17 +288,17 @@
                       <button id="rejectReSignIn" label="&signIn.label;"/>
                       <button id="rejectUnlinkFxaAccount" label="&forget.label;"/>
                     </hbox>
                   </vbox>
                 </hbox>
               </deck>
         </groupbox>
         <groupbox id="syncOptions">
-          <caption><label>&signedIn.engines.caption;</label></caption>
+          <caption><label>&signedIn.engines.label;</label></caption>
           <hbox id="fxaSyncEngines">
             <vbox align="start">
               <checkbox label="&engine.tabs.label;"
                         accesskey="&engine.tabs.accesskey;"
                         preference="engine.tabs"/>
               <checkbox label="&engine.bookmarks.label;"
                         accesskey="&engine.bookmarks.accesskey;"
                         preference="engine.bookmarks"/>
@@ -355,17 +355,17 @@
       <image class="androidLogo"/>
       <label class="text-link"
              href="https://www.mozilla.org/firefox/android/">
         &mobilePromo.androidLink;
       </label>
       <label>&mobilePromo.end;</label>
     </hbox>
     <spacer flex="1"/>
-    <vbox id="tosPP-small">
+    <vbox id="tosPP-small" align="start">
       <label id="tosPP-small-ToS" class="text-link">
         &prefs.tosLink.label;
       </label>
       <label id="tosPP-small-PP" class="text-link">
         &fxaPrivacyNotice.link.label;
       </label>
     </vbox>
     <label class="androidAttribution">&androidAttribution;</label>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -686,16 +686,39 @@ you can use these alternative items. Oth
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection is Not Secure">
 <!ENTITY identity.connectionFile "This page is stored on your computer.">
 <!ENTITY identity.connectionVerified "&brandShortName; verified that you are securely connected to this site, run by:">
 <!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
 
+<!-- Strings for connection state warnings. -->
+<!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
+<!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
+<!ENTITY identity.activeLoaded "You have disabled protection on this page.">
+<!ENTITY identity.weakEncryption "This page uses weak encryption.">
+
+<!-- Strings for connection state warnings in the subview. -->
+<!ENTITY identity.description.insecure "Your connection to this site is not private. Information you submit could be viewed by others (like passwords, messages, credit cards, etc.).">
+<!ENTITY identity.description.weakCipher "Your connection to this website uses weak encryption and is not private.">
+<!ENTITY identity.description.weakCipher2 "Other people can view your information or modify the website's behavior.">
+<!ENTITY identity.description.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
+<!ENTITY identity.description.passiveLoaded "Your connection is not private and information you share with the site could be viewed by others.">
+<!ENTITY identity.description.passiveLoaded2 "This website contains content that is not secure (such as images).">
+<!ENTITY identity.description.passiveLoaded3 "Although &brandShortName; has blocked some content, there is still content on the page that is not secure (such as images).">
+<!ENTITY identity.description.activeLoaded "This website contains content that is not secure (such as scripts) and your connection to it is not private.">
+<!ENTITY identity.description.activeLoaded2 "Information you share with this site could be viewed by others (like passwords, messages, credit cards, etc.).">
+
+<!ENTITY identity.enableMixedContentBlocking.label "Enable protection">
+<!ENTITY identity.enableMixedContentBlocking.accesskey "E">
+<!ENTITY identity.disableMixedContentBlocking.label "Disable protection for now">
+<!ENTITY identity.disableMixedContentBlocking.accesskey "D">
+<!ENTITY identity.learnMore "Learn More">
+
 <!ENTITY identity.moreInfoLinkText2 "More Information">
 
 <!ENTITY identity.permissions "Permissions">
 
 <!-- Name for the tabs toolbar as spoken by screen readers.
      The word "toolbar" is appended automatically and should not be contained below! -->
 <!ENTITY tabsToolbar.label "Browser tabs">
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -324,21 +324,16 @@ offlineApps.notNowAccessKey=N
 offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
 offlineApps.manageUsage=Show settings
 offlineApps.manageUsageAccessKey=S
 
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
-identity.not_secure=Your connection to this site is not private. Information you submit could be viewable to others (for example passwords, messages, credit cards, etc.).
-identity.uses_weak_cipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website's behavior.
-identity.mixed_display_loaded=The connection to this website is not fully secure because it contains unencrypted elements (such as images).
-identity.mixed_active_loaded2=This website contains interactive content that isn't encrypted (such as scripts). Other people can view your information or modify the website's behavior.
-
 identity.unknown.tooltip=This website does not supply identity information.
 
 trackingProtection.intro.title=How Tracking Protection works
 # LOCALIZATION NOTE (trackingProtection.intro.description): %S is brandShortName
 trackingProtection.intro.description=When the shield is visible, that means Firefox is actively blocking content that tracks you.
 # LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
 trackingProtection.intro.step1of3=1 of 3
 trackingProtection.intro.nextButton.label=Next
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -84,15 +84,15 @@ both, to better adapt this sentence to t
 <!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
 
 <!ENTITY signedOut.caption            "Take your Web with you">
 <!ENTITY signedOut.description        "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
 <!ENTITY signedOut.accountBox.title   "Connect with a &syncBrand.fxAccount.label;">
 <!ENTITY signedOut.accountBox.create  "Create Account">
 <!ENTITY signedOut.accountBox.signin  "Sign In">
 
-<!ENTITY signedIn.engines.caption     "Sync between all devices">
+<!ENTITY signedIn.engines.label       "Sync across all devices">
 
 <!ENTITY mobilePromo.start            "Download Firefox for ">
 <!-- LOCALIZATION NOTE (mobilePromo.androidLink): This is a link title that links to https://www.mozilla.org/firefox/android/ -->
 <!ENTITY mobilePromo.androidLink      "Androidâ„¢">
 <!ENTITY mobilePromo.end              " to sync with your mobile device.">
 <!ENTITY androidAttribution           "Android is a trademark of Google Inc.">
--- a/browser/themes/osx/controlcenter/panel.css
+++ b/browser/themes/osx/controlcenter/panel.css
@@ -16,25 +16,28 @@
 
 .identity-popup-expander:-moz-focusring > .button-box,
 #identity-popup-more-info-button:-moz-focusring > .button-box {
   @hudButtonFocused@
 }
 
 #tracking-action-block,
 #tracking-action-unblock,
-#tracking-action-unblock-private {
+#tracking-action-unblock-private,
+#identity-popup-securityView-body > button {
   @hudButton@
   min-height: 30px;
 }
 
 #tracking-action-block:hover:active,
 #tracking-action-unblock:hover:active,
-#tracking-action-unblock-private:hover:active {
+#tracking-action-unblock-private:hover:active,
+#identity-popup-securityView-body > button:hover:active {
   @hudButtonPressed@
 }
 
 #tracking-action-block:-moz-focusring,
 #tracking-action-unblock:-moz-focusring,
-#tracking-action-unblock-private:-moz-focusring {
+#tracking-action-unblock-private:-moz-focusring,
+#identity-popup-securityView-body > button:-moz-focusring {
   @hudButtonFocused@
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -190,18 +190,20 @@ browser.jar:
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
   skin/classic/browser/controlcenter/arrow-subview.svg  (../shared/controlcenter/arrow-subview.svg)
   skin/classic/browser/controlcenter/arrow-subview-back.svg  (../shared/controlcenter/arrow-subview-back.svg)
   skin/classic/browser/controlcenter/conn-not-secure.svg  (../shared/controlcenter/conn-not-secure.svg)
   skin/classic/browser/controlcenter/conn-secure.svg  (../shared/controlcenter/conn-secure.svg)
   skin/classic/browser/controlcenter/conn-degraded.svg  (../shared/controlcenter/conn-degraded.svg)
   skin/classic/browser/controlcenter/mcb-disabled.svg  (../shared/controlcenter/mcb-disabled.svg)
   skin/classic/browser/controlcenter/permissions.svg  (../shared/controlcenter/permissions.svg)
-  skin/classic/browser/controlcenter/tracking-protection.svg                 (../shared/controlcenter/tracking-protection.svg)
-  skin/classic/browser/controlcenter/tracking-protection-disabled.svg        (../shared/controlcenter/tracking-protection-disabled.svg)
+  skin/classic/browser/controlcenter/tracking-protection.svg  (../shared/controlcenter/tracking-protection.svg)
+  skin/classic/browser/controlcenter/tracking-protection-disabled.svg  (../shared/controlcenter/tracking-protection-disabled.svg)
+  skin/classic/browser/controlcenter/warning-gray.svg  (../shared/controlcenter/warning-gray.svg)
+  skin/classic/browser/controlcenter/warning-yellow.svg  (../shared/controlcenter/warning-yellow.svg)
   skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle.png  (customizableui/customize-titleBar-toggle.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png  (customizableui/customize-titleBar-toggle@2x.png)
   skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
   skin/classic/browser/customizableui/customize-illustration@2x.png  (../shared/customizableui/customize-illustration@2x.png)
   skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
   skin/classic/browser/customizableui/customize-illustration-rtl@2x.png  (../shared/customizableui/customize-illustration-rtl@2x.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1,38 +1,44 @@
-/* Show the organization name only for EV certs. */
-#identity-popup-securityView:not(.verifiedIdentity) > #identity-popup-content-owner,
-#identity-popup-securityView:not(.verifiedIdentity) > #identity-popup-securityView-connection,
-/* Show the "Verified by" label only for DV and EV certs. */
-#identity-popup-securityView:not(.verifiedIdentity):not(.verifiedDomain) > #identity-popup-content-verifier,
-/* Show a longer explanation for non-secure sites, mixed content, and weak
-   connection security. Show the organization address for EV certs. */
-#identity-popup-securityView:not(.unknownIdentity):not(.verifiedIdentity):not(.mixedContent):not(.weakCipher) > #identity-popup-content-supplemental,
-/* Show the "Connection is secure" labels only for EV and DV certs. */
-#identity-popup-security-content:not(.verifiedIdentity):not(.verifiedDomain) > .identity-popup-connection-secure,
-#identity-popup-securityView:not(.verifiedIdentity):not(.verifiedDomain) > #identity-popup-securityView-header > .identity-popup-connection-secure,
-/* Show the "Connection is not secure" labels only for non-secure sites. */
-#identity-popup-security-content:not(.unknownIdentity) > .identity-popup-connection-not-secure,
-#identity-popup-securityView:not(.unknownIdentity) > #identity-popup-securityView-header > .identity-popup-connection-not-secure,
-/* Show "This page is stored on your computer" only for file URLs. */
-#identity-popup-security-content:not(.fileURI) > .identity-popup-connection-file-uri,
-#identity-popup-securityView:not(.fileURI) > #identity-popup-securityView-header > .identity-popup-connection-file-uri,
-/* Show "This is a secure internal page" only for whitelisted pages. */
-#identity-popup-securityView:not(.chromeUI) > #identity-popup-securityView-header > .identity-popup-connection-internal,
-#identity-popup-security-content:not(.chromeUI) > .identity-popup-connection-internal,
-/* Hide the subsection arrow for whitelisted chromeUI pages. */
-#identity-popup-security-content.chromeUI + .identity-popup-expander,
-/* Hide the subsection arrow for whitelisted file URI pages. */
-#identity-popup-security-content.fileURI + .identity-popup-expander,
-/* Hide the tracking protection section for whitelisted chromeUI pages. */
-#identity-popup-mainView.chromeUI > #tracking-protection-container {
+%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
+
+/* Hide all conditional elements by default. */
+:-moz-any([when-connection],[when-mixedcontent],[when-ciphers]) {
   display: none;
 }
 
-/* PANEL */
+/* Show the right elements for the right connection states. */
+#identity-popup[connection=not-secure] [when-connection~=not-secure],
+#identity-popup[connection=secure-ev] [when-connection~=secure-ev],
+#identity-popup[connection=secure] [when-connection~=secure],
+#identity-popup[connection=chrome] [when-connection~=chrome],
+#identity-popup[connection=file] [when-connection~=file],
+/* Show weak cipher messages when needed. */
+#identity-popup[ciphers=weak]:not([mixedcontent]) [when-ciphers~=weak],
+/* Show mixed content warnings when needed */
+#identity-popup[mixedcontent~=active-loaded] [when-mixedcontent=active-loaded],
+#identity-popup[mixedcontent~=passive-loaded]:not([mixedcontent~=active-loaded]) [when-mixedcontent=passive-loaded],
+#identity-popup[mixedcontent~=active-blocked]:not([mixedcontent~=passive-loaded]) [when-mixedcontent=active-blocked],
+/* Show the right elements when there is mixed passive content loaded and active blocked. */
+#identity-popup[mixedcontent~=active-blocked][mixedcontent~=passive-loaded] [when-mixedcontent~=active-blocked][when-mixedcontent~=passive-loaded],
+/* Show 'disable MCB' button always when there is mixed active content blocked. */
+#identity-popup-securityView-body[mixedcontent~=active-blocked] > button[when-mixedcontent=active-blocked] {
+  display: inherit;
+}
+
+/* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
+#identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
+/* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
+#identity-popup-securityView-body[mixedcontent~=passive-loaded][mixedcontent~=active-blocked] > description[when-mixedcontent=passive-loaded] {
+  display: none;
+}
 
 #identity-popup,
 #identity-popup:not([panelopen]) .panel-viewstack[viewtype="main"]:not([transitioning]) #identity-popup-mainView {
   /* Tiny hack to ensure the panel shrinks back to its original
      size after closing a subview that is bigger than the main view. */
   max-height: 0;
 }
 
@@ -138,97 +144,114 @@
 
 .identity-popup-expander:hover:active {
   background-color: hsla(210,4%,10%,.12);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 /* CONTENT */
 
-.identity-popup-text {
+#identity-popup-security-content > description,
+#identity-popup-security-descriptions > description,
+#identity-popup-securityView-header > description,
+#identity-popup-securityView-body > description,
+#tracking-protection-content > label {
   white-space: pre-wrap;
   font-size: 110%;
   margin: 0;
 }
 
 .identity-popup-headline {
   margin: 2px 0 4px;
   font-size: 150%;
 }
 
-/* SECURITY */
+.identity-popup-warning-gray {
+  -moz-padding-start: 24px;
+  background: url(chrome://browser/skin/controlcenter/warning-gray.svg) no-repeat 0 50%;
+}
 
-#identity-popup-securityView > .identity-popup-text:not(#identity-popup-content-owner) {
-  margin: 2px 0 4px;
+.identity-popup-warning-yellow {
+  -moz-padding-start: 24px;
+  background: url(chrome://browser/skin/controlcenter/warning-yellow.svg) no-repeat 0 50%;
 }
 
+/* SECURITY */
+
 .identity-popup-connection-secure {
   color: #418220;
 }
 
 .identity-popup-connection-not-secure {
   color: #d74345;
 }
 
-#identity-popup-security-content.chromeUI {
-  background-image: url(chrome://branding/content/icon48.png);
-}
-
-/* SECURITY SUBVIEW */
-
 #identity-popup-securityView {
   padding-bottom: 2em;
   overflow: hidden;
 }
 
 #identity-popup-securityView,
 #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/conn-not-secure.svg);
 }
 
-#identity-popup-securityView.verifiedDomain,
-#identity-popup-securityView.verifiedIdentity,
-#identity-popup-security-content.verifiedDomain,
-#identity-popup-security-content.verifiedIdentity {
+#identity-popup[connection=chrome] #identity-popup-securityView,
+#identity-popup[connection=chrome] #identity-popup-security-content {
+  background-image: url(chrome://branding/content/icon48.png);
+}
+
+#identity-popup[connection^=secure] #identity-popup-securityView,
+#identity-popup[connection^=secure] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/conn-secure.svg);
 }
 
-#identity-popup-securityView.weakCipher,
-#identity-popup-securityView.mixedDisplayContent,
-#identity-popup-securityView.mixedDisplayContentLoadedActiveBlocked,
-#identity-popup-security-content.weakCipher,
-#identity-popup-security-content.mixedDisplayContent,
-#identity-popup-security-content.mixedDisplayContentLoadedActiveBlocked {
+#identity-popup[ciphers=weak] #identity-popup-securityView,
+#identity-popup[ciphers=weak] #identity-popup-security-content,
+#identity-popup[mixedcontent~=passive-loaded] #identity-popup-securityView,
+#identity-popup[mixedcontent~=passive-loaded] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
 }
 
-#identity-popup-securityView.mixedActiveContent,
-#identity-popup-security-content.mixedActiveContent {
+#identity-popup[mixedcontent~=active-loaded] #identity-popup-securityView,
+#identity-popup[mixedcontent~=active-loaded] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);
 }
 
+#identity-popup-security-descriptions > description {
+  margin-top: 6px;
+  color: Graytext;
+}
+
 #identity-popup-securityView-header {
   border-bottom: 1px solid var(--panel-separator-color);
   padding-bottom: 1em;
-  margin-bottom: 1em;
 }
 
-#identity-popup-content-owner {
-  font-weight: 700;
+#identity-popup-securityView-body {
+  -moz-padding-end: 1em;
 }
 
-#identity-popup-content-verifier {
+#identity-popup-content-verifier ~ description {
+  margin-top: 1em;
   color: Graytext;
 }
 
-#identity-popup-content-owner,
-#identity-popup-securityView > #identity-popup-securityView-connection.identity-popup-text {
+description#identity-popup-content-verified-by,
+description#identity-popup-content-owner,
+description#identity-popup-content-verifier,
+#identity-popup-securityView-body > button {
   margin-top: 1em;
 }
 
+#identity-popup-securityView-body > button {
+  margin-inline-start: 0;
+  margin-inline-end: 0;
+}
+
 /* TRACKING PROTECTION */
 
 #tracking-protection-content {
   background-image: url("chrome://browser/skin/controlcenter/tracking-protection.svg");
 }
 
 #tracking-protection-content[state="loaded-tracking-content"]  {
   background-image: url("chrome://browser/skin/controlcenter/tracking-protection-disabled.svg");
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/warning-gray.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="#808080" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+  <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/warning-yellow.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="#ffbf00" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+  <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+</svg>
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -45,17 +45,17 @@ public class GeneratableElementIterator 
 
         // Sort the elements to ensure determinism.
         Arrays.sort(objs, new AlphabeticAnnotatableEntityComparator<Member>());
         mObjects = objs;
 
         // Check for "Wrap ALL the things" flag.
         for (Annotation annotation : aClass.getDeclaredAnnotations()) {
             final String annotationTypeName = annotation.annotationType().getName();
-            if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI")) {
+            if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
                 mIterateEveryEntry = true;
                 break;
             }
         }
 
         findNextValue();
     }
 
@@ -66,17 +66,17 @@ public class GeneratableElementIterator 
     private void findNextValue() {
         while (mElementIndex < mObjects.length) {
             Member candidateElement = mObjects[mElementIndex];
             mElementIndex++;
             for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) {
                 // WrappedJNIMethod has parameters. Use Reflection to obtain them.
                 Class<? extends Annotation> annotationType = annotation.annotationType();
                 final String annotationTypeName = annotationType.getName();
-                if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI")) {
+                if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
                     String stubName = null;
                     boolean isMultithreadedStub = false;
                     boolean noThrow = false;
                     boolean narrowChars = false;
                     boolean catchException = false;
                     try {
                         // Determine the explicitly-given name of the stub to generate, if any.
                         final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
@@ -99,25 +99,25 @@ public class GeneratableElementIterator 
                         narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
 
                         // Determine if we should catch exceptions
                         final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
                         catchExceptionMethod.setAccessible(true);
                         catchException = (Boolean) catchExceptionMethod.invoke(annotation);
 
                     } catch (NoSuchMethodException e) {
-                        System.err.println("Unable to find expected field on WrapElementForJNI annotation. Did the signature change?");
+                        System.err.println("Unable to find expected field on WrapForJNI annotation. Did the signature change?");
                         e.printStackTrace(System.err);
                         System.exit(3);
                     } catch (IllegalAccessException e) {
-                        System.err.println("IllegalAccessException reading fields on WrapElementForJNI annotation. Seems the semantics of Reflection have changed...");
+                        System.err.println("IllegalAccessException reading fields on WrapForJNI annotation. Seems the semantics of Reflection have changed...");
                         e.printStackTrace(System.err);
                         System.exit(4);
                     } catch (InvocationTargetException e) {
-                        System.err.println("InvocationTargetException reading fields on WrapElementForJNI annotation. This really shouldn't happen.");
+                        System.err.println("InvocationTargetException reading fields on WrapForJNI annotation. This really shouldn't happen.");
                         e.printStackTrace(System.err);
                         System.exit(5);
                     }
 
                     // If the method name was not explicitly given in the annotation generate one...
                     if (stubName.isEmpty()) {
                         stubName = Utils.getNativeName(candidateElement);
                     }
--- a/dom/presentation/provider/PresentationDeviceProviderModule.cpp
+++ b/dom/presentation/provider/PresentationDeviceProviderModule.cpp
@@ -23,17 +23,18 @@ static const mozilla::Module::CIDEntry k
 };
 
 static const mozilla::Module::ContractIDEntry kPresentationDeviceProviderContracts[] = {
   { MULTICAST_DNS_PROVIDER_CONTRACT_ID, &kMULTICAST_DNS_PROVIDER_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kPresentationDeviceProviderCategories[] = {
-#if defined(MOZ_WIDGET_ANDROID) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
+#if (defined(MOZ_WIDGET_ANDROID) && ANDROID_VERSION >= 21) || \
+    (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
   { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "MulticastDNSDeviceProvider", MULTICAST_DNS_PROVIDER_CONTRACT_ID },
 #endif
   { nullptr }
 };
 
 static const mozilla::Module kPresentationDeviceProviderModule = {
   mozilla::Module::kVersion,
   kPresentationDeviceProviderCIDs,
--- a/mobile/android/base/ANRReporter.java
+++ b/mobile/android/base/ANRReporter.java
@@ -15,18 +15,18 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.util.Locale;
 import java.util.UUID;
 import java.util.regex.Pattern;
 
 import org.json.JSONObject;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.Looper;
@@ -49,21 +49,21 @@ public final class ANRReporter extends B
     private static final String TRACES_CHARSET = "utf-8";
     private static final String PING_CHARSET = "utf-8";
 
     private static final ANRReporter sInstance = new ANRReporter();
     private static int sRegisteredCount;
     private Handler mHandler;
     private volatile boolean mPendingANR;
 
-    @WrapElementForJNI
+    @WrapForJNI
     private static native boolean requestNativeStack(boolean unwind);
-    @WrapElementForJNI
+    @WrapForJNI
     private static native String getNativeStack();
-    @WrapElementForJNI
+    @WrapForJNI
     private static native void releaseNativeStack();
 
     public static void register(Context context) {
         if (sRegisteredCount++ != 0) {
             // Already registered
             return;
         }
         sInstance.start(context);
--- a/mobile/android/base/DownloadsIntegration.java
+++ b/mobile/android/base/DownloadsIntegration.java
@@ -1,20 +1,20 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import java.io.File;
 import java.lang.IllegalArgumentException;
 import java.util.ArrayList;
 import java.util.List;
 
 import android.app.DownloadManager;
 import android.content.Context;
@@ -103,17 +103,17 @@ public class DownloadsIntegration implem
             // Download Manager package does not exist
             return false;
         }
 
         return (PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state ||
                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT == state);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void scanMedia(final String aFile, String aMimeType) {
         String mimeType = aMimeType;
         if (UNKNOWN_MIME_TYPES.contains(mimeType)) {
             // If this is a generic undefined mimetype, erase it so that we can try to determine
             // one from the file extension below.
             mimeType = "";
         }
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -25,28 +25,28 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.mozglue.RobocopTarget;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
@@ -456,38 +456,38 @@ public class GeckoAppShell
 
     // Synchronously notify a Gecko observer; must be called from Gecko thread.
     public static native void notifyGeckoObservers(String subject, String data);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
 
-    @WrapElementForJNI(allowMultithread = true, noThrow = true)
+    @WrapForJNI(allowMultithread = true, noThrow = true)
     public static void handleUncaughtException(Thread thread, Throwable e) {
         CRASH_HANDLER.uncaughtException(thread, e);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void notifyIME(int type) {
         if (editableListener != null) {
             editableListener.notifyIME(type);
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void notifyIMEContext(int state, String typeHint,
                                         String modeHint, String actionHint) {
         if (editableListener != null) {
             editableListener.notifyIMEContext(state, typeHint,
                                                modeHint, actionHint);
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void notifyIMEChange(String text, int start, int end, int newEnd) {
         if (newEnd < 0) { // Selection change
             editableListener.onSelectionChange(start, end);
         } else { // Text change
             editableListener.onTextChange(text, start, end, newEnd);
         }
     }
 
@@ -529,17 +529,17 @@ public class GeckoAppShell
                 }
                 long waited = SystemClock.uptimeMillis() - time;
                 Log.d(LOGTAG, "Gecko event sync taking too long: " + waited + "ms");
             }
         }
     }
 
     // Signal the Java thread that it's time to wake up
-    @WrapElementForJNI
+    @WrapForJNI
     public static void acknowledgeEvent() {
         synchronized (sEventAckLock) {
             sWaitingForEventAck = false;
             sEventAckLock.notifyAll();
         }
     }
 
     private static final Runnable sCallbackRunnable = new Runnable() {
@@ -550,17 +550,17 @@ public class GeckoAppShell
             if (nextDelay >= 0) {
                 ThreadUtils.getUiHandler().postDelayed(this, nextDelay);
             }
         }
     };
 
     private static native long runUiThreadCallback();
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     private static void requestUiThreadCallback(long delay) {
         ThreadUtils.getUiHandler().postDelayed(sCallbackRunnable, delay);
     }
 
     private static float getLocationAccuracy(Location location) {
         float radius = location.getAccuracy();
         return (location.hasAccuracy() && radius > 0) ? radius : 1001;
     }
@@ -586,17 +586,17 @@ public class GeckoAppShell
                  getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) {
                 lastKnownLocation = location;
             }
         }
 
         return lastKnownLocation;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableLocation(final boolean enable) {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     LocationManager lm = getLocationManager(getContext());
                     if (lm == null) {
                         return;
                     }
@@ -642,22 +642,22 @@ public class GeckoAppShell
             // which allows enabling/disabling location update notifications from the cell radio.
             // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be
             // hitting this problem if the Tegras are confused about missing cell radios.
             Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e);
             return null;
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableLocationHighAccuracy(final boolean enable) {
         locationHighAccuracyEnabled = enable;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null)
             return;
         SensorManager sm = (SensorManager)
             getContext().getSystemService(Context.SENSOR_SERVICE);
 
         switch(aSensortype) {
@@ -717,17 +717,17 @@ public class GeckoAppShell
                 sm.registerListener(gi.getSensorEventListener(), gGameRotationVectorSensor, SensorManager.SENSOR_DELAY_GAME);
             break;
 
         default:
             Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void disableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null)
             return;
 
         SensorManager sm = (SensorManager)
             getContext().getSystemService(Context.SENSOR_SERVICE);
 
@@ -771,60 +771,60 @@ public class GeckoAppShell
             if (gGyroscopeSensor != null)
                 sm.unregisterListener(gi.getSensorEventListener(), gGyroscopeSensor);
             break;
         default:
             Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void startMonitoringGamepad() {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     AndroidGamepadManager.startup();
                 }
             });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void stopMonitoringGamepad() {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     AndroidGamepadManager.shutdown();
                 }
             });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void gamepadAdded(final int device_id, final int service_id) {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     AndroidGamepadManager.gamepadAdded(device_id, service_id);
                 }
             });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void moveTaskToBack() {
         if (getGeckoInterface() != null)
             getGeckoInterface().getActivity().moveTaskToBack(true);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     static void scheduleRestart() {
         getGeckoInterface().doRestart();
     }
 
     // Creates a homescreen shortcut for a web page.
     // This is the entry point from nsIShellService.
-    @WrapElementForJNI
+    @WrapForJNI
     static void createShortcut(final String aTitle, final String aURI, final String aIconData) {
         // We have the favicon data (base64) decoded on the background thread, callback here, then
         // call the other createShortcut method with the decoded favicon.
         // This is slightly contrived, but makes the images available to the favicon cache.
         Favicons.getSizedFavicon(getContext(), aURI, aIconData, Integer.MAX_VALUE, 0,
             new OnFaviconLoadedListener() {
                 @Override
                 public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
@@ -938,41 +938,41 @@ public class GeckoAppShell
                                    halfSize - sHeight,
                                    halfSize + sWidth,
                                    halfSize + sHeight),
                           null);
 
         return bitmap;
     }
 
-    @WrapElementForJNI(stubName = "GetHandlersForMimeTypeWrapper")
+    @WrapForJNI(stubName = "GetHandlersForMimeTypeWrapper")
     static String[] getHandlersForMimeType(String aMimeType, String aAction) {
         Intent intent = getIntentForActionString(aAction);
         if (aMimeType != null && aMimeType.length() > 0)
             intent.setType(aMimeType);
         return getHandlersForIntent(intent);
     }
 
-    @WrapElementForJNI(stubName = "GetHandlersForURLWrapper")
+    @WrapForJNI(stubName = "GetHandlersForURLWrapper")
     static String[] getHandlersForURL(String aURL, String aAction) {
         // aURL may contain the whole URL or just the protocol
         Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
 
         Intent intent = getOpenURIIntent(getContext(), uri.toString(), "",
             TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
 
         return getHandlersForIntent(intent);
     }
 
-    @WrapElementForJNI(stubName = "GetHWEncoderCapability")
+    @WrapForJNI(stubName = "GetHWEncoderCapability")
     static boolean getHWEncoderCapability() {
       return HardwareCodecCapabilityUtils.getHWEncoderCapability();
     }
 
-    @WrapElementForJNI(stubName = "GetHWDecoderCapability")
+    @WrapForJNI(stubName = "GetHWDecoderCapability")
     static boolean getHWDecoderCapability() {
       return HardwareCodecCapabilityUtils.getHWDecoderCapability();
     }
 
     static List<ResolveInfo> queryIntentActivities(Intent intent) {
         final PackageManager pm = getContext().getPackageManager();
 
         // Exclude any non-exported activities: we can't open them even if we want to!
@@ -1023,22 +1023,22 @@ public class GeckoAppShell
     static Intent getIntentForActionString(String aAction) {
         // Default to the view action if no other action as been specified.
         if (TextUtils.isEmpty(aAction)) {
             return new Intent(Intent.ACTION_VIEW);
         }
         return new Intent(aAction);
     }
 
-    @WrapElementForJNI(stubName = "GetExtensionFromMimeTypeWrapper")
+    @WrapForJNI(stubName = "GetExtensionFromMimeTypeWrapper")
     static String getExtensionFromMimeType(String aMimeType) {
         return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
     }
 
-    @WrapElementForJNI(stubName = "GetMimeTypeFromExtensionsWrapper")
+    @WrapForJNI(stubName = "GetMimeTypeFromExtensionsWrapper")
     static String getMimeTypeFromExtensions(String aFileExt) {
         StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
         String type = null;
         String subType = null;
         while (st.hasMoreElements()) {
             String ext = st.nextToken();
             String mt = getMimeTypeFromExtension(ext);
             if (mt == null)
@@ -1089,17 +1089,17 @@ public class GeckoAppShell
      * @param mimeType an optional MIME type string.
      * @param packageName an optional app package name.
      * @param className an optional intent class name.
      * @param action an Android action specifier, such as
      *               <code>Intent.ACTION_SEND</code>.
      * @param title the title to use in <code>ACTION_SEND</code> intents.
      * @return true if the activity started successfully; false otherwise.
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean openUriExternal(String targetURI,
                                           String mimeType,
                                           String packageName,
                                           String className,
                                           String action,
                                           String title) {
         final Context context = getContext();
         final Intent intent = getOpenURIIntent(context, targetURI,
@@ -1377,17 +1377,17 @@ public class GeckoAppShell
     public static void setNotificationClient(NotificationClient client) {
         if (notificationClient == null) {
             notificationClient = client;
         } else {
             Log.d(LOGTAG, "Notification client already set");
         }
     }
 
-    @WrapElementForJNI(stubName = "ShowAlertNotificationWrapper")
+    @WrapForJNI(stubName = "ShowAlertNotificationWrapper")
     public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
                                              String aAlertCookie, String aAlertName) {
         // The intent to launch when the user clicks the expanded notification
         String app = getContext().getClass().getName();
         Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
         notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
         notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
@@ -1404,23 +1404,23 @@ public class GeckoAppShell
                 getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
         ALERT_COOKIES.put(aAlertName, aAlertCookie);
         callObserver(aAlertName, "alertshow", aAlertCookie);
 
         notificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
         int notificationID = aAlertName.hashCode();
         notificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void closeNotification(String aAlertName) {
         String alertCookie = ALERT_COOKIES.get(aAlertName);
         if (alertCookie != null) {
             callObserver(aAlertName, "alertfinished", alertCookie);
             ALERT_COOKIES.remove(aAlertName);
         }
 
         removeObserver(aAlertName);
@@ -1438,39 +1438,39 @@ public class GeckoAppShell
             if (notificationClient.isOngoing(notificationID)) {
                 // When clicked, keep the notification if it displays progress
                 return;
             }
         }
         closeNotification(aAlertName);
     }
 
-    @WrapElementForJNI(stubName = "GetDpiWrapper")
+    @WrapForJNI(stubName = "GetDpiWrapper")
     public static int getDpi() {
         if (sDensityDpi == 0) {
             sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
         }
 
         return sDensityDpi;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static float getDensity() {
         return getContext().getResources().getDisplayMetrics().density;
     }
 
     private static boolean isHighMemoryDevice() {
         return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
     }
 
     /**
      * Returns the colour depth of the default screen. This will either be
      * 24 or 16.
      */
-    @WrapElementForJNI(stubName = "GetScreenDepthWrapper")
+    @WrapForJNI(stubName = "GetScreenDepthWrapper")
     public static synchronized int getScreenDepth() {
         if (sScreenDepth == 0) {
             sScreenDepth = 16;
             PixelFormat info = new PixelFormat();
             PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info);
             if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
                 sScreenDepth = 24;
             }
@@ -1483,23 +1483,23 @@ public class GeckoAppShell
         if (sScreenDepth != 0) {
             Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
             return;
         }
 
         sScreenDepth = aScreenDepth;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void setFullScreen(boolean fullscreen) {
         if (getGeckoInterface() != null)
             getGeckoInterface().setFullScreen(fullscreen);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void performHapticFeedback(boolean aIsLongPress) {
         // Don't perform haptic feedback if a vibration is currently playing,
         // because the haptic feedback will nuke the vibration.
         if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
             LayerView layerView = getLayerView();
             layerView.performHapticFeedback(aIsLongPress ?
                                             HapticFeedbackConstants.LONG_PRESS :
                                             HapticFeedbackConstants.VIRTUAL_KEY);
@@ -1522,104 +1522,104 @@ public class GeckoAppShell
 
     // Vibrate only if haptic feedback is enabled.
     public static void vibrateOnHapticFeedbackEnabled(int[] milliseconds) {
         if (Settings.System.getInt(getContext().getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) > 0) {
             vibrate(convertIntToLongArray(milliseconds), -1);
         }
     }
 
-    @WrapElementForJNI(stubName = "Vibrate1")
+    @WrapForJNI(stubName = "Vibrate1")
     public static void vibrate(long milliseconds) {
         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(milliseconds);
     }
 
-    @WrapElementForJNI(stubName = "VibrateA")
+    @WrapForJNI(stubName = "VibrateA")
     public static void vibrate(long[] pattern, int repeat) {
         // If pattern.length is even, the last element in the pattern is a
         // meaningless delay, so don't include it in vibrationDuration.
         long vibrationDuration = 0;
         int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0);
         for (int i = 0; i < iterLen; i++) {
           vibrationDuration += pattern[i];
         }
 
         sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(pattern, repeat);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void cancelVibrate() {
         sVibrationMaybePlaying = false;
         sVibrationEndTime = 0;
         vibrator().cancel();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void showInputMethodPicker() {
         InputMethodManager imm = (InputMethodManager)
             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showInputMethodPicker();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void setKeepScreenOn(final boolean on) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 // TODO
             }
         });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void notifyDefaultPrevented(final boolean defaultPrevented) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 LayerView view = getLayerView();
                 PanZoomController controller = (view == null ? null : view.getPanZoomController());
                 if (controller != null) {
                     controller.notifyDefaultActionPrevented(defaultPrevented);
                 }
             }
         });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean isNetworkLinkUp() {
         ConnectivityManager cm = (ConnectivityManager)
            getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         try {
             NetworkInfo info = cm.getActiveNetworkInfo();
             if (info == null || !info.isConnected())
                 return false;
         } catch (SecurityException se) {
             return false;
         }
         return true;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean isNetworkLinkKnown() {
         ConnectivityManager cm = (ConnectivityManager)
             getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         try {
             if (cm.getActiveNetworkInfo() == null)
                 return false;
         } catch (SecurityException se) {
             return false;
         }
         return true;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static int networkLinkType() {
         ConnectivityManager cm = (ConnectivityManager)
             getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo info = cm.getActiveNetworkInfo();
         if (info == null) {
             return LINK_TYPE_UNKNOWN;
         }
 
@@ -1668,17 +1668,17 @@ public class GeckoAppShell
                 return LINK_TYPE_4G; // 3.9G
             case TelephonyManager.NETWORK_TYPE_UNKNOWN:
             default:
                 Log.w(LOGTAG, "Connected to an unknown mobile network!");
                 return LINK_TYPE_UNKNOWN;
         }
     }
 
-    @WrapElementForJNI(stubName = "GetSystemColoursWrapper")
+    @WrapForJNI(stubName = "GetSystemColoursWrapper")
     public static int[] getSystemColors() {
         // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
         final int[] attrsAppearance = {
             android.R.attr.textColor,
             android.R.attr.textColorPrimary,
             android.R.attr.textColorPrimaryInverse,
             android.R.attr.textColorSecondary,
             android.R.attr.textColorSecondaryInverse,
@@ -1705,17 +1705,17 @@ public class GeckoAppShell
                 result[idx] = color;
             }
             appearance.recycle();
         }
 
         return result;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void killAnyZombies() {
         GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
             @Override
             public boolean callback(int pid) {
                 if (pid != android.os.Process.myPid())
                     android.os.Process.killProcess(pid);
                 return true;
             }
@@ -1835,17 +1835,17 @@ public class GeckoAppShell
                 String file = split[nameColumn];
                 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
                     Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
             }
             in.close();
         } catch (Exception e) { }
     }
 
-    @WrapElementForJNI(stubName = "GetIconForExtensionWrapper")
+    @WrapForJNI(stubName = "GetIconForExtensionWrapper")
     public static byte[] getIconForExtension(String aExt, int iconSize) {
         try {
             if (iconSize <= 0)
                 iconSize = 16;
 
             if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
                 aExt = aExt.substring(1);
 
@@ -1893,39 +1893,39 @@ public class GeckoAppShell
         if (resolveInfo == null)
             return null;
 
         ActivityInfo activityInfo = resolveInfo.activityInfo;
 
         return activityInfo.loadIcon(pm);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean getShowPasswordSetting() {
         try {
             int showPassword =
                 Settings.System.getInt(getContext().getContentResolver(),
                                        Settings.System.TEXT_SHOW_PASSWORD, 1);
             return (showPassword > 0);
         }
         catch (Exception e) {
             return true;
         }
     }
 
-    @WrapElementForJNI(stubName = "AddPluginViewWrapper")
+    @WrapForJNI(stubName = "AddPluginViewWrapper")
     public static void addPluginView(View view,
                                      float x, float y,
                                      float w, float h,
                                      boolean isFullScreen) {
         if (getGeckoInterface() != null)
              getGeckoInterface().addPluginView(view, new RectF(x, y, x + w, y + h), isFullScreen);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void removePluginView(View view, boolean isFullScreen) {
         if (getGeckoInterface() != null)
             getGeckoInterface().removePluginView(view, isFullScreen);
     }
 
     /**
      * A plugin that wish to be loaded in the WebView must provide this permission
      * in their AndroidManifest.xml.
@@ -2121,17 +2121,17 @@ public class GeckoAppShell
             throws NameNotFoundException, ClassNotFoundException {
         Context pluginContext = getContext().createPackageContext(packageName,
                 Context.CONTEXT_INCLUDE_CODE |
                 Context.CONTEXT_IGNORE_SECURITY);
         ClassLoader pluginCL = pluginContext.getClassLoader();
         return pluginCL.loadClass(className);
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public static Class<?> loadPluginClass(String className, String libName) {
         if (getGeckoInterface() == null)
             return null;
         try {
             final String packageName = getPluginPackage(libName);
             final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
             final Context pluginContext = getContext().createPackageContext(packageName, contextFlags);
             return pluginContext.getClassLoader().loadClass(className);
@@ -2141,17 +2141,17 @@ public class GeckoAppShell
         } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
             Log.w(LOGTAG, "Couldn't find package.", nnfe);
             return null;
         }
     }
 
     private static ContextGetter sContextGetter;
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public static Context getContext() {
         return sContextGetter.getContext();
     }
 
     public static void setContextGetter(ContextGetter cg) {
         sContextGetter = cg;
     }
 
@@ -2205,17 +2205,17 @@ public class GeckoAppShell
     public static android.hardware.Camera sCamera;
 
     static native void cameraCallbackBridge(byte[] data);
 
     static final int kPreferredFPS = 25;
     static byte[] sCameraBuffer;
 
 
-    @WrapElementForJNI(stubName = "InitCameraWrapper")
+    @WrapForJNI(stubName = "InitCameraWrapper")
     static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         if (getGeckoInterface() != null)
                             getGeckoInterface().enableCameraView();
                     } catch (Exception e) {}
@@ -2299,17 +2299,17 @@ public class GeckoAppShell
             result[3] = params.getPreviewFrameRate();
         } catch(RuntimeException e) {
             Log.w(LOGTAG, "initCamera RuntimeException.", e);
             result[0] = result[1] = result[2] = result[3] = 0;
         }
         return result;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     static synchronized void closeCamera() {
         ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         if (getGeckoInterface() != null)
                             getGeckoInterface().disableCameraView();
                     } catch (Exception e) {}
@@ -2321,194 +2321,194 @@ public class GeckoAppShell
             sCamera = null;
             sCameraBuffer = null;
         }
     }
 
     /*
      * Battery API related methods.
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableBatteryNotifications() {
         GeckoBatteryManager.enableNotifications();
     }
 
-    @WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
+    @WrapForJNI(stubName = "HandleGeckoMessageWrapper")
     public static void handleGeckoMessage(final NativeJSContainer message) {
         EventDispatcher.getInstance().dispatchEvent(message);
         message.disposeNative();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void disableBatteryNotifications() {
         GeckoBatteryManager.disableNotifications();
     }
 
-    @WrapElementForJNI(stubName = "GetCurrentBatteryInformationWrapper")
+    @WrapForJNI(stubName = "GetCurrentBatteryInformationWrapper")
     public static double[] getCurrentBatteryInformation() {
         return GeckoBatteryManager.getCurrentInformation();
     }
 
-    @WrapElementForJNI(stubName = "CheckURIVisited")
+    @WrapForJNI(stubName = "CheckURIVisited")
     static void checkUriVisited(String uri) {
         GlobalHistory.getInstance().checkUriVisited(uri);
     }
 
-    @WrapElementForJNI(stubName = "MarkURIVisited")
+    @WrapForJNI(stubName = "MarkURIVisited")
     static void markUriVisited(final String uri) {
         final Context context = getContext();
         final BrowserDB db = GeckoProfile.get(context).getDB();
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 GlobalHistory.getInstance().add(context, db, uri);
             }
         });
     }
 
-    @WrapElementForJNI(stubName = "SetURITitle")
+    @WrapForJNI(stubName = "SetURITitle")
     static void setUriTitle(final String uri, final String title) {
         final Context context = getContext();
         final BrowserDB db = GeckoProfile.get(context).getDB();
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 GlobalHistory.getInstance().update(context.getContentResolver(), db, uri, title);
             }
         });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     static void hideProgressDialog() {
         // unused stub
     }
 
     /*
      * WebSMS related methods.
      */
-    @WrapElementForJNI(stubName = "SendMessageWrapper")
+    @WrapForJNI(stubName = "SendMessageWrapper")
     public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().send(aNumber, aMessage, aRequestId);
     }
 
-    @WrapElementForJNI(stubName = "GetMessageWrapper")
+    @WrapForJNI(stubName = "GetMessageWrapper")
     public static void getMessage(int aMessageId, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().getMessage(aMessageId, aRequestId);
     }
 
-    @WrapElementForJNI(stubName = "DeleteMessageWrapper")
+    @WrapForJNI(stubName = "DeleteMessageWrapper")
     public static void deleteMessage(int aMessageId, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
     }
 
-    @WrapElementForJNI(stubName = "CreateMessageListWrapper")
+    @WrapForJNI(stubName = "CreateMessageListWrapper")
     public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId);
     }
 
-    @WrapElementForJNI(stubName = "GetNextMessageInListWrapper")
+    @WrapForJNI(stubName = "GetNextMessageInListWrapper")
     public static void getNextMessageInList(int aListId, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void clearMessageList(int aListId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().clearMessageList(aListId);
     }
 
     /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
-    @WrapElementForJNI
+    @WrapForJNI
     @RobocopTarget
     public static boolean isTablet() {
         return HardwareUtils.isTablet();
     }
 
     public static void viewSizeChanged() {
         LayerView v = getLayerView();
         if (v != null && v.isIMEEnabled()) {
             sendEventToGecko(GeckoEvent.createBroadcastEvent(
                     "ScrollTo:FocusedInput", ""));
         }
     }
 
-    @WrapElementForJNI(stubName = "GetCurrentNetworkInformationWrapper")
+    @WrapForJNI(stubName = "GetCurrentNetworkInformationWrapper")
     public static double[] getCurrentNetworkInformation() {
         return GeckoNetworkManager.getInstance().getCurrentInformation();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableNetworkNotifications() {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 GeckoNetworkManager.getInstance().enableNotifications();
             }
         });
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void disableNetworkNotifications() {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 GeckoNetworkManager.getInstance().disableNotifications();
             }
         });
     }
 
-    @WrapElementForJNI(stubName = "GetScreenOrientationWrapper")
+    @WrapForJNI(stubName = "GetScreenOrientationWrapper")
     public static short getScreenOrientation() {
         return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void enableScreenOrientationNotifications() {
         GeckoScreenOrientation.getInstance().enableNotifications();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void disableScreenOrientationNotifications() {
         GeckoScreenOrientation.getInstance().disableNotifications();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void lockScreenOrientation(int aOrientation) {
         GeckoScreenOrientation.getInstance().lock(aOrientation);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void unlockScreenOrientation() {
         GeckoScreenOrientation.getInstance().unlock();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean pumpMessageLoop(final Message msg) {
         final Handler geckoHandler = ThreadUtils.sGeckoHandler;
 
         if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
             // Our "queue is empty" message; see runGecko()
             return false;
         }
 
@@ -2516,52 +2516,52 @@ public class GeckoAppShell
             Looper.myLooper().quit();
         } else {
             msg.getTarget().dispatchMessage(msg);
         }
 
         return true;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static void notifyWakeLockChanged(String topic, String state) {
         if (getGeckoInterface() != null)
             getGeckoInterface().notifyWakeLockChanged(topic, state);
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public static void registerSurfaceTextureFrameListener(Object surfaceTexture, final int id) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
             @Override
             public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                 GeckoAppShell.onSurfaceTextureFrameAvailable(surfaceTexture, id);
             }
         });
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean unlockProfile() {
         // Try to kill any zombie Fennec's that might be running
         GeckoAppShell.killAnyZombies();
 
         // Then force unlock this profile
         if (getGeckoInterface() != null) {
             GeckoProfile profile = getGeckoInterface().getProfile();
             File lock = profile.getFile(".parentlock");
             return lock.exists() && lock.delete();
         }
         return false;
     }
 
-    @WrapElementForJNI(stubName = "GetProxyForURIWrapper")
+    @WrapForJNI(stubName = "GetProxyForURIWrapper")
     public static String getProxyForURI(String spec, String scheme, String host, int port) {
         final ProxySelector ps = new ProxySelector();
 
         Proxy proxy = ps.select(scheme, host);
         if (Proxy.NO_PROXY.equals(proxy)) {
             return "DIRECT";
         }
         
@@ -2648,22 +2648,22 @@ public class GeckoAppShell
     // Don't fail silently, tell the user that we weren't able to share the image
     private static final void showImageShareFailureToast() {
         Toast toast = Toast.makeText(getContext(),
                                      getContext().getResources().getString(R.string.share_image_failed),
                                      Toast.LENGTH_SHORT);
         toast.show();
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     static InputStream createInputStream(URLConnection connection) throws IOException {
         return connection.getInputStream();
     }
 
-    @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+    @WrapForJNI(allowMultithread = true, narrowChars = true)
     static URLConnection getConnection(String url) {
         try {
             String spec;
             if (url.startsWith("android://")) {
                 spec = url.substring(10);
             } else {
                 spec = url.substring(8);
             }
@@ -2674,28 +2674,28 @@ public class GeckoAppShell
                 spec = spec.replaceFirst("/", ":/");
             }
         } catch(Exception ex) {
             return null;
         }
         return null;
     }
 
-    @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+    @WrapForJNI(allowMultithread = true, narrowChars = true)
     static String connectionGetMimeType(URLConnection connection) {
         return connection.getContentType();
     }
 
     /**
      * Retrieve the absolute path of an external storage directory.
      *
      * @param type The type of directory to return
      * @return Absolute path of the specified directory or null on failure
      */
-    @WrapElementForJNI
+    @WrapForJNI
     static String getExternalPublicDirectory(final String type) {
         final String state = Environment.getExternalStorageState();
         if (!Environment.MEDIA_MOUNTED.equals(state) &&
             !Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
             // External storage is not available.
             return null;
         }
 
@@ -2714,17 +2714,17 @@ public class GeckoAppShell
         } else if ("music".equals(type)) {
             systemType = Environment.DIRECTORY_MUSIC;
         } else {
             return null;
         }
         return Environment.getExternalStoragePublicDirectory(systemType).getAbsolutePath();
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     static int getMaxTouchPoints() {
         PackageManager pm = getContext().getPackageManager();
         if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) {
             // at least, 5+ fingers.
             return 5;
         } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
             // at least, 2+ fingers.
             return 2;
--- a/mobile/android/base/GeckoJavaSampler.java
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 
 import java.lang.Thread;
 import java.util.Set;
 
 public class GeckoJavaSampler {
     private static final String LOGTAG = "JavaSampler";
     private static Thread sSamplingThread;
     private static SamplingThread sSamplingRunnable;
@@ -121,82 +121,82 @@ public class GeckoJavaSampler {
                 int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length;
                 return mSamples.get(aThreadId)[readPos];
             }
             return null;
         }
     }
 
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "GetThreadNameJavaProfilingWrapper")
+    @WrapForJNI(allowMultithread = true, stubName = "GetThreadNameJavaProfilingWrapper")
     public synchronized static String getThreadName(int aThreadId) {
         if (aThreadId == 0 && sMainThread != null) {
             return sMainThread.getName();
         }
         return null;
     }
 
     private synchronized static Sample getSample(int aThreadId, int aSampleId) {
         return sSamplingRunnable.getSample(aThreadId, aSampleId);
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "GetSampleTimeJavaProfiling")
+    @WrapForJNI(allowMultithread = true, stubName = "GetSampleTimeJavaProfiling")
     public synchronized static double getSampleTime(int aThreadId, int aSampleId) {
         Sample sample = getSample(aThreadId, aSampleId);
         if (sample != null) {
             if (sample.mJavaTime != 0) {
                 return (sample.mJavaTime -
                     SystemClock.elapsedRealtime()) + getProfilerTime();
             }
             System.out.println("Sample: " + sample.mTime);
             return sample.mTime;
         }
         return 0;
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "GetFrameNameJavaProfilingWrapper")
+    @WrapForJNI(allowMultithread = true, stubName = "GetFrameNameJavaProfilingWrapper")
     public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) {
         Sample sample = getSample(aThreadId, aSampleId);
         if (sample != null && aFrameId < sample.mFrames.length) {
             Frame frame = sample.mFrames[aFrameId];
             if (frame == null) {
                 return null;
             }
             return frame.className + "." + frame.methodName + "()";
         }
         return null;
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "StartJavaProfiling")
+    @WrapForJNI(allowMultithread = true, stubName = "StartJavaProfiling")
     public static void start(int aInterval, int aSamples) {
         synchronized (GeckoJavaSampler.class) {
             if (sSamplingRunnable != null) {
                 return;
             }
             sSamplingRunnable = new SamplingThread(aInterval, aSamples);
             sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler");
             sSamplingThread.start();
         }
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "PauseJavaProfiling")
+    @WrapForJNI(allowMultithread = true, stubName = "PauseJavaProfiling")
     public static void pause() {
         synchronized (GeckoJavaSampler.class) {
             sSamplingRunnable.mPauseSampler = true;
         }
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "UnpauseJavaProfiling")
+    @WrapForJNI(allowMultithread = true, stubName = "UnpauseJavaProfiling")
     public static void unpause() {
         synchronized (GeckoJavaSampler.class) {
             sSamplingRunnable.mPauseSampler = false;
         }
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "StopJavaProfiling")
+    @WrapForJNI(allowMultithread = true, stubName = "StopJavaProfiling")
     public static void stop() {
         synchronized (GeckoJavaSampler.class) {
             if (sSamplingThread == null) {
                 return;
             }
 
             sSamplingRunnable.mStopSampler = true;
             try {
--- a/mobile/android/base/RestrictedProfiles.java
+++ b/mobile/android/base/RestrictedProfiles.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.mozglue.RobocopTarget;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.restrictions.DefaultConfiguration;
 import org.mozilla.gecko.restrictions.GuestProfileConfiguration;
 import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
 import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.restrictions.RestrictionConfiguration;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -85,30 +85,30 @@ public class RestrictedProfiles {
 
         throw new IllegalArgumentException("Unknown action " + action);
     }
 
     private static boolean canLoadUrl(final Context context, final String url) {
         return getConfiguration(context).canLoadUrl(url);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean isUserRestricted() {
         return isUserRestricted(GeckoAppShell.getContext());
     }
 
     public static boolean isUserRestricted(final Context context) {
         return getConfiguration(context).isRestricted();
     }
 
     public static boolean isAllowed(final Context context, final Restriction restriction) {
         return getConfiguration(context).isAllowed(restriction);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean isAllowed(int action, String url) {
         final Restriction restriction;
         try {
             restriction = geckoActionToRestriction(action);
         } catch (IllegalArgumentException ex) {
             // Unknown actions represent a coding error, so we
             // refuse the action and log.
             Log.e(LOGTAG, "Unknown action " + action + "; check calling code.");
--- a/mobile/android/base/SurfaceBits.java
+++ b/mobile/android/base/SurfaceBits.java
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 
 import java.nio.ByteBuffer;
 
-@WrapElementForJNI
+@WrapForJNI
 public class SurfaceBits {
     public int width;
     public int height;
     public int format;
     public ByteBuffer buffer;
 }
--- a/mobile/android/base/ThumbnailHelper.java
+++ b/mobile/android/base/ThumbnailHelper.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.util.Log;
 import android.util.TypedValue;
 
 import java.nio.ByteBuffer;
 import java.util.LinkedList;
@@ -148,17 +148,17 @@ public final class ThumbnailHelper {
         }
 
         Log.d(LOGTAG, "Sending thumbnail event: " + mWidth + ", " + mHeight);
         GeckoEvent e = GeckoEvent.createThumbnailEvent(tab.getId(), mWidth, mHeight, mBuffer);
         GeckoAppShell.sendEventToGecko(e);
     }
 
     /* This method is invoked by JNI once the thumbnail data is ready. */
-    @WrapElementForJNI(stubName = "SendThumbnail")
+    @WrapForJNI(stubName = "SendThumbnail")
     public static void notifyThumbnail(ByteBuffer data, int tabId, boolean success, boolean shouldStore) {
         Tab tab = Tabs.getInstance().getTab(tabId);
         ThumbnailHelper helper = ThumbnailHelper.getInstance();
         if (success && tab != null) {
             helper.handleThumbnailData(tab, data, shouldStore ? CachePolicy.STORE : CachePolicy.NO_STORE);
         }
         helper.processNextThumbnail(tab);
     }
rename from mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
rename to mobile/android/base/annotation/WrapForJNI.java
--- a/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
+++ b/mobile/android/base/annotation/WrapForJNI.java
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-package org.mozilla.gecko.mozglue.generatorannotations;
+package org.mozilla.gecko.annotation;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
  * This annotation is used to tag methods that are to have wrapper methods generated in the android
@@ -19,17 +19,17 @@ import java.lang.annotation.Target;
  * generated method stub.
  *
  * allowMultithreaded should be used as sparingly as possible - the resulting code will allow the
  * Java method to be invoked from the C side from multiple threads. Often, this isn't what is wanted
  * and may lead to subtle bugs.
  */
 @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
 @Retention(RetentionPolicy.RUNTIME)
-public @interface WrapElementForJNI {
+public @interface WrapForJNI {
     // Optional parameter specifying the name of the generated method stub. If omitted, the name
     // of the Java method will be used.
     String stubName() default "";
 
     /**
      * If set, the generated method stub will support being called from any thread via the use of
      * GetJNIForThread. This is rarely useful, at time of writing, as well as possibly risky.
      * See information in AndroidBridge.cpp regarding GetJNIForThread.
--- a/mobile/android/base/gfx/DisplayPortMetrics.java
+++ b/mobile/android/base/gfx/DisplayPortMetrics.java
@@ -1,39 +1,39 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.util.FloatUtils;
 
 import android.graphics.RectF;
 
 /*
  * This class keeps track of the area we request Gecko to paint, as well
  * as the resolution of the paint. The area may be different from the visible
  * area of the page, and the resolution may be different from the resolution
  * used in the compositor to render the page. This is so that we can ask Gecko
  * to paint a much larger area without using extra memory, and then render some
  * subsection of that with compositor scaling.
  */
 public final class DisplayPortMetrics {
-    @WrapElementForJNI
+    @WrapForJNI
     public final float resolution;
-    @WrapElementForJNI
+    @WrapForJNI
     private final RectF mPosition;
 
     public DisplayPortMetrics() {
         this(0, 0, 0, 0, 1);
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     public DisplayPortMetrics(float left, float top, float right, float bottom, float resolution) {
         this.resolution = resolution;
         mPosition = new RectF(left, top, right, bottom);
     }
 
     public float getLeft() {
         return mPosition.left;
     }
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -1,20 +1,20 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.util.Log;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
 import javax.microedition.khronos.egl.EGLDisplay;
@@ -245,17 +245,17 @@ public class GLController {
             }
         }
         if (mEGLSurfaceForCompositor == null) {
             Log.w(LOGTAG, "eglCreateWindowSurface returned no surface!");
         }
         return mEGLSurfaceForCompositor != null;
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper")
+    @WrapForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper")
     private synchronized EGLSurface createEGLSurfaceForCompositor() {
         AttemptPreallocateEGLSurfaceForCompositor();
         EGLSurface result = mEGLSurfaceForCompositor;
         mEGLSurfaceForCompositor = null;
         return result;
     }
 
     private String getEGLError() {
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -1,23 +1,23 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.gfx.LayerView.DrawListener;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.mozglue.RobocopTarget;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.AppConstants;
 
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.SystemClock;
@@ -431,49 +431,49 @@ class GeckoLayerClient implements LayerV
             });
 
             setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
             mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
         }
         return mDisplayPort;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
         Tabs tabs = Tabs.getInstance();
         if (isBrowserContentDisplayed && tabs.isSelectedTabId(tabId)) {
             // for foreground tabs, send the viewport update unless the document
             // displayed is different from the content document. In that case, just
             // calculate the display port.
             return handleViewportMessage(metrics, pageSizeUpdate ? ViewportMessageType.PAGE_SIZE : ViewportMessageType.UPDATE);
         } else {
             // for background tabs, request a new display port calculation, so that
             // when we do switch to that tab, we have the correct display port and
             // don't need to draw twice (once to allow the first-paint viewport to
             // get to java, and again once java figures out the display port).
             return DisplayPortCalculator.calculate(metrics, null);
         }
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     void contentDocumentChanged() {
         mContentDocumentIsDisplayed = false;
     }
 
-    @WrapElementForJNI
+    @WrapForJNI
     boolean isContentDocumentDisplayed() {
         return mContentDocumentIsDisplayed;
     }
 
     // This is called on the Gecko thread to determine if we're still interested
     // in the update of this display-port to continue. We can return true here
     // to abort the current update and continue with any subsequent ones. This
     // is useful for slow-to-render pages when the display-port starts lagging
     // behind enough that continuing to draw it is wasted effort.
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public ProgressiveUpdateData progressiveUpdateCallback(boolean aHasPendingNewThebesContent,
                                                            float x, float y, float width, float height,
                                                            float resolution, boolean lowPrecision) {
         // Reset the checkerboard risk flag when switching to low precision
         // rendering.
         if (lowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
             // Skip low precision rendering until we're at risk of checkerboarding.
             if (!mProgressiveUpdateWasInDanger) {
@@ -577,17 +577,17 @@ class GeckoLayerClient implements LayerV
     }
 
     /** The compositor invokes this function just before compositing a frame where the document
       * is different from the document composited on the last frame. In these cases, the viewport
       * information we have in Java is no longer valid and needs to be replaced with the new
       * viewport information provided. setPageRect will never be invoked on the same frame that
       * this function is invoked on; and this function will always be called prior to syncViewportInfo.
       */
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
             float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
         synchronized (getLock()) {
             ImmutableViewportMetrics currentMetrics = getViewportMetrics();
 
             Tab tab = Tabs.getInstance().getSelectedTab();
 
             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
@@ -637,17 +637,17 @@ class GeckoLayerClient implements LayerV
         mContentDocumentIsDisplayed = true;
     }
 
     /** The compositor invokes this function whenever it determines that the page rect
       * has changed (based on the information it gets from layout). If setFirstPaintViewport
       * is invoked on a frame, then this function will not be. For any given frame, this
       * function will be invoked before syncViewportInfo.
       */
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
         synchronized (getLock()) {
             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
             float ourZoom = getViewportMetrics().zoomFactor;
             setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect);
             // Here the page size of the document has changed, but the document being displayed
             // is still the same. Therefore, we don't need to send anything to browser.js; any
             // changes we need to make to the display port will get sent the next time we call
@@ -658,17 +658,17 @@ class GeckoLayerClient implements LayerV
     /** The compositor invokes this function on every frame to figure out what part of the
       * page to display, and to inform Java of the current display port. Since it is called
       * on every frame, it needs to be ultra-fast.
       * It avoids taking any locks or allocating any objects. We keep around a
       * mCurrentViewTransform so we don't need to allocate a new ViewTransform
       * every time we're called. NOTE: we might be able to return a ImmutableViewportMetrics
       * which would avoid the copy into mCurrentViewTransform.
       */
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated) {
         // getViewportMetrics is thread safe so we don't need to synchronize.
         // We save the viewport metrics here, so we later use it later in
         // createFrame (which will be called by nsWindow::DrawWindowUnderlay on
         // the native side, by the compositor). The viewport
         // metrics can change between here and there, as it's accessed outside
         // of the compositor thread.
         mFrameMetrics = getViewportMetrics();
@@ -715,31 +715,31 @@ class GeckoLayerClient implements LayerV
             for (DrawListener listener : mDrawListeners) {
                 listener.drawFinished();
             }
         }
 
         return mCurrentViewTransform;
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public ViewTransform syncFrameMetrics(float offsetX, float offsetY, float zoom,
                 float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
                 boolean layersUpdated, int x, int y, int width, int height, float resolution,
                 boolean isFirstPaint)
     {
         if (isFirstPaint) {
             setFirstPaintViewport(offsetX, offsetY, zoom,
                                   cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
         }
 
         return syncViewportInfo(x, y, width, height, resolution, layersUpdated);
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public LayerRenderer.Frame createFrame() {
         // Create the shaders and textures if necessary.
         if (!mLayerRendererInitialized) {
             if (mLayerRenderer == null) {
                 return null;
             }
             mLayerRenderer.checkMonitoringEnabled();
             mLayerRenderer.createDefaultProgram();
@@ -749,22 +749,22 @@ class GeckoLayerClient implements LayerV
         try {
             return mLayerRenderer.createFrame(mFrameMetrics);
         } catch (Exception e) {
             Log.w(LOGTAG, e);
             return null;
         }
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public void activateProgram() {
         mLayerRenderer.activateDefaultProgram();
     }
 
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public void deactivateProgramAndRestoreState(boolean enableScissor,
             int scissorX, int scissorY, int scissorW, int scissorH)
     {
         mLayerRenderer.deactivateDefaultProgram();
         mLayerRenderer.restoreState(enableScissor, scissorX, scissorY, scissorW, scissorH);
     }
 
     private void geometryChanged(DisplayPortMetrics displayPort) {
--- a/mobile/android/base/gfx/ImmutableViewportMetrics.java
+++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java
@@ -1,16 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.util.FloatUtils;
 
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.util.DisplayMetrics;
 
 /**
  * ImmutableViewportMetrics are used to store the viewport metrics
@@ -48,17 +48,17 @@ public class ImmutableViewportMetrics {
         marginLeft = marginTop = marginRight = marginBottom = 0;
         zoomFactor = 1.0f;
         isRTL = false;
     }
 
     /** This constructor is used by native code in AndroidJavaWrappers.cpp, be
      * careful when modifying the signature.
      */
-    @WrapElementForJNI(allowMultithread = true)
+    @WrapForJNI(allowMultithread = true)
     public ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
         float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
         float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
         float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight,
         float aViewportRectBottom, float aZoomFactor)
     {
         this(aPageRectLeft, aPageRectTop,
              aPageRectRight, aPageRectBottom, aCssPageRectLeft,
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -5,27 +5,27 @@
 
 package org.mozilla.gecko.gfx;
 
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 
 import org.mozilla.gecko.AndroidGamepadManager;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.mozglue.RobocopTarget;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -510,31 +510,31 @@ public class LayerView extends FrameLayo
 
     public Object getNativeWindow() {
         if (mSurfaceView != null)
             return mSurfaceView.getHolder();
 
         return mTextureView.getSurfaceTexture();
     }
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "RegisterCompositorWrapper")
+    @WrapForJNI(allowMultithread = true, stubName = "RegisterCompositorWrapper")
     public static GLController registerCxxCompositor() {
         try {
             LayerView layerView = GeckoAppShell.getLayerView();
             GLController controller = layerView.getGLController();
             controller.compositorCreated();
             return controller;
         } catch (Exception e) {
             Log.e(LOGTAG, "Error registering compositor!", e);
             return null;
         }
     }
 
     //This method is called on the Gecko main thread.
-    @WrapElementForJNI(allowMultithread = true, stubName = "updateZoomedView")
+    @WrapForJNI(allowMultithread = true, stubName = "updateZoomedView")
     public static void updateZoomedView(ByteBuffer data) {
         LayerView layerView = GeckoAppShell.getLayerView();
         if (layerView != null) {
             LayerRenderer layerRenderer = layerView.getRenderer();
             if (layerRenderer != null) {
                 layerRenderer.updateZoomedView(data);
             }
         }
--- a/mobile/android/base/gfx/NativePanZoomController.java
+++ b/mobile/android/base/gfx/NativePanZoomController.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONObject;
 
 import android.graphics.PointF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -92,17 +92,17 @@ class NativePanZoomController implements
     public native void destroy();
     @Override
     public native boolean getRedrawHint();
     @Override
     public native void setOverScrollMode(int overscrollMode);
     @Override
     public native int getOverScrollMode();
 
-    @WrapElementForJNI(allowMultithread = true, stubName = "RequestContentRepaintWrapper")
+    @WrapForJNI(allowMultithread = true, stubName = "RequestContentRepaintWrapper")
     private void requestContentRepaint(float x, float y, float width, float height, float resolution) {
         mTarget.forceRedraw(new DisplayPortMetrics(x, y, x + width, y + height, resolution));
     }
 
     @Override
     public void setOverscrollHandler(final Overscroll listener) {
     }
 }
--- a/mobile/android/base/gfx/ProgressiveUpdateData.java
+++ b/mobile/android/base/gfx/ProgressiveUpdateData.java
@@ -1,24 +1,24 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 
 /**
  * This is the data structure that's returned by the progressive tile update
  * callback function. It encompasses the current viewport and a boolean value
  * representing whether the front-end is interested in the current progressive
  * update continuing.
  */
-@WrapElementForJNI
+@WrapForJNI
 public class ProgressiveUpdateData {
     public float x;
     public float y;
     public float scale;
     public boolean abort;
 
     public void setViewport(ImmutableViewportMetrics viewport) {
         this.x = viewport.viewportRectLeft;
--- a/mobile/android/base/gfx/ViewTransform.java
+++ b/mobile/android/base/gfx/ViewTransform.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 
-@WrapElementForJNI
+@WrapForJNI
 public class ViewTransform {
     public float x;
     public float y;
     public float scale;
     public float fixedLayerMarginLeft;
     public float fixedLayerMarginTop;
     public float fixedLayerMarginRight;
     public float fixedLayerMarginBottom;
--- a/mobile/android/base/mdns/MulticastDNSManager.java
+++ b/mobile/android/base/mdns/MulticastDNSManager.java
@@ -32,17 +32,18 @@ import java.util.Map;
  * @See nsIDNSServiceDiscovery.idl
  */
 public abstract class MulticastDNSManager {
     protected static final String LOGTAG = "GeckoMDNSManager";
     private static MulticastDNSManager instance = null;
 
     public static MulticastDNSManager getInstance(final Context context) {
         if (instance == null) {
-            if (Versions.feature16Plus) {
+            // Bug 1188935: There's a bug on Android 4.4 and before.
+            if (Versions.feature21Plus) {
                 instance = new NsdMulticastDNSManager(context);
             } else {
                 instance = new DummyMulticastDNSManager();
             }
         }
         return instance;
     }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -9,16 +9,17 @@ SPHINX_TREES['fennec'] = 'docs'
 
 include('android-services.mozbuild')
 
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
 constants_jar.sources = [
     'adjust/AdjustHelperInterface.java',
+    'annotation/WrapForJNI.java',
     'SysInfo.java',
 ]
 constants_jar.generated_sources = [
     'preprocessed/org/mozilla/gecko/AdjustConstants.java',
     'preprocessed/org/mozilla/gecko/AppConstants.java',
 ]
 
 if CONFIG['MOZ_INSTALL_TRACKING']:
@@ -49,17 +50,16 @@ resjar.generated_sources += ['android/su
 resjar.javac_flags += ['-Xlint:all']
 
 mgjar = add_java_jar('gecko-mozglue')
 mgjar.sources += [
     'mozglue/ByteBufferInputStream.java',
     'mozglue/ContextUtils.java',
     'mozglue/DirectBufferAllocator.java',
     'mozglue/GeckoLoader.java',
-    'mozglue/generatorannotations/WrapElementForJNI.java',
     'mozglue/JNIObject.java',
     'mozglue/JNITarget.java',
     'mozglue/NativeReference.java',
     'mozglue/NativeZip.java',
     'mozglue/RobocopTarget.java',
     'mozglue/WebRTCJNITarget.java',
 ]
 mgjar.generated_sources = [] # Keep it this way.
@@ -128,16 +128,17 @@ if CONFIG['MOZ_WEBRTC']:
         video_render_root + 'ViERenderer.java',
     ]
     wrjar.sources += [
         audio_root + 'AudioManagerAndroid.java',
         audio_root + 'WebRtcAudioRecord.java',
         audio_root + 'WebRtcAudioTrack.java',
     ]
     wrjar.extra_jars = [
+        'constants.jar',
         'gecko-R.jar',
         'gecko-browser.jar',
         'gecko-util.jar',
         'gecko-mozglue.jar',
     ]
     wrjar.javac_flags += ['-Xlint:all,-deprecation,-cast']
 
 gbjar = add_java_jar('gecko-browser')
--- a/mobile/android/base/mozglue/NativeZip.java
+++ b/mobile/android/base/mozglue/NativeZip.java
@@ -1,16 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.mozglue;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.mozglue.JNITarget;
 
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.util.zip.Inflater;
 import java.util.zip.InflaterInputStream;
 
 public class NativeZip implements NativeReference {
     private static final int DEFLATE = 8;
@@ -64,17 +64,17 @@ public class NativeZip implements Native
         return _getInputStream(mObj, path);
     }
 
     private static native long getZip(String path);
     private static native long getZipFromByteBuffer(ByteBuffer buffer);
     private static native void _release(long obj);
     private native InputStream _getInputStream(long obj, String path);
 
-    @WrapElementForJNI
+    @JNITarget
     private InputStream createInputStream(ByteBuffer buffer, int compression) {
         if (compression != STORE && compression != DEFLATE) {
             throw new IllegalArgumentException("Unexpected compression: " + compression);
         }
 
         InputStream input = new ByteBufferInputStream(buffer, this);
         if (compression == DEFLATE) {
             Inflater inflater = new Inflater(true);
--- a/mobile/android/base/sqlite/MatrixBlobCursor.java
+++ b/mobile/android/base/sqlite/MatrixBlobCursor.java
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 package org.mozilla.gecko.sqlite;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import android.database.AbstractCursor;
 import android.database.CursorIndexOutOfBoundsException;
 import android.util.Log;
 
 /**
  * A mutable cursor implementation backed by an array of {@code Object}s. Use
  * {@link #newRow()} to add rows. Automatically expands internal capacity
@@ -51,17 +51,17 @@ public class MatrixBlobCursor extends Ab
 
     /**
      * Constructs a new cursor with the given initial capacity.
      *
      * @param columnNames names of the columns, the ordering of which
      *  determines column ordering elsewhere in this cursor
      * @param initialCapacity in rows
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public MatrixBlobCursor(String[] columnNames, int initialCapacity) {
         this.columnNames = columnNames;
         this.columnCount = columnNames.length;
 
         if (initialCapacity < 1) {
             initialCapacity = 1;
         }
 
@@ -72,17 +72,17 @@ public class MatrixBlobCursor extends Ab
     }
 
     /**
      * Constructs a new cursor.
      *
      * @param columnNames names of the columns, the ordering of which
      *  determines column ordering elsewhere in this cursor
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public MatrixBlobCursor(String[] columnNames) {
         this(columnNames, 16);
     }
 
     /**
      * Closes the Cursor, releasing all of its resources.
      */
     public void close() {
@@ -127,17 +127,17 @@ public class MatrixBlobCursor extends Ab
      * Adds a new row to the end with the given column values. Not safe
      * for concurrent use.
      *
      * @throws IllegalArgumentException if {@code columnValues.length !=
      *  columnNames.length}
      * @param columnValues in the same order as the the column names specified
      *  at cursor construction time
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public void addRow(Object[] columnValues) {
         if (columnValues.length != columnCount) {
             throw new IllegalArgumentException("columnNames.length = "
                     + columnCount + ", columnValues.length = "
                     + columnValues.length);
         }
 
         int start = rowCount++ * columnCount;
@@ -149,17 +149,17 @@ public class MatrixBlobCursor extends Ab
      * Adds a new row to the end with the given column values. Not safe
      * for concurrent use.
      *
      * @throws IllegalArgumentException if {@code columnValues.size() !=
      *  columnNames.length}
      * @param columnValues in the same order as the the column names specified
      *  at cursor construction time
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public void addRow(Iterable<?> columnValues) {
         final int start = rowCount * columnCount;
 
         if (columnValues instanceof ArrayList<?>) {
             addRow((ArrayList<?>) columnValues, start);
             return;
         }
 
@@ -183,17 +183,17 @@ public class MatrixBlobCursor extends Ab
                     "columnValues.size() < columnNames.length");
         }
 
         // Increase row count here in case we encounter an exception.
         rowCount++;
     }
 
     /** Optimization for {@link ArrayList}. */
-    @WrapElementForJNI
+    @WrapForJNI
     private void addRow(ArrayList<?> columnValues, int start) {
         final int size = columnValues.size();
         if (size != columnCount) {
             throw new IllegalArgumentException("columnNames.length = "
                     + columnCount + ", columnValues.size() = " + size);
         }
 
         final int end = start + columnCount;
--- a/mobile/android/base/sqlite/SQLiteBridgeException.java
+++ b/mobile/android/base/sqlite/SQLiteBridgeException.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sqlite;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 
-@WrapElementForJNI
+@WrapForJNI
 public class SQLiteBridgeException extends RuntimeException {
     static final long serialVersionUID = 1L;
 
     public SQLiteBridgeException() {}
     public SQLiteBridgeException(String msg) {
         super(msg);
     }
 }
--- a/mobile/android/base/util/Clipboard.java
+++ b/mobile/android/base/util/Clipboard.java
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
 import java.util.concurrent.SynchronousQueue;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import android.content.ClipData;
 import android.content.Context;
 import android.util.Log;
 
 public final class Clipboard {
     // Volatile but not synchronized: we don't care about the race condition in
     // init, because both app contexts will be the same, but we do care about a
@@ -27,17 +27,17 @@ public final class Clipboard {
     public static void init(final Context c) {
         if (mContext != null) {
             Log.w(LOGTAG, "Clipboard.init() called twice!");
             return;
         }
         mContext = c.getApplicationContext();
     }
 
-    @WrapElementForJNI(stubName = "GetClipboardTextWrapper")
+    @WrapForJNI(stubName = "GetClipboardTextWrapper")
     public static String getText() {
         // If we're on the UI thread or the background thread, we have a looper on the thread
         // and can just call this directly. For any other threads, post the call to the
         // background thread.
 
         if (ThreadUtils.isOnUiThread() || ThreadUtils.isOnBackgroundThread()) {
             return getClipboardTextImpl();
         }
@@ -54,17 +54,17 @@ public final class Clipboard {
 
         try {
             return sClipboardQueue.take();
         } catch (InterruptedException ie) {
             return "";
         }
     }
 
-    @WrapElementForJNI(stubName = "SetClipboardText")
+    @WrapForJNI(stubName = "SetClipboardText")
     public static void setText(final CharSequence text) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             @SuppressWarnings("deprecation")
             public void run() {
                 // In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager,
                 // which is a subclass of android.text.ClipboardManager.
                 if (Versions.feature11Plus) {
@@ -85,32 +85,32 @@ public final class Clipboard {
                 cm.setText(text);
             }
         });
     }
 
     /**
      * @return true if the clipboard is nonempty, false otherwise.
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public static boolean hasText() {
         if (Versions.feature11Plus) {
             android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
             return cm.hasPrimaryClip();
         }
 
         // Deprecated.
         android.text.ClipboardManager cm = (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
         return cm.hasText();
     }
 
     /**
      * Deletes all text from the clipboard.
      */
-    @WrapElementForJNI
+    @WrapForJNI
     public static void clearText() {
         setText(null);
     }
 
     /**
      * On some devices, access to the clipboard service needs to happen
      * on a thread with a looper, so this function requires a looper is
      * present on the thread.
--- a/mobile/android/base/util/NativeJSContainer.java
+++ b/mobile/android/base/util/NativeJSContainer.java
@@ -1,28 +1,27 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNITarget;
 
 /**
  * NativeJSContainer is a wrapper around the SpiderMonkey JSAPI to make it possible to
  * access Javascript objects in Java.
  *
  * A container must only be used on the thread it is attached to. To use it on another
  * thread, call {@link #clone()} to make a copy, and use the copy on the other thread.
  * When a copy is first used, it becomes attached to the thread using it.
  */
-@JNITarget
-@WrapElementForJNI
+@WrapForJNI
 public final class NativeJSContainer extends NativeJSObject
 {
     private NativeJSContainer() {
     }
 
     /**
      * Make a copy of this container for use by another thread. When the copy is first used,
      * it becomes attached to the thread using it.
--- a/mobile/android/base/util/NativeJSObject.java
+++ b/mobile/android/base/util/NativeJSObject.java
@@ -1,27 +1,26 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
-import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.mozglue.JNITarget;
 
 import android.os.Bundle;
 
 /**
  * NativeJSObject is a wrapper around the SpiderMonkey JSAPI to make it possible to
  * access Javascript objects in Java.
  */
-@JNITarget
-@WrapElementForJNI
+@WrapForJNI
 public class NativeJSObject extends JNIObject
 {
     @SuppressWarnings("serial")
     @JNITarget
     public static final class InvalidPropertyException extends RuntimeException {
         public InvalidPropertyException(final String msg) {
             super(msg);
         }
--- a/mobile/android/config/proguard/proguard.cfg
+++ b/mobile/android/config/proguard/proguard.cfg
@@ -174,30 +174,30 @@
 -keepclasseswithmembers class * {
     @org.mozilla.gecko.mozglue.WebRTCJNITarget <methods>;
 }
 -keepclasseswithmembers class * {
     @org.mozilla.gecko.mozglue.WebRTCJNITarget <fields>;
 }
 
 # Keep generator-targeted entry points.
--keep @interface org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI
--keep @org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI class *
+-keep @interface org.mozilla.gecko.annotation.WrapForJNI
+-keep @org.mozilla.gecko.annotation.WrapForJNI class *
 -keepclassmembers class * {
-    @org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI *;
+    @org.mozilla.gecko.annotation.WrapForJNI *;
 }
 -keepclasseswithmembers class * {
-    @org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI <methods>;
+    @org.mozilla.gecko.annotation.WrapForJNI <methods>;
 }
 -keepclasseswithmembers class * {
-    @org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI <fields>;
+    @org.mozilla.gecko.annotation.WrapForJNI <fields>;
 }
 
 # Keep all members of an annotated class.
--keepclassmembers @org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI class * {
+-keepclassmembers @org.mozilla.gecko.annotation.WrapForJNI class * {
     *;
 }
 
 -keep class **.R$*
 
 # Keep classes, and all their contents, compiled before mozglue.RobocopTarget.
 -keep class org.mozilla.gecko.AppConstants {
     *;
--- a/netwerk/dns/mdns/libmdns/moz.build
+++ b/netwerk/dns/mdns/libmdns/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android' and CONFIG['ANDROID_VERSION'] >= '21':
     EXTRA_COMPONENTS += [
         'nsDNSServiceDiscovery.js',
         'nsDNSServiceDiscovery.manifest',
     ]
 
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16':
     UNIFIED_SOURCES += [
         'MDNSResponderOperator.cpp',
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -80,18 +80,16 @@ const TELEMETRY_INTERVAL = 60000;
 // Delay before intializing telemetry (ms)
 const TELEMETRY_DELAY = 60000;
 // Delay before initializing telemetry if we're testing (ms)
 const TELEMETRY_TEST_DELAY = 100;
 // Execute a scheduler tick every 5 minutes.
 const SCHEDULER_TICK_INTERVAL_MS = 5 * 60 * 1000;
 // When user is idle, execute a scheduler tick every 60 minutes.
 const SCHEDULER_TICK_IDLE_INTERVAL_MS = 60 * 60 * 1000;
-// The maximum number of times a scheduled operation can fail.
-const SCHEDULER_RETRY_ATTEMPTS = 3;
 
 // The tolerance we have when checking if it's midnight (15 minutes).
 const SCHEDULER_MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;
 
 // Coalesce the daily and aborted-session pings if they are both due within
 // two minutes from each other.
 const SCHEDULER_COALESCE_THRESHOLD_MS = 2 * 60 * 1000;
 
@@ -376,19 +374,16 @@ let TelemetryScheduler = {
   _lastSessionCheckpointTime: 0,
 
   // For sanity checking.
   _lastAdhocPingTime: 0,
   _lastTickTime: 0,
 
   _log: null,
 
-  // The number of times a daily ping fails.
-  _dailyPingRetryAttempts: 0,
-
   // The timer which drives the scheduler.
   _schedulerTimer: null,
   // The interval used by the scheduler timer.
   _schedulerInterval: 0,
   _shuttingDown: true,
   _isUserIdle: false,
 
   /**
@@ -541,35 +536,31 @@ let TelemetryScheduler = {
     }
     this._lastTickTime = now;
 
     // Check if the daily ping is due.
     const shouldSendDaily = this._isDailyPingDue(nowDate);
 
     if (shouldSendDaily) {
       this._log.trace("_schedulerTickLogic - Daily ping due.");
-      return Impl._sendDailyPing().then(() => this._dailyPingSucceeded(now),
-                                        () => this._dailyPingFailed(now));
+      this._lastDailyPingTime = now;
+      return Impl._sendDailyPing();
     }
 
     // Check if the aborted-session ping is due. If a daily ping was saved above, it was
     // already duplicated as an aborted-session ping.
     const isAbortedPingDue =
       (now - this._lastSessionCheckpointTime) >= ABORTED_SESSION_UPDATE_INTERVAL_MS;
     if (isAbortedPingDue) {
       this._log.trace("_schedulerTickLogic - Aborted session ping due.");
       return this._saveAbortedPing(now);
     }
 
     // No ping is due.
     this._log.trace("_schedulerTickLogic - No ping due.");
-    // It's possible, because of sleeps, that we're no longer within midnight tolerance for
-    // daily pings. Because of that, daily retry attempts would not be 0 on the next midnight.
-    // Reset that count on do-nothing ticks.
-    this._dailyPingRetryAttempts = 0;
     return Promise.resolve();
   },
 
   /**
    * Update the scheduled pings if some other ping was sent.
    * @param {String} reason The reason of the ping that was sent.
    * @param {Object} [competingPayload=null] The payload of the ping that was sent. The
    *                 reason of this payload will be changed.
@@ -593,43 +584,16 @@ let TelemetryScheduler = {
         this._lastDailyPingTime = now.getTime();
       }
     }
 
     this._rescheduleTimeout();
   },
 
   /**
-   * Called when a scheduled operation successfully completes (ping sent or saved).
-   * @param {Number} now The current time, in milliseconds.
-   */
-  _dailyPingSucceeded: function(now) {
-    this._log.trace("_dailyPingSucceeded");
-    this._lastDailyPingTime = now;
-    this._dailyPingRetryAttempts = 0;
-  },
-
-  /**
-   * Called when a scheduled operation fails (ping sent or saved).
-   * @param {Number} now The current time, in milliseconds.
-   */
-  _dailyPingFailed: function(now) {
-    this._log.error("_dailyPingFailed");
-    this._dailyPingRetryAttempts++;
-
-    // If we reach the maximum number of retry attempts for a daily ping, log the error
-    // and skip this daily ping.
-    if (this._dailyPingRetryAttempts >= SCHEDULER_RETRY_ATTEMPTS) {
-      this._log.error("_pingFailed - The daily ping failed too many times. Skipping it.");
-      this._dailyPingRetryAttempts = 0;
-      this._lastDailyPingTime = now;
-    }
-  },
-
-  /**
    * Stops the scheduler.
    */
   shutdown: function() {
     if (this._shuttingDown) {
       if (this._log) {
         this._log.error("shutdown - Already shut down");
       } else {
         Cu.reportError("TelemetryScheduler.shutdown - Already shut down");
@@ -1889,18 +1853,18 @@ let Impl = {
       addEnvironment: true,
     };
 
     let promise = TelemetryController.submitExternalPing(getPingType(payload), payload, options);
 
     // Also save the payload as an aborted session. If we delay this, aborted-session can
     // lag behind for the profileSubsessionCounter and other state, complicating analysis.
     if (IS_UNIFIED_TELEMETRY) {
-      let abortedPromise = this._saveAbortedSessionPing(payload);
-      promise = promise.then(() => abortedPromise);
+      this._saveAbortedSessionPing(payload)
+          .catch(e => this._log.error("_sendDailyPing - Failed to save the aborted session ping", e));
     }
 
     return promise;
   },
 
   /**
    * Loads session data from the session data file.
    * @return {Promise<boolean>} A promise which is resolved with a true argument when
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -1677,12 +1677,112 @@ add_task(function* test_schedulerUserIdl
   now.setMinutes(50);
   fakeNow(now);
   fakeIdleNotification("idle");
   Assert.equal(schedulerTimeout, 10 * 60 * 1000);
 
   yield TelemetrySession.shutdown();
 });
 
+add_task(function* test_DailyDueAndIdle() {
+  if (gIsAndroid || gIsGonk) {
+    // We don't have the aborted session or the daily ping here.
+    return;
+  }
+
+  yield TelemetrySession.reset();
+  clearPendingPings();
+  PingServer.clearRequests();
+
+  let receivedPingRequest = null;
+  // Register a ping handler that will assert when receiving multiple daily pings.
+  PingServer.registerPingHandler(req => {
+    Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping.");
+    receivedPingRequest = req;
+  });
+
+  let schedulerTickCallback = null;
+  let now = new Date(2030, 1, 1, 0, 0, 0);
+  fakeNow(now);
+  // Fake scheduler functions to control daily collection flow in tests.
+  fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
+  yield TelemetrySession.setup();
+
+  // Trigger the daily ping.
+  let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0);
+  fakeNow(firstDailyDue);
+
+  // Run a scheduler tick: it should trigger the daily ping.
+  Assert.ok(!!schedulerTickCallback);
+  let tickPromise = schedulerTickCallback();
+
+  // Send an idle and then an active user notification.
+  fakeIdleNotification("idle");
+  fakeIdleNotification("active");
+
+  // Wait on the tick promise.
+  yield tickPromise;
+
+  yield TelemetrySend.testWaitOnOutgoingPings();
+
+  // Decode the ping contained in the request and check that's a daily ping.
+  Assert.ok(receivedPingRequest, "Telemetry must send one daily ping.");
+  const receivedPing = decodeRequestPayload(receivedPingRequest);
+  checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true);
+  Assert.equal(receivedPing.payload.info.reason, REASON_DAILY);
+
+  yield TelemetrySession.shutdown();
+});
+
+add_task(function* test_userIdleAndSchedlerTick() {
+  if (gIsAndroid || gIsGonk) {
+    // We don't have the aborted session or the daily ping here.
+    return;
+  }
+
+  yield TelemetrySession.reset();
+  clearPendingPings();
+  PingServer.clearRequests();
+
+  let receivedPingRequest = null;
+  // Register a ping handler that will assert when receiving multiple daily pings.
+  PingServer.registerPingHandler(req => {
+    Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping.");
+    receivedPingRequest = req;
+  });
+
+  let schedulerTickCallback = null;
+  let now = new Date(2030, 1, 1, 0, 0, 0);
+  fakeNow(now);
+  // Fake scheduler functions to control daily collection flow in tests.
+  fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
+  yield TelemetrySession.setup();
+
+  // Move the current date/time to midnight.
+  let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0);
+  fakeNow(firstDailyDue);
+
+  // The active notification should trigger a scheduler tick. The latter will send the
+  // due daily ping.
+  fakeIdleNotification("active");
+
+  // Immediately running another tick should not send a daily ping again.
+  Assert.ok(!!schedulerTickCallback);
+  yield schedulerTickCallback();
+
+  // A new "idle" notification should not send a new daily ping.
+  fakeIdleNotification("idle");
+
+  yield TelemetrySend.testWaitOnOutgoingPings();
+
+  // Decode the ping contained in the request and check that's a daily ping.
+  Assert.ok(receivedPingRequest, "Telemetry must send one daily ping.");
+  const receivedPing = decodeRequestPayload(receivedPingRequest);
+  checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true);
+  Assert.equal(receivedPing.payload.info.reason, REASON_DAILY);
+
+  yield TelemetrySession.shutdown();
+});
+
 add_task(function* stopServer(){
   yield PingServer.stop();
   do_test_finished();
 });
--- a/toolkit/devtools/gcli/commands/rulers.js
+++ b/toolkit/devtools/gcli/commands/rulers.js
@@ -54,42 +54,42 @@ exports.items = [
   // The server rulers command is hidden by default, it's just used by the
   // client command.
   {
     name: "rulers_server",
     runAt: "server",
     hidden: true,
     exec: function(args, context) {
       let env = context.environment;
+      let { document } = env;
 
       // Calling the command again after the rulers have been shown once hides
       // them.
-      if (highlighters.has(env.document)) {
-        let { highlighter, environment } = highlighters.get(env.document);
+      if (highlighters.has(document)) {
+        let { highlighter } = highlighters.get(document);
         highlighter.destroy();
-        environment.destroy();
         return false;
       }
 
       // Otherwise, display the rulers.
       let environment = new HighlighterEnvironment();
       environment.initFromWindow(env.window);
       let highlighter = new RulersHighlighter(environment);
 
       // Store the instance of the rulers highlighter for this document so we
       // can hide it later.
-      highlighters.set(env.document, { highlighter, environment });
+      highlighters.set(document, { highlighter, environment });
 
       // Listen to the highlighter's destroy event which may happen if the
       // window is refreshed or closed with the rulers shown.
       events.once(highlighter, "destroy", () => {
-        if (highlighters.has(env.document)) {
-          let { environment } = highlighters.get(env.document);
+        if (highlighters.has(document)) {
+          let { environment } = highlighters.get(document);
           environment.destroy();
-          highlighters.delete(env.document);
+          highlighters.delete(document);
         }
       });
 
       highlighter.show();
       return true;
     }
   }
 ];
--- a/toolkit/devtools/server/actors/gcli.js
+++ b/toolkit/devtools/server/actors/gcli.js
@@ -260,17 +260,17 @@ const GcliActor = ActorClass({
           throw new Error("environment.chromeDocument is not available in runAt:server commands");
         },
 
         get window() {
           return tabActor.window;
         },
 
         get document() {
-          return tabActor.window.document;
+          return tabActor.window && tabActor.window.document;
         }
       };
 
       return new Requisition(this._system, { environment: environment });
     });
 
     return this._requisitionPromise;
   },
--- a/toolkit/devtools/server/actors/highlighter.css
+++ b/toolkit/devtools/server/actors/highlighter.css
@@ -243,25 +243,16 @@
 :-moz-native-anonymous .rulers-highlighter-elements > g {
   opacity: 0.8;
 }
 
 :-moz-native-anonymous .rulers-highlighter-elements > g > rect {
   fill: #fff;
 }
 
-/*
-  Setting `stroke-width` with a low floating point number, ensure that `path`
-  draws always 1px despites the zoom level, with `crispEdges` on the ancestor.
-*/
-:-moz-native-anonymous .rulers-highlighter-ruler-graduations,
-:-moz-native-anonymous .rulers-highlighter-ruler-markers {
-  stroke-width: 0.0001;
-}
-
 :-moz-native-anonymous .rulers-highlighter-ruler-graduations {
   stroke: #bebebe;
 }
 
 :-moz-native-anonymous .rulers-highlighter-ruler-markers {
   stroke: #202020;
 }
 
@@ -277,9 +268,8 @@
 :-moz-native-anonymous .rulers-highlighter-horizontal-labels > text {
   text-anchor: left;
 }
 
 :-moz-native-anonymous .rulers-highlighter-vertical-labels > text {
   transform: rotate(-90deg);
   text-anchor: end;
 }
-
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -2928,36 +2928,80 @@ RulersHighlighter.prototype = {
     this.markup.getElement(`${prefix}x-axis-text`)
                         .setAttribute("transform", `translate(${-scrollX})`);
     this.markup.getElement(`${prefix}y-axis-ruler`)
                         .setAttribute("transform", `translate(0, ${-scrollY})`);
     this.markup.getElement(`${prefix}y-axis-text`)
                         .setAttribute("transform", `translate(0, ${-scrollY})`);
   },
 
+  _update: function() {
+    setIgnoreLayoutChanges(true);
+
+    let zoom = LayoutHelpers.getCurrentZoom(this.win);
+    let isZoomChanged = zoom !== this._zoom;
+
+    if (isZoomChanged) {
+      this._zoom = zoom;
+      this.updateViewport();
+    }
+
+    setIgnoreLayoutChanges(false, this.win.document.documentElement);
+
+    this._rafID = this.win.requestAnimationFrame(() => this._update());
+  },
+
+  _cancelUpdate: function() {
+    if (this._rafID) {
+      this.win.cancelAnimationFrame(this._rafID);
+      this._rafID = 0;
+    }
+  },
+  updateViewport: function() {
+    let { devicePixelRatio } = this.win;
+
+    // Because `devicePixelRatio` is affected by zoom (see bug 809788),
+    // in order to get the "real" device pixel ratio, we need divide by `zoom`
+    let pixelRatio = devicePixelRatio / this._zoom;
+
+    // The "real" device pixel ratio is used to calculate the max stroke
+    // width we can actually assign: on retina, for instance, it would be 0.5,
+    // where on non high dpi monitor would be 1.
+    let minWidth = 1 / pixelRatio;
+    let strokeWidth = Math.min(minWidth, minWidth / this._zoom);
+
+    this.markup.getElement(this.ID_CLASS_PREFIX + "root").setAttribute("style",
+      `stroke-width:${strokeWidth};`);
+  },
+
   destroy: function() {
     this.hide();
 
     this.win.removeEventListener("scroll", this, true);
     this.win.removeEventListener("pagehide", this, true);
 
     this.markup.destroy();
 
     events.emit(this, "destroy");
   },
 
   show: function() {
     this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
       "hidden");
+
+    this._update();
+
     return true;
   },
 
   hide: function() {
     this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "elements",
       "hidden", "true");
+
+    this._cancelUpdate();
   }
 };
 
 register(RulersHighlighter);
 exports.RulersHighlighter = RulersHighlighter;
 
 /**
  * The SimpleOutlineHighlighter is a class that has the same API than the
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1209,26 +1209,16 @@ float ViewTransform::Y() const
     return mozilla::jni::Field<Y_t>::Get(this, nullptr);
 }
 
 void ViewTransform::Y(float a0) const
 {
     return mozilla::jni::Field<Y_t>::Set(this, nullptr, a0);
 }
 
-constexpr char NativeZip::name[];
-
-constexpr char NativeZip::CreateInputStream_t::name[];
-constexpr char NativeZip::CreateInputStream_t::signature[];
-
-mozilla::jni::Object::LocalRef NativeZip::CreateInputStream(mozilla::jni::Object::Param a0, int32_t a1) const
-{
-    return mozilla::jni::Method<CreateInputStream_t>::Call(this, nullptr, a0, a1);
-}
-
 constexpr char MatrixBlobCursor::name[];
 
 constexpr char MatrixBlobCursor::New_t::name[];
 constexpr char MatrixBlobCursor::New_t::signature[];
 
 MatrixBlobCursor::LocalRef MatrixBlobCursor::New(mozilla::jni::ObjectArray::Param a0)
 {
     return mozilla::jni::Constructor<New_t>::Call(nullptr, nullptr, a0);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -2821,51 +2821,16 @@ public:
     };
 
     float Y() const;
 
     void Y(float) const;
 
 };
 
-class NativeZip : public mozilla::jni::Class<NativeZip>
-{
-public:
-    typedef mozilla::jni::Ref<NativeZip> Ref;
-    typedef mozilla::jni::LocalRef<NativeZip> LocalRef;
-    typedef mozilla::jni::GlobalRef<NativeZip> GlobalRef;
-    typedef const mozilla::jni::Param<NativeZip>& Param;
-
-    static constexpr char name[] =
-            "org/mozilla/gecko/mozglue/NativeZip";
-
-protected:
-    NativeZip(jobject instance) : Class(instance) {}
-
-public:
-    struct CreateInputStream_t {
-        typedef NativeZip Owner;
-        typedef mozilla::jni::Object::LocalRef ReturnType;
-        typedef mozilla::jni::Object::Param SetterType;
-        typedef mozilla::jni::Args<
-                mozilla::jni::Object::Param,
-                int32_t> Args;
-        static constexpr char name[] = "createInputStream";
-        static constexpr char signature[] =
-                "(Ljava/nio/ByteBuffer;I)Ljava/io/InputStream;";
-        static const bool isStatic = false;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    mozilla::jni::Object::LocalRef CreateInputStream(mozilla::jni::Object::Param, int32_t) const;
-
-};
-
 class MatrixBlobCursor : public mozilla::jni::Class<MatrixBlobCursor>
 {
 public:
     typedef mozilla::jni::Ref<MatrixBlobCursor> Ref;
     typedef mozilla::jni::LocalRef<MatrixBlobCursor> LocalRef;
     typedef mozilla::jni::GlobalRef<MatrixBlobCursor> GlobalRef;
     typedef const mozilla::jni::Param<MatrixBlobCursor>& Param;