Merge m-c to b2g-inbound on a CLOSED TREE. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 30 Jul 2014 17:17:58 -0400
changeset 197003 60eeadfddc0e9bd15fc899ae7a1a9b2b1483e279
parent 197002 9b069f2afd7f3629e6753668d55c20fb666ad63d (current diff)
parent 196928 005424a764da20705e205aeb0083b2ae7f4b559d (diff)
child 197004 a22de27ba8d772457f5cce1a0386a1eadc6772b8
push id47022
push usercbook@mozilla.com
push dateThu, 31 Jul 2014 11:07:00 +0000
treeherdermozilla-inbound@597be00ffc53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.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 m-c to b2g-inbound on a CLOSED TREE. a=merge
browser/components/loop/content/libs/sdk-content/css/ot.css
browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-chrome.png
browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-copy-firefox.png
browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-firefox.png
browser/components/loop/content/libs/sdk-content/images/rtc/access-predenied-chrome.png
browser/components/loop/content/libs/sdk-content/images/rtc/access-prompt-chrome.png
browser/components/loop/content/libs/sdk-content/images/rtc/audioonly-publisher.png
browser/components/loop/content/libs/sdk-content/images/rtc/audioonly-subscriber.png
browser/components/loop/content/libs/sdk-content/images/rtc/buttons.png
browser/components/loop/content/libs/sdk-content/images/rtc/loader.gif
browser/components/loop/content/libs/sdk-content/images/rtc/mic-off.png
browser/components/loop/content/libs/sdk-content/images/rtc/mic-on.png
browser/components/loop/content/libs/sdk-content/images/rtc/speaker-off.png
browser/components/loop/content/libs/sdk-content/images/rtc/speaker-on.png
browser/components/loop/content/libs/sdk-content/js/dynamic_config.min.js
browser/components/loop/content/libs/sdk.js
xpcom/tests/TestRefPtr.cpp
--- a/accessible/jsat/moz.build
+++ b/accessible/jsat/moz.build
@@ -1,17 +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/.
 
-JS_MODULES_PATH = 'modules/accessibility'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.accessibility += [
     'AccessFu.jsm',
     'Constants.jsm',
     'ContentControl.jsm',
     'EventManager.jsm',
     'Gestures.jsm',
     'OutputGenerator.jsm',
     'PointerAdapter.jsm',
     'Presentation.jsm',
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -3,13 +3,11 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 DIRS += ["source/modules/system"]
 
-JS_MODULES_PATH = 'modules/sdk'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.sdk += [
     'source/app-extension/bootstrap.js',
 ]
--- a/addon-sdk/source/modules/system/moz.build
+++ b/addon-sdk/source/modules/system/moz.build
@@ -1,11 +1,9 @@
 # -*- 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/.
 
-JS_MODULES_PATH = 'modules/sdk/system'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.sdk.system += [
     'XulApp.js',
 ]
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -484,16 +484,17 @@
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
 @BINPATH@/components/FormHistoryStartup.js
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/formautofill.manifest
 @BINPATH@/components/FormAutofillContentService.js
+@BINPATH@/components/FormAutofillStartup.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2531,17 +2531,17 @@ let BrowserOnClick = {
         secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
 
         if (isMalware) {
           // Get the stop badware "why is this blocked" report url,
           // append the current url, and go there.
           try {
             let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
             reportURL += location;
-            content.location = reportURL;
+            gBrowser.loadURI(reportURL);
           } catch (e) {
             Components.utils.reportError("Couldn't get malware report URL: " + e);
           }
         }
         else { // It's a phishing site, not malware
           openHelpLink("phishing-malware", false, "current");
         }
         break;
@@ -2556,26 +2556,27 @@ let BrowserOnClick = {
   /**
    * This functions prevents navigation from happening directly through the <a>
    * link in about:newtab (which is loaded in the parent and therefore would load
    * the next page also in the parent) and instructs the browser to open the url
    * in the current tab which will make it update the remoteness of the tab.
    */
   onE10sAboutNewTab: function(event, ownerDoc) {
     let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
-    if (!isTopFrame || event.button != 0) {
+    if (!isTopFrame) {
       return;
     }
 
     let anchorTarget = event.originalTarget.parentNode;
 
     if (anchorTarget instanceof HTMLAnchorElement &&
         anchorTarget.classList.contains("newtab-link")) {
       event.preventDefault();
-      openUILinkIn(anchorTarget.href, "current");
+      let where = whereToOpenLink(event, false, false);
+      openUILinkIn(anchorTarget.href, where);
     }
   },
 
   /**
    * The about:tabcrashed can't do window.reload() because that
    * would reload the page but not use a remote browser.
    */
   onAboutTabCrashed: function(event, ownerDoc) {
@@ -2596,21 +2597,21 @@ let BrowserOnClick = {
       TabCrashReporter.reloadCrashedTabs();
     }
   },
 
   ignoreWarningButton: function (isMalware) {
     // Allow users to override and continue through to the site,
     // but add a notify bar as a reminder, so that they don't lose
     // track after, e.g., tab switching.
-    gBrowser.loadURIWithFlags(content.location.href,
+    gBrowser.loadURIWithFlags(gBrowser.currentURI.spec,
                               nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                               null, null, null);
 
-    Services.perms.add(makeURI(content.location.href), "safe-browsing",
+    Services.perms.add(gBrowser.currentURI, "safe-browsing",
                        Ci.nsIPermissionManager.ALLOW_ACTION,
                        Ci.nsIPermissionManager.EXPIRE_SESSION);
 
     let buttons = [{
       label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
       accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
       callback: function() { getMeOutOfHere(); }
     }];
@@ -2672,17 +2673,17 @@ function getMeOutOfHere() {
     url = prefs.getComplexValue("browser.startup.homepage",
                                 Ci.nsIPrefLocalizedString).data;
     // If url is a pipe-delimited set of pages, just take the first one.
     if (url.contains("|"))
       url = url.split("|")[0];
   } catch(e) {
     Components.utils.reportError("Couldn't get homepage pref: " + e);
   }
-  content.location = url;
+  gBrowser.loadURI(url);
 }
 
 function BrowserFullScreen()
 {
   window.fullScreen = !window.fullScreen;
 }
 
 function _checkDefaultAndSwitchToMetro() {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -912,32 +912,16 @@
                        ondragenter="homeButtonObserver.onDragOver(event)"
                        ondrop="homeButtonObserver.onDrop(event)"
                        ondragexit="homeButtonObserver.onDragExit(event)"
                        key="goHome"
                        onclick="BrowserGoHome(event);"
                        cui-areatype="toolbar"
                        aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
 
-        <!-- XXX Bug 1013989 will provide a label for the button -->
-        <!-- This uses badged to be compatible with the social api code it shares.
-             We may also want it to be badged in the future, for notification
-             purposes. -->
-        <toolbarbutton id="loop-call-button"
-                       class="toolbarbutton-1 chromeclass-toolbar-additional"
-                       persist="class"
-                       type="badged"
-                       removable="true"
-                       tooltiptext="&loopCallButton.tooltip;"
-                       oncommand="LoopUI.openCallPanel(event);"
-                       cui-areatype="toolbar"
-                       >
-        </toolbarbutton>
-
-
         <toolbarbutton id="social-share-button"
                        class="toolbarbutton-1 chromeclass-toolbar-additional"
                        label="&sharePageCmd.label;"
                        tooltiptext="&sharePageCmd.label;"
                        cui-areatype="toolbar"
                        removable="true"
                        hidden="true"
                        command="Social:SharePage"/>
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -47,17 +47,17 @@ const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding"
 ];
 
 /**
  * The current version. We can use this to auto-add new default widgets as necessary.
  * (would be const but isn't because of testing purposes)
  */
-let kVersion = 0;
+let kVersion = 1;
 
 /**
  * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
  * on their IDs.
  */
 let gPalette = new Map();
 
 /**
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -893,16 +893,38 @@ const CustomizableWidgets = [{
     }
   }, {
     id: "email-link-button",
     tooltiptext: "email-link-button.tooltiptext3",
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForWindow(win.content);
     }
+  }, {
+    id: "loop-call-button",
+    type: "custom",
+    // XXX Bug 1013989 will provide a label for the button
+    label: "loop-call-button.label",
+    tooltiptext: "loop-call-button.tooltiptext",
+    defaultArea: CustomizableUI.AREA_NAVBAR,
+    introducedInVersion: 1,
+    onBuild: function(aDocument) {
+      let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+      node.setAttribute("id", this.id);
+      node.classList.add("toolbarbutton-1");
+      node.classList.add("chromeclass-toolbar-additional");
+      node.setAttribute("type", "badged");
+      node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+      node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+      node.setAttribute("removable", "true");
+      node.addEventListener("command", function(event) {
+        aDocument.defaultView.LoopUI.openCallPanel(event);
+      });
+      return node;
+    }
   }];
 
 #ifdef XP_WIN
 #ifdef MOZ_METRO
 if (Services.metro && Services.metro.supported) {
   let widgetArgs = {tooltiptext: "switch-to-metro-button2.tooltiptext"};
   let brandShortName = BrandBundle.GetStringFromName("brandShortName");
   let metroTooltip = CustomizableUI.getLocalizedProperty(widgetArgs, "tooltiptext",
--- a/browser/components/loop/.gitignore
+++ b/browser/components/loop/.gitignore
@@ -1,2 +1,1 @@
 .module-cache
-standalone/content/config.js
rename from browser/components/loop/content/libs/sdk-content/css/ot.css
rename to browser/components/loop/content/shared/libs/sdk-content/css/ot.css
rename from browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-chrome.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/access-denied-chrome.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-copy-firefox.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/access-denied-copy-firefox.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/access-denied-firefox.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/access-denied-firefox.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/access-predenied-chrome.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/access-predenied-chrome.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/access-prompt-chrome.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/access-prompt-chrome.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/audioonly-publisher.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/audioonly-publisher.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/audioonly-subscriber.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/audioonly-subscriber.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/buttons.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/buttons.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/loader.gif
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/loader.gif
rename from browser/components/loop/content/libs/sdk-content/images/rtc/mic-off.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/mic-off.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/mic-on.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/mic-on.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/speaker-off.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/speaker-off.png
rename from browser/components/loop/content/libs/sdk-content/images/rtc/speaker-on.png
rename to browser/components/loop/content/shared/libs/sdk-content/images/rtc/speaker-on.png
rename from browser/components/loop/content/libs/sdk-content/js/dynamic_config.min.js
rename to browser/components/loop/content/shared/libs/sdk-content/js/dynamic_config.min.js
rename from browser/components/loop/content/libs/sdk.js
rename to browser/components/loop/content/shared/libs/sdk.js
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -48,24 +48,24 @@ browser.jar:
   content/browser/loop/shared/libs/lodash-2.4.1.js    (content/shared/libs/lodash-2.4.1.js)
   content/browser/loop/shared/libs/jquery-2.1.0.js    (content/shared/libs/jquery-2.1.0.js)
   content/browser/loop/shared/libs/backbone-1.1.2.js  (content/shared/libs/backbone-1.1.2.js)
 
   # Shared sounds
   content/browser/loop/shared/sounds/Firefox-Long.ogg (content/shared/sounds/Firefox-Long.ogg)
 
   # Partner SDK assets
-  content/browser/loop/libs/sdk.js                                                    (content/libs/sdk.js)
-  content/browser/loop/sdk-content/css/ot.css                                 (content/libs/sdk-content/css/ot.css)
-  content/browser/loop/sdk-content/js/dynamic_config.min.js                   (content/libs/sdk-content/js/dynamic_config.min.js)
-  content/browser/loop/sdk-content/images/rtc/access-denied-chrome.png        (content/libs/sdk-content/images/rtc/access-denied-chrome.png)
-  content/browser/loop/sdk-content/images/rtc/access-denied-copy-firefox.png  (content/libs/sdk-content/images/rtc/access-denied-copy-firefox.png)
-  content/browser/loop/sdk-content/images/rtc/access-denied-firefox.png       (content/libs/sdk-content/images/rtc/access-denied-firefox.png)
-  content/browser/loop/sdk-content/images/rtc/access-predenied-chrome.png     (content/libs/sdk-content/images/rtc/access-predenied-chrome.png)
-  content/browser/loop/sdk-content/images/rtc/access-prompt-chrome.png        (content/libs/sdk-content/images/rtc/access-prompt-chrome.png)
-  content/browser/loop/sdk-content/images/rtc/audioonly-publisher.png         (content/libs/sdk-content/images/rtc/audioonly-publisher.png)
-  content/browser/loop/sdk-content/images/rtc/audioonly-subscriber.png        (content/libs/sdk-content/images/rtc/audioonly-subscriber.png)
-  content/browser/loop/sdk-content/images/rtc/buttons.png                     (content/libs/sdk-content/images/rtc/buttons.png)
-  content/browser/loop/sdk-content/images/rtc/loader.gif                      (content/libs/sdk-content/images/rtc/loader.gif)
-  content/browser/loop/sdk-content/images/rtc/mic-off.png                     (content/libs/sdk-content/images/rtc/mic-off.png)
-  content/browser/loop/sdk-content/images/rtc/mic-on.png                      (content/libs/sdk-content/images/rtc/mic-on.png)
-  content/browser/loop/sdk-content/images/rtc/speaker-off.png                 (content/libs/sdk-content/images/rtc/speaker-off.png)
-  content/browser/loop/sdk-content/images/rtc/speaker-on.png                  (content/libs/sdk-content/images/rtc/speaker-on.png)
+  content/browser/loop/libs/sdk.js                                                    (content/shared/libs/sdk.js)
+  content/browser/loop/sdk-content/css/ot.css                                 (content/shared/libs/sdk-content/css/ot.css)
+  content/browser/loop/sdk-content/js/dynamic_config.min.js                   (content/shared/libs/sdk-content/js/dynamic_config.min.js)
+  content/browser/loop/sdk-content/images/rtc/access-denied-chrome.png        (content/shared/libs/sdk-content/images/rtc/access-denied-chrome.png)
+  content/browser/loop/sdk-content/images/rtc/access-denied-copy-firefox.png  (content/shared/libs/sdk-content/images/rtc/access-denied-copy-firefox.png)
+  content/browser/loop/sdk-content/images/rtc/access-denied-firefox.png       (content/shared/libs/sdk-content/images/rtc/access-denied-firefox.png)
+  content/browser/loop/sdk-content/images/rtc/access-predenied-chrome.png     (content/shared/libs/sdk-content/images/rtc/access-predenied-chrome.png)
+  content/browser/loop/sdk-content/images/rtc/access-prompt-chrome.png        (content/shared/libs/sdk-content/images/rtc/access-prompt-chrome.png)
+  content/browser/loop/sdk-content/images/rtc/audioonly-publisher.png         (content/shared/libs/sdk-content/images/rtc/audioonly-publisher.png)
+  content/browser/loop/sdk-content/images/rtc/audioonly-subscriber.png        (content/shared/libs/sdk-content/images/rtc/audioonly-subscriber.png)
+  content/browser/loop/sdk-content/images/rtc/buttons.png                     (content/shared/libs/sdk-content/images/rtc/buttons.png)
+  content/browser/loop/sdk-content/images/rtc/loader.gif                      (content/shared/libs/sdk-content/images/rtc/loader.gif)
+  content/browser/loop/sdk-content/images/rtc/mic-off.png                     (content/shared/libs/sdk-content/images/rtc/mic-off.png)
+  content/browser/loop/sdk-content/images/rtc/mic-on.png                      (content/shared/libs/sdk-content/images/rtc/mic-on.png)
+  content/browser/loop/sdk-content/images/rtc/speaker-off.png                 (content/shared/libs/sdk-content/images/rtc/speaker-off.png)
+  content/browser/loop/sdk-content/images/rtc/speaker-on.png                  (content/shared/libs/sdk-content/images/rtc/speaker-on.png)
--- a/browser/components/loop/moz.build
+++ b/browser/components/loop/moz.build
@@ -1,22 +1,20 @@
 # -*- 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/.
 
 JAR_MANIFESTS += ['jar.mn']
 
-JS_MODULES_PATH = 'modules/loop'
-
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
 BROWSER_CHROME_MANIFESTS += [
     'test/mochitest/browser.ini',
 ]
 
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.loop += [
     'MozLoopAPI.jsm',
     'MozLoopPushHandler.jsm',
     'MozLoopService.jsm',
     'MozLoopWorker.js',
 ]
--- a/browser/components/loop/standalone/.gitignore
+++ b/browser/components/loop/standalone/.gitignore
@@ -1,2 +1,5 @@
+.module-cache
 node_modules
 *.pyc
+content/config.js
+content/VERSION.txt
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -16,13 +16,28 @@ lint:
 	@$(NODE_LOCAL_BIN)/jshint *.js content test
 
 runserver: config
 	@node server.js
 
 frontend:
 	@echo "Not implemented yet."
 
+# Try hg first, if not fall back to git.
+SOURCE_STAMP := $(shell hg parent --template '{node|short}\n' 2> /dev/null)
+ifndef SOURCE_STAMP
+SOURCE_STAMP := $(shell git describe --always --tag)
+endif
+
+SOURCE_DATE := $(shell hg parent --template '{date|date}\n' 2> /dev/null)
+ifndef SOURCE_DATE
+SOURCE_DATE := $(shell git log -1 --format="%H%n%aD")
+endif
+
+version:
+	@echo $(SOURCE_STAMP) > content/VERSION.txt
+	@echo $(SOURCE_DATE) >> content/VERSION.txt
+
 config:
 	@echo "var loop = loop || {};" > content/config.js
 	@echo "loop.config = loop.config || {};" >> content/config.js
 	@echo "loop.config.serverUrl          = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
 	@echo "loop.config.pendingCallTimeout = `echo $(LOOP_PENDING_CALL_TIMEOUT)`;" >> content/config.js
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -17,17 +17,25 @@
       <h1>Loop</h1>
     </header>
 
     <div id="messages"></div>
 
     <div id="main"></div>
 
     <!-- libs -->
-    <script src="https://static.opentok.com/webrtc/v2.2/js/opentok.min.js"></script>
+    <script>
+      window.OTProperties = {
+        cdnURL: 'shared/libs/',
+      };
+      window.OTProperties.assetURL = window.OTProperties.cdnURL + 'sdk-content/';
+      window.OTProperties.configURL = window.OTProperties.assetURL + 'js/dynamic_config.min.js';
+      window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css';
+    </script>
+    <script type="text/javascript" src="shared/libs/sdk.js"></script>
     <script type="text/javascript" src="libs/webl10n-20130617.js"></script>
     <script type="text/javascript" src="shared/libs/react-0.10.0.js"></script>
     <script type="text/javascript" src="shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="shared/libs/backbone-1.1.2.js"></script>
 
     <!-- app scripts -->
     <script type="text/javascript" src="config.js"></script>
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -17,19 +17,17 @@ XPIDL_SOURCES += [
 XPIDL_MODULE = 'sessionstore'
 
 EXTRA_COMPONENTS += [
     'nsSessionStartup.js',
     'nsSessionStore.js',
     'nsSessionStore.manifest',
 ]
 
-JS_MODULES_PATH = 'modules/sessionstore'
-
-EXTRA_JS_MODULES = [
+EXTRA_JS_MODULES.sessionstore = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
     'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'PrivacyLevel.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
@@ -41,13 +39,13 @@ EXTRA_JS_MODULES = [
     'SessionWorker.js',
     'SessionWorker.jsm',
     'TabAttributes.jsm',
     'TabState.jsm',
     'TabStateCache.jsm',
     'Utils.jsm',
 ]
 
-EXTRA_PP_JS_MODULES += [
+EXTRA_PP_JS_MODULES.sessionstore += [
     'SessionSaver.jsm',
     'SessionStore.jsm',
 ]
 
--- a/browser/components/tabview/moz.build
+++ b/browser/components/tabview/moz.build
@@ -1,14 +1,12 @@
 # -*- 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/.
 
-EXTRA_JS_MODULES = ['modules/utils.jsm']
-JS_MODULES_PATH  = 'modules/tabview'
-
+EXTRA_JS_MODULES.tabview = ['modules/utils.jsm']
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
--- a/browser/components/translation/moz.build
+++ b/browser/components/translation/moz.build
@@ -1,15 +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/.
 
-JS_MODULES_PATH = 'modules/translation'
-
-EXTRA_JS_MODULES = [
+EXTRA_JS_MODULES.translation = [
     'BingTranslator.jsm',
     'cld2/cld-worker.js',
     'cld2/cld-worker.js.mem',
     'LanguageDetector.jsm',
     'Translation.jsm',
     'TranslationContentHandler.jsm',
     'TranslationDocument.jsm'
 ]
--- a/browser/devtools/app-manager/moz.build
+++ b/browser/devtools/app-manager/moz.build
@@ -2,19 +2,17 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
-JS_MODULES_PATH = 'modules/devtools/app-manager'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools["app-manager"] += [
     'app-projects.js',
     'app-validator.js',
     'builtin-adb-store.js',
     'connection-store.js',
     'device-store.js',
     'simulators-store.js',
     'webapps-store.js',
 ]
--- a/browser/devtools/canvasdebugger/moz.build
+++ b/browser/devtools/canvasdebugger/moz.build
@@ -1,12 +1,10 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/canvasdebugger'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.canvasdebugger += [
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/commandline/moz.build
+++ b/browser/devtools/commandline/moz.build
@@ -1,11 +1,9 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/commandline'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.commandline += [
     'commands-index.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/debugger/moz.build
+++ b/browser/devtools/debugger/moz.build
@@ -1,13 +1,11 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/debugger'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.debugger += [
     'debugger-commands.js',
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/eyedropper/moz.build
+++ b/browser/devtools/eyedropper/moz.build
@@ -1,14 +1,12 @@
 # -*- 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/.
 
-JS_MODULES_PATH = 'modules/devtools/eyedropper'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.eyedropper += [
     'commands.js',
     'eyedropper.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/inspector/moz.build
+++ b/browser/devtools/inspector/moz.build
@@ -1,14 +1,12 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/inspector'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.inspector += [
     'breadcrumbs.js',
     'inspector-commands.js',
     'inspector-panel.js',
     'selector-search.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/markupview/moz.build
+++ b/browser/devtools/markupview/moz.build
@@ -1,14 +1,12 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
-JS_MODULES_PATH = 'modules/devtools/markupview'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.markupview += [
     'html-editor.js',
     'markup-view.js',
 ]
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -33,13 +33,11 @@ DIRS += [
 
 EXTRA_COMPONENTS += [
     'devtools-clhandler.js',
     'devtools-clhandler.manifest',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
-JS_MODULES_PATH = 'modules/devtools'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools += [
     'main.js',
 ]
--- a/browser/devtools/netmonitor/moz.build
+++ b/browser/devtools/netmonitor/moz.build
@@ -1,12 +1,10 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/netmonitor'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.netmonitor += [
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/profiler/moz.build
+++ b/browser/devtools/profiler/moz.build
@@ -1,18 +1,16 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
-JS_MODULES_PATH = 'modules/devtools/profiler'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.profiler += [
     'cleopatra.js',
     'commands.js',
     'consts.js',
     'controller.js',
     'panel.js',
     'sidebar.js',
 ]
--- a/browser/devtools/responsivedesign/moz.build
+++ b/browser/devtools/responsivedesign/moz.build
@@ -1,11 +1,9 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools += [
     'resize-commands.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/scratchpad/moz.build
+++ b/browser/devtools/scratchpad/moz.build
@@ -1,14 +1,12 @@
 # -*- 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/.
 
-JS_MODULES_PATH = 'modules/devtools/scratchpad'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.scratchpad += [
     'scratchpad-commands.js',
     'scratchpad-panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/shadereditor/moz.build
+++ b/browser/devtools/shadereditor/moz.build
@@ -1,12 +1,10 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/shadereditor'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.shadereditor += [
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/sourceeditor/moz.build
+++ b/browser/devtools/sourceeditor/moz.build
@@ -1,17 +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/.
 
-JS_MODULES_PATH = 'modules/devtools/sourceeditor'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.sourceeditor += [
     'autocomplete.js',
     'css-autocompleter.js',
     'css-tokenizer.js',
     'debugger.js',
     'editor.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/styleinspector/moz.build
+++ b/browser/devtools/styleinspector/moz.build
@@ -2,17 +2,15 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
-JS_MODULES_PATH = 'modules/devtools/styleinspector'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.styleinspector += [
     'computed-view.js',
     'css-parsing-utils.js',
     'rule-view.js',
     'style-inspector-overlays.js',
     'style-inspector.js',
 ]
--- a/browser/devtools/tilt/moz.build
+++ b/browser/devtools/tilt/moz.build
@@ -1,15 +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/.
 
-JS_MODULES_PATH = 'modules/devtools/tilt'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.tilt += [
     'tilt-commands.js',
     'tilt-gl.js',
     'tilt-math.js',
     'tilt-utils.js',
     'tilt-visualizer-style.js',
     'tilt-visualizer.js',
     'tilt.js',
     'TiltWorkerCrafter.js',
--- a/browser/devtools/webaudioeditor/moz.build
+++ b/browser/devtools/webaudioeditor/moz.build
@@ -1,12 +1,10 @@
 # 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/.
 
-JS_MODULES_PATH = 'modules/devtools/webaudioeditor'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.webaudioeditor += [
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/webconsole/moz.build
+++ b/browser/devtools/webconsole/moz.build
@@ -1,18 +1,16 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
-JS_MODULES_PATH = 'modules/devtools/webconsole'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.webconsole += [
     'console-commands.js',
     'console-output.js',
     'hudservice.js',
     'network-panel.js',
     'panel.js',
     'webconsole.js',
 ]
--- a/browser/devtools/webide/content/details.js
+++ b/browser/devtools/webide/content/details.js
@@ -78,29 +78,29 @@ function updateUI() {
     if (manifest.description) {
       document.querySelector("#descriptionHeader").classList.remove("hidden");
       document.querySelector("#description").textContent = manifest.description;
     }
 
     document.querySelector("#type").classList.remove("hidden");
 
     if (project.type == "runtimeApp") {
-      let manifest = AppManager.getProjectManifestURL(project);
+      let manifestURL = AppManager.getProjectManifestURL(project);
       document.querySelector("#type").textContent = manifest.type || "web";
       document.querySelector("#manifestURLHeader").classList.remove("hidden");
-      document.querySelector("#manifestURL").textContent = manifest;
+      document.querySelector("#manifestURL").textContent = manifestURL;
     } else {
       document.querySelector("#type").textContent = project.type + " " + (manifest.type || "web");
     }
 
     if (project.type == "packaged") {
-      let manifest = AppManager.getProjectManifestURL(project);
-      if (manifest) {
+      let manifestURL = AppManager.getProjectManifestURL(project);
+      if (manifestURL) {
         document.querySelector("#manifestURLHeader").classList.remove("hidden");
-        document.querySelector("#manifestURL").textContent = manifest;
+        document.querySelector("#manifestURL").textContent = manifestURL;
       }
     }
   }
 
   let errorsNode = document.querySelector("#errorslist");
   let warningsNode = document.querySelector("#warningslist");
 
   if (project.errors) {
--- a/browser/devtools/webide/content/prefs.xhtml
+++ b/browser/devtools/webide/content/prefs.xhtml
@@ -54,16 +54,26 @@
         </label>
       </li>
     </ul>
 
     <h2>&prefs_editor_title;</h2>
 
     <ul>
       <li>
+        <label><span>&prefs_options_keybindings;</span>
+          <select data-pref="devtools.editor.keymap">
+            <option value="default">&prefs_options_keybindings_default;</option>
+            <option value="vim">Vim</option>
+            <option value="emacs">Emacs</option>
+            <option value="sublime">Sublime</option>
+          </select>
+        </label>
+      </li>
+      <li>
         <label><span>&prefs_options_tabsize;</span>
           <select data-pref="devtools.editor.tabsize">
             <option value="2">2</option>
             <option value="4">4</option>
             <option value="8">8</option>
           </select>
         </label>
       </li>
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -627,17 +627,17 @@ let UI = {
     if (this.toolboxIframe) {
       return;
     }
 
     let splitter = document.querySelector(".devtools-horizontal-splitter");
     splitter.removeAttribute("hidden");
 
     let iframe = document.createElement("iframe");
-    document.querySelector("window").insertBefore(iframe, splitter.nextSibling);
+    document.querySelector("notificationbox").insertBefore(iframe, splitter.nextSibling);
     let host = devtools.Toolbox.HostType.CUSTOM;
     let options = { customIframe: iframe };
     this.toolboxIframe = iframe;
 
     let height = Services.prefs.getIntPref("devtools.toolbox.footer.height");
     iframe.height = height;
 
     document.querySelector("#action-button-debug").setAttribute("active", "true");
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -176,15 +176,14 @@
       <iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
       <iframe id="deck-panel-projecteditor" flex="1"/>
       <iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
       <iframe id="deck-panel-prefs" flex="1" src="prefs.xhtml"/>
       <iframe id="deck-panel-permissionstable" flex="1" src="permissionstable.xhtml"/>
       <iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
       <iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
     </deck>
+    <splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
+    <!-- toolbox iframe will be inserted here -->
   </notificationbox>
 
-  <splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
-
-  <!-- toolbox iframe will be inserted here -->
 
 </window>
--- a/browser/devtools/webide/locales/en-US/webide.dtd
+++ b/browser/devtools/webide/locales/en-US/webide.dtd
@@ -112,16 +112,18 @@
 <!ENTITY prefs_options_expandtab "Soft tabs">
 <!ENTITY prefs_options_expandtab_tooltip "Use spaces instead of the tab character">
 <!ENTITY prefs_options_detectindentation "Autoindent">
 <!ENTITY prefs_options_detectindentation_tooltip "Guess indentation based on source content">
 <!ENTITY prefs_options_autocomplete "Autocomplete">
 <!ENTITY prefs_options_autocomplete_tooltip "Enable code autocompletion">
 <!ENTITY prefs_options_autoclosebrackets "Autoclose brackets">
 <!ENTITY prefs_options_autoclosebrackets_tooltip "Automatically insert closing brackets">
+<!ENTITY prefs_options_keybindings "Keybindings">
+<!ENTITY prefs_options_keybindings_default "Default">
 
 <!-- Permissions Table -->
 <!ENTITY permissionstable_title "Permissions Table">
 <!ENTITY permissionstable_name_header "Name">
 
 <!-- Runtime Details -->
 <!ENTITY runtimedetails_title "Runtime Info">
 <!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -7,16 +7,14 @@
 DIRS += [
     'content',
     'components',
     'themes',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
-JS_MODULES_PATH = 'modules/devtools/webide'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.devtools.webide += [
     'modules/addons.js',
     'modules/app-manager.js',
     'modules/remote-resources.js',
     'modules/runtimes.js'
 ]
--- a/browser/experiments/moz.build
+++ b/browser/experiments/moz.build
@@ -2,17 +2,15 @@
 # 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/.
 
 EXTRA_COMPONENTS += [
     'Experiments.manifest',
     'ExperimentsService.js',
 ]
 
-JS_MODULES_PATH = 'modules/experiments'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.experiments += [
   'Experiments.jsm',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
 SPHINX_TREES['experiments'] = 'docs'
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -446,16 +446,17 @@
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
 @BINPATH@/components/FormHistoryStartup.js
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/formautofill.manifest
 @BINPATH@/components/FormAutofillContentService.js
+@BINPATH@/components/FormAutofillStartup.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 @BINPATH@/browser/components/BrowserProfileMigrators.manifest
 @BINPATH@/browser/components/ProfileMigrator.js
 @BINPATH@/browser/components/ChromeProfileMigrator.js
 @BINPATH@/browser/components/FirefoxProfileMigrator.js
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -710,18 +710,16 @@ just addresses the organization to follo
 <!ENTITY getUserMedia.selectCamera.label "Camera to share:">
 <!ENTITY getUserMedia.selectCamera.accesskey "C">
 <!ENTITY getUserMedia.selectWindowOrScreen.label "Window or screen to share:">
 <!ENTITY getUserMedia.selectWindowOrScreen.accesskey "W">
 <!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
 <!ENTITY getUserMedia.selectMicrophone.accesskey "M">
 <!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
 
-<!ENTITY loopCallButton.tooltip "Invite someone to talk">
-
 <!ENTITY mixedContentBlocked.moreinfo "Most websites will still work properly even when this content is blocked.">
 
 <!ENTITY pointerLock.notification.message "Press ESC at any time to show it again.">
 
 <!ENTITY pluginNotification.showAll.label "Show All">
 <!ENTITY pluginNotification.showAll.accesskey "S">
 
 <!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -91,8 +91,11 @@ email-link-button.label = Email Link
 email-link-button.tooltiptext3 = Email a link to this page
 
 # LOCALIZATION NOTE(quit-button.tooltiptext.linux2): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
+
+loop-call-button.label = Invite someone to talk
+loop-call-button.tooltiptext = Invite someone to talk
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -72,16 +72,28 @@ DEBUGGER_INFO = {
   },
 
   "lldb": {
     "interactive": True,
     "args": "--",
     "requiresEscapedArgs": True
   },
 
+  # Visual Studio Debugger Support
+  "devenv.exe": {
+    "interactive": True,
+    "args": "-debugexe"
+  },
+
+  # Visual C++ Express Debugger Support
+  "wdexpress.exe": {
+    "interactive": True,
+    "args": "-debugexe"
+  },
+
   # valgrind doesn't explain much about leaks unless you set the
   # '--leak-check=full' flag. But there are a lot of objects that are
   # semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
   # uninteresting output from those objects. We set '--smc-check==all-non-file'
   # and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
   # deals properly with JIT'd JavaScript code.
   "valgrind": {
     "interactive": False,
--- a/config/config.mk
+++ b/config/config.mk
@@ -50,17 +50,16 @@ endif
   HOST_CSRCS \
   HOST_CMMSRCS \
   HOST_LIBRARY_NAME \
   HOST_PROGRAM \
   HOST_SIMPLE_PROGRAMS \
   IS_COMPONENT \
   JAR_MANIFEST \
   JAVA_JAR_TARGETS \
-  JS_MODULES_PATH \
   LD_VERSION_SCRIPT \
   LIBRARY_NAME \
   LIBS \
   MAKE_FRAMEWORK \
   MODULE \
   MSVC_ENABLE_PGO \
   NO_DIST_INSTALL \
   PARALLEL_DIRS \
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1216,20 +1216,18 @@ endif
 EXTRA_MANIFESTS = $(filter %.manifest,$(EXTRA_COMPONENTS) $(EXTRA_PP_COMPONENTS))
 ifneq (,$(EXTRA_MANIFESTS))
 libs:: $(call mkdir_deps,$(FINAL_TARGET))
 	$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest $(patsubst %,'manifest components/%',$(notdir $(EXTRA_MANIFESTS))))
 endif
 
 ################################################################################
 # Copy each element of EXTRA_JS_MODULES to
-# $(FINAL_TARGET)/$(JS_MODULES_PATH). JS_MODULES_PATH defaults to "modules"
-# if it is undefined.
-JS_MODULES_PATH ?= modules
-FINAL_JS_MODULES_PATH := $(FINAL_TARGET)/$(JS_MODULES_PATH)
+# $(FINAL_TARGET)/modules.
+FINAL_JS_MODULES_PATH := $(FINAL_TARGET)/modules
 
 ifdef EXTRA_JS_MODULES
 ifndef NO_DIST_INSTALL
 EXTRA_JS_MODULES_FILES := $(EXTRA_JS_MODULES)
 EXTRA_JS_MODULES_DEST := $(FINAL_JS_MODULES_PATH)
 INSTALL_TARGETS += EXTRA_JS_MODULES
 endif
 endif
--- a/configure.in
+++ b/configure.in
@@ -3839,17 +3839,16 @@ MOZ_LOCALE_SWITCHER=
 MOZ_ANDROID_SEARCH_ACTIVITY=
 MOZ_ANDROID_MLS_STUMBLER=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
 MOZ_PAY=
 MOZ_AUDIO_CHANNEL_MANAGER=
 NSS_NO_LIBPKIX=
 MOZ_CONTENT_SANDBOX=
-MOZ_GMP_SANDBOX=
 JSGC_USE_EXACT_ROOTING=1
 JSGC_GENERATIONAL=
 
 case "$target_os" in
     mingw*)
         NS_ENABLE_TSF=1
         AC_DEFINE(NS_ENABLE_TSF)
         ;;
@@ -5079,16 +5078,30 @@ MOZ_ARG_DISABLE_BOOL(webm,
     MOZ_WEBM=1)
 
 if test -n "$MOZ_WEBM"; then
     AC_DEFINE(MOZ_WEBM)
     MOZ_VPX=1
 fi;
 
 dnl ========================================================
+dnl = Apple platform decoder support
+dnl ========================================================
+if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
+  MOZ_APPLEMEDIA=1
+fi
+
+if test -n "$MOZ_APPLEMEDIA"; then
+  AC_DEFINE(MOZ_APPLEMEDIA)
+  # hack in frameworks for fmp4 - see bug 1029974
+  # We load VideoToolbox and CoreMedia dynamically, so they don't appear here.
+  LDFLAGS="$LDFLAGS -framework AudioToolbox"
+fi
+
+dnl ========================================================
 dnl = DirectShow support
 dnl ========================================================
 if test "$OS_ARCH" = "WINNT"; then
     dnl Enable DirectShow support by default.
     MOZ_DIRECTSHOW=1
 fi
 
 MOZ_ARG_DISABLE_BOOL(directshow,
@@ -5138,20 +5151,18 @@ MOZ_ARG_DISABLE_BOOL(ffmpeg,
 
 if test -n "$MOZ_FFMPEG"; then
     AC_DEFINE(MOZ_FFMPEG)
 fi;
 
 dnl ========================================================
 dnl = Built-in fragmented MP4 support.
 dnl ========================================================
-if test -n "$MOZ_WMF" -o -n "$MOZ_FFMPEG"; then
-    dnl Enable fragmented MP4 parser on Windows by default.
-    dnl We will also need to enable it on other platforms as we implement
-    dnl platform decoder support there too.
+if test -n "$MOZ_WMF" -o -n "$MOZ_FFMPEG" -o -n "$MOZ_APPLEMEDIA"; then
+    dnl Enable fragmented MP4 parser on platforms with decoder support.
     MOZ_FMP4=1
 fi
 
 MOZ_ARG_DISABLE_BOOL(fmp4,
 [  --disable-fmp4  Disable support for in built Fragmented MP4 parsing],
     MOZ_FMP4=,
     MOZ_FMP4=1)
 
@@ -5185,32 +5196,16 @@ MOZ_ARG_ENABLE_BOOL(android-omx,
     MOZ_ANDROID_OMX=1,
     MOZ_ANDROID_OMX=)
 
 if test -n "$MOZ_ANDROID_OMX"; then
   AC_DEFINE(MOZ_ANDROID_OMX)
 fi
 
 dnl ========================================================
-dnl = Disable platform MP3 decoder on OSX
-dnl ========================================================
-if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
-  MOZ_APPLEMEDIA=1
-fi
-
-MOZ_ARG_DISABLE_BOOL(apple-media,
-[  --disable-apple-media  Disable support for Apple AudioToolbox/VideoToolbox],
-    MOZ_APPLEMEDIA=,
-    MOZ_APPLEMEDIA=1)
-
-if test -n "$MOZ_APPLEMEDIA"; then
-  AC_DEFINE(MOZ_APPLEMEDIA)
-fi
-
-dnl ========================================================
 dnl = Enable getUserMedia support
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(media-navigator,
 [  --enable-media-navigator  Enable support for getUserMedia],
     MOZ_MEDIA_NAVIGATOR=1,
     MOZ_MEDIA_NAVIGATOR=)
 
 if test -n "$MOZ_MEDIA_NAVIGATOR"; then
@@ -6402,38 +6397,16 @@ MOZ_ARG_ENABLE_BOOL(content-sandbox,
 
 if test -n "$MOZ_CONTENT_SANDBOX"; then
     AC_DEFINE(MOZ_CONTENT_SANDBOX)
 fi
 
 AC_SUBST(MOZ_CONTENT_SANDBOX)
 
 dnl ========================================================
-dnl = Gecko Media Plugin sandboxing
-dnl ========================================================
-case $OS_TARGET in
-WINNT)
-    MOZ_GMP_SANDBOX=1
-    ;;
-Linux)
-    case $CPU_ARCH in
-    x86_64|x86)
-        MOZ_GMP_SANDBOX=1
-        ;;
-    esac
-    ;;
-esac
-
-if test -n "$MOZ_GMP_SANDBOX"; then
-    AC_DEFINE(MOZ_GMP_SANDBOX)
-fi
-
-AC_SUBST(MOZ_GMP_SANDBOX)
-
-dnl ========================================================
 dnl =
 dnl = Module specific options
 dnl =
 dnl ========================================================
 MOZ_ARG_HEADER(Individual module options)
 
 dnl ========================================================
 dnl = Disable feed handling components
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -743,26 +743,16 @@ private:
   nsCOMPtr<nsIFile> mFile;
   bool mWholeFile;
   bool mStoredFile;
 };
 
 } // dom namespace
 } // file namespace
 
-class MOZ_STACK_CLASS nsDOMFileInternalUrlHolder {
-public:
-  nsDOMFileInternalUrlHolder(nsIDOMBlob* aFile, nsIPrincipal* aPrincipal
-                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-  ~nsDOMFileInternalUrlHolder();
-  nsAutoString mUrl;
-private:
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
 class nsDOMFileList MOZ_FINAL : public nsIDOMFileList,
                                 public nsWrapperCache
 {
   ~nsDOMFileList() {}
 
 public:
   nsDOMFileList(nsISupports *aParent) : mParent(aParent)
   {
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -1059,26 +1059,8 @@ nsDOMFileList::GetLength(uint32_t* aLeng
 
 NS_IMETHODIMP
 nsDOMFileList::Item(uint32_t aIndex, nsIDOMFile **aFile)
 {
   NS_IF_ADDREF(*aFile = Item(aIndex));
 
   return NS_OK;
 }
-
-////////////////////////////////////////////////////////////////////////////
-// nsDOMFileInternalUrlHolder implementation
-
-nsDOMFileInternalUrlHolder::nsDOMFileInternalUrlHolder(nsIDOMBlob* aFile,
-                                                       nsIPrincipal* aPrincipal
-                                                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) {
-  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-  aFile->GetInternalUrl(aPrincipal, mUrl);
-}
- 
-nsDOMFileInternalUrlHolder::~nsDOMFileInternalUrlHolder() {
-  if (!mUrl.IsEmpty()) {
-    nsAutoCString narrowUrl;
-    CopyUTF16toUTF8(mUrl, narrowUrl);
-    nsBlobProtocolHandler::RemoveDataEntry(narrowUrl);
-  }
-}
--- a/content/media/fmp4/MP4Decoder.cpp
+++ b/content/media/fmp4/MP4Decoder.cpp
@@ -12,16 +12,20 @@
 #include "prlog.h"
 
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 #ifdef MOZ_FFMPEG
 #include "FFmpegRuntimeLinker.h"
 #endif
+#ifdef MOZ_APPLEMEDIA
+#include "apple/AppleCMLinker.h"
+#include "apple/AppleVTLinker.h"
+#endif
 
 namespace mozilla {
 
 MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
 {
   return new MediaDecoderStateMachine(this, new MP4Reader(this));
 }
 
@@ -98,24 +102,50 @@ IsFFmpegAvailable()
 
   // If we can link to FFmpeg, then we can almost certainly play H264 and AAC
   // with it.
   return FFmpegRuntimeLinker::Link();
 #endif
 }
 
 static bool
+IsAppleAvailable()
+{
+#ifndef MOZ_APPLEMEDIA
+  // Not the right platform.
+  return false;
+#else
+  if (!Preferences::GetBool("media.apple.mp4.enabled", false)) {
+    // Disabled by preference.
+    return false;
+  }
+  // Attempt to load the required frameworks.
+  bool haveCoreMedia = AppleCMLinker::Link();
+  if (!haveCoreMedia) {
+    return false;
+  }
+  bool haveVideoToolbox = AppleVTLinker::Link();
+  if (!haveVideoToolbox) {
+    return false;
+  }
+  // All hurdles cleared!
+  return true;
+#endif
+}
+
+static bool
 HavePlatformMPEGDecoders()
 {
   return Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
 #ifdef XP_WIN
          // We have H.264/AAC platform decoders on Windows Vista and up.
          IsVistaOrLater() ||
 #endif
          IsFFmpegAvailable() ||
+         IsAppleAvailable() ||
          // TODO: Other platforms...
          false;
 }
 
 /* static */
 bool
 MP4Decoder::IsEnabled()
 {
--- a/content/media/fmp4/PlatformDecoderModule.cpp
+++ b/content/media/fmp4/PlatformDecoderModule.cpp
@@ -6,16 +6,19 @@
 
 #include "PlatformDecoderModule.h"
 #ifdef XP_WIN
 #include "WMFDecoderModule.h"
 #endif
 #ifdef MOZ_FFMPEG
 #include "FFmpegRuntimeLinker.h"
 #endif
+#ifdef MOZ_APPLEMEDIA
+#include "AppleDecoderModule.h"
+#endif
 #include "mozilla/Preferences.h"
 #include "EMEDecoderModule.h"
 #include "mozilla/CDMProxy.h"
 #include "SharedThreadPool.h"
 #include "MediaTaskQueue.h"
 
 namespace mozilla {
 
@@ -37,16 +40,19 @@ PlatformDecoderModule::Init()
 
   Preferences::AddBoolVarCache(&sUseBlankDecoder,
                                "media.fragmented-mp4.use-blank-decoder");
   Preferences::AddBoolVarCache(&sFFmpegDecoderEnabled,
                                "media.fragmented-mp4.ffmpeg.enabled", false);
 #ifdef XP_WIN
   WMFDecoderModule::Init();
 #endif
+#ifdef MOZ_APPLEMEDIA
+  AppleDecoderModule::Init();
+#endif
 }
 
 class CreateTaskQueueTask : public nsRunnable {
 public:
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
     mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
     return NS_OK;
@@ -112,12 +118,18 @@ PlatformDecoderModule::Create()
     return m.forget();
   }
 #endif
 #ifdef MOZ_FFMPEG
   if (sFFmpegDecoderEnabled) {
     return FFmpegRuntimeLinker::CreateDecoderModule();
   }
 #endif
+#ifdef MOZ_APPLEMEDIA
+  nsAutoPtr<AppleDecoderModule> m(new AppleDecoderModule());
+  if (NS_SUCCEEDED(m->Startup())) {
+    return m.forget();
+  }
+#endif
   return nullptr;
 }
 
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleATDecoder.cpp
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <AudioToolbox/AudioToolbox.h>
+#include "AppleUtils.h"
+#include "MP4Reader.h"
+#include "MP4Decoder.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mp4_demuxer/DecoderData.h"
+#include "nsIThread.h"
+#include "AppleATDecoder.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+namespace mozilla {
+
+AppleATDecoder::AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                               MediaTaskQueue* anAudioTaskQueue,
+                               MediaDataDecoderCallback* aCallback)
+  : mConfig(aConfig)
+  , mTaskQueue(anAudioTaskQueue)
+  , mCallback(aCallback)
+  , mConverter(nullptr)
+  , mStream(nullptr)
+  , mCurrentAudioFrame(0)
+  , mSamplePosition(0)
+  , mHaveOutput(false)
+{
+  MOZ_COUNT_CTOR(AppleATDecoder);
+  LOG("Creating Apple AudioToolbox AAC decoder");
+  LOG("Audio Decoder configuration: %s %d Hz %d channels %d bits per channel",
+      mConfig.mime_type,
+      mConfig.samples_per_second,
+      mConfig.channel_count,
+      mConfig.bits_per_sample);
+  // TODO: Verify aConfig.mime_type.
+}
+
+AppleATDecoder::~AppleATDecoder()
+{
+  MOZ_COUNT_DTOR(AppleATDecoer);
+  MOZ_ASSERT(!mConverter);
+  MOZ_ASSERT(!mStream);
+}
+
+static void
+_MetadataCallback(void *aDecoder,
+                  AudioFileStreamID aStream,
+                  AudioFileStreamPropertyID aProperty,
+                  UInt32 *aFlags)
+{
+  LOG("AppleATDecoder metadata callback");
+  AppleATDecoder* decoder = static_cast<AppleATDecoder*>(aDecoder);
+  decoder->MetadataCallback(aStream, aProperty, aFlags);
+}
+
+static void
+_SampleCallback(void *aDecoder,
+                UInt32 aNumBytes, UInt32 aNumPackets,
+                const void *aData,
+                AudioStreamPacketDescription *aPackets)
+{
+  LOG("AppleATDecoder sample callback %u bytes %u packets",
+      aNumBytes, aNumPackets);
+  AppleATDecoder* decoder = static_cast<AppleATDecoder*>(aDecoder);
+  decoder->SampleCallback(aNumBytes, aNumPackets, aData, aPackets);
+}
+
+nsresult
+AppleATDecoder::Init()
+{
+  LOG("Initializing Apple AudioToolbox AAC decoder");
+  AudioFileTypeID fileType = kAudioFileAAC_ADTSType;
+  OSStatus rv = AudioFileStreamOpen(this,
+                                    _MetadataCallback,
+                                    _SampleCallback,
+                                    fileType,
+                                    &mStream);
+  if (rv) {
+    NS_ERROR("Couldn't open AudioFileStream");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+AppleATDecoder::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  LOG("mp4 input sample %p %lld us %lld pts%s %llu bytes audio",
+      aSample,
+      aSample->duration,
+      aSample->composition_timestamp,
+      aSample->is_sync_point ? " keyframe" : "",
+      (unsigned long long)aSample->size);
+
+  // Queue a task to perform the actual decoding on a separate thread.
+  mTaskQueue->Dispatch(
+      NS_NewRunnableMethodWithArg<nsAutoPtr<mp4_demuxer::MP4Sample>>(
+        this,
+        &AppleATDecoder::SubmitSample,
+        nsAutoPtr<mp4_demuxer::MP4Sample>(aSample)));
+
+  return NS_OK;
+}
+
+nsresult
+AppleATDecoder::Flush()
+{
+  LOG("Flushing AudioToolbox AAC decoder");
+  OSStatus rv = AudioConverterReset(mConverter);
+  if (rv) {
+    LOG("Error %d resetting AudioConverter", rv);
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+AppleATDecoder::Drain()
+{
+  LOG("Draining AudioToolbox AAC decoder");
+  mTaskQueue->AwaitIdle();
+  return Flush();
+}
+
+nsresult
+AppleATDecoder::Shutdown()
+{
+  LOG("Shutdown: Apple AudioToolbox AAC decoder");
+  OSStatus rv1 = AudioConverterDispose(mConverter);
+  if (rv1) {
+    LOG("error %d disposing of AudioConverter", rv1);
+  } else {
+    mConverter = nullptr;
+  }
+
+  OSStatus rv2 = AudioFileStreamClose(mStream);
+  if (rv2) {
+    LOG("error %d closing AudioFileStream", rv2);
+  } else {
+    mStream = nullptr;
+  }
+
+  return (rv1 && rv2) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void
+AppleATDecoder::MetadataCallback(AudioFileStreamID aFileStream,
+                                 AudioFileStreamPropertyID aPropertyID,
+                                 UInt32* aFlags)
+{
+  if (aPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) {
+    SetupDecoder();
+  }
+}
+
+struct PassthroughUserData {
+  AppleATDecoder* mDecoder;
+  UInt32 mNumPackets;
+  UInt32 mDataSize;
+  const void *mData;
+  AudioStreamPacketDescription *mPacketDesc;
+  bool mDone;
+};
+
+// Error value we pass through the decoder to signal that nothing
+// has gone wrong during decoding, but more data is needed.
+const uint32_t kNeedMoreData = 'MOAR';
+
+static OSStatus
+_PassthroughInputDataCallback(AudioConverterRef aAudioConverter,
+                              UInt32 *aNumDataPackets /* in/out */,
+                              AudioBufferList *aData /* in/out */,
+                              AudioStreamPacketDescription **aPacketDesc,
+                              void *aUserData)
+{
+  PassthroughUserData *userData = (PassthroughUserData *)aUserData;
+  if (userData->mDone) {
+    // We make sure this callback is run _once_, with all the data we received
+    // from |AudioFileStreamParseBytes|. When we return an error, the decoder
+    // simply passes the return value on to the calling method,
+    // |SampleCallback|; and flushes all of the audio frames it had
+    // buffered. It does not change the decoder's state.
+    LOG("requested too much data; returning\n");
+    *aNumDataPackets = 0;
+    return kNeedMoreData;
+  }
+
+  userData->mDone = true;
+
+  LOG("AudioConverter wants %u packets of audio data\n", *aNumDataPackets);
+
+  *aNumDataPackets = userData->mNumPackets;
+  *aPacketDesc = userData->mPacketDesc;
+
+  aData->mBuffers[0].mNumberChannels = userData->mDecoder->mConfig.channel_count;
+  aData->mBuffers[0].mDataByteSize = userData->mDataSize;
+  aData->mBuffers[0].mData = const_cast<void *>(userData->mData);
+
+  return noErr;
+}
+
+void
+AppleATDecoder::SampleCallback(uint32_t aNumBytes,
+                               uint32_t aNumPackets,
+                               const void* aData,
+                               AudioStreamPacketDescription* aPackets)
+{
+  // Pick a multiple of the frame size close to a power of two
+  // for efficient allocation.
+  const uint32_t MAX_AUDIO_FRAMES = 128;
+  const uint32_t decodedSize = MAX_AUDIO_FRAMES * mConfig.channel_count *
+    sizeof(AudioDataValue);
+
+  // Descriptions for _decompressed_ audio packets. ignored.
+  nsAutoArrayPtr<AudioStreamPacketDescription>
+      packets(new AudioStreamPacketDescription[MAX_AUDIO_FRAMES]);
+
+  // This API insists on having packets spoon-fed to it from a callback.
+  // This structure exists only to pass our state and the result of the
+  // parser on to the callback above.
+  PassthroughUserData userData =
+      { this, aNumPackets, aNumBytes, aData, aPackets, false };
+
+  do {
+    // Decompressed audio buffer
+    nsAutoArrayPtr<uint8_t> decoded(new uint8_t[decodedSize]);
+
+    AudioBufferList decBuffer;
+    decBuffer.mNumberBuffers = 1;
+    decBuffer.mBuffers[0].mNumberChannels = mConfig.channel_count;
+    decBuffer.mBuffers[0].mDataByteSize = decodedSize;
+    decBuffer.mBuffers[0].mData = decoded.get();
+
+    // in: the max number of packets we can handle from the decoder.
+    // out: the number of packets the decoder is actually returning.
+    UInt32 numFrames = MAX_AUDIO_FRAMES;
+
+    OSStatus rv = AudioConverterFillComplexBuffer(mConverter,
+                                                  _PassthroughInputDataCallback,
+                                                  &userData,
+                                                  &numFrames /* in/out */,
+                                                  &decBuffer,
+                                                  packets.get());
+
+    if (rv && rv != kNeedMoreData) {
+      LOG("Error decoding audio stream: %#x\n", rv);
+      mCallback->Error();
+      break;
+    }
+    LOG("%d frames decoded", numFrames);
+
+    // If we decoded zero frames then AudioConverterFillComplexBuffer is out
+    // of data to provide.  We drained its internal buffer completely on the
+    // last pass.
+    if (numFrames == 0 && rv == kNeedMoreData) {
+      LOG("FillComplexBuffer out of data exactly\n");
+      mCallback->InputExhausted();
+      break;
+    }
+
+    const int rate = mConfig.samples_per_second;
+    int64_t time = FramesToUsecs(mCurrentAudioFrame, rate).value();
+    int64_t duration = FramesToUsecs(numFrames, rate).value();
+
+    LOG("pushed audio at time %lfs; duration %lfs\n",
+        (double)time / USECS_PER_S, (double)duration / USECS_PER_S);
+
+    AudioData *audio = new AudioData(mSamplePosition,
+                                     time, duration, numFrames,
+                                     reinterpret_cast<AudioDataValue *>(decoded.forget()),
+                                     rate);
+    mCallback->Output(audio);
+    mHaveOutput = true;
+
+    mCurrentAudioFrame += numFrames;
+
+    if (rv == kNeedMoreData) {
+      // No error; we just need more data.
+      LOG("FillComplexBuffer out of data\n");
+      mCallback->InputExhausted();
+      break;
+    }
+  } while (true);
+}
+
+void
+AppleATDecoder::SetupDecoder()
+{
+  AudioStreamBasicDescription inputFormat, outputFormat;
+  // Fill in the input format description from the stream.
+  AppleUtils::GetProperty(mStream,
+      kAudioFileStreamProperty_DataFormat, &inputFormat);
+
+  // Fill in the output format manually.
+  PodZero(&outputFormat);
+  outputFormat.mFormatID = kAudioFormatLinearPCM;
+  outputFormat.mSampleRate = inputFormat.mSampleRate;
+  outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
+#if defined(MOZ_SAMPLE_TYPE_FLOAT32)
+  outputFormat.mBitsPerChannel = 32;
+  outputFormat.mFormatFlags =
+    kLinearPCMFormatFlagIsFloat |
+    0;
+#else
+# error Unknown audio sample type
+#endif
+  // Set up the decoder so it gives us one sample per frame
+  outputFormat.mFramesPerPacket = 1;
+  outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame
+        = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
+
+  OSStatus rv = AudioConverterNew(&inputFormat, &outputFormat, &mConverter);
+  if (rv) {
+    LOG("Error %d constructing AudioConverter", rv);
+    mConverter = nullptr;
+    mCallback->Error();
+  }
+  mHaveOutput = false;
+}
+
+void
+AppleATDecoder::SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample)
+{
+  mSamplePosition = aSample->byte_offset;
+  OSStatus rv = AudioFileStreamParseBytes(mStream,
+                                          aSample->size,
+                                          aSample->data,
+                                          0);
+  if (rv != noErr) {
+    LOG("Error %d parsing audio data", rv);
+    mCallback->Error();
+  }
+
+  // Sometimes we need multiple input samples before AudioToolbox
+  // starts decoding. If we haven't seen any output yet, ask for
+  // more data here.
+  if (!mHaveOutput) {
+    mCallback->InputExhausted();
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleATDecoder.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_AppleATDecoder_h
+#define mozilla_AppleATDecoder_h
+
+#include <AudioToolbox/AudioToolbox.h>
+#include "PlatformDecoderModule.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIThread.h"
+
+namespace mozilla {
+
+class MediaTaskQueue;
+class MediaDataDecoderCallback;
+
+class AppleATDecoder : public MediaDataDecoder {
+public:
+  AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                 MediaTaskQueue* aVideoTaskQueue,
+                 MediaDataDecoderCallback* aCallback);
+  ~AppleATDecoder();
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+
+  // Internal callbacks for the platform C api. Don't call externally.
+  void MetadataCallback(AudioFileStreamID aFileStream,
+                        AudioFileStreamPropertyID aPropertyID,
+                        UInt32* aFlags);
+  void SampleCallback(uint32_t aNumBytes,
+                      uint32_t aNumPackets,
+                      const void* aData,
+                      AudioStreamPacketDescription* aPackets);
+
+  // Callbacks also need access to the config.
+  const mp4_demuxer::AudioDecoderConfig& mConfig;
+
+private:
+  RefPtr<MediaTaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
+  AudioConverterRef mConverter;
+  AudioFileStreamID mStream;
+  uint64_t mCurrentAudioFrame;
+  int64_t mSamplePosition;
+  bool mHaveOutput;
+
+  void SetupDecoder();
+  void SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AppleATDecoder_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleCMFunctions.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+// Construct references to each of the CoreMedia symbols we use.
+
+LINK_FUNC(CMVideoFormatDescriptionCreate)
+LINK_FUNC(CMBlockBufferCreateWithMemoryBlock)
+LINK_FUNC(CMSampleBufferCreate)
+LINK_FUNC(CMTimeMake)
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleCMLinker.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+
+#include "AppleCMLinker.h"
+#include "MainThreadUtils.h"
+#include "nsDebug.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+namespace mozilla {
+
+AppleCMLinker::LinkStatus
+AppleCMLinker::sLinkStatus = LinkStatus_INIT;
+
+void* AppleCMLinker::sLink = nullptr;
+nsrefcnt AppleCMLinker::sRefCount = 0;
+
+#define LINK_FUNC(func) typeof(func) func;
+#include "AppleCMFunctions.h"
+#undef LINK_FUNC
+
+/* static */ bool
+AppleCMLinker::Link()
+{
+  // Bump our reference count every time we're called.
+  // Add a lock or change the thread assertion if
+  // you need to call this off the main thread.
+  MOZ_ASSERT(NS_IsMainThread());
+  ++sRefCount;
+
+  if (sLinkStatus) {
+    return sLinkStatus == LinkStatus_SUCCEEDED;
+  }
+
+  const char* dlname =
+    "/System/Library/Frameworks/CoreMedia.framework/CoreMedia";
+  if (!(sLink = dlopen(dlname, RTLD_NOW | RTLD_LOCAL))) {
+    NS_WARNING("Couldn't load CoreMedia framework");
+    goto fail;
+  }
+
+#define LINK_FUNC(func)                                        \
+  func = (typeof(func))dlsym(sLink, #func);                    \
+  if (!func) {                                                 \
+    NS_WARNING("Couldn't load CoreMedia function " #func ); \
+    goto fail;                                                 \
+  }
+#include "AppleCMFunctions.h"
+#undef LINK_FUNC
+
+  LOG("Loaded CoreMedia framework.");
+  sLinkStatus = LinkStatus_SUCCEEDED;
+  return true;
+
+fail:
+  Unlink();
+
+  sLinkStatus = LinkStatus_FAILED;
+  return false;
+}
+
+/* static */ void
+AppleCMLinker::Unlink()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sLink && sRefCount > 0, "Unbalanced Unlink()");
+  --sRefCount;
+  if (sLink && sRefCount < 1) {
+    LOG("Unlinking CoreMedia framework.");
+    dlclose(sLink);
+    sLink = nullptr;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleCMLinker.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef AppleCMLinker_h
+#define AppleCMLinker_h
+
+extern "C" {
+#pragma GCC visibility push(default)
+#include <CoreMedia/CoreMedia.h>
+#pragma GCC visibility pop
+}
+
+#include "nscore.h"
+
+namespace mozilla {
+
+class AppleCMLinker
+{
+public:
+  static bool Link();
+  static void Unlink();
+
+private:
+  static void* sLink;
+  static nsrefcnt sRefCount;
+
+  static enum LinkStatus {
+    LinkStatus_INIT = 0,
+    LinkStatus_FAILED,
+    LinkStatus_SUCCEEDED
+  } sLinkStatus;
+};
+
+#define LINK_FUNC(func) extern typeof(func)* func;
+#include "AppleCMFunctions.h"
+#undef LINK_FUNC
+
+} // namespace mozilla
+
+#endif // AppleCMLinker_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleDecoderModule.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AppleATDecoder.h"
+#include "AppleCMLinker.h"
+#include "AppleDecoderModule.h"
+#include "AppleVTDecoder.h"
+#include "AppleVTLinker.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/DebugOnly.h"
+
+namespace mozilla {
+
+bool AppleDecoderModule::sIsEnabled = false;
+
+AppleDecoderModule::AppleDecoderModule()
+{
+}
+
+AppleDecoderModule::~AppleDecoderModule()
+{
+}
+
+/* static */
+void
+AppleDecoderModule::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+  sIsEnabled = Preferences::GetBool("media.apple.mp4.enabled", false);
+  if (!sIsEnabled) {
+    return;
+  }
+
+  // dlopen CoreMedia.framework if it's available.
+  sIsEnabled = AppleCMLinker::Link();
+  if (!sIsEnabled) {
+    return;
+  }
+
+  // dlopen VideoToolbox.framework if it's available.
+  sIsEnabled = AppleVTLinker::Link();
+}
+
+nsresult
+AppleDecoderModule::Startup()
+{
+  // We don't have any per-instance initialization to do.
+  // Check whether ::Init() above succeeded to know if
+  // we're functional.
+  if (!sIsEnabled) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+AppleDecoderModule::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+  AppleVTLinker::Unlink();
+  AppleCMLinker::Unlink();
+
+  return NS_OK;
+}
+
+MediaDataDecoder*
+AppleDecoderModule::CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                                      mozilla::layers::LayersBackend aLayersBackend,
+                                      mozilla::layers::ImageContainer* aImageContainer,
+                                      MediaTaskQueue* aVideoTaskQueue,
+                                      MediaDataDecoderCallback* aCallback)
+{
+  return new AppleVTDecoder(aConfig, aVideoTaskQueue, aCallback, aImageContainer);
+}
+
+MediaDataDecoder*
+AppleDecoderModule::CreateAACDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                                     MediaTaskQueue* aAudioTaskQueue,
+                                     MediaDataDecoderCallback* aCallback)
+{
+  return new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleDecoderModule.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_AppleDecoderModule_h
+#define mozilla_AppleDecoderModule_h
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class AppleDecoderModule : public PlatformDecoderModule {
+public:
+  AppleDecoderModule();
+  virtual ~AppleDecoderModule();
+
+  // Perform any per-instance initialization.
+  // Main thread only.
+  nsresult Startup();
+
+  // Called when the decoders have shutdown. Main thread only.
+  // Does this really need to be main thread only????
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+  // Decode thread.
+  virtual MediaDataDecoder*
+  CreateH264Decoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                    mozilla::layers::LayersBackend aLayersBackend,
+                    mozilla::layers::ImageContainer* aImageContainer,
+                    MediaTaskQueue* aVideoTaskQueue,
+                    MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+  // Decode thread.
+  virtual MediaDataDecoder* CreateAACDecoder(
+    const mp4_demuxer::AudioDecoderConfig& aConfig,
+    MediaTaskQueue* aAudioTaskQueue,
+    MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+  static void Init();
+private:
+  static bool sIsEnabled;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AppleDecoderModule_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleUtils.cpp
@@ -0,0 +1,84 @@
+/* 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/. */
+
+// Utility functions to help with Apple API calls.
+
+#include <AudioToolbox/AudioToolbox.h>
+#include "AppleUtils.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#define WARN(...) PR_LOG(GetDemuxerLog(), PR_LOG_WARNING, (__VA_ARGS__))
+#else
+#define WARN(...)
+#endif
+
+#define PROPERTY_ID_FORMAT "%c%c%c%c"
+#define PROPERTY_ID_PRINT(x) ((x) >> 24), \
+                             ((x) >> 16) & 0xff, \
+                             ((x) >> 8) & 0xff, \
+                              (x) & 0xff
+
+namespace mozilla {
+
+nsresult
+AppleUtils::GetProperty(AudioFileStreamID aAudioFileStream,
+                        AudioFileStreamPropertyID aPropertyID,
+                        void *aData)
+{
+  UInt32 size;
+  Boolean writeable;
+  OSStatus rv = AudioFileStreamGetPropertyInfo(aAudioFileStream, aPropertyID,
+                                                             &size, &writeable);
+
+  if (rv) {
+    WARN("Couldn't get property " PROPERTY_ID_FORMAT "\n",
+         PROPERTY_ID_PRINT(aPropertyID));
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = AudioFileStreamGetProperty(aAudioFileStream, aPropertyID,
+                                  &size, aData);
+
+  return NS_OK;
+}
+
+void
+AppleUtils::SetCFDict(CFMutableDictionaryRef dict,
+                      const char* key,
+                      const char* value)
+{
+  // We avoid using the CFSTR macros because there's no way to release those.
+  AutoCFRelease<CFStringRef> keyRef =
+    CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
+  AutoCFRelease<CFStringRef> valueRef =
+    CFStringCreateWithCString(NULL, value, kCFStringEncodingUTF8);
+  CFDictionarySetValue(dict, keyRef, valueRef);
+}
+
+void
+AppleUtils::SetCFDict(CFMutableDictionaryRef dict,
+                      const char* key,
+                      int32_t value)
+{
+  AutoCFRelease<CFNumberRef> valueRef =
+    CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
+  AutoCFRelease<CFStringRef> keyRef =
+    CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
+  CFDictionarySetValue(dict, keyRef, valueRef);
+}
+
+void
+AppleUtils::SetCFDict(CFMutableDictionaryRef dict,
+                      const char* key,
+                      bool value)
+{
+  AutoCFRelease<CFStringRef> keyRef =
+    CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
+  CFDictionarySetValue(dict, keyRef, value ? kCFBooleanTrue : kCFBooleanFalse);
+}
+
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleUtils.h
@@ -0,0 +1,66 @@
+/* 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/. */
+
+// Utility functions to help with Apple API calls.
+
+#ifndef mozilla_AppleUtils_h
+#define mozilla_AppleUtils_h
+
+#include <AudioToolbox/AudioToolbox.h>
+#include "nsError.h"
+
+namespace mozilla {
+
+struct AppleUtils {
+  // Helper to retrieve properties from AudioFileStream objects.
+  static nsresult GetProperty(AudioFileStreamID aAudioFileStream,
+                              AudioFileStreamPropertyID aPropertyID,
+                              void *aData);
+
+  // Helper to set a string, string pair on a CFMutableDictionaryRef.
+  static void SetCFDict(CFMutableDictionaryRef dict,
+                        const char* key,
+                        const char* value);
+  // Helper to set a string, int32_t pair on a CFMutableDictionaryRef.
+  static void SetCFDict(CFMutableDictionaryRef dict,
+                        const char* key,
+                        int32_t value);
+  // Helper to set a string, bool pair on a CFMutableDictionaryRef.
+  static void SetCFDict(CFMutableDictionaryRef dict,
+                        const char* key,
+                        bool value);
+};
+
+// Wrapper class to call CFRelease on reference types
+// when they go out of scope.
+template <class T>
+class AutoCFRelease {
+public:
+  AutoCFRelease(T aRef)
+    : mRef(aRef)
+  {
+  }
+  ~AutoCFRelease()
+  {
+    if (mRef) {
+      CFRelease(mRef);
+    }
+  }
+  // Return the wrapped ref so it can be used as an in parameter.
+  operator T()
+  {
+    return mRef;
+  }
+  // Return a pointer to the wrapped ref for use as an out parameter.
+  T* receive()
+  {
+    return &mRef;
+  }
+private:
+  T mRef;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AppleUtils_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleVTDecoder.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <CoreFoundation/CFString.h>
+
+#include "AppleUtils.h"
+#include "mozilla/SHA1.h"
+#include "mp4_demuxer/DecoderData.h"
+#include "MP4Reader.h"
+#include "MP4Decoder.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "AppleCMLinker.h"
+#include "AppleVTDecoder.h"
+#include "AppleVTLinker.h"
+#include "prlog.h"
+#include "MediaData.h"
+#include "VideoUtils.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#define LOG_MEDIA_SHA1
+#else
+#define LOG(...)
+#endif
+
+namespace mozilla {
+
+AppleVTDecoder::AppleVTDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                               MediaTaskQueue* aVideoTaskQueue,
+                               MediaDataDecoderCallback* aCallback,
+                               layers::ImageContainer* aImageContainer)
+  : mConfig(aConfig)
+  , mTaskQueue(aVideoTaskQueue)
+  , mCallback(aCallback)
+  , mImageContainer(aImageContainer)
+  , mFormat(nullptr)
+  , mSession(nullptr)
+{
+  MOZ_COUNT_CTOR(AppleVTDecoder);
+  // TODO: Verify aConfig.mime_type.
+  LOG("Creating AppleVTDecoder for %dx%d h.264 video",
+      mConfig.display_width,
+      mConfig.display_height
+     );
+}
+
+AppleVTDecoder::~AppleVTDecoder()
+{
+  MOZ_COUNT_DTOR(AppleVTDecoder);
+}
+
+nsresult
+AppleVTDecoder::Init()
+{
+  nsresult rv = InitializeSession();
+  return rv;
+}
+
+nsresult
+AppleVTDecoder::Shutdown()
+{
+  if (mSession) {
+    LOG("%s: cleaning up session %p", __func__, mSession);
+    VTDecompressionSessionInvalidate(mSession);
+    CFRelease(mSession);
+    mSession = nullptr;
+  }
+  if (mFormat) {
+    LOG("%s: releasing format %p", __func__, mFormat);
+    CFRelease(mFormat);
+    mFormat = nullptr;
+  }
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
+      aSample,
+      aSample->composition_timestamp,
+      aSample->duration,
+      aSample->is_sync_point ? " keyframe" : "",
+      aSample->size);
+
+#ifdef LOG_MEDIA_SHA1
+  SHA1Sum hash;
+  hash.update(aSample->data, aSample->size);
+  uint8_t digest_buf[SHA1Sum::kHashSize];
+  hash.finish(digest_buf);
+  nsAutoCString digest;
+  for (size_t i = 0; i < sizeof(digest_buf); i++) {
+    digest.AppendPrintf("%02x", digest_buf[i]);
+  }
+  LOG("    sha1 %s", digest.get());
+#endif // LOG_MEDIA_SHA1
+
+  mTaskQueue->Dispatch(
+      NS_NewRunnableMethodWithArg<nsAutoPtr<mp4_demuxer::MP4Sample>>(
+          this,
+          &AppleVTDecoder::SubmitFrame,
+          nsAutoPtr<mp4_demuxer::MP4Sample>(aSample)));
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::Flush()
+{
+  mReorderQueue.Clear();
+  return Drain();
+}
+
+nsresult
+AppleVTDecoder::Drain()
+{
+  OSStatus rv = VTDecompressionSessionWaitForAsynchronousFrames(mSession);
+  if (rv != noErr) {
+    LOG("Error %d draining frames", rv);
+    return NS_ERROR_FAILURE;
+  }
+  return DrainReorderedFrames();
+}
+
+//
+// Implementation details.
+//
+
+// Context object to hold a copy of sample metadata.
+class FrameRef {
+public:
+  Microseconds timestamp;
+  Microseconds duration;
+  int64_t byte_offset;
+  bool is_sync_point;
+
+  explicit FrameRef(mp4_demuxer::MP4Sample* aSample)
+  {
+    MOZ_ASSERT(aSample);
+    timestamp = aSample->composition_timestamp;
+    duration = aSample->duration;
+    byte_offset = aSample->byte_offset;
+    is_sync_point = aSample->is_sync_point;
+  }
+};
+
+// Callback passed to the VideoToolbox decoder for returning data.
+// This needs to be static because the API takes a C-style pair of
+// function and userdata pointers. This validates parameters and
+// forwards the decoded image back to an object method.
+static void
+PlatformCallback(void* decompressionOutputRefCon,
+                 void* sourceFrameRefCon,
+                 OSStatus status,
+                 VTDecodeInfoFlags flags,
+                 CVImageBufferRef image,
+                 CMTime presentationTimeStamp,
+                 CMTime presentationDuration)
+{
+  LOG("AppleVideoDecoder %s status %d flags %d", __func__, status, flags);
+
+  AppleVTDecoder* decoder =
+    static_cast<AppleVTDecoder*>(decompressionOutputRefCon);
+  nsAutoPtr<FrameRef> frameRef =
+    nsAutoPtr<FrameRef>(static_cast<FrameRef*>(sourceFrameRefCon));
+
+  LOG("mp4 output frame %lld pts %lld duration %lld us%s",
+    frameRef->byte_offset,
+    frameRef->timestamp,
+    frameRef->duration,
+    frameRef->is_sync_point ? " keyframe" : ""
+  );
+
+  // Validate our arguments.
+  if (status != noErr || !image) {
+    NS_WARNING("VideoToolbox decoder returned no data");
+    return;
+  }
+  if (flags & kVTDecodeInfo_FrameDropped) {
+    NS_WARNING("  ...frame dropped...");
+  }
+  MOZ_ASSERT(CFGetTypeID(image) == CVPixelBufferGetTypeID(),
+    "VideoToolbox returned an unexpected image type");
+
+  // Forward the data back to an object method which can access
+  // the correct MP4Reader callback.
+  decoder->OutputFrame(image, frameRef);
+}
+
+nsresult
+AppleVTDecoder::DrainReorderedFrames()
+{
+  while (!mReorderQueue.IsEmpty()) {
+    mCallback->Output(mReorderQueue.Pop());
+  }
+  return NS_OK;
+}
+
+// Copy and return a decoded frame.
+nsresult
+AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
+                            nsAutoPtr<FrameRef> aFrameRef)
+{
+  size_t width = CVPixelBufferGetWidth(aImage);
+  size_t height = CVPixelBufferGetHeight(aImage);
+  LOG("  got decoded frame data... %ux%u %s", width, height,
+      CVPixelBufferIsPlanar(aImage) ? "planar" : "chunked");
+#ifdef DEBUG
+  size_t planes = CVPixelBufferGetPlaneCount(aImage);
+  for (size_t i = 0; i < planes; ++i) {
+    size_t stride = CVPixelBufferGetBytesPerRowOfPlane(aImage, i);
+    LOG("     plane %u %ux%u rowbytes %u",
+        (unsigned)i,
+        CVPixelBufferGetWidthOfPlane(aImage, i),
+        CVPixelBufferGetHeightOfPlane(aImage, i),
+        (unsigned)stride);
+  }
+  MOZ_ASSERT(planes == 2);
+#endif // DEBUG
+
+  VideoData::YCbCrBuffer buffer;
+
+  // Lock the returned image data.
+  CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
+  if (rv != kCVReturnSuccess) {
+    NS_ERROR("error locking pixel data");
+    mCallback->Error();
+    return NS_ERROR_FAILURE;
+  }
+  // Y plane.
+  buffer.mPlanes[0].mData =
+    static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 0));
+  buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0);
+  buffer.mPlanes[0].mWidth = width;
+  buffer.mPlanes[0].mHeight = height;
+  buffer.mPlanes[0].mOffset = 0;
+  buffer.mPlanes[0].mSkip = 0;
+  // Cb plane.
+  buffer.mPlanes[1].mData =
+    static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
+  buffer.mPlanes[1].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
+  buffer.mPlanes[1].mWidth = (width+1) / 2;
+  buffer.mPlanes[1].mHeight = (height+1) / 2;
+  buffer.mPlanes[1].mOffset = 0;
+  buffer.mPlanes[1].mSkip = 1;
+  // Cr plane.
+  buffer.mPlanes[2].mData =
+    static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
+  buffer.mPlanes[2].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
+  buffer.mPlanes[2].mWidth = (width+1) / 2;
+  buffer.mPlanes[2].mHeight = (height+1) / 2;
+  buffer.mPlanes[2].mOffset = 1;
+  buffer.mPlanes[2].mSkip = 1;
+
+  // Bounds.
+  VideoInfo info;
+  info.mDisplay = nsIntSize(width, height);
+  info.mHasVideo = true;
+  gfx::IntRect visible = gfx::IntRect(0,
+                                      0,
+                                      mConfig.display_width,
+                                      mConfig.display_height);
+
+  // Copy the image data into our own format.
+  nsAutoPtr<VideoData> data;
+  data =
+    VideoData::Create(info,
+                      mImageContainer,
+                      nullptr,
+                      aFrameRef->byte_offset,
+                      aFrameRef->timestamp,
+                      aFrameRef->duration,
+                      buffer,
+                      aFrameRef->is_sync_point,
+                      aFrameRef->timestamp,
+                      visible);
+  // Unlock the returned image data.
+  CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
+
+  // Frames come out in DTS order but we need to output them
+  // in composition order.
+  mReorderQueue.Push(data.forget());
+  if (mReorderQueue.Length() > 2) {
+    VideoData* readyData = mReorderQueue.Pop();
+    mCallback->Output(readyData);
+  }
+
+  return NS_OK;
+}
+
+// Helper to fill in a timestamp structure.
+static CMSampleTimingInfo
+TimingInfoFromSample(mp4_demuxer::MP4Sample* aSample)
+{
+  CMSampleTimingInfo timestamp;
+
+  timestamp.duration = CMTimeMake(aSample->duration, USECS_PER_S);
+  timestamp.presentationTimeStamp =
+    CMTimeMake(aSample->composition_timestamp, USECS_PER_S);
+  // No DTS value available from libstagefright.
+  timestamp.decodeTimeStamp = CMTimeMake(0, USECS_PER_S);
+
+  return timestamp;
+}
+
+nsresult
+AppleVTDecoder::SubmitFrame(mp4_demuxer::MP4Sample* aSample)
+{
+  // For some reason this gives me a double-free error with stagefright.
+  AutoCFRelease<CMBlockBufferRef> block = nullptr;
+  AutoCFRelease<CMSampleBufferRef> sample = nullptr;
+  VTDecodeInfoFlags flags;
+  OSStatus rv;
+
+  // FIXME: This copies the sample data. I think we can provide
+  // a custom block source which reuses the aSample buffer.
+  // But note that there may be a problem keeping the samples
+  // alive over multiple frames.
+  rv = CMBlockBufferCreateWithMemoryBlock(NULL // Struct allocator.
+                                         ,aSample->data
+                                         ,aSample->size
+                                         ,kCFAllocatorNull // Block allocator.
+                                         ,NULL // Block source.
+                                         ,0    // Data offset.
+                                         ,aSample->size
+                                         ,false
+                                         ,block.receive());
+  NS_ASSERTION(rv == noErr, "Couldn't create CMBlockBuffer");
+  CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample);
+  rv = CMSampleBufferCreate(NULL, block, true, 0, 0, mFormat, 1, 1, &timestamp, 0, NULL, sample.receive());
+  NS_ASSERTION(rv == noErr, "Couldn't create CMSampleBuffer");
+  rv = VTDecompressionSessionDecodeFrame(mSession,
+                                         sample,
+                                         0,
+                                         new FrameRef(aSample),
+                                         &flags);
+  NS_ASSERTION(rv == noErr, "Couldn't pass frame to decoder");
+
+  // Ask for more data.
+  if (mTaskQueue->IsEmpty()) {
+    LOG("AppleVTDecoder task queue empty; requesting more data");
+    mCallback->InputExhausted();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::InitializeSession()
+{
+  OSStatus rv;
+  AutoCFRelease<CFMutableDictionaryRef> extensions =
+    CFDictionaryCreateMutable(NULL, 0,
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+  AppleUtils::SetCFDict(extensions, "CVImageBufferChromaLocationBottomField", "left");
+  AppleUtils::SetCFDict(extensions, "CVImageBufferChromaLocationTopField", "left");
+  AppleUtils::SetCFDict(extensions, "FullRangeVideo", true);
+
+  AutoCFRelease<CFMutableDictionaryRef> atoms =
+    CFDictionaryCreateMutable(NULL, 0,
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+  AutoCFRelease<CFDataRef> avc_data = CFDataCreate(NULL,
+      mConfig.extra_data.begin(), mConfig.extra_data.length());
+
+#ifdef LOG_MEDIA_SHA1
+  SHA1Sum avc_hash;
+  avc_hash.update(mConfig.extra_data.begin(), mConfig.extra_data.length());
+  uint8_t digest_buf[SHA1Sum::kHashSize];
+  avc_hash.finish(digest_buf);
+  nsAutoCString avc_digest;
+  for (size_t i = 0; i < sizeof(digest_buf); i++) {
+    avc_digest.AppendPrintf("%02x", digest_buf[i]);
+  }
+  LOG("AVCDecoderConfig %ld bytes sha1 %s",
+      mConfig.extra_data.length(), avc_digest.get());
+#endif // LOG_MEDIA_SHA1
+
+  CFDictionarySetValue(atoms, CFSTR("avcC"), avc_data);
+  CFDictionarySetValue(extensions, CFSTR("SampleDescriptionExtensionAtoms"), atoms);
+  rv = CMVideoFormatDescriptionCreate(NULL, // Use default allocator.
+                                      kCMVideoCodecType_H264,
+                                      mConfig.display_width,
+                                      mConfig.display_height,
+                                      extensions,
+                                      &mFormat);
+  if (rv != noErr) {
+    NS_ERROR("Couldn't create format description!");
+    return NS_ERROR_FAILURE;
+  }
+
+  // Contruct video decoder selection spec.
+  AutoCFRelease<CFMutableDictionaryRef> spec =
+    CFDictionaryCreateMutable(NULL, 0,
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+  // This key is supported (or ignored) but not declared prior to OSX 10.9.
+  AutoCFRelease<CFStringRef>
+        kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder =
+        CFStringCreateWithCString(NULL, "EnableHardwareAcceleratedVideoDecoder",
+            kCFStringEncodingUTF8);
+
+  CFDictionarySetValue(spec,
+      kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
+      kCFBooleanTrue);
+
+  VTDecompressionOutputCallbackRecord cb = { PlatformCallback, this };
+  rv = VTDecompressionSessionCreate(NULL, // Allocator.
+                                    mFormat,
+                                    spec, // Video decoder selection.
+                                    NULL, // Output video format.
+                                    &cb,
+                                    &mSession);
+  if (rv != noErr) {
+    NS_ERROR("Couldn't create decompression session!");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleVTDecoder.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_AppleVTDecoder_h
+#define mozilla_AppleVTDecoder_h
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIThread.h"
+#include "ReorderQueue.h"
+
+#include "VideoToolbox/VideoToolbox.h"
+
+namespace mozilla {
+
+class MediaTaskQueue;
+class MediaDataDecoderCallback;
+namespace layers {
+  class ImageContainer;
+}
+class FrameRef;
+
+class AppleVTDecoder : public MediaDataDecoder {
+public:
+  AppleVTDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                 MediaTaskQueue* aVideoTaskQueue,
+                 MediaDataDecoderCallback* aCallback,
+                 layers::ImageContainer* aImageContainer);
+  ~AppleVTDecoder();
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+  // Return hook for VideoToolbox callback.
+  nsresult OutputFrame(CVPixelBufferRef aImage,
+                       nsAutoPtr<FrameRef> frameRef);
+private:
+  const mp4_demuxer::VideoDecoderConfig& mConfig;
+  RefPtr<MediaTaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
+  layers::ImageContainer* mImageContainer;
+  CMVideoFormatDescriptionRef mFormat;
+  VTDecompressionSessionRef mSession;
+  ReorderQueue mReorderQueue;
+
+  // Method to pass a frame to VideoToolbox for decoding.
+  nsresult SubmitFrame(mp4_demuxer::MP4Sample* aSample);
+  // Method to set up the decompression session.
+  nsresult InitializeSession();
+  nsresult DrainReorderedFrames();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AppleVTDecoder_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleVTFunctions.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+// Construct references to each of the VideoToolbox symbols we use.
+
+LINK_FUNC(VTDecompressionSessionCreate)
+LINK_FUNC(VTDecompressionSessionDecodeFrame)
+LINK_FUNC(VTDecompressionSessionInvalidate)
+LINK_FUNC(VTDecompressionSessionWaitForAsynchronousFrames)
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleVTLinker.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+
+#include "AppleVTLinker.h"
+#include "MainThreadUtils.h"
+#include "nsDebug.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+namespace mozilla {
+
+AppleVTLinker::LinkStatus
+AppleVTLinker::sLinkStatus = LinkStatus_INIT;
+
+void* AppleVTLinker::sLink = nullptr;
+nsrefcnt AppleVTLinker::sRefCount = 0;
+
+#define LINK_FUNC(func) typeof(func) func;
+#include "AppleVTFunctions.h"
+#undef LINK_FUNC
+
+/* static */ bool
+AppleVTLinker::Link()
+{
+  // Bump our reference count every time we're called.
+  // Add a lock or change the thread assertion if
+  // you need to call this off the main thread.
+  MOZ_ASSERT(NS_IsMainThread());
+  ++sRefCount;
+
+  if (sLinkStatus) {
+    return sLinkStatus == LinkStatus_SUCCEEDED;
+  }
+
+  const char* dlname =
+    "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox";
+  if (!(sLink = dlopen(dlname, RTLD_NOW | RTLD_LOCAL))) {
+    NS_WARNING("Couldn't load VideoToolbox framework");
+    goto fail;
+  }
+
+#define LINK_FUNC(func)                                        \
+  func = (typeof(func))dlsym(sLink, #func);                    \
+  if (!func) {                                                 \
+    NS_WARNING("Couldn't load VideoToolbox function " #func ); \
+    goto fail;                                                 \
+  }
+#include "AppleVTFunctions.h"
+#undef LINK_FUNC
+
+  LOG("Loaded VideoToolbox framework.");
+  sLinkStatus = LinkStatus_SUCCEEDED;
+  return true;
+
+fail:
+  Unlink();
+
+  sLinkStatus = LinkStatus_FAILED;
+  return false;
+}
+
+/* static */ void
+AppleVTLinker::Unlink()
+{
+  // We'll be called by multiple Decoders, one intantiated for
+  // each media element. Therefore we receive must maintain a
+  // reference count to avoidunloading our symbols when other
+  // instances still need them.
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sLink && sRefCount > 0, "Unbalanced Unlink()");
+  --sRefCount;
+  if (sLink && sRefCount < 1) {
+    LOG("Unlinking VideoToolbox framework.");
+    dlclose(sLink);
+    sLink = nullptr;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/AppleVTLinker.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef AppleVTLinker_h
+#define AppleVTLinker_h
+
+extern "C" {
+#pragma GCC visibility push(default)
+#include "VideoToolbox/VideoToolbox.h"
+#pragma GCC visibility pop
+}
+
+#include "nscore.h"
+
+namespace mozilla {
+
+class AppleVTLinker
+{
+public:
+  static bool Link();
+  static void Unlink();
+
+private:
+  static void* sLink;
+  static nsrefcnt sRefCount;
+
+  static enum LinkStatus {
+    LinkStatus_INIT = 0,
+    LinkStatus_FAILED,
+    LinkStatus_SUCCEEDED
+  } sLinkStatus;
+};
+
+#define LINK_FUNC(func) extern typeof(func)* func;
+#include "AppleVTFunctions.h"
+#undef LINK_FUNC
+
+} // namespace mozilla
+
+#endif // AppleVTLinker_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/ReorderQueue.h
@@ -0,0 +1,29 @@
+/* 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/. */
+
+// Queue for ordering decoded video frames by presentation time.
+// Decoders often return frames out of order, which we need to
+// buffer so we can forward them in correct presentation order.
+
+#ifndef mozilla_ReorderQueue_h
+#define mozilla_ReorderQueue_h
+
+#include <MediaData.h>
+#include <nsTPriorityQueue.h>
+
+namespace mozilla {
+
+struct ReorderQueueComparator
+{
+  bool LessThan(VideoData* const& a, VideoData* const& b) const
+  {
+    return a->mTime < b->mTime;
+  }
+};
+
+typedef nsTPriorityQueue<VideoData*, ReorderQueueComparator> ReorderQueue;
+
+} // namespace mozilla
+
+#endif // mozilla_ReorderQueue_h
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/apple/VideoToolbox/VideoToolbox.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+// Stub header for VideoToolbox framework API.
+// We include our own copy so we can build on MacOS versions
+// where it's not available.
+
+#ifndef mozilla_VideoToolbox_VideoToolbox_h
+#define mozilla_VideoToolbox_VideoToolbox_h
+
+// CoreMedia is available starting in OS X 10.7,
+// so we need to dlopen it as well to run on 10.6,
+// but we can depend on the real framework headers at build time.
+
+#include <CoreMedia/CMBase.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreVideo/CVPixelBuffer.h>
+#include <CoreMedia/CMSampleBuffer.h>
+#include <CoreMedia/CMFormatDescription.h>
+#include <CoreMedia/CMTime.h>
+
+typedef uint32_t VTDecodeFrameFlags;
+typedef uint32_t VTDecodeInfoFlags;
+enum {
+  kVTDecodeInfo_Asynchronous = 1UL << 0,
+  kVTDecodeInfo_FrameDropped = 1UL << 1,
+};
+
+typedef struct OpaqueVTDecompressionSession* VTDecompressionSessionRef;
+typedef void (*VTDecompressionOutputCallback)(
+    void*,
+    void*,
+    OSStatus,
+    VTDecodeInfoFlags,
+    CVImageBufferRef,
+    CMTime,
+    CMTime
+);
+typedef struct VTDecompressionOutputCallbackRecord {
+    VTDecompressionOutputCallback decompressionOutputCallback;
+    void*                         decompressionOutputRefCon;
+} VTDecompressionOutputCallbackRecord;
+
+OSStatus
+VTDecompressionSessionCreate(
+    CFAllocatorRef,
+    CMVideoFormatDescriptionRef,
+    CFDictionaryRef,
+    CFDictionaryRef,
+    const VTDecompressionOutputCallbackRecord*,
+    VTDecompressionSessionRef*
+);
+
+OSStatus
+VTDecompressionSessionDecodeFrame(
+    VTDecompressionSessionRef,
+    CMSampleBufferRef,
+    VTDecodeFrameFlags,
+    void*,
+    VTDecodeInfoFlags*
+);
+
+OSStatus
+VTDecompressionSessionWaitForAsynchronousFrames(
+    VTDecompressionSessionRef
+);
+
+void
+VTDecompressionSessionInvalidate(
+    VTDecompressionSessionRef
+);
+
+#endif // mozilla_VideoToolbox_VideoToolbox_h
--- a/content/media/fmp4/moz.build
+++ b/content/media/fmp4/moz.build
@@ -37,11 +37,27 @@ if CONFIG['MOZ_FFMPEG']:
         'ffmpeg/libav53',
         'ffmpeg/libav54',
         'ffmpeg/libav55',
     ]
     LOCAL_INCLUDES += [
         'ffmpeg',
     ]
 
+if CONFIG['MOZ_APPLEMEDIA']:
+  EXPORTS += [
+      'apple/AppleDecoderModule.h',
+  ]
+  UNIFIED_SOURCES += [
+      'apple/AppleATDecoder.cpp',
+      'apple/AppleCMLinker.cpp',
+      'apple/AppleDecoderModule.cpp',
+      'apple/AppleUtils.cpp',
+      'apple/AppleVTDecoder.cpp',
+      'apple/AppleVTLinker.cpp',
+  ]
+  LDFLAGS += [
+      '-framework AudioToolbox',
+  ]
+
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
--- a/content/media/gmp/GMPChild.cpp
+++ b/content/media/gmp/GMPChild.cpp
@@ -21,18 +21,16 @@ using mozilla::dom::CrashReporterChild;
 #include <stdlib.h> // for _exit()
 #else
 #include <unistd.h> // for _exit()
 #endif
 
 #if defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
-#elif defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
-#include "mozilla/Sandbox.h"
 #endif
 
 namespace mozilla {
 namespace gmp {
 
 GMPChild::GMPChild()
   : mLib(nullptr)
   , mGetAPIFunc(nullptr)
@@ -95,23 +93,16 @@ GMPChild::LoadPluginLibrary(const std::s
   nsAutoString binaryName =                            baseName + NS_LITERAL_STRING(".dll");
 #else
 #error not defined
 #endif
   libFile->AppendRelativePath(binaryName);
 
   nsAutoCString nativePath;
   libFile->GetNativePath(nativePath);
-
-#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
-  // Enable sandboxing here -- we know the plugin file's path, but
-  // this process's execution hasn't been affected by its content yet.
-  mozilla::SetMediaPluginSandbox(nativePath.get());
-#endif
-
   mLib = PR_LoadLibrary(nativePath.get());
   if (!mLib) {
     return false;
   }
 
   GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
   if (!initFunc) {
     return false;
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -324,17 +324,16 @@ AudioDestinationNode::AudioDestinationNo
                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
                             MediaStreamGraph::GetInstance();
   AudioNodeEngine* engine = aIsOffline ?
                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
                                                              aLength, aSampleRate) :
                             static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
 
   mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
-  mStream->SetAudioChannelType(aChannel);
   mStream->AddMainThreadListener(this);
   mStream->AddAudioOutput(&gWebAudioOutputKey);
 
   if (aChannel != AudioChannel::Normal) {
     ErrorResult rv;
     SetMozAudioChannelType(aChannel, rv);
   }
 }
@@ -534,16 +533,20 @@ AudioDestinationNode::SetMozAudioChannel
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (aValue != mAudioChannel &&
       CheckAudioChannelPermissions(aValue)) {
     mAudioChannel = aValue;
 
+    if (mStream) {
+      mStream->SetAudioChannelType(mAudioChannel);
+    }
+
     if (mAudioChannelAgent) {
       CreateAudioChannelAgent();
     }
   }
 }
 
 bool
 AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue)
--- a/content/svg/content/src/SVGSVGElement.cpp
+++ b/content/svg/content/src/SVGSVGElement.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stdint.h>
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/BasicEvents.h"
+#include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Likely.h"
 
 #include "nsGkAtoms.h"
 #include "nsLayoutUtils.h"
 #include "nsLayoutStylesheetCache.h"
 #include "DOMSVGNumber.h"
 #include "DOMSVGLength.h"
@@ -513,21 +513,24 @@ SVGSVGElement::SetCurrentScaleTranslate(
   mCurrentScale = s;
   mCurrentTranslate = SVGPoint(x, y);
 
   // now dispatch the appropriate event if we are the root element
   nsIDocument* doc = GetCurrentDoc();
   if (doc) {
     nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
     if (presShell && IsRoot()) {
-      bool scaling = (mPreviousScale != mCurrentScale);
       nsEventStatus status = nsEventStatus_eIgnore;
-      WidgetGUIEvent event(true, scaling ? NS_SVG_ZOOM : NS_SVG_SCROLL, 0);
-      event.eventStructType = scaling ? NS_SVGZOOM_EVENT : NS_EVENT;
-      presShell->HandleDOMEventWithTarget(this, &event, &status);
+      if (mPreviousScale != mCurrentScale) {
+        InternalSVGZoomEvent svgZoomEvent(true, NS_SVG_ZOOM);
+        presShell->HandleDOMEventWithTarget(this, &svgZoomEvent, &status);
+      } else {
+        WidgetEvent svgScrollEvent(true, NS_SVG_SCROLL);
+        presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
+      }
       InvalidateTransformNotifyFrame();
     }
   }
 }
 
 void
 SVGSVGElement::SetCurrentTranslate(float x, float y)
 {
--- a/content/svg/content/src/SVGZoomEvent.cpp
+++ b/content/svg/content/src/SVGZoomEvent.cpp
@@ -1,19 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/dom/SVGZoomEvent.h"
 #include "DOMSVGPoint.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/Element.h"
 #include "mozilla/dom/SVGSVGElement.h"
-#include "nsIPresShell.h"
+#include "mozilla/dom/SVGZoomEvent.h"
 #include "nsIDocument.h"
-#include "mozilla/dom/Element.h"
+#include "nsIPresShell.h"
 #include "prtime.h"
 
 namespace mozilla {
 namespace dom {
 
 //----------------------------------------------------------------------
 // Implementation
 
@@ -22,33 +23,30 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGZo
 NS_IMPL_ADDREF_INHERITED(SVGZoomEvent, UIEvent)
 NS_IMPL_RELEASE_INHERITED(SVGZoomEvent, UIEvent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGZoomEvent)
 NS_INTERFACE_MAP_END_INHERITING(UIEvent)
 
 SVGZoomEvent::SVGZoomEvent(EventTarget* aOwner,
                            nsPresContext* aPresContext,
-                           WidgetGUIEvent* aEvent)
+                           InternalSVGZoomEvent* aEvent)
   : UIEvent(aOwner, aPresContext,
-            aEvent ? aEvent : new WidgetGUIEvent(false, NS_SVG_ZOOM, 0))
+            aEvent ? aEvent : new InternalSVGZoomEvent(false, NS_SVG_ZOOM))
   , mPreviousScale(0)
   , mNewScale(0)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->eventStructType = NS_SVGZOOM_EVENT;
     mEvent->time = PR_Now();
   }
 
-  mEvent->mFlags.mCancelable = false;
-
   // We must store the "Previous" and "New" values before this event is
   // dispatched. Reading the values from the root 'svg' element after we've
   // been dispatched is not an option since event handler code may change
   // currentScale and currentTranslate in response to this event.
   nsIPresShell *presShell;
   if (mPresContext && (presShell = mPresContext->GetPresShell())) {
     nsIDocument *doc = presShell->GetDocument();
     if (doc) {
@@ -90,14 +88,14 @@ SVGZoomEvent::~SVGZoomEvent()
 
 ////////////////////////////////////////////////////////////////////////
 // Exported creation functions:
 
 nsresult
 NS_NewDOMSVGZoomEvent(nsIDOMEvent** aInstancePtrResult,
                       mozilla::dom::EventTarget* aOwner,
                       nsPresContext* aPresContext,
-                      mozilla::WidgetGUIEvent* aEvent)
+                      mozilla::InternalSVGZoomEvent* aEvent)
 {
   mozilla::dom::SVGZoomEvent* it =
     new mozilla::dom::SVGZoomEvent(aOwner, aPresContext, aEvent);
   return CallQueryInterface(it, aInstancePtrResult);
 }
--- a/content/svg/content/src/SVGZoomEvent.h
+++ b/content/svg/content/src/SVGZoomEvent.h
@@ -23,17 +23,17 @@ namespace dom {
 class SVGZoomEvent MOZ_FINAL : public UIEvent
 {
 public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGZoomEvent, UIEvent)
   NS_DECL_ISUPPORTS_INHERITED
 
   SVGZoomEvent(EventTarget* aOwner, nsPresContext* aPresContext,
-               WidgetGUIEvent* aEvent);
+               InternalSVGZoomEvent* aEvent);
 
   // Forward to base class
   NS_FORWARD_TO_UIEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
   {
     return SVGZoomEventBinding::Wrap(aCx, this);
   }
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -724,20 +724,20 @@ EventDispatcher::CreateEvent(EventTarget
     case NS_TEXT_EVENT:
       return NS_NewDOMUIEvent(aDOMEvent, aOwner, aPresContext,
                               aEvent->AsTextEvent());
     case NS_CLIPBOARD_EVENT:
       return NS_NewDOMClipboardEvent(aDOMEvent, aOwner, aPresContext,
                                      aEvent->AsClipboardEvent());
     case NS_SVGZOOM_EVENT:
       return NS_NewDOMSVGZoomEvent(aDOMEvent, aOwner, aPresContext,
-                                   aEvent->AsGUIEvent());
+                                   aEvent->AsSVGZoomEvent());
     case NS_SMIL_TIME_EVENT:
-      return NS_NewDOMTimeEvent(aDOMEvent, aOwner, aPresContext, aEvent);
-
+      return NS_NewDOMTimeEvent(aDOMEvent, aOwner, aPresContext,
+                                aEvent->AsSMILTimeEvent());
     case NS_COMMAND_EVENT:
       return NS_NewDOMCommandEvent(aDOMEvent, aOwner, aPresContext,
                                    aEvent->AsCommandEvent());
     case NS_SIMPLE_GESTURE_EVENT:
       return NS_NewDOMSimpleGestureEvent(aDOMEvent, aOwner, aPresContext,
                                          aEvent->AsSimpleGestureEvent());
     case NS_POINTER_EVENT:
       return NS_NewDOMPointerEvent(aDOMEvent, aOwner, aPresContext,
--- a/dom/interfaces/events/nsIDOMEvent.idl
+++ b/dom/interfaces/events/nsIDOMEvent.idl
@@ -301,22 +301,22 @@ nsresult
 NS_NewDOMSVGEvent(nsIDOMEvent** aResult,
                   mozilla::dom::EventTarget* aOwner,
                   nsPresContext* aPresContext,
                   mozilla::WidgetEvent* aEvent);
 nsresult
 NS_NewDOMSVGZoomEvent(nsIDOMEvent** aResult,
                       mozilla::dom::EventTarget* aOwner,
                       nsPresContext* aPresContext,
-                      mozilla::WidgetGUIEvent* aEvent);
+                      mozilla::InternalSVGZoomEvent* aEvent);
 nsresult
 NS_NewDOMTimeEvent(nsIDOMEvent** aResult,
                    mozilla::dom::EventTarget* aOwner,
                    nsPresContext* aPresContext,
-                   mozilla::WidgetEvent* aEvent);
+                   mozilla::InternalSMILTimeEvent* aEvent);
 nsresult
 NS_NewDOMXULCommandEvent(nsIDOMEvent** aResult,
                          mozilla::dom::EventTarget* aOwner,
                          nsPresContext* aPresContext,
                          mozilla::WidgetInputEvent* aEvent);
 nsresult
 NS_NewDOMCommandEvent(nsIDOMEvent** aInstancePtrResult,
                       mozilla::dom::EventTarget* aOwner,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -913,17 +913,17 @@ ContentChild::AllocPBackgroundChild(Tran
 
 bool
 ContentChild::RecvSetProcessSandbox()
 {
   // We may want to move the sandbox initialization somewhere else
   // at some point; see bug 880808.
 #if defined(MOZ_CONTENT_SANDBOX)
 #if defined(XP_LINUX)
-    SetContentProcessSandbox();
+    SetCurrentProcessSandbox();
 #elif defined(XP_WIN)
     mozilla::SandboxTarget::Instance()->StartSandbox();
 #endif
 #endif
     return true;
 }
 
 bool
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -41,19 +41,17 @@ UNIFIED_SOURCES += [
     'MediaManager.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'PeerConnection.js',
     'PeerConnection.manifest',
 ]
 
-JS_MODULES_PATH = 'modules/media'
-
-EXTRA_JS_MODULES += [
+EXTRA_JS_MODULES.media += [
     'IdpProxy.jsm',
     'PeerConnectionIdp.jsm',
     'RTCStatsReport.jsm',
 ]
 
 if CONFIG['MOZ_B2G']:
     EXPORTS.mozilla += [
         'MediaPermissionGonk.h',
--- a/dom/mobilemessage/src/moz.build
+++ b/dom/mobilemessage/src/moz.build
@@ -14,17 +14,17 @@ EXPORTS.mozilla.dom.mobilemessage += [
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     SOURCES += [
         'android/MobileMessageDatabaseService.cpp',
         'android/SmsService.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
-    EXTRA_JS_MODULES = [
+    EXTRA_JS_MODULES += [
         'gonk/mms_consts.js',
         'gonk/MmsPduHelper.jsm',
         'gonk/MobileMessageDB.jsm',
         'gonk/wap_consts.js',
         'gonk/WspPduHelper.jsm',
     ]
     EXTRA_COMPONENTS += [
         'gonk/MmsService.js',
--- a/dom/network/src/moz.build
+++ b/dom/network/src/moz.build
@@ -22,17 +22,17 @@ UNIFIED_SOURCES += [
     'TCPServerSocketParent.cpp',
     'TCPSocketChild.cpp',
     'TCPSocketParent.cpp',
     'UDPSocketChild.cpp',
     'UDPSocketParent.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
-    EXTRA_JS_MODULES = [
+    EXTRA_JS_MODULES += [
         'NetworkStatsDB.jsm',
         'NetworkStatsService.jsm',
     ]
 
 EXTRA_COMPONENTS += [
     'TCPServerSocket.js',
     'TCPSocket.manifest',
     'TCPSocketParentIntermediary.js',
--- a/dom/smil/TimeEvent.cpp
+++ b/dom/smil/TimeEvent.cpp
@@ -1,44 +1,36 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/ContentEvents.h"
 #include "mozilla/dom/TimeEvent.h"
-#include "mozilla/BasicEvents.h"
 #include "nsIDocShell.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
 namespace dom {
 
 TimeEvent::TimeEvent(EventTarget* aOwner,
                      nsPresContext* aPresContext,
-                     WidgetEvent* aEvent)
+                     InternalSMILTimeEvent* aEvent)
   : Event(aOwner, aPresContext,
-          aEvent ? aEvent : new InternalUIEvent(false, 0))
-  , mDetail(0)
+          aEvent ? aEvent : new InternalSMILTimeEvent(false, 0))
+  , mDetail(mEvent->AsSMILTimeEvent()->detail)
 {
   SetIsDOMBinding();
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->eventStructType = NS_SMIL_TIME_EVENT;
   }
 
-  if (mEvent->eventStructType == NS_SMIL_TIME_EVENT) {
-    mDetail = mEvent->AsUIEvent()->detail;
-  }
-
-  mEvent->mFlags.mBubbles = false;
-  mEvent->mFlags.mCancelable = false;
-
   if (mPresContext) {
     nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell();
     if (docShell) {
       mView = docShell->GetWindow();
     }
   }
 }
 
@@ -87,15 +79,15 @@ TimeEvent::InitTimeEvent(const nsAString
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsresult
 NS_NewDOMTimeEvent(nsIDOMEvent** aInstancePtrResult,
                    EventTarget* aOwner,
                    nsPresContext* aPresContext,
-                   WidgetEvent* aEvent)
+                   InternalSMILTimeEvent* aEvent)
 {
   TimeEvent* it = new TimeEvent(aOwner, aPresContext, aEvent);
   NS_ADDREF(it);
   *aInstancePtrResult = static_cast<Event*>(it);
   return NS_OK;
 }
--- a/dom/smil/TimeEvent.h
+++ b/dom/smil/TimeEvent.h
@@ -14,17 +14,17 @@ namespace mozilla {
 namespace dom {
 
 class TimeEvent MOZ_FINAL : public Event,
                             public nsIDOMTimeEvent
 {
 public:
   TimeEvent(EventTarget* aOwner,
             nsPresContext* aPresContext,
-            WidgetEvent* aEvent);
+            InternalSMILTimeEvent* aEvent);
 
   // nsISupports interface:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TimeEvent, Event)
 
   // nsIDOMTimeEvent interface:
   NS_DECL_NSIDOMTIMEEVENT
 
--- a/dom/smil/nsSMILTimedElement.cpp
+++ b/dom/smil/nsSMILTimedElement.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 
-#include "mozilla/BasicEvents.h"
+#include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/SVGAnimationElement.h"
 #include "nsSMILTimedElement.h"
 #include "nsAttrValueInlines.h"
 #include "nsSMILAnimationFunction.h"
 #include "nsSMILTimeValue.h"
 #include "nsSMILTimeValueSpec.h"
 #include "nsSMILInstanceTime.h"
@@ -87,18 +87,17 @@ namespace
   public:
     AsyncTimeEventRunner(nsIContent* aTarget, uint32_t aMsg, int32_t aDetail)
       : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail)
     {
     }
 
     NS_IMETHOD Run()
     {
-      InternalUIEvent event(true, mMsg);
-      event.eventStructType = NS_SMIL_TIME_EVENT;
+      InternalSMILTimeEvent event(true, mMsg);
       event.detail = mDetail;
 
       nsPresContext* context = nullptr;
       nsIDocument* doc = mTarget->GetCurrentDoc();
       if (doc) {
         nsCOMPtr<nsIPresShell> shell = doc->GetShell();
         if (shell) {
           context = shell->GetPresContext();
--- a/dom/wappush/src/moz.build
+++ b/dom/wappush/src/moz.build
@@ -1,14 +1,14 @@
 # -*- 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'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
-    EXTRA_JS_MODULES = [
+    EXTRA_JS_MODULES += [
         'gonk/CpPduHelper.jsm',
         'gonk/SiPduHelper.jsm',
         'gonk/SlPduHelper.jsm',
         'gonk/WapPushManager.js',
         'gonk/WbxmlPduHelper.jsm'
     ]
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -540,18 +540,18 @@ GLBlitHelper::DeleteTexBlitProgram()
     if (mTexYUVPlanarBlit_Program) {
         mGL->fDeleteProgram(mTexYUVPlanarBlit_Program);
         mTexYUVPlanarBlit_Program = 0;
     }
 }
 
 void
 GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
-                                        const gfx::IntSize& srcSize,
-                                        const gfx::IntSize& destSize)
+                                           const gfx::IntSize& srcSize,
+                                           const gfx::IntSize& destSize)
 {
     MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
     MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
 
     MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
 
     ScopedBindFramebuffer boundFB(mGL);
     ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
@@ -562,19 +562,19 @@ GLBlitHelper::BlitFramebufferToFramebuff
     mGL->fBlitFramebuffer(0, 0,  srcSize.width,  srcSize.height,
                           0, 0, destSize.width, destSize.height,
                           LOCAL_GL_COLOR_BUFFER_BIT,
                           LOCAL_GL_NEAREST);
 }
 
 void
 GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
-                                        const gfx::IntSize& srcSize,
-                                        const gfx::IntSize& destSize,
-                                        const GLFormats& srcFormats)
+                                           const gfx::IntSize& srcSize,
+                                           const gfx::IntSize& destSize,
+                                           const GLFormats& srcFormats)
 {
     MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
     MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
 
     if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
         BlitFramebufferToFramebuffer(srcFB, destFB,
                                      srcSize, destSize);
         return;
@@ -585,17 +585,21 @@ GLBlitHelper::BlitFramebufferToFramebuff
 
     BlitFramebufferToTexture(srcFB, tex, srcSize, srcSize);
     BlitTextureToFramebuffer(tex, destFB, srcSize, destSize);
 
     mGL->fDeleteTextures(1, &tex);
 }
 
 void
-GLBlitHelper::BindAndUploadYUVTexture(Channel which, uint32_t width, uint32_t height, void* data, bool needsAllocation)
+GLBlitHelper::BindAndUploadYUVTexture(Channel which,
+                                      uint32_t width,
+                                      uint32_t height,
+                                      void* data,
+                                      bool needsAllocation)
 {
     MOZ_ASSERT(which < Channel_Max, "Invalid channel!");
     GLuint* srcTexArr[3] = {&mSrcTexY, &mSrcTexCb, &mSrcTexCr};
     GLuint& tex = *srcTexArr[which];
     if (!tex) {
         MOZ_ASSERT(needsAllocation);
         tex = CreateTexture(mGL, LOCAL_GL_LUMINANCE, LOCAL_GL_LUMINANCE, LOCAL_GL_UNSIGNED_BYTE,
                             gfx::IntSize(width, height), false);
@@ -623,34 +627,36 @@ GLBlitHelper::BindAndUploadYUVTexture(Ch
                          LOCAL_GL_LUMINANCE,
                          LOCAL_GL_UNSIGNED_BYTE,
                          data);
     }
 }
 
 #ifdef MOZ_WIDGET_GONK
 void
-GLBlitHelper::BindAndUploadExternalTexture(EGLImage image) {
+GLBlitHelper::BindAndUploadExternalTexture(EGLImage image)
+{
     MOZ_ASSERT(image != EGL_NO_IMAGE, "Bad EGLImage");
 
     if (!mSrcTexEGL) {
         mGL->fGenTextures(1, &mSrcTexEGL);
         mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mSrcTexEGL);
         mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
         mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
         mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
         mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
     } else {
         mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mSrcTexEGL);
     }
     mGL->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_EXTERNAL_OES, image);
 }
 
 bool
-GLBlitHelper::BlitGrallocImage(layers::GrallocImage* grallocImage, bool yFlip) {
+GLBlitHelper::BlitGrallocImage(layers::GrallocImage* grallocImage, bool yFlip)
+{
     ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
     mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
 
     EGLint attrs[] = {
         LOCAL_EGL_IMAGE_PRESERVED, LOCAL_EGL_TRUE,
         LOCAL_EGL_NONE, LOCAL_EGL_NONE
     };
     EGLImage image = sEGLLibrary.fCreateImage(sEGLLibrary.Display(),
@@ -708,33 +714,40 @@ GLBlitHelper::BlitPlanarYCbCrImage(layer
     for (int i = 0; i < 3; i++) {
         mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
         mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
     }
     return true;
 }
 
 bool
-GLBlitHelper::BlitImageToTexture(layers::Image* srcImage, const gfx::IntSize& destSize, GLuint destTex, GLenum destTarget, bool yFlip, GLuint xoffset, GLuint yoffset, GLuint cropWidth, GLuint cropHeight)
+GLBlitHelper::BlitImageToTexture(layers::Image* srcImage,
+                                 const gfx::IntSize& destSize,
+                                 GLuint destTex,
+                                 GLenum destTarget,
+                                 bool yFlip,
+                                 GLuint xoffset,
+                                 GLuint yoffset,
+                                 GLuint cropWidth,
+                                 GLuint cropHeight)
 {
     ScopedGLDrawState autoStates(mGL);
 
     BlitType type;
-    switch (srcImage->GetFormat())
-    {
-        case ImageFormat::PLANAR_YCBCR:
-            type = ConvertPlanarYCbCr;
-            break;
-        case ImageFormat::GRALLOC_PLANAR_YCBCR:
+    switch (srcImage->GetFormat()) {
+    case ImageFormat::PLANAR_YCBCR:
+        type = ConvertPlanarYCbCr;
+        break;
+    case ImageFormat::GRALLOC_PLANAR_YCBCR:
 #ifdef MOZ_WIDGET_GONK
-            type = ConvertGralloc;
-            break;
+        type = ConvertGralloc;
+        break;
 #endif
-        default:
-            return false;
+    default:
+        return false;
     }
 
     bool init = InitTexQuadProgram(type);
     if (!init) {
         return false;
     }
 
     if (!mFBO) {
@@ -762,19 +775,19 @@ GLBlitHelper::BlitImageToTexture(layers:
         return BlitPlanarYCbCrImage(yuvImage, yFlip);
     }
 
     return false;
 }
 
 void
 GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
-                                    const gfx::IntSize& srcSize,
-                                    const gfx::IntSize& destSize,
-                                    GLenum srcTarget)
+                                       const gfx::IntSize& srcSize,
+                                       const gfx::IntSize& destSize,
+                                       GLenum srcTarget)
 {
     MOZ_ASSERT(mGL->fIsTexture(srcTex));
     MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
 
     if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
         ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
         MOZ_ASSERT(srcWrapper.IsComplete());
 
@@ -808,19 +821,19 @@ GLBlitHelper::BlitTextureToFramebuffer(G
         printf_stderr("[%s:%d] Fatal Error: Failed to prepare to blit texture->framebuffer.\n");
         MOZ_CRASH();
     }
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
 }
 
 void
 GLBlitHelper::BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
-                                    const gfx::IntSize& srcSize,
-                                    const gfx::IntSize& destSize,
-                                    GLenum destTarget)
+                                       const gfx::IntSize& srcSize,
+                                       const gfx::IntSize& destSize,
+                                       GLenum destTarget)
 {
     MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
     MOZ_ASSERT(mGL->fIsTexture(destTex));
 
     if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
         ScopedFramebufferForTexture destWrapper(mGL, destTex, destTarget);
 
         BlitFramebufferToFramebuffer(srcFB, destWrapper.FB(),
@@ -835,19 +848,19 @@ GLBlitHelper::BlitFramebufferToTexture(G
     mGL->fCopyTexSubImage2D(destTarget, 0,
                        0, 0,
                        0, 0,
                        srcSize.width, srcSize.height);
 }
 
 void
 GLBlitHelper::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
-                                const gfx::IntSize& srcSize,
-                                const gfx::IntSize& destSize,
-                                GLenum srcTarget, GLenum destTarget)
+                                   const gfx::IntSize& srcSize,
+                                   const gfx::IntSize& destSize,
+                                   GLenum srcTarget, GLenum destTarget)
 {
     MOZ_ASSERT(mGL->fIsTexture(srcTex));
     MOZ_ASSERT(mGL->fIsTexture(destTex));
 
     // Generally, just use the CopyTexSubImage path
     ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
 
     BlitFramebufferToTexture(srcWrapper.FB(), destTex,
--- a/gfx/gl/GLReadTexImageHelper.cpp
+++ b/gfx/gl/GLReadTexImageHelper.cpp
@@ -1,21 +1,24 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=4 et sw=4 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GLReadTexImageHelper.h"
+
+#include "gfx2DGlue.h"
+#include "gfxTypes.h"
 #include "GLContext.h"
 #include "OGLShaderProgram.h"
-#include "gfxTypes.h"
 #include "ScopedGLHelpers.h"
+
 #include "mozilla/gfx/2D.h"
-#include "gfx2DGlue.h"
+#include "mozilla/Move.h"
 
 namespace mozilla {
 namespace gl {
 
 using namespace mozilla::gfx;
 
 GLReadTexImageHelper::GLReadTexImageHelper(GLContext* gl)
     : mGL(gl)
@@ -76,28 +79,27 @@ readTextureImageFS_TEXTURE_RECTANGLE[] =
     "#ifdef GL_ES\n"
     "precision mediump float;\n"
     "#endif\n"
     "varying vec2 vTexCoord;\n"
     "uniform sampler2DRect uTexture;\n"
     "void main() { gl_FragColor = texture2DRect(uTexture, vTexCoord).bgra; }";
 
 GLuint
-GLReadTexImageHelper::TextureImageProgramFor(GLenum aTextureTarget, int aConfig) {
+GLReadTexImageHelper::TextureImageProgramFor(GLenum aTextureTarget,
+                                             int aConfig)
+{
     int variant = 0;
     const GLchar* readTextureImageFS = nullptr;
-    if (aTextureTarget == LOCAL_GL_TEXTURE_2D)
-    {
-        if (aConfig & mozilla::layers::ENABLE_TEXTURE_RB_SWAP)
-        {   // Need to swizzle R/B.
+    if (aTextureTarget == LOCAL_GL_TEXTURE_2D) {
+        if (aConfig & mozilla::layers::ENABLE_TEXTURE_RB_SWAP) {
+            // Need to swizzle R/B.
             readTextureImageFS = readTextureImageFS_TEXTURE_2D_BGRA;
             variant = 1;
-        }
-        else
-        {
+        } else {
             readTextureImageFS = readTextureImageFS_TEXTURE_2D;
             variant = 0;
         }
     } else if (aTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL) {
         readTextureImageFS = readTextureImageFS_TEXTURE_EXTERNAL;
         variant = 2;
     } else if (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) {
         readTextureImageFS = readTextureImageFS_TEXTURE_RECTANGLE;
@@ -200,107 +202,134 @@ GetActualReadFormats(GLContext* gl,
         return false;
     } else {
         readFormat = destFormat;
         readType = destType;
         return true;
     }
 }
 
-static void SwapRAndBComponents(DataSourceSurface* surf)
+static void
+SwapRAndBComponents(DataSourceSurface* surf)
 {
-  uint8_t *row = surf->GetData();
-  if (!row) {
-      MOZ_ASSERT(false, "SwapRAndBComponents: Failed to get data from DataSourceSurface.");
-      return;
-  }
-
-  size_t rowBytes = surf->GetSize().width*4;
-  size_t rowHole = surf->Stride() - rowBytes;
+    DataSourceSurface::MappedSurface map;
+    MOZ_ALWAYS_TRUE( surf->Map(DataSourceSurface::MapType::READ_WRITE, &map) );
+    MOZ_ASSERT(map.mStride >= 0);
 
-  size_t rows = surf->GetSize().height;
-
-  while (rows) {
-
-    const uint8_t *rowEnd = row + rowBytes;
+    const size_t rowBytes = surf->GetSize().width*4;
+    const size_t rowHole = map.mStride - rowBytes;
 
-    while (row != rowEnd) {
-      row[0] ^= row[2];
-      row[2] ^= row[0];
-      row[0] ^= row[2];
-      row += 4;
+    uint8_t* row = map.mData;
+    if (!row) {
+        MOZ_ASSERT(false, "SwapRAndBComponents: Failed to get data from"
+                          " DataSourceSurface.");
+        surf->Unmap();
+        return;
     }
 
-    row += rowHole;
-    --rows;
-  }
+    const size_t rows = surf->GetSize().height;
+    for (size_t i = 0; i < rows; i++) {
+        const uint8_t* rowEnd = row + rowBytes;
+
+        while (row != rowEnd) {
+            Swap(row[0], row[2]);
+            row += 4;
+        }
+
+        row += rowHole;
+    }
+
+    surf->Unmap();
 }
 
-static uint16_t PackRGB565(uint8_t r, uint8_t g, uint8_t b)
+static uint16_t
+PackRGB565(uint8_t r, uint8_t g, uint8_t b)
 {
     uint16_t pixel = ((r << 11) & 0xf800) |
                      ((g <<  5) & 0x07e0) |
                      ((b      ) & 0x001f);
 
     return pixel;
 }
 
-static void CopyDataSourceSurface(DataSourceSurface* aSource,
-                                  DataSourceSurface* aDest)
+static void
+CopyDataSourceSurface(DataSourceSurface* aSource,
+                      DataSourceSurface* aDest)
 {
-  MOZ_ASSERT(aSource->GetSize() == aDest->GetSize());
-  MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-             aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
+    // Don't worry too much about speed.
+    MOZ_ASSERT(aSource->GetSize() == aDest->GetSize());
+    MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+               aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
+               aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+               aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
+    MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+               aDest->GetFormat() == SurfaceFormat::R8G8B8X8 ||
+               aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+               aDest->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+               aDest->GetFormat() == SurfaceFormat::R5G6B5);
 
-  uint8_t *srcRow = aSource->GetData();
-  size_t srcRowBytes = aSource->GetSize().width * BytesPerPixel(aSource->GetFormat());
-  size_t srcRowHole = aSource->Stride() - srcRowBytes;
+    const bool isSrcBGR = aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+                          aSource->GetFormat() == SurfaceFormat::B8G8R8X8;
+    const bool isDestBGR = aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+                           aDest->GetFormat() == SurfaceFormat::B8G8R8X8;
+    const bool needsSwap02 = isSrcBGR != isDestBGR;
 
-  uint8_t *destRow = aDest->GetData();
-  size_t destRowBytes = aDest->GetSize().width * BytesPerPixel(aDest->GetFormat());
-  size_t destRowHole = aDest->Stride() - destRowBytes;
+    const bool srcHasAlpha = aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+                             aSource->GetFormat() == SurfaceFormat::B8G8R8A8;
+    const bool destHasAlpha = aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+                              aDest->GetFormat() == SurfaceFormat::B8G8R8A8;
+    const bool needsAlphaMask = !srcHasAlpha && destHasAlpha;
 
-  bool needsRBSwap = false;
-  if (aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-      aDest->GetFormat() == SurfaceFormat::B8G8R8X8) {
-      needsRBSwap = true;
-  }
+    const bool needsConvertTo16Bits = aDest->GetFormat() == SurfaceFormat::R5G6B5;
 
-  bool needsConvertTo16Bits = false;
-  if (aDest->GetFormat() == SurfaceFormat::R5G6B5) {
-      needsConvertTo16Bits = true;
-  }
+    DataSourceSurface::MappedSurface srcMap;
+    DataSourceSurface::MappedSurface destMap;
+    MOZ_ALWAYS_TRUE( aSource->Map(DataSourceSurface::MapType::READ, &srcMap) );
+    MOZ_ALWAYS_TRUE( aDest->Map(DataSourceSurface::MapType::WRITE, &destMap) );
+    MOZ_ASSERT(srcMap.mStride >= 0);
+    MOZ_ASSERT(destMap.mStride >= 0);
 
-  size_t rows = aSource->GetSize().height;
+    const size_t srcBPP = BytesPerPixel(aSource->GetFormat());
+    const size_t srcRowBytes = aSource->GetSize().width * srcBPP;
+    const size_t srcRowHole = srcMap.mStride - srcRowBytes;
 
-  while (rows) {
-    const uint8_t *srcRowEnd = srcRow + srcRowBytes;
+    const size_t destBPP = BytesPerPixel(aDest->GetFormat());
+    const size_t destRowBytes = aDest->GetSize().width * destBPP;
+    const size_t destRowHole = destMap.mStride - destRowBytes;
+
+    uint8_t* srcRow = srcMap.mData;
+    uint8_t* destRow = destMap.mData;
+    const size_t rows = aSource->GetSize().height;
+    for (size_t i = 0; i < rows; i++) {
+        const uint8_t* srcRowEnd = srcRow + srcRowBytes;
 
-    while (srcRow != srcRowEnd) {
-      uint8_t r = needsRBSwap ? srcRow[2] : srcRow[0];
-      uint8_t g = srcRow[1];
-      uint8_t b = needsRBSwap ? srcRow[0] : srcRow[2];
-      uint8_t a = srcRow[3];
+        while (srcRow != srcRowEnd) {
+            uint8_t d0 = needsSwap02 ? srcRow[2] : srcRow[0];
+            uint8_t d1 = srcRow[1];
+            uint8_t d2 = needsSwap02 ? srcRow[0] : srcRow[2];
+            uint8_t d3 = needsAlphaMask ? 0xff : srcRow[3];
 
-      if (needsConvertTo16Bits) {
-        *(uint16_t*)destRow = PackRGB565(r, g, b);
-      } else {
-        destRow[0] = r;
-        destRow[1] = g;
-        destRow[2] = b;
-        destRow[3] = a;
-      }
-      srcRow += BytesPerPixel(aSource->GetFormat());
-      destRow += BytesPerPixel(aDest->GetFormat());
+            if (needsConvertTo16Bits) {
+                *(uint16_t*)destRow = PackRGB565(d0, d1, d2);
+            } else {
+                destRow[0] = d0;
+                destRow[1] = d1;
+                destRow[2] = d2;
+                destRow[3] = d3;
+            }
+            srcRow += srcBPP;
+            destRow += destBPP;
+        }
+
+        srcRow += srcRowHole;
+        destRow += destRowHole;
     }
 
-    srcRow += srcRowHole;
-    destRow += destRowHole;
-    --rows;
-  }
+    aSource->Unmap();
+    aDest->Unmap();
 }
 
 static int
 CalcRowStride(int width, int pixelSize, int alignment)
 {
     MOZ_ASSERT(alignment);
 
     int rowStride = width * pixelSize;
@@ -321,47 +350,48 @@ GuessAlignment(int width, int pixelSize,
             NS_WARNING("Bad alignment for GLES. Will use temp surf for readback.");
             return 0;
         }
     }
     return alignment;
 }
 
 void
-ReadPixelsIntoDataSurface(GLContext* gl, DataSourceSurface* dest) {
+ReadPixelsIntoDataSurface(GLContext* gl, DataSourceSurface* dest)
+{
     gl->MakeCurrent();
     MOZ_ASSERT(dest->GetSize().width != 0);
     MOZ_ASSERT(dest->GetSize().height != 0);
 
     bool hasAlpha = dest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
                     dest->GetFormat() == SurfaceFormat::R8G8B8A8;
 
     int destPixelSize;
     GLenum destFormat;
     GLenum destType;
 
     switch (dest->GetFormat()) {
-        case SurfaceFormat::B8G8R8A8:
-        case SurfaceFormat::B8G8R8X8:
-            // Needs host (little) endian ARGB.
-            destFormat = LOCAL_GL_BGRA;
-            destType = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
-            break;
-        case SurfaceFormat::R8G8B8A8:
-        case SurfaceFormat::R8G8B8X8:
-            // Needs host (little) endian ABGR.
-            destFormat = LOCAL_GL_RGBA;
-            destType = LOCAL_GL_UNSIGNED_BYTE;
-            break;
-        case SurfaceFormat::R5G6B5:
-            destFormat = LOCAL_GL_RGB;
-            destType = LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV;
-            break;
-        default:
-            MOZ_CRASH("Bad format.");
+    case SurfaceFormat::B8G8R8A8:
+    case SurfaceFormat::B8G8R8X8:
+        // Needs host (little) endian ARGB.
+        destFormat = LOCAL_GL_BGRA;
+        destType = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
+        break;
+    case SurfaceFormat::R8G8B8A8:
+    case SurfaceFormat::R8G8B8X8:
+        // Needs host (little) endian ABGR.
+        destFormat = LOCAL_GL_RGBA;
+        destType = LOCAL_GL_UNSIGNED_BYTE;
+        break;
+    case SurfaceFormat::R5G6B5:
+        destFormat = LOCAL_GL_RGB;
+        destType = LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV;
+        break;
+    default:
+        MOZ_CRASH("Bad format.");
     }
     destPixelSize = BytesPerPixel(dest->GetFormat());
     MOZ_ASSERT(dest->GetSize().width * destPixelSize <= dest->Stride());
 
     GLenum readFormat = destFormat;
     GLenum readType = destType;
     bool needsTempSurf = !GetActualReadFormats(gl,
                                                destFormat, destType,
@@ -377,17 +407,21 @@ ReadPixelsIntoDataSurface(GLContext* gl,
     }
     if (needsTempSurf) {
         if (gl->DebugMode()) {
             NS_WARNING("Needing intermediary surface for ReadPixels. This will be slow!");
         }
         SurfaceFormat readFormatGFX;
 
         switch (readFormat) {
-            case LOCAL_GL_RGBA:
+            case LOCAL_GL_RGBA: {
+                readFormatGFX = hasAlpha ? SurfaceFormat::R8G8B8A8
+                                         : SurfaceFormat::R8G8B8X8;
+                break;
+            }
             case LOCAL_GL_BGRA: {
                 readFormatGFX = hasAlpha ? SurfaceFormat::B8G8R8A8
                                          : SurfaceFormat::B8G8R8X8;
                 break;
             }
             case LOCAL_GL_RGB: {
                 MOZ_ASSERT(destPixelSize == 2);
                 MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_SHORT_5_6_5_REV);
@@ -452,18 +486,17 @@ ReadPixelsIntoDataSurface(GLContext* gl,
         CopyDataSourceSurface(readSurf, dest);
     }
 
     // Check if GL is giving back 1.0 alpha for
     // RGBA reads to RGBA images from no-alpha buffers.
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
         gl->Vendor() == gl::GLVendor::NVIDIA &&
-        (dest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
-         dest->GetFormat() == SurfaceFormat::B8G8R8A8) &&
+        hasAlpha &&
         width && height)
     {
         GLint alphaBits = 0;
         gl->fGetIntegerv(LOCAL_GL_ALPHA_BITS, &alphaBits);
         if (!alphaBits) {
             const uint32_t alphaMask = gfxPackedPixelNoPreMultiply(0xff,0,0,0);
 
             MOZ_ASSERT(dest->GetSize().width * destPixelSize == dest->Stride());
@@ -478,46 +511,50 @@ ReadPixelsIntoDataSurface(GLContext* gl,
                     *itr |= alphaMask;
                 }
             }
         }
     }
 #endif
 }
 
-static TemporaryRef<DataSourceSurface> YInvertImageSurface(DataSourceSurface* aSurf)
+static TemporaryRef<DataSourceSurface>
+YInvertImageSurface(DataSourceSurface* aSurf)
 {
-  RefPtr<DataSourceSurface> temp =
-    Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(),
-                                               aSurf->GetFormat(),
-                                               aSurf->Stride());
-  if (!temp) {
-    return nullptr;
-  }
-  DataSourceSurface::MappedSurface map;
-  if (!temp->Map(DataSourceSurface::MapType::WRITE, &map)) {
-    return nullptr;
-  }
-  RefPtr<DrawTarget> dt =
-    Factory::CreateDrawTargetForData(BackendType::CAIRO,
-                                     map.mData,
-                                     temp->GetSize(),
-                                     map.mStride,
-                                     temp->GetFormat());
-  if (!dt) {
+    RefPtr<DataSourceSurface> temp =
+      Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(),
+                                                 aSurf->GetFormat(),
+                                                 aSurf->Stride());
+    if (!temp) {
+        return nullptr;
+    }
+
+    DataSourceSurface::MappedSurface map;
+    if (!temp->Map(DataSourceSurface::MapType::WRITE, &map)) {
+        return nullptr;
+    }
+
+    RefPtr<DrawTarget> dt =
+      Factory::CreateDrawTargetForData(BackendType::CAIRO,
+                                       map.mData,
+                                       temp->GetSize(),
+                                       map.mStride,
+                                       temp->GetFormat());
+    if (!dt) {
+        temp->Unmap();
+        return nullptr;
+    }
+
+    dt->SetTransform(Matrix::Translation(0.0, aSurf->GetSize().height) *
+                     Matrix::Scaling(1.0, -1.0));
+    Rect rect(0, 0, aSurf->GetSize().width, aSurf->GetSize().height);
+    dt->DrawSurface(aSurf, rect, rect, DrawSurfaceOptions(),
+                    DrawOptions(1.0, CompositionOp::OP_SOURCE, AntialiasMode::NONE));
     temp->Unmap();
-    return nullptr;
-  }
-  dt->SetTransform(Matrix::Translation(0.0, aSurf->GetSize().height) *
-                   Matrix::Scaling(1.0, -1.0));
-  Rect rect(0, 0, aSurf->GetSize().width, aSurf->GetSize().height);
-  dt->DrawSurface(aSurf, rect, rect, DrawSurfaceOptions(),
-                  DrawOptions(1.0, CompositionOp::OP_SOURCE, AntialiasMode::NONE));
-  temp->Unmap();
-  return temp.forget();
+    return temp.forget();
 }
 
 TemporaryRef<DataSourceSurface>
 ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, SurfaceFormat aFormat)
 {
     gl->MakeCurrent();
     gl->GuaranteeResolve();
     gl->fActiveTexture(LOCAL_GL_TEXTURE0);
@@ -535,27 +572,29 @@ ReadBackSurface(GLContext* gl, GLuint aT
         return nullptr;
     }
 
     uint32_t currentPackAlignment = 0;
     gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*)&currentPackAlignment);
     if (currentPackAlignment != 4) {
         gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
     }
+
     gl->fGetTexImage(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, surf->GetData());
+
     if (currentPackAlignment != 4) {
         gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment);
     }
 
     if (aFormat == SurfaceFormat::R8G8B8A8 || aFormat == SurfaceFormat::R8G8B8X8) {
-      SwapRAndBComponents(surf);
+        SwapRAndBComponents(surf);
     }
 
     if (aYInvert) {
-      surf = YInvertImageSurface(surf);
+        surf = YInvertImageSurface(surf);
     }
 
     return surf.forget();
 }
 
 #define CLEANUP_IF_GLERROR_OCCURRED(x)                                      \
     if (DidGLErrorOccur(x)) {                                               \
         isurf = nullptr;                                                    \
@@ -697,11 +736,10 @@ GLReadTexImageHelper::ReadTexImage(GLuin
     if (oldTexUnit != LOCAL_GL_TEXTURE0)
         mGL->fActiveTexture(oldTexUnit);
 
     return isurf.forget();
 }
 
 #undef CLEANUP_IF_GLERROR_OCCURRED
 
-
 }
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -62,16 +62,17 @@
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
 
 // #define APZC_ENABLE_RENDERTRACE
 
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
 #if ENABLE_APZC_LOGGING
+#  include "LayersLogging.h"
 #  define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #  define APZC_LOG_FM(fm, prefix, ...) \
     { std::stringstream ss; \
       ss << nsPrintfCString(prefix, __VA_ARGS__).get(); \
       AppendToString(ss, fm, ":", "", true); \
       APZC_LOG("%s", ss.str().c_str()); \
     }
 #else
--- a/gfx/thebes/gfxFontFamilyList.h
+++ b/gfx/thebes/gfxFontFamilyList.h
@@ -160,59 +160,46 @@ struct FontFamilyName MOZ_FINAL {
     nsString       mName; // empty if mType != eFamily_named
 };
 
 inline bool
 operator==(const FontFamilyName& a, const FontFamilyName& b) {
     return a.mType == b.mType && a.mName == b.mName;
 }
 
-class FontFamilyList;
-
-template<>
-struct HasDangerousPublicDestructor<FontFamilyList>
-{
-  static const bool value = true;
-};
-
 /**
  * font family list, array of font families and a default font type.
  * font family names are either named strings or generics. the default
  * font type is used to preserve the variable font fallback behavior
  */ 
 
-class FontFamilyList MOZ_FINAL {
+class FontFamilyList {
 public:
-    FontFamilyList() : mDefaultFontType(eFamily_none) {
-        MOZ_COUNT_CTOR(FontFamilyList);
+    FontFamilyList()
+        : mDefaultFontType(eFamily_none)
+    {
     }
 
     FontFamilyList(FontFamilyType aGenericType)
         : mDefaultFontType(eFamily_none)
     {
         Append(FontFamilyName(aGenericType));
-        MOZ_COUNT_CTOR(FontFamilyList);
     }
 
     FontFamilyList(const nsAString& aFamilyName,
                    QuotedName aQuoted)
         : mDefaultFontType(eFamily_none)
     {
         Append(FontFamilyName(aFamilyName, aQuoted));
-        MOZ_COUNT_CTOR(FontFamilyList);
     }
 
     FontFamilyList(const FontFamilyList& aOther)
-        : mFontlist(aOther.mFontlist), mDefaultFontType(aOther.mDefaultFontType)
+        : mFontlist(aOther.mFontlist)
+        , mDefaultFontType(aOther.mDefaultFontType)
     {
-        MOZ_COUNT_CTOR(FontFamilyList);
-    }
-
-    ~FontFamilyList() {
-        MOZ_COUNT_DTOR(FontFamilyList);
     }
 
     void Append(const FontFamilyName& aFamilyName) {
         mFontlist.AppendElement(aFamilyName);
     }
 
     void Append(const nsTArray<nsString>& aFamilyNameList) {
         uint32_t len = aFamilyNameList.Length();
@@ -305,18 +292,16 @@ public:
     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
         return mFontlist.SizeOfExcludingThis(aMallocSizeOf);
     }
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
         return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
     }
 
-    NS_INLINE_DECL_REFCOUNTING(FontFamilyList)
-
 private:
     nsTArray<FontFamilyName>   mFontlist;
     FontFamilyType             mDefaultFontType; // none, serif or sans-serif
 };
 
 inline bool
 operator==(const FontFamilyList& a, const FontFamilyList& b) {
     return a.Equals(b);
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -55,16 +55,26 @@ JSID_IS_STRING(jsid id)
 
 static MOZ_ALWAYS_INLINE JSString *
 JSID_TO_STRING(jsid id)
 {
     MOZ_ASSERT(JSID_IS_STRING(id));
     return (JSString *)JSID_BITS(id);
 }
 
+/*
+ * Only JSStrings that have been interned via the JSAPI can be turned into
+ * jsids by API clients.
+ *
+ * N.B. if a jsid is backed by a string which has not been interned, that
+ * string must be appropriately rooted to avoid being collected by the GC.
+ */
+JS_PUBLIC_API(jsid)
+INTERNED_STRING_TO_JSID(JSContext *cx, JSString *str);
+
 static MOZ_ALWAYS_INLINE bool
 JSID_IS_ZERO(jsid id)
 {
     return JSID_BITS(id) == 0;
 }
 
 static MOZ_ALWAYS_INLINE bool
 JSID_IS_INT(jsid id)
--- a/js/public/OldDebugAPI.h
+++ b/js/public/OldDebugAPI.h
@@ -125,93 +125,16 @@ extern JS_PUBLIC_API(const jschar *)
 JS_GetScriptSourceMap(JSContext *cx, JSScript *script);
 
 extern JS_PUBLIC_API(unsigned)
 JS_GetScriptBaseLineNumber(JSContext *cx, JSScript *script);
 
 extern JS_PUBLIC_API(unsigned)
 JS_GetScriptLineExtent(JSContext *cx, JSScript *script);
 
-/************************************************************************/
-
-/*
- * JSAbstractFramePtr is the public version of AbstractFramePtr, a pointer to a
- * StackFrame or baseline JIT frame.
- */
-class JS_PUBLIC_API(JSAbstractFramePtr)
-{
-    uintptr_t ptr_;
-    jsbytecode *pc_;
-
-  protected:
-    JSAbstractFramePtr()
-      : ptr_(0), pc_(nullptr)
-    { }
-
-  public:
-    JSAbstractFramePtr(void *raw, jsbytecode *pc);
-
-    uintptr_t raw() const { return ptr_; }
-    jsbytecode *pc() const { return pc_; }
-
-    operator bool() const { return !!ptr_; }
-
-    JSObject *scopeChain(JSContext *cx);
-    JSObject *callObject(JSContext *cx);
-
-    JSFunction *maybeFun();
-    JSScript *script();
-
-    bool getThisValue(JSContext *cx, JS::MutableHandleValue thisv);
-
-    bool isDebuggerFrame();
-
-    bool evaluateInStackFrame(JSContext *cx,
-                              const char *bytes, unsigned length,
-                              const char *filename, unsigned lineno,
-                              JS::MutableHandleValue rval);
-
-    bool evaluateUCInStackFrame(JSContext *cx,
-                                const jschar *chars, unsigned length,
-                                const char *filename, unsigned lineno,
-                                JS::MutableHandleValue rval);
-};
-
-class JS_PUBLIC_API(JSNullFramePtr) : public JSAbstractFramePtr
-{
-  public:
-    JSNullFramePtr()
-      : JSAbstractFramePtr()
-    {}
-};
-
-/*
- * This class does not work when IonMonkey is active. It's only used by jsd,
- * which can only be used when IonMonkey is disabled.
- *
- * To find the calling script and line number, use JS_DescribeSciptedCaller.
- * To summarize the call stack, use JS::DescribeStack.
- */
-class JS_PUBLIC_API(JSBrokenFrameIterator)
-{
-    void *data_;
-
-  public:
-    explicit JSBrokenFrameIterator(JSContext *cx);
-    ~JSBrokenFrameIterator();
-
-    bool done() const;
-    JSBrokenFrameIterator& operator++();
-
-    JSAbstractFramePtr abstractFramePtr() const;
-    jsbytecode *pc() const;
-
-    bool isConstructing() const;
-};
-
 
 /************************************************************************/
 
 /**
  * Add various profiling-related functions as properties of the given object.
  */
 extern JS_PUBLIC_API(bool)
 JS_DefineProfilingFunctions(JSContext *cx, JSObject *obj);
--- a/js/src/jit-test/tests/latin1/toLowerCase-toUpperCase.js
+++ b/js/src/jit-test/tests/latin1/toLowerCase-toUpperCase.js
@@ -27,19 +27,21 @@ function testToLowerCase() {
 testToLowerCase();
 
 function testToUpperCase() {
     var s1 = "abcdefgABCDEFGH 12345";
     assertEq(isLatin1(s1), true);
 
     // Latin1
     var s2 = s1.toUpperCase();
+    assertEq(isLatin1(s2), true);
     assertEq(s2, "ABCDEFGABCDEFGH 12345");
 
     s2 = s1.toLocaleUpperCase();
+    assertEq(isLatin1(s2), true);
     assertEq(s2, "ABCDEFGABCDEFGH 12345");
 
     // TwoByte
     s2 = "abcdefg\u1200ABCDEFGH 12345\u1E0F".toUpperCase();
     assertEq(s2, "ABCDEFG\u1200ABCDEFGH 12345\u1E0E");
 
     s2 = "abcdefg\u1200ABCDEFGH 12345\u1E0F".toLocaleUpperCase();
     assertEq(s2, "ABCDEFG\u1200ABCDEFGH 12345\u1E0E");
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1789,17 +1789,17 @@ IonBuilder::inlineUnsafeSetReservedSlot(
 
     callInfo.setImplicitlyUsedUnchecked();
 
     MStoreFixedSlot *store = MStoreFixedSlot::New(alloc(), callInfo.getArg(0), slot, callInfo.getArg(2));
     current->add(store);
     current->push(store);
 
     if (NeedsPostBarrier(info(), callInfo.getArg(2)))
-        current->add(MPostWriteBarrier::New(alloc(), callInfo.thisArg(), callInfo.getArg(2)));
+        current->add(MPostWriteBarrier::New(alloc(), callInfo.getArg(0), callInfo.getArg(2)));
 
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineUnsafeGetReservedSlot(CallInfo &callInfo)
 {
     if (callInfo.argc() != 2 || callInfo.constructing())
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1425,16 +1425,18 @@ ReturnType Simulator::getFromVFPRegister
     memcpy(&value, buffer, register_size * sizeof(vfp_registers_[0]));
     return value;
 }
 
 // These forced-instantiations are for jsapi-tests. Evidently, nothing
 // requires these to be instantiated.
 template double Simulator::getFromVFPRegister<double, 2>(int reg_index);
 template float Simulator::getFromVFPRegister<float, 1>(int reg_index);
+template void Simulator::setVFPRegister<double, 2>(int reg_index, const double& value);
+template void Simulator::setVFPRegister<float, 1>(int reg_index, const float& value);
 
 void
 Simulator::getFpArgs(double *x, double *y, int32_t *z)
 {
     if (UseHardFpABI()) {
         *x = get_double_from_d_register(0);
         *y = get_double_from_d_register(1);
         *z = get_register(0);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -798,26 +798,16 @@ JS_NumberValue(double d)
     return DOUBLE_TO_JSVAL(d);
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(bool)
 JS_StringHasBeenInterned(JSContext *cx, JSString *str);
 
-/*
- * Only JSStrings that have been interned via the JSAPI can be turned into
- * jsids by API clients.
- *
- * N.B. if a jsid is backed by a string which has not been interned, that
- * string must be appropriately rooted to avoid being collected by the GC.
- */
-JS_PUBLIC_API(jsid)
-INTERNED_STRING_TO_JSID(JSContext *cx, JSString *str);
-
 namespace JS {
 
 // Container class for passing in script source buffers to the JS engine.  This
 // not only groups the buffer and length values, it also provides a way to
 // optionally pass ownership of the buffer to the JS engine without copying.
 // Rules for use:
 //
 //  1) The data array must be allocated with js_malloc() or js_realloc() if
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -698,37 +698,54 @@ str_substring(JSContext *cx, unsigned ar
 }
 
 template <typename CharT>
 static JSString *
 ToLowerCase(JSContext *cx, JSLinearString *str)
 {
     // Unlike toUpperCase, toLowerCase has the nice invariant that if the input
     // is a Latin1 string, the output is also a Latin1 string.
+    UniquePtr<CharT[], JS::FreePolicy> newChars;
     size_t length = str->length();
-    ScopedJSFreePtr<CharT> newChars(cx->pod_malloc<CharT>(length + 1));
-    if (!newChars)
-        return nullptr;
-
     {
         AutoCheckCannotGC nogc;
         const CharT *chars = str->chars<CharT>(nogc);
-        for (size_t i = 0; i < length; i++) {
+
+        // Look for the first upper case character.
+        size_t i = 0;
+        for (; i < length; i++) {
+            jschar c = chars[i];
+            if (unicode::ToLowerCase(c) != c)
+                break;
+        }
+
+        // If all characters are lower case, return the input string.
+        if (i == length)
+            return str;
+
+        newChars = cx->make_pod_array<CharT>(length + 1);
+        if (!newChars)
+            return nullptr;
+
+        PodCopy(newChars.get(), chars, i);
+
+        for (; i < length; i++) {
             jschar c = unicode::ToLowerCase(chars[i]);
-            MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= 0xff);
+            MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
             newChars[i] = c;
         }
+
         newChars[length] = 0;
     }
 
     JSString *res = NewStringDontDeflate<CanGC>(cx, newChars.get(), length);
     if (!res)
         return nullptr;
 
-    newChars.forget();
+    newChars.release();
     return res;
 }
 
 static inline bool
 ToLowerCaseHelper(JSContext *cx, CallReceiver call)
 {
     RootedString str(cx, ThisToStringForStringProto(cx, call));
     if (!str)
@@ -775,40 +792,111 @@ str_toLocaleLowerCase(JSContext *cx, uns
 
         args.rval().set(result);
         return true;
     }
 
     return ToLowerCaseHelper(cx, args);
 }
 
+template <typename DestChar, typename SrcChar>
+static void
+ToUpperCaseImpl(DestChar *destChars, const SrcChar *srcChars, size_t firstLowerCase, size_t length)
+{
+    MOZ_ASSERT(firstLowerCase < length);
+
+    for (size_t i = 0; i < firstLowerCase; i++)
+        destChars[i] = srcChars[i];
+
+    for (size_t i = firstLowerCase; i < length; i++) {
+        jschar c = unicode::ToUpperCase(srcChars[i]);
+        MOZ_ASSERT_IF((IsSame<DestChar, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
+        destChars[i] = c;
+    }
+
+    destChars[length] = '\0';
+}
+
 template <typename CharT>
 static JSString *
 ToUpperCase(JSContext *cx, JSLinearString *str)
 {
-    // toUpperCase on a Latin1 string can yield a non-Latin1 string. For now,
-    // we use a TwoByte string for the result.
+    typedef UniquePtr<Latin1Char[], JS::FreePolicy> Latin1CharPtr;
+    typedef UniquePtr<jschar[], JS::FreePolicy> TwoByteCharPtr;
+
+    mozilla::MaybeOneOf<Latin1CharPtr, TwoByteCharPtr> newChars;
     size_t length = str->length();
-    ScopedJSFreePtr<jschar> newChars(cx->pod_malloc<jschar>(length + 1));
-    if (!newChars)
-        return nullptr;
-
     {
         AutoCheckCannotGC nogc;
         const CharT *chars = str->chars<CharT>(nogc);
-        for (size_t i = 0; i < length; i++)
-            newChars[i] = unicode::ToUpperCase(chars[i]);
-        newChars[length] = 0;
+
+        // Look for the first lower case character.
+        size_t i = 0;
+        for (; i < length; i++) {
+            jschar c = chars[i];
+            if (unicode::ToUpperCase(c) != c)
+                break;
+        }
+
+        // If all characters are upper case, return the input string.
+        if (i == length)
+            return str;
+
+        // If the string is Latin1, check if it contains the MICRO SIGN (0xb5)
+        // or SMALL LETTER Y WITH DIAERESIS (0xff) character. The corresponding
+        // upper case characters are not in the Latin1 range.
+        bool resultIsLatin1;
+        if (IsSame<CharT, Latin1Char>::value) {
+            resultIsLatin1 = true;
+            for (size_t j = i; j < length; j++) {
+                Latin1Char c = chars[j];
+                if (c == 0xb5 || c == 0xff) {
+                    MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR);
+                    resultIsLatin1 = false;
+                    break;
+                } else {
+                    MOZ_ASSERT(unicode::ToUpperCase(c) <= JSString::MAX_LATIN1_CHAR);
+                }
+            }
+        } else {
+            resultIsLatin1 = false;
+        }
+
+        if (resultIsLatin1) {
+            Latin1CharPtr buf = cx->make_pod_array<Latin1Char>(length + 1);
+            if (!buf)
+                return nullptr;
+
+            ToUpperCaseImpl(buf.get(), chars, i, length);
+            newChars.construct<Latin1CharPtr>(buf);
+        } else {
+            TwoByteCharPtr buf = cx->make_pod_array<jschar>(length + 1);
+            if (!buf)
+                return nullptr;
+
+            ToUpperCaseImpl(buf.get(), chars, i, length);
+            newChars.construct<TwoByteCharPtr>(buf);
+        }
     }
 
-    JSString *res = NewString<CanGC>(cx, newChars.get(), length);
-    if (!res)
-        return nullptr;
-
-    newChars.forget();
+    JSString *res;
+    if (newChars.constructed<Latin1CharPtr>()) {
+        res = NewStringDontDeflate<CanGC>(cx, newChars.ref<Latin1CharPtr>().get(), length);
+        if (!res)
+            return nullptr;
+
+        newChars.ref<Latin1CharPtr>().release();
+    } else {
+        res = NewStringDontDeflate<CanGC>(cx, newChars.ref<TwoByteCharPtr>().get(), length);
+        if (!res)
+            return nullptr;
+
+        newChars.ref<TwoByteCharPtr>().release();
+    }
+
     return res;
 }
 
 static bool
 ToUpperCaseHelper(JSContext *cx, CallReceiver call)
 {
     RootedString str(cx, ThisToStringForStringProto(cx, call));
     if (!str)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -57,25 +57,29 @@
 #include "jit/arm/Simulator-arm.h"
 #include "jit/Ion.h"
 #include "js/OldDebugAPI.h"
 #include "js/StructuredClone.h"
 #include "perf/jsperf.h"
 #include "shell/jsheaptools.h"
 #include "shell/jsoptparse.h"
 #include "vm/ArgumentsObject.h"
+#include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/Monitor.h"
 #include "vm/Shape.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 
+#include "vm/Interpreter-inl.h"
+#include "vm/Stack-inl.h"
+
 #ifdef XP_WIN
 # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
 #else
 # include <libgen.h>
 #endif
 
 using namespace js;
 using namespace js::cli;
@@ -2672,24 +2676,39 @@ EvalInFrame(JSContext *cx, unsigned argc
             return false;
         ac.construct(cx, DefaultObjectForContextOrNull(cx));
     }
 
     AutoStableStringChars stableChars(cx);
     if (!stableChars.initTwoByte(cx, str))
         return JSTRAP_ERROR;
 
-    mozilla::Range<const jschar> chars = stableChars.twoByteRange();
-    JSAbstractFramePtr frame(fi.abstractFramePtr().raw(), fi.pc());
+    AbstractFramePtr frame = fi.abstractFramePtr();
     RootedScript fpscript(cx, frame.script());
-    bool ok = !!frame.evaluateUCInStackFrame(cx, chars.start().get(), chars.length(),
-                                             fpscript->filename(),
-                                             JS_PCToLineNumber(cx, fpscript,
-                                                               fi.pc()),
-                                             MutableHandleValue::fromMarkedLocation(vp));
+
+    RootedObject scope(cx);
+    {
+        RootedObject scopeChain(cx, frame.scopeChain());
+        AutoCompartment ac(cx, scopeChain);
+        scope = GetDebugScopeForFrame(cx, frame, fi.pc());
+    }
+    Rooted<Env*> env(cx, scope);
+    if (!env)
+        return false;
+
+    if (!ComputeThis(cx, frame))
+        return false;
+    RootedValue thisv(cx, frame.thisValue());
+
+    bool ok;
+    {
+        AutoCompartment ac(cx, env);
+        ok = EvaluateInEnv(cx, env, thisv, frame, stableChars.twoByteRange(), fpscript->filename(),
+                           JS_PCToLineNumber(cx, fpscript, fi.pc()), args.rval());
+    }
     return ok;
 }
 
 struct WorkerInput
 {
     JSRuntime *runtime;
     jschar *chars;
     size_t length;
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -496,180 +496,8 @@ JS::FormatStackDump(JSContext *cx, char 
         num++;
     }
 
     if (!num)
         buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
 
     return buf;
 }
-
-JSAbstractFramePtr::JSAbstractFramePtr(void *raw, jsbytecode *pc)
-  : ptr_(uintptr_t(raw)), pc_(pc)
-{ }
-
-JSObject *
-JSAbstractFramePtr::scopeChain(JSContext *cx)
-{
-    AbstractFramePtr frame(*this);
-    RootedObject scopeChain(cx, frame.scopeChain());
-    AutoCompartment ac(cx, scopeChain);
-    return GetDebugScopeForFrame(cx, frame, pc());
-}
-
-JSObject *
-JSAbstractFramePtr::callObject(JSContext *cx)
-{
-    AbstractFramePtr frame(*this);
-    if (!frame.isFunctionFrame())
-        return nullptr;
-
-    JSObject *o = GetDebugScopeForFrame(cx, frame, pc());
-
-    /*
-     * Given that fp is a function frame and GetDebugScopeForFrame always fills
-     * in missing scopes, we can expect to find fp's CallObject on 'o'. Note:
-     *  - GetDebugScopeForFrame wraps every ScopeObject (missing or not) with
-     *    a DebugScopeObject proxy.
-     *  - If fp is an eval-in-function, then fp has no callobj of its own and
-     *    JS_GetFrameCallObject will return the innermost function's callobj.
-     */
-    while (o) {
-        ScopeObject &scope = o->as<DebugScopeObject>().scope();
-        if (scope.is<CallObject>())
-            return o;
-        o = o->enclosingScope();
-    }
-    return nullptr;
-}
-
-JSFunction *
-JSAbstractFramePtr::maybeFun()
-{
-    AbstractFramePtr frame(*this);
-    return frame.maybeFun();
-}
-
-JSScript *
-JSAbstractFramePtr::script()
-{
-    AbstractFramePtr frame(*this);
-    return frame.script();
-}
-
-bool
-JSAbstractFramePtr::getThisValue(JSContext *cx, MutableHandleValue thisv)
-{
-    AbstractFramePtr frame(*this);
-
-    RootedObject scopeChain(cx, frame.scopeChain());
-    js::AutoCompartment ac(cx, scopeChain);
-    if (!ComputeThis(cx, frame))
-        return false;
-
-    thisv.set(frame.thisValue());
-    return true;
-}
-
-bool
-JSAbstractFramePtr::isDebuggerFrame()
-{
-    AbstractFramePtr frame(*this);
-    return frame.isDebuggerFrame();
-}
-
-bool
-JSAbstractFramePtr::evaluateInStackFrame(JSContext *cx,
-                                         const char *bytes, unsigned length,
-                                         const char *filename, unsigned lineno,
-                                         MutableHandleValue rval)
-{
-    if (!CheckDebugMode(cx))
-        return false;
-
-    size_t len = length;
-    jschar *chars = InflateString(cx, bytes, &len);
-    if (!chars)
-        return false;
-    length = (unsigned) len;
-
-    bool ok = evaluateUCInStackFrame(cx, chars, length, filename, lineno, rval);
-    js_free(chars);
-
-    return ok;
-}
-
-bool
-JSAbstractFramePtr::evaluateUCInStackFrame(JSContext *cx,
-                                           const jschar *chars, unsigned length,
-                                           const char *filename, unsigned lineno,
-                                           MutableHandleValue rval)
-{
-    if (!CheckDebugMode(cx))
-        return false;
-
-    RootedObject scope(cx, scopeChain(cx));
-    Rooted<Env*> env(cx, scope);
-    if (!env)
-        return false;
-
-    AbstractFramePtr frame(*this);
-    if (!ComputeThis(cx, frame))
-        return false;
-    RootedValue thisv(cx, frame.thisValue());
-
-    js::AutoCompartment ac(cx, env);
-    return EvaluateInEnv(cx, env, thisv, frame, mozilla::Range<const jschar>(chars, length),
-                         filename, lineno, rval);
-}
-
-JSBrokenFrameIterator::JSBrokenFrameIterator(JSContext *cx)
-{
-    // Show all frames on the stack whose principal is subsumed by the current principal.
-    NonBuiltinScriptFrameIter iter(cx,
-                                   ScriptFrameIter::ALL_CONTEXTS,
-                                   ScriptFrameIter::GO_THROUGH_SAVED,
-                                   cx->compartment()->principals);
-    data_ = iter.copyData();
-}
-
-JSBrokenFrameIterator::~JSBrokenFrameIterator()
-{
-    js_free((ScriptFrameIter::Data *)data_);
-}
-
-bool
-JSBrokenFrameIterator::done() const
-{
-    NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return iter.done();
-}
-
-JSBrokenFrameIterator &
-JSBrokenFrameIterator::operator++()
-{
-    ScriptFrameIter::Data *data = (ScriptFrameIter::Data *)data_;
-    NonBuiltinScriptFrameIter iter(*data);
-    ++iter;
-    *data = iter.data_;
-    return *this;
-}
-
-JSAbstractFramePtr
-JSBrokenFrameIterator::abstractFramePtr() const
-{
-    NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return JSAbstractFramePtr(iter.abstractFramePtr().raw(), iter.pc());
-}
-
-jsbytecode *
-JSBrokenFrameIterator::pc() const
-{
-    NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return iter.pc();
-}
-
-bool
-JSBrokenFrameIterator::isConstructing() const
-{
-    NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return iter.isConstructing();
-}
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -130,21 +130,16 @@ class AbstractFramePtr
     }
 
     MOZ_IMPLICIT AbstractFramePtr(jit::RematerializedFrame *fp)
       : ptr_(fp ? uintptr_t(fp) | Tag_RematerializedFrame : 0)
     {
         MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp);
     }
 
-    explicit AbstractFramePtr(JSAbstractFramePtr frame)
-        : ptr_(uintptr_t(frame.raw()))
-    {
-    }
-
     static AbstractFramePtr FromRaw(void *raw) {
         AbstractFramePtr frame;
         frame.ptr_ = uintptr_t(raw);
         return frame;
     }
 
     bool isScriptFrameIterData() const {
         return !!ptr_ && (ptr_ & TagMask) == Tag_ScriptFrameIterData;
@@ -1674,18 +1669,16 @@ class FrameIter
     jit::InlineFrameIterator ionInlineFrames_;
 
     void popActivation();
     void popInterpreterFrame();
     void nextJitFrame();
     void popJitFrame();
     void popAsmJSFrame();
     void settleOnActivation();
-
-    friend class ::JSBrokenFrameIterator;
 };
 
 class ScriptFrameIter : public FrameIter
 {
     void settle() {
         while (!done() && !hasScript())
             FrameIter::operator++();
     }
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -269,17 +269,17 @@ interface nsIXPCFunctionThisTranslator :
 %{ C++
 // For use with the service manager
 // {CB6593E0-F9B2-11d2-BDD6-000064657374}
 #define NS_XPCONNECT_CID \
 { 0xcb6593e0, 0xf9b2, 0x11d2, \
     { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
 %}
 
-[noscript, uuid(9ec7367c-0dde-4b7a-963c-5a590ee3ee42)]
+[noscript, uuid(89b8525d-5c22-48bd-8a79-66aac9cd705e)]
 interface nsIXPConnect : nsISupports
 {
 %{ C++
   NS_DEFINE_STATIC_CID_ACCESSOR(NS_XPCONNECT_CID)
 %}
 
     /**
      * Creates a new global object using the given aCOMObj as the global
@@ -429,18 +429,16 @@ interface nsIXPConnect : nsISupports
     readonly attribute nsIStackFrame                CurrentJSStack;
     readonly attribute nsAXPCNativeCallContextPtr   CurrentNativeCallContext;
 
     void debugDump(in short depth);
     void debugDumpObject(in nsISupports aCOMObj, in short depth);
     void debugDumpJSStack(in boolean showArgs,
                           in boolean showLocals,
                           in boolean showThisProps);
-    void debugDumpEvalInJSStackFrame(in uint32_t aFrameNumber,
-                                     in string aSourceText);
 
     /**
     * wrapJSAggregatedToNative is just like wrapJS except it is used in cases
     * where the JSObject is also aggregated to some native xpcom Object.
     * At present XBL is the only system that might want to do this.
     *
     * XXX write more!
     *
--- a/js/xpconnect/src/XPCDebug.cpp
+++ b/js/xpconnect/src/XPCDebug.cpp
@@ -50,65 +50,8 @@ xpc_PrintJSStack(JSContext* cx, bool sho
 
     char *buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
     if (!buf)
         DebugDump("%s", "Failed to format JavaScript stack for dump\n");
 
     state.restore();
     return buf;
 }
-
-/***************************************************************************/
-
-static void
-xpcDumpEvalErrorReporter(JSContext *cx, const char *message,
-                         JSErrorReport *report)
-{
-    DebugDump("Error: %s\n", message);
-}
-
-bool
-xpc_DumpEvalInJSStackFrame(JSContext* cx, uint32_t frameno, const char* text)
-{
-    if (!cx || !text) {
-        DebugDump("%s", "invalid params passed to xpc_DumpEvalInJSStackFrame!\n");
-        return false;
-    }
-
-    DebugDump("js[%d]> %s\n", frameno, text);
-
-    uint32_t num = 0;
-
-    JSAbstractFramePtr frame = JSNullFramePtr();
-
-    JSBrokenFrameIterator iter(cx);
-    while (!iter.done()) {
-        if (num == frameno) {
-            frame = iter.abstractFramePtr();
-            break;
-        }
-        ++iter;
-        num++;
-    }
-
-    if (!frame) {
-        DebugDump("%s", "invalid frame number!\n");
-        return false;
-    }
-
-    JS::AutoSaveExceptionState exceptionState(cx);
-    JSErrorReporter older = JS_SetErrorReporter(cx, xpcDumpEvalErrorReporter);
-
-    JS::RootedValue rval(cx);
-    JSString* str;
-    JSAutoByteString bytes;
-    if (frame.evaluateInStackFrame(cx, text, strlen(text), "eval", 1, &rval) &&
-        nullptr != (str = ToString(cx, rval)) &&
-        bytes.encodeLatin1(cx, str)) {
-        DebugDump("%s\n", bytes.ptr());
-    } else {
-        DebugDump("%s", "eval failed!\n");
-    }
-
-    JS_SetErrorReporter(cx, older);
-    exceptionState.restore();
-    return true;
-}
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -940,29 +940,16 @@ nsXPConnect::DebugPrintJSStack(bool show
     if (!cx)
         printf("there is no JSContext on the nsIThreadJSContextStack!\n");
     else
         return xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps);
 
     return nullptr;
 }
 
-/* void debugDumpEvalInJSStackFrame (in uint32_t aFrameNumber, in string aSourceText); */
-NS_IMETHODIMP
-nsXPConnect::DebugDumpEvalInJSStackFrame(uint32_t aFrameNumber, const char *aSourceText)
-{
-    JSContext* cx = GetCurrentJSContext();
-    if (!cx)
-        printf("there is no JSContext on the nsIThreadJSContextStack!\n");
-    else
-        xpc_DumpEvalInJSStackFrame(cx, aFrameNumber, aSourceText);
-
-    return NS_OK;
-}
-
 /* jsval variantToJS (in JSContextPtr ctx, in JSObjectPtr scope, in nsIVariant value); */
 NS_IMETHODIMP
 nsXPConnect::VariantToJS(JSContext* ctx, JSObject* scopeArg, nsIVariant* value,
                          MutableHandleValue _retval)
 {
     NS_PRECONDITION(ctx, "bad param");
     NS_PRECONDITION(scopeArg, "bad param");
     NS_PRECONDITION(value, "bad param");
@@ -1403,26 +1390,16 @@ JS_EXPORT_API(char*) PrintJSStack()
 {
     nsresult rv;
     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
     return (NS_SUCCEEDED(rv) && xpc) ?
         xpc->DebugPrintJSStack(true, true, false) :
         nullptr;
 }
 
-JS_EXPORT_API(void) DumpJSEval(uint32_t frameno, const char* text)
-{
-    nsresult rv;
-    nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
-    if (NS_SUCCEEDED(rv) && xpc)
-        xpc->DebugDumpEvalInJSStackFrame(frameno, text);
-    else
-        printf("failed to get XPConnect service!\n");
-}
-
 JS_EXPORT_API(void) DumpCompleteHeap()
 {
     nsCOMPtr<nsICycleCollectorListener> listener =
       do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
     if (!listener) {
       NS_WARNING("Failed to create CC logger");
       return;
     }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2962,20 +2962,16 @@ xpc_DumpJSStack(JSContext* cx, bool show
 
 // Return a newly-allocated string containing a representation of the
 // current JS stack.  It is the *caller's* responsibility to free this
 // string with JS_smprintf_free().
 extern char*
 xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals,
                  bool showThisProps);
 
-extern bool
-xpc_DumpEvalInJSStackFrame(JSContext* cx, uint32_t frameno, const char* text);
-
-
 /***************************************************************************/
 
 // Definition of nsScriptError, defined here because we lack a place to put
 // XPCOM objects associated with the JavaScript engine.
 class nsScriptError : public nsIScriptError {
 public:
     nsScriptError();
 
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -44,17 +44,17 @@ include box-properties/reftest.list
 # box-shadow/
 include box-shadow/reftest.list
 
 # bugs/
 include bugs/reftest.list
 
 # canvas
 include canvas/reftest.list
-skip-if(xulFennec) include ../../dom/canvas/test/reftest/reftest.list
+include ../../dom/canvas/test/reftest/reftest.list
 
 # css animations
 include css-animations/reftest.list
 
 # blending/
 include css-blending/reftest.list
 
 # Tests for the css-break spec
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12240,17 +12240,18 @@ AppendGeneric(nsCSSKeyword aKeyword, Fon
   }
 
   return false;
 }
 
 bool
 CSSParserImpl::ParseFamily(nsCSSValue& aValue)
 {
-  nsRefPtr<FontFamilyList> familyList = new FontFamilyList();
+  nsRefPtr<css::FontFamilyListRefCnt> familyList =
+    new css::FontFamilyListRefCnt();
   nsAutoString family;
   bool single, quoted;
 
   // keywords only have meaning in the first position
   if (!ParseOneFamily(family, single, quoted))
     return false;
 
   // check for keywords, but only when keywords appear by themselves
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -106,17 +106,17 @@ nsCSSValue::nsCSSValue(nsCSSValueTokenSt
 
 nsCSSValue::nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue)
   : mUnit(eCSSUnit_GridTemplateAreas)
 {
   mValue.mGridTemplateAreas = aValue;
   mValue.mGridTemplateAreas->AddRef();
 }
 
-nsCSSValue::nsCSSValue(FontFamilyList* aValue)
+nsCSSValue::nsCSSValue(css::FontFamilyListRefCnt* aValue)
   : mUnit(eCSSUnit_FontFamilyList)
 {
   mValue.mFontFamilyList = aValue;
   mValue.mFontFamilyList->AddRef();
 }
 
 nsCSSValue::nsCSSValue(const nsCSSValue& aCopy)
   : mUnit(aCopy.mUnit)
@@ -486,17 +486,17 @@ void nsCSSValue::SetTokenStreamValue(nsC
 void nsCSSValue::SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue)
 {
   Reset();
   mUnit = eCSSUnit_GridTemplateAreas;
   mValue.mGridTemplateAreas = aValue;
   mValue.mGridTemplateAreas->AddRef();
 }
 
-void nsCSSValue::SetFontFamilyListValue(FontFamilyList* aValue)
+void nsCSSValue::SetFontFamilyListValue(css::FontFamilyListRefCnt* aValue)
 {
   Reset();
   mUnit = eCSSUnit_FontFamilyList;
   mValue.mFontFamilyList = aValue;
   mValue.mFontFamilyList->AddRef();
 }
 
 void nsCSSValue::SetPairValue(const nsCSSValuePair* aValue)
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -195,16 +195,51 @@ private:
   {
   }
 
   GridTemplateAreasValue(const GridTemplateAreasValue& aOther) MOZ_DELETE;
   GridTemplateAreasValue&
   operator=(const GridTemplateAreasValue& aOther) MOZ_DELETE;
 };
 
+class FontFamilyListRefCnt MOZ_FINAL : public FontFamilyList {
+public:
+    FontFamilyListRefCnt()
+        : FontFamilyList()
+    {
+        MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+    }
+
+    FontFamilyListRefCnt(FontFamilyType aGenericType)
+        : FontFamilyList(aGenericType)
+    {
+        MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+    }
+
+    FontFamilyListRefCnt(const nsAString& aFamilyName,
+                         QuotedName aQuoted)
+        : FontFamilyList(aFamilyName, aQuoted)
+    {
+        MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+    }
+
+    FontFamilyListRefCnt(const FontFamilyListRefCnt& aOther)
+        : FontFamilyList(aOther)
+    {
+        MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+    }
+
+    NS_INLINE_DECL_REFCOUNTING(FontFamilyListRefCnt);
+
+private:
+    ~FontFamilyListRefCnt() {
+        MOZ_COUNT_DTOR(FontFamilyListRefCnt);
+    }
+};
+
 }
 }
 
 enum nsCSSUnit {
   eCSSUnit_Null         = 0,      // (n/a) null unit, value is not specified
   eCSSUnit_Auto         = 1,      // (n/a) value is algorithmic
   eCSSUnit_Inherit      = 2,      // (n/a) value is inherited
   eCSSUnit_Initial      = 3,      // (n/a) value is default UA value
@@ -366,17 +401,17 @@ public:
   nsCSSValue(float aValue, nsCSSUnit aUnit);
   nsCSSValue(const nsString& aValue, nsCSSUnit aUnit);
   nsCSSValue(Array* aArray, nsCSSUnit aUnit);
   explicit nsCSSValue(mozilla::css::URLValue* aValue);
   explicit nsCSSValue(mozilla::css::ImageValue* aValue);
   explicit nsCSSValue(nsCSSValueGradient* aValue);
   explicit nsCSSValue(nsCSSValueTokenStream* aValue);
   explicit nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue);
-  explicit nsCSSValue(mozilla::FontFamilyList* aValue);
+  explicit nsCSSValue(mozilla::css::FontFamilyListRefCnt* aValue);
   nsCSSValue(const nsCSSValue& aCopy);
   ~nsCSSValue() { Reset(); }
 
   nsCSSValue&  operator=(const nsCSSValue& aCopy);
   bool        operator==(const nsCSSValue& aOther) const;
 
   bool operator!=(const nsCSSValue& aOther) const
   {
@@ -634,17 +669,17 @@ public:
                           float aComponent3,
                           float aAlpha, nsCSSUnit aUnit);
   void SetArrayValue(nsCSSValue::Array* aArray, nsCSSUnit aUnit);
   void SetURLValue(mozilla::css::URLValue* aURI);
   void SetImageValue(mozilla::css::ImageValue* aImage);
   void SetGradientValue(nsCSSValueGradient* aGradient);
   void SetTokenStreamValue(nsCSSValueTokenStream* aTokenStream);
   void SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue);
-  void SetFontFamilyListValue(mozilla::FontFamilyList* aFontListValue);
+  void SetFontFamilyListValue(mozilla::css::FontFamilyListRefCnt* aFontListValue);
   void SetPairValue(const nsCSSValuePair* aPair);
   void SetPairValue(const nsCSSValue& xValue, const nsCSSValue& yValue);
   void SetSharedListValue(nsCSSValueSharedList* aList);
   void SetDependentListValue(nsCSSValueList* aList);
   void SetDependentPairListValue(nsCSSValuePairList* aList);
   void SetTripletValue(const nsCSSValueTriplet* aTriplet);
   void SetTripletValue(const nsCSSValue& xValue, const nsCSSValue& yValue, const nsCSSValue& zValue);
   void SetAutoValue();
@@ -702,17 +737,17 @@ protected:
     nsCSSRect_heap* mRect;
     nsCSSValueTriplet_heap* mTriplet;
     nsCSSValueList_heap* mList;
     nsCSSValueList* mListDependent;
     nsCSSValueSharedList* mSharedList;
     nsCSSValuePairList_heap* mPairList;
     nsCSSValuePairList* mPairListDependent;
     nsCSSValueFloatColor* mFloatColor;
-    mozilla::FontFamilyList* mFontFamilyList;
+    mozilla::css::FontFamilyListRefCnt* mFontFamilyList;
   } mValue;
 };
 
 struct nsCSSValue::Array MOZ_FINAL {
 
   // return |Array| with reference count of zero
   static Array* Create(size_t aItemCount) {
     return new (aItemCount) Array(aItemCount);
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -753,20 +753,16 @@ function BuildConditionSandbox(aURL) {
     sandbox.browserIsRemote = gBrowserIsRemote;
 
     try {
         sandbox.asyncPanZoom = prefs.getBoolPref("layers.async-pan-zoom.enabled");
     } catch (e) {
         sandbox.asyncPanZoom = false;
     }
 
-    // Distinguish the Fennecs:
-    sandbox.xulFennec    = sandbox.Android &&  sandbox.browserIsRemote;
-    sandbox.nativeFennec = sandbox.Android && !sandbox.browserIsRemote;
-
     if (!gDumpedConditionSandbox) {
         dump("REFTEST INFO | Dumping JSON representation of sandbox \n");
         dump("REFTEST INFO | " + JSON.stringify(sandbox) + " \n");
         gDumpedConditionSandbox = true;
     }
     return sandbox;
 }
 
--- a/layout/xul/test/chrome.ini
+++ b/layout/xul/test/chrome.ini
@@ -9,13 +9,15 @@ skip-if = buildapp == 'mulet'
 [test_bug381167.xhtml]
 [test_bug393970.xul]
 [test_bug398982-1.xul]
 [test_bug398982-2.xul]
 [test_bug467442.xul]
 [test_bug477754.xul]
 [test_bug703150.xul]
 skip-if = buildapp == 'mulet'
+[test_bug987230.xul]
+skip-if = os == 'linux' # No native mousedown event
 [test_popupSizeTo.xul]
 [test_resizer.xul]
 [test_stack.xul]
 [test_windowminmaxsize.xul]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/layout/xul/test/test_bug987230.xul
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=987230
+-->
+<window title="Mozilla Bug 987230"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="SimpleTest.waitForFocus(nextTest, window)">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=987230"
+     target="_blank">Mozilla Bug 987230</a>
+  </body>
+
+  <vbox>
+    <toolbar>
+      <toolbarbutton id="toolbarbutton-anchor"
+                     label="Anchor"
+                     consumeanchor="toolbarbutton-anchor"
+                     onclick="onAnchorClick(event)"
+                     style="padding: 50px !important; list-style-image: url(chrome://branding/content/icon32.png)"/>
+    </toolbar>
+    <spacer flex="1"/>
+    <hbox id="hbox-anchor"
+          style="padding: 20px"
+          onclick="onAnchorClick(event)">
+      <hbox id="inner-anchor"
+        consumeanchor="hbox-anchor"
+        >
+        Another anchor
+      </hbox>
+    </hbox>
+    <spacer flex="1"/>
+  </vbox>
+
+  <panel id="mypopup"
+         type="arrow"
+         onpopupshown="onMyPopupShown(event)"
+         onpopuphidden="onMyPopupHidden(event)">This is a test popup</panel>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 987230 **/
+  SimpleTest.waitForExplicitFinish();
+
+  const Ci = Components.interfaces;
+  const Cc = Components.classes;
+
+  let platform = navigator.platform.toLowerCase();
+  let isWindows = platform.startsWith("win");
+  let mouseDown = isWindows ? 2 : 1;
+  let mouseUp = isWindows ? 4 : 2;
+  let mouseMove = isWindows ? 1 : 5;
+  let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+  let scale = utils.screenPixelsPerCSSPixel;
+
+
+  function synthesizeNativeMouseClick(aElement, aOffsetX, aOffsetY) {
+    let rect = aElement.getBoundingClientRect();
+    let win = aElement.ownerDocument.defaultView;
+    let x = aOffsetX + win.mozInnerScreenX + rect.left;
+    let y = aOffsetY + win.mozInnerScreenY + rect.top;
+
+    utils.sendNativeMouseEvent(x * scale, y * scale, mouseDown, 0, null);
+    utils.sendNativeMouseEvent(x * scale, y * scale, mouseUp, 0, null);
+  }
+
+  function onMyPopupHidden(e) {
+    ok(true, "Popup hidden");
+    if (outerAnchor.id == "toolbarbutton-anchor") {
+      popupHasShown = false;
+      outerAnchor = document.getElementById("hbox-anchor");
+      anchor = document.getElementById("inner-anchor");
+      nextTest();
+    } else {
+      //XXXgijs set mouse position back outside the iframe:
+      let frameRect = window.frameElement.getBoundingClientRect();
+      let outsideOfFrameX = (window.mozInnerScreenX + frameRect.width + 100) * scale;
+      let outsideOfFrameY = Math.max(0, window.mozInnerScreenY - 100) * scale;
+
+      utils.sendNativeMouseEvent(outsideOfFrameX, outsideOfFrameY, mouseMove, 0, null);
+      SimpleTest.finish();
+    }
+  }
+
+  let popupHasShown = false;
+  function onMyPopupShown(e) {
+    popupHasShown = true;
+    synthesizeNativeMouseClick(outerAnchor, 5, 5);
+  }
+
+  function onAnchorClick(e) {
+    info("click: " + e.target.id);
+    ok(!popupHasShown, "Popup should only be shown once");
+    popup.openPopup(anchor, "bottomcenter topright");
+  }
+
+  let popup = document.getElementById("mypopup");
+  let outerAnchor = document.getElementById("toolbarbutton-anchor");
+  let anchor = document.getAnonymousElementByAttribute(outerAnchor, "class", "toolbarbutton-icon");
+
+  function nextTest(e) {
+    synthesizeMouse(outerAnchor, 5, 5, {});
+  }
+
+  ]]>
+  </script>
+</window>
--- a/media/libstagefright/binding/include/mp4_demuxer/mp4_demuxer.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/mp4_demuxer.h
@@ -62,11 +62,12 @@ public:
 
 private:
   AudioDecoderConfig mAudioConfig;
   VideoDecoderConfig mVideoConfig;
   CryptoFile mCrypto;
 
   nsAutoPtr<StageFrightPrivate> mPrivate;
 };
-}
 
-#endif
+} // namespace mozilla
+
+#endif // MP4_DEMUXER_H_
--- a/mfbt/RefPtr.h
+++ b/mfbt/RefPtr.h
@@ -387,166 +387,9 @@ template<typename T>
 OutParamRef<T>
 byRef(RefPtr<T>& aPtr)
 {
   return OutParamRef<T>(aPtr);
 }
 
 } // namespace mozilla
 
-#if 0
-
-// Command line that builds these tests
-//
-//   cp RefPtr.h test.cc && g++ -g -Wall -pedantic -DDEBUG -o test test.cc && ./test
-
-using namespace mozilla;
-
-struct Foo : public RefCounted<Foo>
-{
-  MOZ_DECLARE_REFCOUNTED_TYPENAME(Foo)
-  Foo() : mDead(false) {}
-  ~Foo()
-  {
-    MOZ_ASSERT(!mDead);
-    mDead = true;
-    sNumDestroyed++;
-  }
-
-  bool mDead;
-  static int sNumDestroyed;
-};
-int Foo::sNumDestroyed;
-
-struct Bar : public Foo {};
-
-TemporaryRef<Foo>
-NewFoo()
-{
-  return RefPtr<Foo>(new Foo());
-}
-
-TemporaryRef<Foo>
-NewBar()
-{
-  return new Bar();
-}
-
-void
-GetNewFoo(Foo** f)
-{
-  *f = new Bar();
-  // Kids, don't try this at home
-  (*f)->AddRef();
-}
-
-void
-GetPassedFoo(Foo** f)
-{
-  // Kids, don't try this at home
-  (*f)->AddRef();
-}
-
-void
-GetNewFoo(RefPtr<Foo>* f)
-{
-  *f = new Bar();
-}
-
-void
-GetPassedFoo(RefPtr<Foo>* f)
-{}
-
-TemporaryRef<Foo>
-GetNullFoo()
-{
-  return 0;
-}
-
-int
-main(int argc, char** argv)
-{
-  // This should blow up
-//    Foo* f = new Foo(); delete f;
-
-  MOZ_ASSERT(0 == Foo::sNumDestroyed);
-  {
-    RefPtr<Foo> f = new Foo();
-    MOZ_ASSERT(f->refCount() == 1);
-  }
-  MOZ_ASSERT(1 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f1 = NewFoo();
-    RefPtr<Foo> f2(NewFoo());
-    MOZ_ASSERT(1 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(3 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> b = NewBar();
-    MOZ_ASSERT(3 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(4 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f1;
-    {
-      f1 = new Foo();
-      RefPtr<Foo> f2(f1);
-      RefPtr<Foo> f3 = f2;
-      MOZ_ASSERT(4 == Foo::sNumDestroyed);
-    }
-    MOZ_ASSERT(4 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(5 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f = new Foo();
-    f.forget();
-    MOZ_ASSERT(6 == Foo::sNumDestroyed);
-  }
-
-  {
-    RefPtr<Foo> f = new Foo();
-    GetNewFoo(byRef(f));
-    MOZ_ASSERT(7 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(8 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f = new Foo();
-    GetPassedFoo(byRef(f));
-    MOZ_ASSERT(8 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(9 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f = new Foo();
-    GetNewFoo(&f);
-    MOZ_ASSERT(10 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(11 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f = new Foo();
-    GetPassedFoo(&f);
-    MOZ_ASSERT(11 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(12 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f1 = new Bar();
-  }
-  MOZ_ASSERT(13 == Foo::sNumDestroyed);
-
-  {
-    RefPtr<Foo> f = GetNullFoo();
-    MOZ_ASSERT(13 == Foo::sNumDestroyed);
-  }
-  MOZ_ASSERT(13 == Foo::sNumDestroyed);
-
-  return 0;
-}
-
-#endif
-
 #endif /* mozilla_RefPtr_h */
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestRefPtr.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RefPtr.h"
+
+using mozilla::RefCounted;
+using mozilla::RefPtr;
+using mozilla::TemporaryRef;
+
+class Foo : public RefCounted<Foo>
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(Foo)
+
+  Foo() : mDead(false) {}
+
+  static int sNumDestroyed;
+
+  ~Foo()
+  {
+    MOZ_ASSERT(!mDead);
+    mDead = true;
+    sNumDestroyed++;
+  }
+
+private:
+  bool mDead;
+};
+int Foo::sNumDestroyed;
+
+struct Bar : public Foo {};
+
+TemporaryRef<Foo>
+NewFoo()
+{
+  return RefPtr<Foo>(new Foo());
+}
+
+TemporaryRef<Foo>
+NewBar()
+{
+  return new Bar();
+}
+
+void
+GetNewFoo(Foo** aFoo)
+{
+  *aFoo = new Bar();
+  // Kids, don't try this at home
+  (*aFoo)->AddRef();
+}
+
+void
+GetPassedFoo(Foo** aFoo)
+{
+  // Kids, don't try this at home
+  (*aFoo)->AddRef();
+}
+
+void
+GetNewFoo(RefPtr<Foo>* aFoo)
+{
+  *aFoo = new Bar();
+}
+
+void
+GetPassedFoo(RefPtr<Foo>* aFoo)
+{}
+
+TemporaryRef<Foo>
+GetNullFoo()
+{
+  return 0;
+}
+
+int
+main()
+{
+  MOZ_RELEASE_ASSERT(0 == Foo::sNumDestroyed);
+  {
+    RefPtr<Foo> f = new Foo();
+    MOZ_RELEASE_ASSERT(f->refCount() == 1);
+  }
+  MOZ_RELEASE_ASSERT(1 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f1 = NewFoo();
+    RefPtr<Foo> f2(NewFoo());
+    MOZ_RELEASE_ASSERT(1 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(3 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> b = NewBar();
+    MOZ_RELEASE_ASSERT(3 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(4 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f1;
+    {
+      f1 = new Foo();
+      RefPtr<Foo> f2(f1);
+      RefPtr<Foo> f3 = f2;
+      MOZ_RELEASE_ASSERT(4 == Foo::sNumDestroyed);
+    }
+    MOZ_RELEASE_ASSERT(4 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(5 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f = new Foo();
+    f.forget();
+    MOZ_RELEASE_ASSERT(6 == Foo::sNumDestroyed);
+  }
+
+  {
+    RefPtr<Foo> f = new Foo();
+    GetNewFoo(byRef(f));
+    MOZ_RELEASE_ASSERT(7 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(8 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f = new Foo();
+    GetPassedFoo(byRef(f));
+    MOZ_RELEASE_ASSERT(8 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(9 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f = new Foo();
+    GetNewFoo(&f);
+    MOZ_RELEASE_ASSERT(10 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(11 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f = new Foo();
+    GetPassedFoo(&f);
+    MOZ_RELEASE_ASSERT(11 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(12 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f1 = new Bar();
+  }
+  MOZ_RELEASE_ASSERT(13 == Foo::sNumDestroyed);
+
+  {
+    RefPtr<Foo> f = GetNullFoo();
+    MOZ_RELEASE_ASSERT(13 == Foo::sNumDestroyed);
+  }
+  MOZ_RELEASE_ASSERT(13 == Foo::sNumDestroyed);
+
+  return 0;
+}
+
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -16,16 +16,17 @@ CPP_UNIT_TESTS += [
     'TestCountZeroes',
     'TestEndian',
     'TestEnumSet',
     'TestFloatingPoint',
     'TestIntegerPrintfMacros',
     'TestMacroArgs',
     'TestMacroForEach',
     'TestPair',
+    'TestRefPtr',
     'TestRollingMean',
     'TestSHA1',
     'TestTypedEnum',
     'TestTypeTraits',
     'TestUniquePtr',
     'TestWeakPtr',
 ]
 
--- a/mobile/android/base/AndroidGamepadManager.java
+++ b/mobile/android/base/AndroidGamepadManager.java
@@ -1,37 +1,31 @@
 /* -*- 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.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.hardware.input.InputManager;
-import android.os.Build;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-import java.lang.Math;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
 
 public class AndroidGamepadManager {
     // This is completely arbitrary.
     private static final float TRIGGER_PRESSED_THRESHOLD = 0.25f;
     private static final long POLL_TIMER_PERIOD = 1000; // milliseconds
 
     private static enum Axis {
         X(MotionEvent.AXIS_X),
@@ -329,17 +323,17 @@ public class AndroidGamepadManager {
 
     private static void removeGamepad(int deviceId) {
         Gamepad gamepad = sGamepads.get(deviceId);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadAddRemoveEvent(gamepad.id, false));
         sGamepads.remove(deviceId);
     }
 
     private static void addDeviceListener() {
-        if (Build.VERSION.SDK_INT < 16) {
+        if (Versions.preJB) {
             // Poll known gamepads to see if they've disappeared.
             sPollTimer = new Timer();
             sPollTimer.scheduleAtFixedRate(new TimerTask() {
                     public void run() {
                         for (Integer deviceId : sGamepads.keySet()) {
                             if (InputDevice.getDevice(deviceId) == null) {
                                 removeGamepad(deviceId);
                             }
@@ -373,17 +367,17 @@ public class AndroidGamepadManager {
 
                 public void onInputDeviceChanged(int deviceId) {
                 }
             };
         ((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).registerInputDeviceListener(sListener, ThreadUtils.getUiHandler());
     }
 
     private static void removeDeviceListener() {
-        if (Build.VERSION.SDK_INT < 16) {
+        if (Versions.preJB) {
             if (sPollTimer != null) {
                 sPollTimer.cancel();
                 sPollTimer = null;
             }
             return;
         }
         ((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).unregisterInputDeviceListener(sListener);
         sListener = null;
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -3,34 +3,81 @@
  * 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.RobocopTarget;
 
+import android.os.Build;
+
 /**
  * A collection of constants that pertain to the build and runtime state of the
  * application. Typically these are sourced from build-time definitions (see
  * Makefile.in). This is a Java-side substitute for nsIXULAppInfo, amongst
  * other things.
  *
  * See also SysInfo.java, which includes some of the values available from
  * nsSystemInfo inside Gecko.
  */
 @RobocopTarget
 public class AppConstants {
     public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
+    // Maintain a separate search package name so that we can speciailize it in the standalone search project
+    public static final String SEARCH_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
     public static final String MANGLED_ANDROID_PACKAGE_NAME = "@MANGLED_ANDROID_PACKAGE_NAME@";
 
     /**
+     * Encapsulates access to compile-time version definitions, allowing
+     * for dead code removal for particular APKs.
+     */
+    public static final class Versions {
+        public static final int MIN_SDK_VERSION = @MOZ_ANDROID_MIN_SDK_VERSION@;
+        public static final int MAX_SDK_VERSION =
+#ifdef MOZ_ANDROID_MAX_SDK_VERSION
+        @MOZ_ANDROID_MAX_SDK_VERSION@;
+#else
+        999;
+#endif
+
+        /*
+         * The SDK_INT >= N check can only pass if our MAX_SDK_VERSION is
+         * _greater than or equal_ to that number, because otherwise we
+         * won't be installed on the device.
+         *
+         * If MIN_SDK_VERSION is greater than or equal to the number, there
+         * is no need to do the runtime check.
+         */
+        public static final boolean feature10Plus = MIN_SDK_VERSION >= 10 || (MAX_SDK_VERSION >= 10 && Build.VERSION.SDK_INT >= 10);
+        public static final boolean feature11Plus = MIN_SDK_VERSION >= 11 || (MAX_SDK_VERSION >= 11 && Build.VERSION.SDK_INT >= 11);
+        public static final boolean feature12Plus = MIN_SDK_VERSION >= 12 || (MAX_SDK_VERSION >= 12 && Build.VERSION.SDK_INT >= 12);
+        public static final boolean feature14Plus = MIN_SDK_VERSION >= 14 || (MAX_SDK_VERSION >= 14 && Build.VERSION.SDK_INT >= 14);
+        public static final boolean feature15Plus = MIN_SDK_VERSION >= 15 || (MAX_SDK_VERSION >= 15 && Build.VERSION.SDK_INT >= 15);
+        public static final boolean feature16Plus = MIN_SDK_VERSION >= 16 || (MAX_SDK_VERSION >= 16 && Build.VERSION.SDK_INT >= 16);
+        public static final boolean feature17Plus = MIN_SDK_VERSION >= 17 || (MAX_SDK_VERSION >= 17 && Build.VERSION.SDK_INT >= 17);
+        public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
+
+        /*
+         * If our MIN_SDK_VERSION is 14 or higher, we must be an ICS device.
+         * If our MAX_SDK_VERSION is lower than ICS, we must not be an ICS device.
+         * Otherwise, we need a range check.
+         */
+        public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
+        public static final boolean preICS = MAX_SDK_VERSION < 14 || (MIN_SDK_VERSION < 14 && Build.VERSION.SDK_INT < 14);
+        public static final boolean preHCMR2 = MAX_SDK_VERSION < 13 || (MIN_SDK_VERSION < 13 && Build.VERSION.SDK_INT < 13);
+        public static final boolean preHCMR1 = MAX_SDK_VERSION < 12 || (MIN_SDK_VERSION < 12 && Build.VERSION.SDK_INT < 12);
+        public static final boolean preHC = MAX_SDK_VERSION < 11 || (MIN_SDK_VERSION < 11 && Build.VERSION.SDK_INT < 11);
+    }
+
+    /**
      * The name of the Java class that launches the browser.
      */
     public static final String BROWSER_INTENT_CLASS_NAME = "org.mozilla.gecko.BrowserApp";
+    public static final String SEARCH_INTENT_CLASS_NAME = "org.mozilla.search.MainActivity";
 
     public static final String GRE_MILESTONE = "@GRE_MILESTONE@";
 
     public static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
     public static final String MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
 
     // For the benefit of future archaeologists: APP_BUILDID and
     // MOZ_APP_BUILDID are *exactly* the same.
@@ -135,19 +182,21 @@ public class AppConstants {
 
     public static final boolean MOZ_WEBSMS_BACKEND =
 #ifdef MOZ_WEBSMS_BACKEND
     true;
 #else
     false;
 #endif
 
+    // Android Beam is only supported on API14+, so we don't even bother building
+    // it if this APK doesn't include API14 support.
     public static final boolean MOZ_ANDROID_BEAM =
 #ifdef MOZ_ANDROID_BEAM
-    true;
+    Versions.feature14Plus;
 #else
     false;
 #endif
 
     // See this wiki page for more details about channel specific build defines:
     // https://wiki.mozilla.org/Platform/Channel-specific_build_defines
     public static final boolean RELEASE_BUILD =
 #ifdef RELEASE_BUILD
--- a/mobile/android/base/BaseGeckoInterface.java
+++ b/mobile/android/base/BaseGeckoInterface.java
@@ -1,25 +1,25 @@
 /* -*- 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.AppConstants.Versions;
+import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.prompts.PromptService;
 
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.RectF;
 import android.hardware.SensorEventListener;
 import android.location.LocationListener;
-import android.os.Build;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.AbsoluteLayout;
 
 public class BaseGeckoInterface implements GeckoAppShell.GeckoInterface {
     // Bug 908744: Implement GeckoEventListener
     // Bug 908752: Implement SensorEventListener
@@ -74,17 +74,17 @@ public class BaseGeckoInterface implemen
             @Override
             public void run() {
                 // Hide/show the system notification bar
                 Window window = getActivity().getWindow();
                 window.setFlags(fullscreen ?
                                 WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
                                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
 
-                if (Build.VERSION.SDK_INT >= 11) {
+                if (Versions.feature11Plus) {
                     window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
                 }
             }
         });
     }
 
     // Bug 908779: Implement this
     public void addPluginView(final View view, final RectF rect, final boolean isFullScreen) {}
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -2,29 +2,27 @@
  * 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 java.io.File;
 import java.io.FileNotFoundException;
-import java.lang.Class;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
-import java.util.Set;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
@@ -48,33 +46,33 @@ import org.mozilla.gecko.health.SessionI
 import org.mozilla.gecko.home.BrowserSearch;
 import org.mozilla.gecko.home.HomeBanner;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
+import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.tabspanel.TabsPanel;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.ToolbarProgressView;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.ButtonToast;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.app.Activity;
@@ -317,17 +315,17 @@ public class BrowserApp extends GeckoApp
                     // Go forward on R1
                     Tabs.getInstance().getSelectedTab().doForward();
                     return true;
             }
         }
 
         // Check if this was a shortcut. Meta keys exists only on 11+.
         final Tab tab = Tabs.getInstance().getSelectedTab();
-        if (Build.VERSION.SDK_INT >= 11 && tab != null && event.isCtrlPressed()) {
+        if (Versions.feature11Plus && tab != null && event.isCtrlPressed()) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_LEFT_BRACKET:
                     tab.doBack();
                     return true;
 
                 case KeyEvent.KEYCODE_RIGHT_BRACKET:
                     tab.doForward();
                     return true;
@@ -551,35 +549,22 @@ public class BrowserApp extends GeckoApp
             "Updater:Launch");
 
         Distribution distribution = Distribution.init(this);
 
         // Init suggested sites engine in BrowserDB.
         final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
         BrowserDB.setSuggestedSites(suggestedSites);
 
-        // Shipping Native casting is optional and dependent on whether you've downloaded the support
-        // and google play libraries
-        if (AppConstants.MOZ_MEDIA_PLAYER) {
-            try {
-                Class<?> mediaManagerClass = Class.forName("org.mozilla.gecko.MediaPlayerManager");
-                Method init = mediaManagerClass.getMethod("init", Context.class);
-                init.invoke(null, this);
-            } catch(Exception ex) {
-                // Ignore failures
-                Log.i(LOGTAG, "No native casting support", ex);
-            }
-        }
-
         JavaAddonManager.getInstance().init(appContext);
         mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
         mBrowserHealthReporter = new BrowserHealthReporter();
 
-        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
+        if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
                     @Override
                     public NdefMessage createNdefMessage(NfcEvent event) {
                         Tab tab = Tabs.getInstance().getSelectedTab();
                         if (tab == null || tab.isPrivate()) {
                             return null;
@@ -807,17 +792,17 @@ public class BrowserApp extends GeckoApp
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
             }
             return true;
         }
 
         if (itemId == R.id.site_settings) {
             // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Get", null));
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            if (Versions.preHC) {
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "site_settings");
             }
             return true;
         }
 
         if (itemId == R.id.paste) {
             String text = Clipboard.getText();
             if (!TextUtils.isEmpty(text)) {
@@ -833,17 +818,17 @@ public class BrowserApp extends GeckoApp
             if (tab != null && tab.hasFeeds()) {
                 JSONObject args = new JSONObject();
                 try {
                     args.put("tabId", tab.getId());
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "error building json arguments");
                 }
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+                if (Versions.preHC) {
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "subscribe");
                 }
             }
             return true;
         }
 
         if (itemId == R.id.add_search_engine) {
             // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
@@ -853,17 +838,17 @@ public class BrowserApp extends GeckoApp
                 try {
                     args.put("tabId", tab.getId());
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "error building json arguments");
                     return true;
                 }
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Add", args.toString()));
 
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+                if (Versions.preHC) {
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "add_search_engine");
                 }
             }
             return true;
         }
 
         if (itemId == R.id.copyurl) {
             Tab tab = Tabs.getInstance().getSelectedTab();
@@ -957,17 +942,17 @@ public class BrowserApp extends GeckoApp
             "Menu:Remove",
             "Reader:ListStatusRequest",
             "Reader:Removed",
             "Reader:Share",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch");
 
-        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
+        if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
                 // automatically on API 14+
                 nfc.setNdefPushMessageCallback(null, this);
             }
         }
@@ -2323,17 +2308,17 @@ public class BrowserApp extends GeckoApp
         // Add add-on menu items, if any exist.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
                 addAddonMenuItemToMenu(mMenu, item);
             }
         }
 
         // Action providers are available only ICS+.
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
             GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
             share.setActionProvider(provider);
         }
 
         return true;
     }
 
@@ -2404,21 +2389,27 @@ public class BrowserApp extends GeckoApp
         MenuItem share = aMenu.findItem(R.id.share);
         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
         MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
         MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
         MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
         MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
 
-        // Only show the "Quit" menu item on pre-ICS, television devices, or if the user has explicitly enabled the clear on shutdown pref.
+        // Only show the "Quit" menu item on pre-ICS, television devices,
+        // or if the user has explicitly enabled the clear on shutdown pref.
+        // (We check the pref last to save the pref read.)
         // In ICS+, it's easy to kill an app through the task switcher.
         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
-        final Set<String> clearItems = PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
-        aMenu.findItem(R.id.quit).setVisible(clearItems.size() > 0 || Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
+        final boolean visible = Versions.preICS ||
+                                HardwareUtils.isTelevision() ||
+                                !PrefUtils.getStringSet(GeckoSharedPrefs.forProfile(this),
+                                                        ClearOnShutdownPref.PREF,
+                                                        new HashSet<String>()).isEmpty();
+        aMenu.findItem(R.id.quit).setVisible(visible);
 
         if (tab == null || tab.getURL() == null) {
             bookmark.setEnabled(false);
             back.setEnabled(false);
             forward.setEnabled(false);
             share.setEnabled(false);
             saveAsPDF.setEnabled(false);
             findInPage.setEnabled(false);
@@ -2462,17 +2453,17 @@ public class BrowserApp extends GeckoApp
         // be on the BrowserToolbar context menu
         MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
         MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
         MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
         MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, !isAboutHome(tab));
         MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
 
         // Action providers are available only ICS+.
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
             if (provider != null) {
                 Intent shareIntent = provider.getIntent();
 
                 // For efficiency, the provider's intent is only set once
                 if (shareIntent == null) {
                     shareIntent = new Intent(Intent.ACTION_SEND);
                     shareIntent.setType("text/plain");
@@ -2766,17 +2757,17 @@ public class BrowserApp extends GeckoApp
             final TelemetryContract.Method method =
                 (isViewAction ? TelemetryContract.Method.INTENT : TelemetryContract.Method.HOMESCREEN);
 
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method);
         }
 
         super.onNewIntent(intent);
 
-        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
+        if (AppConstants.MOZ_ANDROID_BEAM && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
             String uri = intent.getDataString();
             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
         }
 
         if (!mInitialized) {
             return;
         }
 
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -17,20 +17,22 @@ import com.google.android.gms.cast.Cast;
 import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
 import com.google.android.gms.cast.CastDevice;
 import com.google.android.gms.cast.CastMediaControlIntent;
 import com.google.android.gms.cast.MediaInfo;
 import com.google.android.gms.cast.MediaMetadata;
 import com.google.android.gms.cast.MediaStatus;
 import com.google.android.gms.cast.RemoteMediaPlayer;
 import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
+import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.api.GoogleApiClient;
 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
 import com.google.android.gms.common.api.ResultCallback;
 import com.google.android.gms.common.api.Status;
+import com.google.android.gms.common.GooglePlayServicesUtil;
 
 import android.content.Context;
 import android.os.Bundle;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
 
 /* Implementation of GeckoMediaPlayer for talking to ChromeCast devices */
 class ChromeCast implements GeckoMediaPlayer {
@@ -132,16 +134,21 @@ class ChromeCast implements GeckoMediaPl
                 debug("Problem opening media during loading", e);
             }
 
             callback.sendError(null);
         }
     }
 
     public ChromeCast(Context context, RouteInfo route) {
+        int status =  GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
+        if (status != ConnectionResult.SUCCESS) {
+            throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")");
+        }
+
         this.context = context;
         this.route = route;
         this.canMirror = route.supportsControlCategory(CastMediaControlIntent.categoryForCast(MIRROR_RECIEVER_APP_ID));
     }
 
     // This dumps everything we can find about the device into JSON. This will hopefully make it
     // easier to filter out duplicate devices from different sources in js.
     public JSONObject toJSON() {
--- a/mobile/android/base/ContactService.java
+++ b/mobile/android/base/ContactService.java
@@ -9,32 +9,32 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map.Entry;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.AlertDialog;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.DialogInterface;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
@@ -1623,17 +1623,18 @@ public class ContactService implements G
         } else if (DEBUG) {
             Log.i(LOGTAG, "Using group ID: " + mGroupId + " (" + mGroupTitle + ")");
         }
     }
 
     private static boolean isAutoAddGroup(Cursor cursor) {
         // For Honeycomb and up, the default group is the first one which has the AUTO_ADD flag set.
         // For everything below Honeycomb, use the default "System Group: My Contacts" group
-        return (Build.VERSION.SDK_INT >= 11 && !cursor.isNull(GROUP_AUTO_ADD) &&
+        return (Versions.feature11Plus &&
+                !cursor.isNull(GROUP_AUTO_ADD) &&
                 cursor.getInt(GROUP_AUTO_ADD) != 0);
     }
 
     private long getGroupId(String groupName) {
         long groupId = -1;
         Cursor cursor = getGroups(Groups.TITLE + " = '" + groupName + "'");
 
         cursor.moveToPosition(-1);
@@ -1676,17 +1677,17 @@ public class ContactService implements G
     }
 
     private Cursor getGroups(String selectArg) {
         String[] columns = new String[] {
             Groups.ACCOUNT_NAME,
             Groups.ACCOUNT_TYPE,
             Groups._ID,
             Groups.TITLE,
-            (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Groups.AUTO_ADD : Groups._ID)
+            (Versions.feature11Plus ? Groups.AUTO_ADD : Groups._ID)
         };
 
         if (selectArg != null) {
             selectArg = "AND " + selectArg;
         } else {
             selectArg = "";
         }
 
--- a/mobile/android/base/DataReportingNotification.java
+++ b/mobile/android/base/DataReportingNotification.java
@@ -1,26 +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;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.Typeface;
-import android.os.Build;
 import android.support.v4.app.NotificationCompat;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.text.style.StyleSpan;
 
 public class DataReportingNotification {
 
@@ -82,17 +82,17 @@ public class DataReportingNotification {
             prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
 
             PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);
             final Resources resources = context.getResources();
 
             // Create and send notification.
             String notificationTitle = resources.getString(R.string.datareporting_notification_title);
             String notificationSummary;
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+            if (Versions.preJB) {
                 notificationSummary = resources.getString(R.string.datareporting_notification_action);
             } else {
                 // Display partial version of Big Style notification for supporting devices.
                 notificationSummary = resources.getString(R.string.datareporting_notification_summary);
             }
             String notificationAction = resources.getString(R.string.datareporting_notification_action);
             String notificationBigSummary = resources.getString(R.string.datareporting_notification_summary);
 
--- a/mobile/android/base/DoorHangerPopup.java
+++ b/mobile/android/base/DoorHangerPopup.java
@@ -1,34 +1,33 @@
 /* -*- 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.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.ArrowPopup;
-import org.mozilla.gecko.widget.DoorHanger;
-import org.mozilla.gecko.prompts.PromptInput;
+import java.util.HashSet;
+import java.util.List;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.prompts.PromptInput;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.ArrowPopup;
+import org.mozilla.gecko.widget.DoorHanger;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.Log;
 import android.view.View;
 import android.widget.CheckBox;
 
-import java.util.HashSet;
-import java.util.List;
-
 public class DoorHangerPopup extends ArrowPopup
                              implements GeckoEventListener,
                                         Tabs.OnTabsChangedListener,
                                         DoorHanger.OnButtonClickListener {
     private static final String LOGTAG = "GeckoDoorHangerPopup";
 
     // Stores a set of all active DoorHanger notifications. A DoorHanger is
     // uniquely identified by its tabId and value.
@@ -306,23 +305,23 @@ public class DoorHangerPopup extends Arr
         if (isShowing()) {
             show();
             return;
         }
 
         // Make the popup focusable for accessibility. This gets done here
         // so the node can be accessibility focused, but on pre-ICS devices this
         // causes crashes, so it is done after the popup is shown.
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             setFocusable(true);
         }
 
         show();
 
-        if (Build.VERSION.SDK_INT < 14) {
+        if (Versions.preICS) {
             // Make the popup focusable for keyboard accessibility.
             setFocusable(true);
         }
     }
 
     //Show all inter-DoorHanger dividers (ie. Dividers on all visible DoorHangers except the last one)
     private void showDividers() {
         int count = mContent.getChildCount();
--- a/mobile/android/base/GeckoAccessibility.java
+++ b/mobile/android/base/GeckoAccessibility.java
@@ -1,43 +1,42 @@
 /* -*- 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.gfx.LayerView;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UiAsyncTask;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningServiceInfo;
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
 import com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient;
 import com.googlecode.eyesfree.braille.selfbraille.WriteData;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-
 public class GeckoAccessibility {
     private static final String LOGTAG = "GeckoAccessibility";
     private static final int VIRTUAL_CURSOR_PREVIOUS = 1;
     private static final int VIRTUAL_CURSOR_POSITION = 2;
     private static final int VIRTUAL_CURSOR_NEXT = 3;
 
     private static boolean sEnabled;
     // Used to store the JSON message and populate the event later in the code path.
@@ -69,18 +68,17 @@ public class GeckoAccessibility {
                             (ActivityManager) app.getSystemService(Context.ACTIVITY_SERVICE);
                         List<RunningServiceInfo> runningServices = activityManager.getRunningServices(Integer.MAX_VALUE);
 
                         for (RunningServiceInfo runningServiceInfo : runningServices) {
                             sEnabled = sServiceWhitelist.contains(runningServiceInfo.service.getClassName());
                             if (sEnabled)
                                 break;
                         }
-                        if (sEnabled && sSelfBrailleClient == null &&
-                            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                        if (Versions.feature16Plus && sEnabled && sSelfBrailleClient == null) {
                             sSelfBrailleClient = new SelfBrailleClient(GeckoAppShell.getContext(), false);
                         }
                     }
 
                     try {
                         ret.put("enabled", sEnabled);
                     } catch (Exception ex) {
                         Log.e(LOGTAG, "Error building JSON arguments for Accessibility:Settings:", ex);
@@ -112,23 +110,23 @@ public class GeckoAccessibility {
         event.setChecked(message.optBoolean("checked"));
         event.setPassword(message.optBoolean("password"));
         event.setAddedCount(message.optInt("addedCount", -1));
         event.setRemovedCount(message.optInt("removedCount", -1));
         event.setFromIndex(message.optInt("fromIndex", -1));
         event.setItemCount(message.optInt("itemCount", -1));
         event.setCurrentItemIndex(message.optInt("currentItemIndex", -1));
         event.setBeforeText(message.optString("beforeText"));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        if (Versions.feature14Plus) {
             event.setToIndex(message.optInt("toIndex", -1));
             event.setScrollable(message.optBoolean("scrollable"));
             event.setScrollX(message.optInt("scrollX", -1));
             event.setScrollY(message.optInt("scrollY", -1));
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+        if (Versions.feature15Plus) {
             event.setMaxScrollX(message.optInt("maxScrollX", -1));
             event.setMaxScrollY(message.optInt("maxScrollY", -1));
         }
     }
 
     private static void sendDirectAccessibilityEvent(int eventType, JSONObject message) {
         final AccessibilityEvent accEvent = AccessibilityEvent.obtain(eventType);
         accEvent.setClassName(GeckoAccessibility.class.getName());
@@ -148,17 +146,17 @@ public class GeckoAccessibility {
             return;
 
         final int eventType = message.optInt("eventType", -1);
         if (eventType < 0) {
             Log.e(LOGTAG, "No accessibility event type provided");
             return;
         }
 
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+        if (Versions.preJB) {
             // Before Jelly Bean we send events directly from here while spoofing the source by setting
             // the package and class name manually.
             ThreadUtils.postToBackgroundThread(new Runnable() {
                     @Override
                     public void run() {
                         sendDirectAccessibilityEvent(eventType, message);
                 }
             });
@@ -242,17 +240,17 @@ public class GeckoAccessibility {
         // Set either the focus blink or the current caret position/selection
         data.setSelectionStart(selectionStart);
         data.setSelectionEnd(selectionEnd);
         sSelfBrailleClient.write(data);
     }
 
     public static void setDelegate(LayerView layerview) {
         // Only use this delegate in Jelly Bean.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+        if (Versions.feature16Plus) {
             layerview.setAccessibilityDelegate(new GeckoAccessibilityDelegate());
             layerview.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
     }
 
     public static void onLayerViewFocusChanged(LayerView layerview, boolean gainFocus) {
         if (sEnabled)
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:Focus",
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -21,16 +21,17 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.FullScreenState;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PluginLayer;
@@ -73,17 +74,16 @@ import android.graphics.BitmapFactory;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.location.Location;
 import android.location.LocationListener;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.StrictMode;
 import android.provider.ContactsContract;
 import android.provider.MediaStore.Images.Media;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -290,40 +290,43 @@ public abstract class GeckoApp
                 break;
         }
     }
 
     public void refreshChrome() { }
 
     @Override
     public void invalidateOptionsMenu() {
-        if (mMenu == null)
+        if (mMenu == null) {
             return;
+        }
 
         onPrepareOptionsMenu(mMenu);
 
-        if (Build.VERSION.SDK_INT >= 11)
+        if (Versions.feature11Plus) {
             super.invalidateOptionsMenu();
+        }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         mMenu = menu;
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.gecko_app_menu, mMenu);
         return true;
     }
 
     @Override
     public MenuInflater getMenuInflater() {
-        if (Build.VERSION.SDK_INT >= 11)
+        if (Versions.feature11Plus) {
             return new GeckoMenuInflater(this);
-        else
+        } else {
             return super.getMenuInflater();
+        }
     }
 
     public MenuPanel getMenuPanel() {
         if (mMenuPanel == null) {
             onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
             invalidateOptionsMenu();
         }
         return mMenuPanel;
@@ -364,33 +367,33 @@ public abstract class GeckoApp
 
     @Override
     public void closeMenu() {
         closeOptionsMenu();
     }
 
     @Override
     public View onCreatePanelView(int featureId) {
-        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
+        if (Versions.feature11Plus && featureId == Window.FEATURE_OPTIONS_PANEL) {
             if (mMenuPanel == null) {
                 mMenuPanel = new MenuPanel(this, null);
             } else {
                 // Prepare the panel everytime before showing the menu.
                 onPreparePanel(featureId, mMenuPanel, mMenu);
             }
 
             return mMenuPanel; 
         }
   
         return super.onCreatePanelView(featureId);
     }
 
     @Override
     public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
+        if (Versions.feature11Plus && featureId == Window.FEATURE_OPTIONS_PANEL) {
             if (mMenuPanel == null) {
                 mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
             }
 
             GeckoMenu gMenu = new GeckoMenu(this, null);
             gMenu.setCallback(this);
             gMenu.setMenuPresenter(this);
             menu = gMenu;
@@ -399,30 +402,31 @@ public abstract class GeckoApp
             return onCreateOptionsMenu(menu);
         }
 
         return super.onCreatePanelMenu(featureId, menu);
     }
 
     @Override
     public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
+        if (Versions.feature11Plus && featureId == Window.FEATURE_OPTIONS_PANEL) {
             return onPrepareOptionsMenu(menu);
+        }
 
         return super.onPreparePanel(featureId, view, menu);
     }
 
     @Override
     public boolean onMenuOpened(int featureId, Menu menu) {
         // exit full-screen mode whenever the menu is opened
         if (mLayerView != null && mLayerView.isFullScreen()) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
         }
 
-        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
+        if (Versions.feature11Plus && featureId == Window.FEATURE_OPTIONS_PANEL) {
             if (mMenu == null) {
                 // getMenuPanel() will force the creation of the menu as well
                 MenuPanel panel = getMenuPanel();
                 onPreparePanel(featureId, panel, mMenu);
             }
 
             // Scroll custom menu to the top
             if (mMenuPanel != null)
@@ -458,17 +462,17 @@ public abstract class GeckoApp
             return true;
         }
 
         return super.onOptionsItemSelected(item);
     }
 
     @Override
     public void onOptionsMenuClosed(Menu menu) {
-        if (Build.VERSION.SDK_INT >= 11) {
+        if (Versions.feature11Plus) {
             mMenuPanel.removeAllViews();
             mMenuPanel.addView((GeckoMenu) mMenu);
         }
     }
  
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
@@ -1044,18 +1048,19 @@ public abstract class GeckoApp
             @Override
             public void run() {
                 // Hide/show the system notification bar
                 Window window = getWindow();
                 window.setFlags(fullscreen ?
                                 WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
                                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
 
-                if (Build.VERSION.SDK_INT >= 11)
+                if (Versions.feature11Plus) {
                     window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
+                }
             }
         });
     }
 
     /**
      * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified
      **/
     protected void earlyStartJavaSampler(Intent intent)
@@ -1336,17 +1341,17 @@ public abstract class GeckoApp
 
     protected void initializeChrome() {
         mDoorHangerPopup = new DoorHangerPopup(this);
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
 
         if (mCameraView == null) {
             // Pre-ICS devices need the camera surface in a visible layout.
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            if (Versions.preICS) {
                 mCameraView = new SurfaceView(this);
                 ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
             }
         }
 
         if (mLayerView == null) {
             LayerView layerView = (LayerView) findViewById(R.id.layer_view);
             layerView.initializeView(EventDispatcher.getInstance());
@@ -1732,17 +1737,17 @@ public abstract class GeckoApp
                     }
                 }
             }
         };
         mCameraOrientationEventListener.enable();
 
         // Try to make it fully transparent.
         if (mCameraView != null && (mCameraView instanceof SurfaceView)) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            if (Versions.feature11Plus) {
                 mCameraView.setAlpha(0.0f);
             }
             ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
             // Some phones (eg. nexus S) need at least a 8x16 preview size
             mCameraLayout.addView(mCameraView,
                                   new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
         }
     }
@@ -2572,17 +2577,17 @@ public abstract class GeckoApp
         if (resultant == null) {
             return;
         }
 
         onLocaleChanged(resultant);
     }
 
     private void setSystemUiVisible(final boolean visible) {
-        if (Build.VERSION.SDK_INT < 14) {
+        if (Versions.preICS) {
             return;
         }
 
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 if (visible) {
                     mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -27,16 +27,17 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Queue;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.mozglue.RobocopTarget;
@@ -83,17 +84,16 @@ import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -827,17 +827,17 @@ public class GeckoAppShell
         intent.putExtra("duplicate", false);
 
         intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
         getContext().sendBroadcast(intent);
     }
 
     @JNITarget
     static public int getPreferredIconSize() {
-        if (android.os.Build.VERSION.SDK_INT >= 11) {
+        if (Versions.feature11Plus) {
             ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
             return am.getLauncherLargeIconSize();
         } else {
             switch (getDpi()) {
                 case DisplayMetrics.DENSITY_MEDIUM:
                     return 48;
                 case DisplayMetrics.DENSITY_XHIGH:
                     return 96;
@@ -1824,17 +1824,17 @@ public class GeckoAppShell
                         vreader.close();
                     }
                 } catch (IOException ex) {
                     // nothing
                 }
             }
 
             // disable on KitKat (bug 957694)
-            if (Build.VERSION.SDK_INT >= 19) {
+            if (Versions.feature19Plus) {
                 Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)");
                 return null;
             }
         }
 
         ArrayList<String> directories = new ArrayList<String>();
         PackageManager pm = getContext().getPackageManager();
         List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
--- a/mobile/android/base/GeckoEditable.java
+++ b/mobile/android/base/GeckoEditable.java
@@ -1,48 +1,47 @@
 /* -*- 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 java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Semaphore;
+
+import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
-import org.json.JSONObject;
-
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.Editable;
 import android.text.InputFilter;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.style.CharacterStyle;
 import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Semaphore;
-
 // interface for the IC thread
 interface GeckoEditableClient {
     void sendEvent(GeckoEvent event);
     Editable getEditable();
     void setUpdateGecko(boolean update);
     void setSuppressKeyUp(boolean suppress);
     Handler getInputConnectionHandler();
     boolean setInputConnectionHandler(Handler handler);
@@ -246,18 +245,18 @@ final class GeckoEditable
             }
             ++mIcUpdateSeqno;
         }
 
         private KeyEvent [] synthesizeKeyEvents(CharSequence cs) {
             try {
                 if (mKeyMap == null) {
                     mKeyMap = KeyCharacterMap.load(
-                        Build.VERSION.SDK_INT < 11 ? KeyCharacterMap.ALPHA :
-                                                     KeyCharacterMap.VIRTUAL_KEYBOARD);
+                        Versions.preHC ? KeyCharacterMap.ALPHA :
+                                         KeyCharacterMap.VIRTUAL_KEYBOARD);
                 }
             } catch (Exception e) {
                 // KeyCharacterMap.UnavailableExcepton is not found on Gingerbread;
                 // besides, it seems like HC and ICS will throw something other than
                 // KeyCharacterMap.UnavailableExcepton; so use a generic Exception here
                 return null;
             }
             KeyEvent [] keyEvents = mKeyMap.getEvents(cs.toString().toCharArray());
@@ -483,18 +482,19 @@ final class GeckoEditable
                             ? GeckoEvent.IME_RANGE_SELECTEDCONVERTEDTEXT
                             : GeckoEvent.IME_RANGE_CONVERTEDTEXT;
                 tp.set(emptyTp);
                 for (CharacterStyle span : styleSpans) {
                     span.updateDrawState(tp);
                 }
                 int tpUnderlineColor = 0;
                 float tpUnderlineThickness = 0.0f;
-                // These TextPaint fields only exist on Android ICS+ and are not in the SDK
-                if (Build.VERSION.SDK_INT >= 14) {
+
+                // These TextPaint fields only exist on Android ICS+ and are not in the SDK.
+                if (Versions.feature14Plus) {
                     tpUnderlineColor = (Integer)getField(tp, "underlineColor", 0);
                     tpUnderlineThickness = (Float)getField(tp, "underlineThickness", 0.0f);
                 }
                 if (tpUnderlineColor != 0) {
                     rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE | GeckoEvent.IME_RANGE_LINECOLOR;
                     rangeLineColor = tpUnderlineColor;
                     // Approximately translate underline thickness to what Gecko understands
                     if (tpUnderlineThickness <= 0.5f) {
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -1,42 +1,40 @@
 /* -*- 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 java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.DisplayPortMetrics;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.mozglue.JNITarget;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.mozglue.generatorannotations.GeneratorOptions;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapEntireClassForJNI;
-import org.mozilla.gecko.mozglue.RobocopTarget;
 
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
 import android.location.Address;
 import android.location.Location;
-import android.os.Build;
 import android.os.SystemClock;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-import java.nio.ByteBuffer;
-import java.util.concurrent.ArrayBlockingQueue;
-
 /* We're not allowed to hold on to most events given to us
  * so we save the parts of the events we want to use in GeckoEvent.
  * Fields have different meanings depending on the event type.
  */
 
 /* This class is referenced by Robocop via reflection; use care when
  * modifying the signature.
  */
@@ -326,26 +324,26 @@ public class GeckoEvent {
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
             case KeyEvent.KEYCODE_DPAD_LEFT:
             case KeyEvent.KEYCODE_DPAD_RIGHT:
             case KeyEvent.KEYCODE_DPAD_DOWN:
             case KeyEvent.KEYCODE_DPAD_UP:
                 return true;
             default:
-                if (Build.VERSION.SDK_INT >= 12) {
+                if (Versions.feature12Plus) {
                     return KeyEvent.isGamepadButton(keyCode);
                 }
                 return GeckoEvent.isGamepadButton(keyCode);
         }
     }
 
     /**
      * This method is a replacement for the the KeyEvent.isGamepadButton method to be
-     * compatible with Build.VERSION.SDK_INT < 12. This is an implementantion of the
+     * compatible with Build.VERSION.SDK_INT < 12. This is an implementation of the
      * same method isGamepadButton available after SDK 12.
      * @param keyCode int with the key code (Android key constant from KeyEvent).
      * @return True if the keycode is a gamepad button, such as {@link #KEYCODE_BUTTON_A}.
      */
     private static boolean isGamepadButton(int keyCode) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_BUTTON_A:
             case KeyEvent.KEYCODE_BUTTON_B:
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -5,25 +5,25 @@
 
 package org.mozilla.gecko;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.SynchronousQueue;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.R;
 import android.content.Context;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.SpannableString;
 import android.text.method.KeyListener;
@@ -948,20 +948,20 @@ class GeckoInputConnection
     @Override
     public void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint) {
         // For some input type we will use a widget to display the ui, for those we must not
         // display the ime. We can display a widget for date and time types and, if the sdk version
         // is 11 or greater, for datetime/month/week as well.
         if (typeHint != null &&
             (typeHint.equalsIgnoreCase("date") ||
              typeHint.equalsIgnoreCase("time") ||
-             (Build.VERSION.SDK_INT >= 11 && (typeHint.equalsIgnoreCase("datetime") ||
-                                              typeHint.equalsIgnoreCase("month") ||
-                                              typeHint.equalsIgnoreCase("week") ||
-                                              typeHint.equalsIgnoreCase("datetime-local"))))) {
+             (Versions.feature11Plus && (typeHint.equalsIgnoreCase("datetime") ||
+                                         typeHint.equalsIgnoreCase("month") ||
+                                         typeHint.equalsIgnoreCase("week") ||
+                                         typeHint.equalsIgnoreCase("datetime-local"))))) {
             state = IME_STATE_DISABLED;
         }
 
         // mIMEState and the mIME*Hint fields should only be changed by notifyIMEContext,
         // and not reset anywhere else. Usually, notifyIMEContext is called right after a
         // focus or blur, so resetting mIMEState during the focus or blur seems harmless.
         // However, this behavior is not guaranteed. Gecko may call notifyIMEContext
         // independent of focus change; that is, a focus change may not be accompanied by
--- a/mobile/android/base/InputMethods.java
+++ b/mobile/android/base/InputMethods.java
@@ -1,26 +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;
 
-import org.mozilla.gecko.mozglue.RobocopTarget;
+import java.util.Collection;
+
+import org.mozilla.gecko.AppConstants.Versions;
 
 import android.content.Context;
-import android.os.Build;
 import android.provider.Settings.Secure;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
-import java.util.Collection;
-import java.util.Locale;
-
 final public class InputMethods {
     public static final String METHOD_ANDROID_LATINIME = "com.android.inputmethod.latin/.LatinIME";
     public static final String METHOD_ATOK = "com.justsystems.atokmobile.service/.AtokInputMethodService";
     public static final String METHOD_GOOGLE_JAPANESE_INPUT = "com.google.android.inputmethod.japanese/.MozcService";
     public static final String METHOD_GOOGLE_LATINIME = "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME";
     public static final String METHOD_HTC_TOUCH_INPUT = "com.htc.android.htcime/.HTCIMEService";
     public static final String METHOD_IWNN = "jp.co.omronsoft.iwnnime.ml/.standardcommon.IWnnLanguageSwitcher";
     public static final String METHOD_OPENWNN_PLUS = "com.owplus.ime.openwnnplus/.OpenWnnJAJP";
@@ -50,27 +48,29 @@ final public class InputMethods {
     }
 
     public static InputMethodManager getInputMethodManager(Context context) {
         return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     }
 
     public static boolean needsSoftResetWorkaround(String inputMethod) {
         // Stock latin IME on Android 4.2 and above
-        return Build.VERSION.SDK_INT >= 17 && (METHOD_ANDROID_LATINIME.equals(inputMethod) ||
-                                               METHOD_GOOGLE_LATINIME.equals(inputMethod));
+        return Versions.feature17Plus &&
+               (METHOD_ANDROID_LATINIME.equals(inputMethod) ||
+                METHOD_GOOGLE_LATINIME.equals(inputMethod));
     }
 
     public static boolean shouldCommitCharAsKey(String inputMethod) {
         return METHOD_HTC_TOUCH_INPUT.equals(inputMethod);
     }
 
     public static boolean isGestureKeyboard(Context context) {
         // SwiftKey is a gesture keyboard, but it doesn't seem to need any special-casing
         // to do AwesomeBar auto-spacing.
         String inputMethod = getCurrentInputMethod(context);
-        return (Build.VERSION.SDK_INT >= 17 && (METHOD_ANDROID_LATINIME.equals(inputMethod) ||
-                                                METHOD_GOOGLE_LATINIME.equals(inputMethod))) ||
+        return (Versions.feature17Plus &&
+                (METHOD_ANDROID_LATINIME.equals(inputMethod) ||
+                 METHOD_GOOGLE_LATINIME.equals(inputMethod))) ||
                METHOD_SWYPE.equals(inputMethod) ||
                METHOD_SWYPE_BETA.equals(inputMethod) ||
                METHOD_TOUCHPAL_KEYBOARD.equals(inputMethod);
     }
 }
--- a/mobile/android/base/LightweightTheme.java
+++ b/mobile/android/base/LightweightTheme.java
@@ -1,41 +1,41 @@
 /* -*- 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 java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
-import org.json.JSONObject;
 
 import android.app.Application;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewParent;
 
-import java.util.ArrayList;
-import java.util.List;
-
 public class LightweightTheme implements GeckoEventListener {
     private static final String LOGTAG = "GeckoLightweightTheme";
 
     private final Application mApplication;
     /* inner-access */ final Handler mHandler;
 
     private Bitmap mBitmap;
     private int mColor;
@@ -248,17 +248,17 @@ public class LightweightTheme implements
 
         int offsetX = 0;
         int offsetY = 0;
 
         // Find if this view or any of its ancestors has been translated or scrolled.
         ViewParent parent;
         View curView = view;
         do {
-            if (Build.VERSION.SDK_INT >= 11) {
+            if (Versions.feature11Plus) {
                 offsetX += (int) curView.getTranslationX() - curView.getScrollX();
                 offsetY += (int) curView.getTranslationY() - curView.getScrollY();
             } else {
                 offsetX -= curView.getScrollX();
                 offsetY -= curView.getScrollY();
             }
 
             parent = curView.getParent();
--- a/mobile/android/base/MediaPlayerManager.java
+++ b/mobile/android/base/MediaPlayerManager.java
@@ -1,16 +1,17 @@
 /* -*- 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.util.EventCallback;
+import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import android.content.Context;
@@ -36,17 +37,17 @@ interface GeckoMediaPlayer {
     public void mirror(EventCallback callback);
     public void message(String message, EventCallback callback);
 }
 
 /* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
  * from Gecko to the correct caster based on the id of the display
  */
 class MediaPlayerManager implements NativeEventListener,
-                                      GeckoAppShell.AppStateListener {
+                                    GeckoAppShell.AppStateListener {
     private static final String LOGTAG = "GeckoMediaPlayerManager";
 
     private static final boolean SHOW_DEBUG = false;
     // Simplified debugging interfaces
     private static void debug(String msg, Exception e) {
         if (SHOW_DEBUG) {
             Log.e(LOGTAG, msg, e);
         }
@@ -58,19 +59,21 @@ class MediaPlayerManager implements Nati
         }
     }
 
     private final Context context;
     private final MediaRouter mediaRouter;
     private final HashMap<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
     private static MediaPlayerManager instance;
 
+    @JNITarget
     public static void init(Context context) {
         if (instance != null) {
             debug("MediaPlayerManager initialized twice");
+            return;
         }
 
         instance = new MediaPlayerManager(context);
     }
 
     private MediaPlayerManager(Context context) {
         this.context = context;
 
@@ -87,16 +90,17 @@ class MediaPlayerManager implements Nati
                                                                   "MediaPlayer:Play",
                                                                   "MediaPlayer:Pause",
                                                                   "MediaPlayer:Get",
                                                                   "MediaPlayer:End",
                                                                   "MediaPlayer:Mirror",
                                                                   "MediaPlayer:Message");
     }
 
+    @JNITarget
     public static void onDestroy() {
         if (instance == null) {
             return;
         }
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(instance,
                                                                     "MediaPlayer:Load",
                                                                     "MediaPlayer:Start",
--- a/mobile/android/base/MemoryMonitor.java
+++ b/mobile/android/base/MemoryMonitor.java
@@ -1,26 +1,26 @@
 /* -*- 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.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Build;
 import android.util.Log;
 
 /**
   * This is a utility class to keep track of how much memory and disk-space pressure
   * the system is under. It receives input from GeckoActivity via the onLowMemory() and
   * onTrimMemory() functions, and also listens for some system intents related to
   * disk-space notifications. Internally it will track how much memory and disk pressure
   * the system is under, and perform various actions to help alleviate the pressure.
@@ -84,18 +84,18 @@ class MemoryMonitor extends BroadcastRec
             // We need to wait on Gecko here, because if we haven't reduced
             // memory usage enough when we return from this, Android will kill us.
             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createNoOpEvent());
         }
     }
 
     public void onTrimMemory(int level) {
         Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
-        if (Build.VERSION.SDK_INT < 14) {
-            // this won't even get called pre-ICS
+        if (Versions.preICS) {
+            // This won't even get called pre-ICS.
             return;
         }
 
         if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
             increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
         } else if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
             increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
         } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
--- a/mobile/android/base/animation/AnimatorProxy.java
+++ b/mobile/android/base/animation/AnimatorProxy.java
@@ -1,27 +1,28 @@
 /* -*- 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.animation;
 
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+import org.mozilla.gecko.AppConstants.Versions;
+
 import android.graphics.Matrix;
 import android.graphics.RectF;
-import android.os.Build;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
 
-import java.lang.ref.WeakReference;
-import java.util.WeakHashMap;
-
 class AnimatorProxy {
     private static final WeakHashMap<View, AnimatorProxy> PROXIES =
             new WeakHashMap<View, AnimatorProxy>();
 
     private static interface AnimatorProxyImpl {
         public float getAlpha();
         public void setAlpha(float alpha);
 
@@ -37,17 +38,17 @@ class AnimatorProxy {
     private AnimatorProxyImpl mImpl;
 
     private AnimatorProxy(AnimatorProxyImpl impl) {
         mImpl = impl;
     }
 
     public static AnimatorProxy create(View view) {
         AnimatorProxy proxy = PROXIES.get(view);
-        boolean needsAnimationProxy = (Build.VERSION.SDK_INT < 11);
+        final boolean needsAnimationProxy = Versions.preHC;
 
         // If the view's animation proxy has been overridden from somewhere else, we need to
         // create a new AnimatorProxy for the view.
         if (proxy == null || (needsAnimationProxy && proxy.mImpl != view.getAnimation())) {
             AnimatorProxyImpl impl = (needsAnimationProxy ? new AnimatorProxyPreHC(view) :
                                                             new AnimatorProxyPostHC(view));
 
             proxy = new AnimatorProxy(impl);
--- a/mobile/android/base/animation/PropertyAnimator.java
+++ b/mobile/android/base/animation/PropertyAnimator.java
@@ -1,29 +1,30 @@
 /* -*- 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.animation;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mozilla.gecko.AppConstants.Versions;
+
+import android.os.Handler;
 import android.support.v4.view.ViewCompat;
-import android.os.Build;
-import android.os.Handler;
 import android.view.Choreographer;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
-import java.util.ArrayList;
-import java.util.List;
-
 public class PropertyAnimator implements Runnable {
     private static final String LOGTAG = "GeckoPropertyAnimator";
 
     public static enum Property {
         ALPHA,
         TRANSLATION_X,
         TRANSLATION_Y,
         SCROLL_X,
@@ -158,17 +159,17 @@ public class PropertyAnimator implements
         } else {
             treeObserver = null;
         }
 
         // Try to start animation after any on-going layout round
         // in the current view tree. OnPreDrawListener seems broken
         // on pre-Honeycomb devices, start animation immediatelly
         // in this case.
-        if (Build.VERSION.SDK_INT >= 11 && treeObserver != null && treeObserver.isAlive()) {
+        if (Versions.feature11Plus && treeObserver != null && treeObserver.isAlive()) {
             treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                 @Override
                 public boolean onPreDraw() {
                     if (treeObserver.isAlive()) {
                         treeObserver.removeOnPreDrawListener(this);
                     }
 
                     mFramePoster.postFirstAnimationFrame();
@@ -181,35 +182,35 @@ public class PropertyAnimator implements
 
         if (mListeners != null) {
             for (PropertyAnimationListener listener : mListeners) {
                 listener.onPropertyAnimationStart();
             }
         }
     }
 
-
     /**
      * Stop the animation, optionally snapping to the end position.
      * onPropertyAnimationEnd is only called when snapping to the end position.
      */
     public void stop(boolean snapToEndPosition) {
         mFramePoster.cancelAnimationFrame();
 
         // Make sure to snap to the end position.
         for (ElementHolder element : mElementsList) {
             if (snapToEndPosition)
                 invalidate(element, element.to);
 
             ViewCompat.setHasTransientState(element.view, false);
 
-            if (shouldEnableHardwareLayer(element))
+            if (shouldEnableHardwareLayer(element)) {
                 element.view.setLayerType(View.LAYER_TYPE_NONE, null);
-            else
+            } else {
                 element.view.setDrawingCacheEnabled(false);
+            }
         }
 
         mElementsList.clear();
 
         if (mListeners != null) {
             if (snapToEndPosition) {
                 for (PropertyAnimationListener listener : mListeners) {
                     listener.onPropertyAnimationEnd();
@@ -221,29 +222,33 @@ public class PropertyAnimator implements
         }
     }
 
     public void stop() {
         stop(true);
     }
 
     private boolean shouldEnableHardwareLayer(ElementHolder element) {
-        if (!mUseHardwareLayer)
+        if (Versions.preHC) {
             return false;
+        }
 
-        if (Build.VERSION.SDK_INT < 11)
+        if (!mUseHardwareLayer) {
             return false;
+        }
 
-        if (!(element.view instanceof ViewGroup))
+        if (!(element.view instanceof ViewGroup)) {
             return false;
+        }
 
         if (element.property == Property.ALPHA ||
             element.property == Property.TRANSLATION_Y ||
-            element.property == Property.TRANSLATION_X)
+            element.property == Property.TRANSLATION_X) {
             return true;
+        }
 
         return false;
     }
 
     private void invalidate(final ElementHolder element, final float delta) {
         final View view = element.view;
 
         // check to see if the view was detached between the check above and this code
@@ -264,33 +269,34 @@ public class PropertyAnimator implements
         else if (element.property == Property.WIDTH)
             element.proxy.setWidth((int) delta);
         else if (element.property == Property.HEIGHT)
             element.proxy.setHeight((int) delta);
     }
 
     private static abstract class FramePoster {
         public static FramePoster create(Runnable r) {
-            if (Build.VERSION.SDK_INT >= 16)
+            if (Versions.feature16Plus) {
                 return new FramePosterPostJB(r);
-            else
-                return new FramePosterPreJB(r);
+            }
+
+            return new FramePosterPreJB(r);
         }
 
         public abstract void postFirstAnimationFrame();
         public abstract void postNextAnimationFrame();
         public abstract void cancelAnimationFrame();
     }
 
     private static class FramePosterPreJB extends FramePoster {
         // Default refresh rate in ms.
         private static final int INTERVAL = 10;
 
-        private Handler mHandler;
-        private Runnable mRunnable;
+        private final Handler mHandler;
+        private final Runnable mRunnable;
 
         public FramePosterPreJB(Runnable r) {
             mHandler = new Handler();
             mRunnable = r;
         }
 
         @Override
         public void postFirstAnimationFrame() {
@@ -304,18 +310,18 @@ public class PropertyAnimator implements
 
         @Override
         public void cancelAnimationFrame() {
             mHandler.removeCallbacks(mRunnable);
         }
     }
 
     private static class FramePosterPostJB extends FramePoster {
-        private Choreographer mChoreographer;
-        private Choreographer.FrameCallback mCallback;
+        private final Choreographer mChoreographer;
+        private final Choreographer.FrameCallback mCallback;
 
         public FramePosterPostJB(final Runnable r) {
             mChoreographer = Choreographer.getInstance();
 
             mCallback = new Choreographer.FrameCallback() {
                 @Override
                 public void doFrame(long frameTimeNanos) {
                     r.run();
--- a/mobile/android/base/db/AbstractTransactionalProvider.java
+++ b/mobile/android/base/db/AbstractTransactionalProvider.java
@@ -1,20 +1,21 @@
 /* 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.db;
 
+import org.mozilla.gecko.AppConstants.Versions;
+
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
 /**
  * This abstract class exists to capture some of the transaction-handling
  * commonalities in Fennec's DB layer.
  *
  * In particular, this abstracts DB access, batching, and a particular
@@ -89,17 +90,17 @@ public abstract class AbstractTransactio
     final ThreadLocal<Boolean> isInBatchOperation = new ThreadLocal<Boolean>();
 
     /**
      * Return true if OS version and database parallelism support indicates
      * that this provider should bundle writes into transactions.
      */
     @SuppressWarnings("static-method")
     protected boolean shouldUseTransactions() {
-        return Build.VERSION.SDK_INT >= 11;
+        return Versions.feature11Plus;
     }
 
     private boolean isInBatch() {
         final Boolean isInBatch = isInBatchOperation.get();
         if (isInBatch == null) {
             return false;
         }
         return isInBatch.booleanValue();
--- a/mobile/android/base/db/PerProfileDatabaseProvider.java
+++ b/mobile/android/base/db/PerProfileDatabaseProvider.java
@@ -1,19 +1,19 @@
 /* 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.db;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
 
 import android.content.Context;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
 
 /**
  * Abstract class containing methods needed to make a SQLite-based content
  * provider with a database helper of type T, where one database helper is
  * held per profile.
  */
 public abstract class PerProfileDatabaseProvider<T extends SQLiteOpenHelper> extends AbstractPerProfileDatabaseProvider {
     private PerProfileDatabases<T> databases;
@@ -37,17 +37,17 @@ public abstract class PerProfileDatabase
     @Override
     public boolean onCreate() {
         synchronized (this) {
             databases = new PerProfileDatabases<T>(
                 getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
                     @Override
                     public T makeDatabaseHelper(Context context, String databasePath) {
                         final T helper = createDatabaseHelper(context, databasePath);
-                        if (Build.VERSION.SDK_INT >= 16) {
+                        if (Versions.feature16Plus) {
                             helper.setWriteAheadLoggingEnabled(true);
                         }
                         return helper;
                     }
                 });
         }
 
         return true;
--- a/mobile/android/base/db/SharedBrowserDatabaseProvider.java
+++ b/mobile/android/base/db/SharedBrowserDatabaseProvider.java
@@ -1,23 +1,23 @@
 /* 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.db;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
-import android.os.Build;
 import android.util.Log;
 
 /**
  * A ContentProvider subclass that provides per-profile browser.db access
  * that can be safely shared between multiple providers.
  *
  * If multiple ContentProvider classes wish to share a database, it's
  * vitally important that they use the same SQLiteOpenHelpers for access.
@@ -45,17 +45,17 @@ public abstract class SharedBrowserDatab
             if (databases != null) {
                 return true;
             }
 
             final DatabaseHelperFactory<BrowserDatabaseHelper> helperFactory = new DatabaseHelperFactory<BrowserDatabaseHelper>() {
                 @Override
                 public BrowserDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
                     final BrowserDatabaseHelper helper = new BrowserDatabaseHelper(context, databasePath);
-                    if (Build.VERSION.SDK_INT >= 16) {
+                    if (Versions.feature16Plus) {
                         helper.setWriteAheadLoggingEnabled(true);
                     }
                     return helper;
                 }
             };
 
             databases = new PerProfileDatabases<BrowserDatabaseHelper>(getContext(), BrowserDatabaseHelper.DATABASE_NAME, helperFactory);
         }
--- a/mobile/android/base/db/TabsProvider.java
+++ b/mobile/android/base/db/TabsProvider.java
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.db;
 
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserContract.Clients;
 import org.mozilla.gecko.db.BrowserContract.Tabs;
 
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriMatcher;
 import android.database.Cursor;
@@ -166,17 +167,17 @@ public class TabsProvider extends PerPro
 
         @Override
         public void onOpen(SQLiteDatabase db) {
             debug("Opening tabs.db: " + db.getPath());
             db.rawQuery("PRAGMA synchronous=OFF", null).close();
 
             if (shouldUseTransactions()) {
                 // Modern Android allows WAL to be enabled through a mode flag.
-                if (Build.VERSION.SDK_INT < 16) {
+                if (Versions.preJB) {
                     db.enableWriteAheadLogging();
                 }
                 db.setLockingEnabled(false);
                 return;
             }
 
             // If we're not using transactions (in particular, prior to
             // Honeycomb), then we can do some lesser optimizations.
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -1,32 +1,31 @@
 /* -*- 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.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.EventDispatcher;
 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.EventDispatcher;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import org.json.JSONObject;
-
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.os.Build;
 import android.util.FloatMath;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -289,17 +288,17 @@ class JavaPanZoomController
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     /** This function MUST be called on the UI thread */
     @Override
     public boolean onKeyEvent(KeyEvent event) {
-        if (Build.VERSION.SDK_INT <= 11) {
+        if (Versions.preHCMR1) {
             return false;
         }
 
         if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
             && event.getAction() == KeyEvent.ACTION_DOWN) {
 
             switch (event.getKeyCode()) {
             case KeyEvent.KEYCODE_ZOOM_IN:
@@ -309,17 +308,17 @@ class JavaPanZoomController
             }
         }
         return false;
     }
 
     /** This function MUST be called on the UI thread */
     @Override
     public boolean onMotionEvent(MotionEvent event) {
-        if (Build.VERSION.SDK_INT <= 11) {
+        if (Versions.preHCMR1) {
             return false;
         }
 
         switch (event.getSource() & InputDevice.SOURCE_CLASS_MASK) {
         case InputDevice.SOURCE_CLASS_POINTER:
             switch (event.getAction() & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_SCROLL: return handlePointerScroll(event);
             }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 
 import org.mozilla.gecko.AndroidGamepadManager;
+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.TouchEventInterceptor;
@@ -25,17 +26,16 @@ 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;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
-import android.os.Build;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
@@ -105,17 +105,17 @@ public class LayerView extends FrameLayo
         super(context, attrs);
 
         mGLController = GLController.getInstance(this);
         mPaintState = PAINT_START;
         mBackgroundColor = Color.WHITE;
         mFullScreenState = FullScreenState.NONE;
 
         mTouchInterceptors = new ArrayList<TouchEventInterceptor>();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        if (Versions.feature14Plus) {
             mOverscroll = new OverscrollEdgeEffect(this);
         } else {
             mOverscroll = null;
         }
         Tabs.registerOnTabsChangedListener(this);
     }
 
     public LayerView(Context context) {
--- a/mobile/android/base/gfx/OverscrollEdgeEffect.java
+++ b/mobile/android/base/gfx/OverscrollEdgeEffect.java
@@ -1,20 +1,21 @@
 /* -*- 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.AppConstants.Versions;
+
 import android.content.Context;
 import android.graphics.Canvas;
-import android.os.Build;
+import android.view.View;
 import android.widget.EdgeEffect;
-import android.view.View;
 
 
 public class OverscrollEdgeEffect implements Overscroll {
     // Used to index particular edges in the edges array
     private static final int TOP = 0;
     private static final int BOTTOM = 1;
     private static final int LEFT = 2;
     private static final int RIGHT = 3;
@@ -52,17 +53,17 @@ public class OverscrollEdgeEffect implem
                 return mEdges[LEFT];
             } else {
                 return mEdges[RIGHT];
             }
         }
     }
 
     private void invalidate() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+        if (Versions.feature16Plus) {
             mView.postInvalidateOnAnimation();
         } else {
             mView.postInvalidateDelayed(10);
         }
     }
 
     public void setVelocity(final float velocity, final Axis axis) {
         final EdgeEffect edge = getEdgeForAxisAndSide(axis, velocity);
@@ -113,17 +114,17 @@ public class OverscrollEdgeEffect implem
         }
 
         // If the edge effect is animating off screen, invalidate.
         if (invalidate) {
             invalidate();
         }
     }
 
-    public boolean draw(final EdgeEffect edge, final Canvas canvas, final float translateX, final float translateY, final float rotation) {
+    private static boolean draw(final EdgeEffect edge, final Canvas canvas, final float translateX, final float translateY, final float rotation) {
         final int state = canvas.save();
         canvas.translate(translateX, translateY);
         canvas.rotate(rotation);
         boolean invalidate = edge.draw(canvas);
         canvas.restoreToCount(state);
 
         return invalidate;
     }
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -1,44 +1,40 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
-import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.Property;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.EllipsisTextView;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.text.Html;
-import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 public class HomeBanner extends LinearLayout
                         implements GeckoEventListener {
     private static final String LOGTAG = "GeckoHomeBanner";
 
     // Used for tracking scroll length
     private float mTouchY = -1;
 
@@ -128,17 +124,17 @@ public class HomeBanner extends LinearLa
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "HomeBanner:Data");
     }
 
     @Override
     public void setVisibility(int visibility) {
         // On pre-Honeycomb devices, setting the visibility to GONE won't actually
         // hide the view unless we clear animations first.
-        if (Build.VERSION.SDK_INT < 11 && visibility == View.GONE) {
+        if (Versions.preHC && visibility == View.GONE) {
             clearAnimation();
         }
 
         super.setVisibility(visibility);
     }
 
     public void setScrollingPages(boolean scrollingPages) {
         mScrollingPages = scrollingPages;
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -4,28 +4,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -192,17 +192,17 @@ public class HomePager extends ViewPager
         mInitialPanelId = panelId;
 
         // Update the home banner message each time the HomePager is loaded.
         if (mHomeBanner != null) {
             mHomeBanner.update();
         }
 
         // Only animate on post-HC devices, when a non-null animator is given
-        final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
+        final boolean shouldAnimate = Versions.feature11Plus && animator != null;
 
         final HomeAdapter adapter = new HomeAdapter(mContext, fm);
         adapter.setOnAddPanelListener(mAddPanelListener);
         adapter.setCanLoadHint(!shouldAnimate);
         setAdapter(adapter);
 
         // Don't show the tabs strip until we have the
         // list of panels in place.
--- a/mobile/android/base/menu/GeckoMenuInflater.java
+++ b/mobile/android/base/menu/GeckoMenuInflater.java
@@ -1,43 +1,40 @@
 /* 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.menu;
 
+import java.io.IOException;
+
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Xml;
 import android.view.InflateException;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.SubMenu;
 
-import java.io.IOException;
-
-public class GeckoMenuInflater extends MenuInflater { 
-    private static final String LOGTAG = "GeckoMenuInflater";
-
+public class GeckoMenuInflater extends MenuInflater {
     private static final String TAG_MENU = "menu";
     private static final String TAG_ITEM = "item";
     private static final int NO_ID = 0;
 
-    private Context mContext;
+    private final Context mContext;
 
-    // Private class to hold the parsed menu item. 
+    // Private class to hold the parsed menu item.
     private class ParsedItem {
         public int id;
         public int order;
         public CharSequence title;
         public int iconRes;
         public boolean checkable;
         public boolean checked;
         public boolean visible;
@@ -68,26 +65,26 @@ public class GeckoMenuInflater extends M
         } catch (IOException e) {
             throw new InflateException("Error inflating menu XML", e);
         } finally {
             if (parser != null)
                 parser.close();
         }
     }
 
-    private void parseMenu(XmlResourceParser parser, AttributeSet attrs, Menu menu) 
+    private void parseMenu(XmlResourceParser parser, AttributeSet attrs, Menu menu)
                            throws XmlPullParserException, IOException {
         ParsedItem item = null;
-   
+
         String tag;
         int eventType = parser.getEventType();
 
         do {
             tag = parser.getName();
-    
+
             switch (eventType) {
                 case XmlPullParser.START_TAG:
                     if (tag.equals(TAG_ITEM)) {
                         // Parse the menu item.
                         item = new ParsedItem();
                         parseItem(item, attrs);
                      } else if (tag.equals(TAG_MENU)) {
                         if (item != null) {
@@ -131,25 +128,27 @@ public class GeckoMenuInflater extends M
         item.title = a.getText(R.styleable.MenuItem_android_title);
         item.iconRes = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
         item.checkable = a.getBoolean(R.styleable.MenuItem_android_checkable, false);
         item.checked = a.getBoolean(R.styleable.MenuItem_android_checked, false);
         item.visible = a.getBoolean(R.styleable.MenuItem_android_visible, true);
         item.enabled = a.getBoolean(R.styleable.MenuItem_android_enabled, true);
         item.hasSubMenu = false;
 
-        if (Build.VERSION.SDK_INT >= 11)
+        if (Versions.feature11Plus) {
             item.showAsAction = a.getInt(R.styleable.MenuItem_android_showAsAction, 0);
+        }
 
         a.recycle();
     }
-        
+
     public void setValues(ParsedItem item, MenuItem menuItem) {
         menuItem.setChecked(item.checked)
                 .setVisible(item.visible)
                 .setEnabled(item.enabled)
                 .setCheckable(item.checkable)
                 .setIcon(item.iconRes);
 
-        if (Build.VERSION.SDK_INT >= 11)
+        if (Versions.feature11Plus) {
             menuItem.setShowAsAction(item.showAsAction);
+        }
     }
 }
--- a/mobile/android/base/menu/GeckoMenuItem.java
+++ b/mobile/android/base/menu/GeckoMenuItem.java
@@ -1,59 +1,57 @@
 /* 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.menu;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.view.ActionProvider;
 import android.view.ContextMenu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 
 public class GeckoMenuItem implements MenuItem {
-    private static final String LOGTAG = "GeckoMenuItem";
-
     public static final int SHOW_AS_ACTION_NEVER = 0;
     public static final int SHOW_AS_ACTION_IF_ROOM = 1;
     public static final int SHOW_AS_ACTION_ALWAYS = 2;
 
-    // A View that can show a MenuItem should be able to initialize from 
+    // A View that can show a MenuItem should be able to initialize from
     // the properties of the MenuItem.
     public static interface Layout {
         public void initialize(GeckoMenuItem item);
         public void setShowIcon(boolean show);
     }
 
     public static interface OnShowAsActionChangedListener {
         public boolean hasActionItemBar();
         public void onShowAsActionChanged(GeckoMenuItem item);
     }
 
-    private int mId;
-    private int mOrder;
+    private final int mId;
+    private final int mOrder;
     private View mActionView;
     private int mActionEnum;
     private CharSequence mTitle;
     private CharSequence mTitleCondensed;
     private boolean mCheckable;
     private boolean mChecked;
     private boolean mVisible = true;
     private boolean mEnabled = true;
     private Drawable mIcon;
     private int mIconRes;
     private GeckoActionProvider mActionProvider;
-    private GeckoMenu mMenu;
+    private final GeckoMenu mMenu;
     private GeckoSubMenu mSubMenu;
     private MenuItem.OnMenuItemClickListener mMenuItemClickListener = null;
     private OnShowAsActionChangedListener mShowAsActionChangedListener;
 
     public GeckoMenuItem(GeckoMenu menu, int id, int order, int titleRes) {
         mMenu = menu;
         mId = id;
         mOrder = order;
@@ -73,17 +71,17 @@ public class GeckoMenuItem implements Me
     }
 
     @Override
     public boolean expandActionView() {
         return false;
     }
 
     public boolean hasActionProvider() {
-        if (Build.VERSION.SDK_INT < 14) {
+        if (Versions.preICS) {
             return false;
         }
 
         return (mActionProvider != null);
     }
 
     public int getActionEnum() {
         return mActionEnum;
--- a/mobile/android/base/menu/MenuItemActionView.java
+++ b/mobile/android/base/menu/MenuItemActionView.java
@@ -3,57 +3,55 @@
  * 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.menu;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
-import android.os.Build;
 
 public class MenuItemActionView extends LinearLayout
                                 implements GeckoMenuItem.Layout,
                                            View.OnClickListener {
-    private static final String LOGTAG = "GeckoMenuItemActionView";
-
-    private MenuItemDefault mMenuItem;
-    private MenuItemActionBar mMenuButton;
-    private List<ImageButton> mActionButtons;
-    private List<View.OnClickListener> mActionButtonListeners = new ArrayList<View.OnClickListener>();
+    private final MenuItemDefault mMenuItem;
+    private final MenuItemActionBar mMenuButton;
+    private final List<ImageButton> mActionButtons;
+    private final List<View.OnClickListener> mActionButtonListeners = new ArrayList<View.OnClickListener>();
 
     public MenuItemActionView(Context context) {
         this(context, null);
     }
 
     public MenuItemActionView(Context context, AttributeSet attrs) {
         this(context, attrs, R.attr.menuItemActionViewStyle);
     }
 
     @TargetApi(14)
     public MenuItemActionView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs);
 
         // Set these explicitly, since setting a style isn't supported for LinearLayouts until V11.
-        if (Build.VERSION.SDK_INT >= 11) {
+        if (Versions.feature11Plus) {
             setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
             setDividerDrawable(getResources().getDrawable(R.drawable.divider_vertical));
         }
 
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             setDividerPadding(0);
         }
 
         LayoutInflater.from(context).inflate(R.layout.menu_item_action_view, this);
         mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
         mMenuButton = (MenuItemActionBar) findViewById(R.id.menu_item_button);
         mActionButtons = new ArrayList<ImageButton>();
     }
@@ -73,18 +71,19 @@ public class MenuItemActionView extends 
             mMenuButton.setVisibility(View.GONE);
         }
 
         super.onLayout(changed, left, top, right, bottom);
     }
 
     @Override
     public void initialize(GeckoMenuItem item) {
-        if (item == null)
+        if (item == null) {
             return;
+        }
 
         mMenuItem.initialize(item);
         mMenuButton.initialize(item);
         setEnabled(item.isEnabled());
     }
 
     @Override
     public void setEnabled(boolean enabled) {
--- a/mobile/android/base/menu/MenuPanel.java
+++ b/mobile/android/base/menu/MenuPanel.java
@@ -1,19 +1,19 @@
 /* -*- 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.menu;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
 /**
  * The outer container for the custom menu. On phones with h/w menu button,
@@ -36,13 +36,15 @@ public class MenuPanel extends LinearLay
         DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
         int restrictedHeightSpec = MeasureSpec.makeMeasureSpec((int) (0.75 * metrics.heightPixels), MeasureSpec.AT_MOST);
 
         super.onMeasure(widthMeasureSpec, restrictedHeightSpec);
     }
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent (AccessibilityEvent event) {
-        if (Build.VERSION.SDK_INT >= 14) // Build.VERSION_CODES.ICE_CREAM_SANDWICH
+        if (Versions.feature14Plus) {
             onPopulateAccessibilityEvent(event);
+        }
+
         return true;
     }
 }
--- a/mobile/android/base/preferences/GeckoPreferenceFragment.java
+++ b/mobile/android/base/preferences/GeckoPreferenceFragment.java
@@ -2,28 +2,28 @@
  * 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.preferences;
 
 import java.util.Locale;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 
@@ -103,27 +103,27 @@ public class GeckoPreferenceFragment ext
     private void updateTitle() {
         final String newTitle = getTitle();
         if (newTitle == null) {
             Log.d(LOGTAG, "No new title to show.");
             return;
         }
 
         final PreferenceActivity activity = (PreferenceActivity) getActivity();
-        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && activity.isMultiPane()) {
+        if (Versions.feature11Plus && activity.isMultiPane()) {
             // In a multi-pane activity, the title is "Settings", and the action
             // bar is along the top of the screen. We don't want to change those.
             activity.showBreadCrumbs(newTitle, newTitle);
             return;
         }
 
         Log.v(LOGTAG, "Setting activity title to " + newTitle);
         activity.setTitle(newTitle);
 
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             final ActionBar actionBar = activity.getActionBar();
             if (actionBar != null) {
                 actionBar.setTitle(newTitle);
             }
         }
     }
 
     @Override
@@ -170,17 +170,17 @@ public class GeckoPreferenceFragment ext
             final String packageName = activity.getPackageName();
             resid = resources.getIdentifier(resourceName, "xml", packageName);
         }
 
         if (resid == 0) {
             // The resource was invalid. Use the default resource.
             Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
 
-            boolean isMultiPane = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) &&
+            boolean isMultiPane = Versions.feature11Plus &&
                                   ((PreferenceActivity) activity).isMultiPane();
             resid = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
         }
 
         return resid;
     }
 
     @Override
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko.preferences;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.DataReportingNotification;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoActivityStatus;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoEvent;
@@ -47,17 +48,16 @@ import android.app.FragmentManager;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Configuration;
-import android.os.Build;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceActivity;
@@ -148,17 +148,17 @@ OnSharedPreferenceChangeListener
 
     private void finishChoosingTransition() {
         finish();
         if (NO_TRANSITIONS) {
             overridePendingTransition(0, 0);
         }
     }
     private void updateActionBarTitle(int title) {
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             final String newTitle = getString(title);
             if (newTitle != null) {
                 Log.v(LOGTAG, "Setting action bar title to " + newTitle);
 
                 final ActionBar actionBar = getActionBar();
                 if (actionBar != null) {
                     actionBar.setTitle(newTitle);
                 }
@@ -185,17 +185,17 @@ OnSharedPreferenceChangeListener
         final String newTitle = getString(title);
         showBreadCrumbs(newTitle, newTitle);
     }
 
     private void updateTitleForPrefsResource(int res) {
         // If we're a multi-pane view, the activity title is really
         // the header bar above the fragment.
         // Find out which fragment we're showing, and use that.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && isMultiPane()) {
+        if (Versions.feature11Plus && isMultiPane()) {
             int title = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, -1);
             if (res == R.xml.preferences) {
                 // This should only occur when res == R.xml.preferences,
                 // but showing "Settings" is better than crashing or showing
                 // "Fennec".
                 updateActionBarTitle(R.string.settings_title);
             }
 
@@ -222,17 +222,17 @@ OnSharedPreferenceChangeListener
     }
 
     private void onLocaleChanged(Locale newLocale) {
         Log.d(LOGTAG, "onLocaleChanged: " + newLocale);
 
         BrowserLocaleManager.getInstance().updateConfiguration(getApplicationContext(), newLocale);
         this.lastLocale = newLocale;
 
-        if (Build.VERSION.SDK_INT >= 11 && isMultiPane()) {
+        if (Versions.feature11Plus && isMultiPane()) {
             // This takes care of the left pane.
             invalidateHeaders();
 
             // Detach and reattach the current prefs pane so that it
             // reflects the new locale.
             final FragmentManager fragmentManager = getFragmentManager();
             int id = getResources().getIdentifier("android:id/prefs", null, null);
             final Fragment current = fragmentManager.findFragmentById(id);
@@ -296,49 +296,49 @@ OnSharedPreferenceChangeListener
         // See also the workaround below for Bug 1015209.
         localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
 
         // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
         // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
         // (or set it) before super.onCreate() is called so Android can display
         // the correct Fragment resource.
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.feature11Plus) {
             if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
                 // Set up the default fragment if there is no explicit fragment to show.
                 setupTopLevelFragmentIntent();
 
                 // This is the default header, because it's the first one.
                 // I know, this is an affront to all human decency. And yet.
                 updateTitle(getString(R.string.pref_header_customize));
             }
 
             if (onIsMultiPane()) {
                 // So that Android doesn't put the fragment title (or nothing at
                 // all) in the action bar.
                 updateActionBarTitle(R.string.settings_title);
 
-                if (Build.VERSION.SDK_INT < 13) {
+                if (android.os.Build.VERSION.SDK_INT < 13) {
                     // Affected by Bug 1015209 -- no detach/attach.
                     // If we try rejigging fragments, we'll crash, so don't
                     // enable locale switching at all.
                     localeSwitchingIsEnabled = false;
                 }
             }
         }
 
         super.onCreate(savedInstanceState);
 
         // Use setResourceToOpen to specify these extras.
         Bundle intentExtras = getIntent().getExtras();
 
         // For versions of Android lower than Honeycomb, use xml resources instead of
         // Fragments because of an Android bug in ActionBar (described in bug 866352 and
         // fixed in bug 833625).
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.preHC) {
             // Write prefs to our custom GeckoSharedPrefs file.
             getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME);
 
             int res = 0;
             if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) {
                 // Fetch resource id from intent.
                 String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES);
                 if (resourceName != null) {
@@ -376,17 +376,17 @@ OnSharedPreferenceChangeListener
                 if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) {
                     final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem;
                     return longClickListener.onLongClick(view);
                 }
                 return false;
             }
         });
 
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (Versions.feature14Plus) {
             final ActionBar actionBar = getActionBar();
             if (actionBar != null) {
                 actionBar.setHomeButtonEnabled(true);
             }
         }
 
         // N.B., if we ever need to redisplay the locale selection UI without
         // just finishing and recreating the activity, right here we'll need to
@@ -450,17 +450,17 @@ OnSharedPreferenceChangeListener
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         if (!hasFocus || mInitialized)
             return;
 
         mInitialized = true;
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.preHC) {
             PreferenceScreen screen = getPreferenceScreen();
             mPrefsRequestId = setupPreferences(screen);
         }
     }
 
     @Override
     public void onBackPressed() {
         super.onBackPressed();
@@ -478,17 +478,17 @@ OnSharedPreferenceChangeListener
         if (mPrefsRequestId > 0) {
             PrefsHelper.removeObserver(mPrefsRequestId);
         }
     }
 
     @Override
     public void onPause() {
         // Symmetric with onResume.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.feature11Plus) {
             if (isMultiPane()) {
                 SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
                 prefs.unregisterOnSharedPreferenceChangeListener(this);
             }
         }
 
         super.onPause();
 
@@ -500,17 +500,17 @@ OnSharedPreferenceChangeListener
     @Override
     public void onResume() {
         super.onResume();
 
         if (getApplication() instanceof GeckoApplication) {
             ((GeckoApplication) getApplication()).onActivityResume(this);
         }
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.feature11Plus) {
             // Watch prefs, otherwise we don't reliably get told when they change.
             // See documentation for onSharedPreferenceChange for more.
             // Inexplicably only needed on tablet.
             if (isMultiPane()) {
                 SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
                 prefs.registerOnSharedPreferenceChangeListener(this);
             }
         }
@@ -1228,17 +1228,17 @@ OnSharedPreferenceChangeListener
                     }
                 }
             }
 
             @Override
             public void prefValue(String prefName, final boolean value) {
                 final Preference pref = getField(prefName);
                 final CheckBoxPrefSetter prefSetter;
-                if (Build.VERSION.SDK_INT < 14) {
+                if (Versions.preICS) {
                     prefSetter = new CheckBoxPrefSetter();
                 } else {
                     prefSetter = new TwoStatePrefSetter();
                 }
                 ThreadUtils.postToUiThread(new Runnable() {
                     public void run() {
                         prefSetter.setBooleanPref(pref, value);
                     }
@@ -1278,17 +1278,17 @@ OnSharedPreferenceChangeListener
                 }
             }
 
             @Override
             public void prefValue(String prefName, final int value) {
                 final Preference pref = getField(prefName);
                 final CheckBoxPrefSetter prefSetter;
                 if (PREFS_GEO_REPORTING.equals(prefName)) {
-                    if (Build.VERSION.SDK_INT < 14) {
+                    if (Versions.preICS) {
                         prefSetter = new CheckBoxPrefSetter();
                     } else {
                         prefSetter = new TwoStatePrefSetter();
                     }
                     ThreadUtils.postToUiThread(new Runnable() {
                         @Override
                         public void run() {
                             prefSetter.setBooleanPref(pref, value == 1);
@@ -1333,17 +1333,17 @@ OnSharedPreferenceChangeListener
     public static void setResourceToOpen(final Intent intent, final String resource) {
         if (intent == null) {
             throw new IllegalArgumentException("intent must not be null");
         }
         if (resource == null) {
             return;
         }
 
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        if (Versions.preHC) {
             intent.putExtra("resource", resource);
         } else {
             intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
 
             Bundle fragmentArgs = new Bundle();
             fragmentArgs.putString("resource", resource);
             intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
         }
--- a/mobile/android/base/prompts/IconGridInput.java
+++ b/mobile/android/base/prompts/IconGridInput.java
@@ -5,24 +5,24 @@
 
 package org.mozilla.gecko.prompts;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
-import android.os.Build;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
@@ -80,17 +80,17 @@ public class IconGridInput extends Promp
             }
         }
 
         view.setNumColumns(Math.min(items.size(), maxColumns));
         view.setOnItemClickListener(this);
         // Despite what the docs say, setItemChecked was not moved into the AbsListView class until sometime between
         // Android 2.3.7 and Android 4.0.3. For other versions the item won't be visually highlighted, BUT we really only
         // mSelected will still be set so that we default to its behavior.
-        if (Build.VERSION.SDK_INT >= 11 && mSelected > -1) {
+        if (Versions.feature11Plus && mSelected > -1) {
             view.setItemChecked(mSelected, true);
         }
 
         mAdapter = new IconGridAdapter(context, -1, items);
         view.setAdapter(mAdapter);
         mView = view;
         return mView;
     }
--- a/mobile/android/base/prompts/PromptInput.java
+++ b/mobile/android/base/prompts/PromptInput.java
@@ -5,23 +5,23 @@
 
 package org.mozilla.gecko.prompts;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.widget.AllCapsTextView;
 import org.mozilla.gecko.widget.DateTimePicker;
 import org.mozilla.gecko.widget.FloatingHintEditText;
 
 import android.content.Context;
 import android.content.res.Configuration;
-import android.os.Build;
 import android.text.Html;
 import android.text.InputType;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.view.inputmethod.InputMethodManager;
@@ -224,17 +224,17 @@ public class PromptInput {
         }
 
         private static String formatDateString(String dateFormat, Calendar calendar) {
             return new SimpleDateFormat(dateFormat).format(calendar.getTime());
         }
 
         @Override
         public Object getValue() {
-            if (Build.VERSION.SDK_INT < 11 && mType.equals("date")) {
+            if (Versions.preHC && mType.equals("date")) {
                 // We can't use the custom DateTimePicker with a sdk older than 11.
                 // Fallback on the native DatePicker.
                 DatePicker dp = (DatePicker)mView;
                 GregorianCalendar calendar =
                     new GregorianCalendar(dp.getYear(),dp.getMonth(),dp.getDayOfMonth());
                 return formatDateString("yyyy-MM-dd",calendar);
             } else if (mType.equals("time")) {
                 TimePicker tp = (TimePicker)mView;
@@ -273,30 +273,31 @@ public class PromptInput {
 
         public MenulistInput(JSONObject obj) {
             super(obj);
             mListitems = Prompt.getStringArray(obj, "values");
             mSelected = obj.optInt("selected");
         }
 
         public View getView(final Context context) throws UnsupportedOperationException {
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            if (Versions.preHC) {
                 spinner = new Spinner(context);
             } else {
                 spinner = new Spinner(context, Spinner.MODE_DIALOG);
             }
             try {
                 if (mListitems.length > 0) {
                     ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, mListitems);
                     adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
 
                     spinner.setAdapter(adapter);
                     spinner.setSelection(mSelected);
                 }
-            } catch(Exception ex) { }
+            } catch (Exception ex) {
+            }
 
             if (!TextUtils.isEmpty(mLabel)) {
                 LinearLayout container = new LinearLayout(context);
                 container.setOrientation(LinearLayout.VERTICAL);
 
                 textView = new AllCapsTextView(context, null);
                 textView.setText(mLabel);
                 container.addView(textView);
--- a/mobile/android/base/prompts/TabInput.java
+++ b/mobile/android/base/prompts/TabInput.java
@@ -1,39 +1,33 @@
 /* -*- 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.prompts;
 
+import java.util.LinkedHashMap;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.json.JSONException;
-
 import android.content.Context;
-import android.os.Build;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
-import android.view.LayoutInflater;
 import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
 import android.widget.ListView;
 import android.widget.TabHost;
-import android.widget.TabWidget;
 import android.widget.TextView;
 
-import java.util.LinkedHashMap;
-
 public class TabInput extends PromptInput implements AdapterView.OnItemClickListener {
     public static final String INPUT_TYPE = "tabs";
     public static final String LOGTAG = "GeckoTabInput";
 
     /* Keeping the order of this in sync with the JSON is important. */
     final private LinkedHashMap<String, PromptListItem[]> mTabs;
 
     private TabHost mHost;
@@ -71,17 +65,17 @@ public class TabInput extends PromptInpu
                     listView.setCacheColorHint(0);
                     listView.setOnItemClickListener(TabInput.this);
                     listView.setAdapter(adapter);
                     return listView;
                 }
             });
 
             // On older android versions, we use a custom style for the tabs.
-            if (Build.VERSION.SDK_INT < 11) {
+            if (Versions.preHC) {
                 TextView textview = (TextView) inflater.inflate(R.layout.tab_prompt_tab, null);
                 textview.setText(title);
                 spec.setIndicator(textview);
             } else {
                 spec.setIndicator(title);
             }
             mHost.addTab(spec);
         }
--- a/mobile/android/base/sync/receivers/SyncAccountDeletedService.java
+++ b/mobile/android/base/sync/receivers/SyncAccountDeletedService.java
@@ -9,16 +9,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.Sync11Configuration;
 import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.config.AccountPickler;
 import org.mozilla.gecko.sync.config.ClientRecordTerminator;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
+import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
 
 import android.accounts.AccountManager;
 import android.app.IntentService;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -72,16 +73,20 @@ public class SyncAccountDeletedService e
       Logger.warn(LOG_TAG, "Got exception fetching account parameters from intent data; not deleting client record.");
       return;
     }
 
     // Bug 770785: delete the Account's client record.
     Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
         "deleting client record from server.");
     deleteClientRecord(context, accountName, params.password, params.serverURL);
+
+    // Delete client database and non-local tabs.
+    Logger.info(LOG_TAG, "Deleting the entire clients database and non-local tabs");
+    FennecTabsRepository.deleteNonLocalClientsAndTabs(context);
   }
 
   public static void deletePickle(final Context context) {
     try {
       AccountPickler.deletePickle(context, Constants.ACCOUNT_PICKLE_FILENAME);
     } catch (Exception e) {
       // This should never happen, but we really don't want to die in a background thread.
       Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
--- a/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
+++ b/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
@@ -28,16 +28,18 @@ import org.mozilla.gecko.sync.repositori
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
 public class FennecTabsRepository extends Repository {
+  private static final String LOG_TAG = "FennecTabsRepository";
+
   protected final ClientsDataDelegate clientsDataDelegate;
 
   public FennecTabsRepository(ClientsDataDelegate clientsDataDelegate) {
     this.clientsDataDelegate = clientsDataDelegate;
   }
 
   /**
    * Note that -- unlike most repositories -- this will only fetch Fennec's tabs,
@@ -346,9 +348,40 @@ public class FennecTabsRepository extend
         cursor.moveToNext();
       }
     } finally {
       cursor.moveToPosition(position);
     }
 
     return record;
   }
+
+  /**
+   * Deletes all non-local clients and remote tabs.
+   *
+   * This function doesn't delete non-local clients due to bug in TabsProvider. Refer Bug 1025128.
+   *
+   * Upon remote tabs deletion, the clients without tabs are not shown in UI.
+   */
+  public static void deleteNonLocalClientsAndTabs(Context context) {
+    final String nonLocalTabsSelection = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
+
+    ContentProviderClient tabsProvider = context.getContentResolver()
+            .acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
+    if (tabsProvider == null) {
+        Logger.warn(LOG_TAG, "Unable to create tabsProvider!");
+        return;
+    }
+
+    try {
+      Logger.info(LOG_TAG, "Clearing all non-local tabs for default profile.");
+      tabsProvider.delete(BrowserContractHelpers.TABS_CONTENT_URI, nonLocalTabsSelection, null);
+    } catch (RemoteException e) {
+      Logger.warn(LOG_TAG, "Error while deleting", e);
+    } finally {
+      try {
+        tabsProvider.release();
+      } catch (Exception e) {
+        Logger.warn(LOG_TAG, "Got exception releasing tabsProvider!", e);
+      }
+    }
+  }
 }
--- a/mobile/android/base/tabspanel/TabsPanel.java
+++ b/mobile/android/base/tabspanel/TabsPanel.java
@@ -1,15 +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.tabspanel;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoAppShell.AppStateListener;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.LightweightThemeDrawable;
 import org.mozilla.gecko.R;
@@ -20,34 +21,32 @@ import org.mozilla.gecko.animation.ViewH
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.gecko.widget.IconTabWidget;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
 public class TabsPanel extends LinearLayout
                        implements GeckoPopupMenu.OnMenuItemClickListener,
                                   LightweightTheme.OnChangeListener,
                                   IconTabWidget.OnTabChangedListener {
-    @SuppressWarnings("unused")
     private static final String LOGTAG = "Gecko" + TabsPanel.class.getSimpleName();
 
     public static enum Panel {
         NORMAL_TABS,
         PRIVATE_TABS,
         REMOTE_TABS
     }
 
@@ -503,17 +502,17 @@ public class TabsPanel extends LinearLay
 
     public Panel getCurrentPanel() {
         return mCurrentPanel;
     }
 
     public void prepareTabsAnimation(PropertyAnimator animator) {
         // Not worth doing this on pre-Honeycomb without proper
         // hardware accelerated animations.
-        if (Build.VERSION.SDK_INT < 11) {
+        if (Versions.preHC) {
             return;
         }
 
         if (mIsSideBar) {
             final int tabsPanelWidth = getWidth();
             if (mVisible) {
                 ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
                 ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
@@ -534,17 +533,17 @@ public class TabsPanel extends LinearLay
             animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
         }
 
         mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
     }
 
     public void finishTabsAnimation() {
-        if (Build.VERSION.SDK_INT < 11) {
+        if (Versions.preHC) {
             return;
         }
 
         mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
         mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
 
         // If the tray is now hidden, call hide() on current panel and unset it as the current panel
         // to avoid hide() being called again when the tray is opened next.
--- a/mobile/android/base/tests/testAndroidLog.js
+++ b/mobile/android/base/tests/testAndroidLog.js
@@ -1,40 +1,66 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+const TAG = "AndroidLogTest";
+
+const VERBOSE_MESSAGE = "This is a verbose message.";
+const DEBUG_MESSAGE = "This is a debug message.";
+const INFO_MESSAGE = "This is an info message.";
+const WARNING_MESSAGE = "This is a warning message.";
+const ERROR_MESSAGE = "This is an error message.";
+
+// Number of bytes we expect to log.  This isn't equivalent to the number
+// of characters, although the difference is consistent, so we can calculate it
+// from the lengths of the messages and tag.  We include the length of "Gecko"
+// because the module prepends it to the tag.
+const VERBOSE_BYTES = "Gecko".length + TAG.length + VERBOSE_MESSAGE.length + 3;
+const DEBUG_BYTES = "Gecko".length + TAG.length + DEBUG_MESSAGE.length + 3;
+const INFO_BYTES = "Gecko".length + TAG.length + INFO_MESSAGE.length + 3;
+const WARNING_BYTES = "Gecko".length + TAG.length + WARNING_MESSAGE.length + 3;
+const ERROR_BYTES = "Gecko".length + TAG.length + ERROR_MESSAGE.length + 3;
+
 add_task(function test_AndroidLog() {
   Components.utils.import("resource://gre/modules/AndroidLog.jsm");
 
   do_check_true(!!AndroidLog);
 
   do_check_true("v" in AndroidLog && typeof AndroidLog.v == "function");
   do_check_true("d" in AndroidLog && typeof AndroidLog.d == "function");
   do_check_true("i" in AndroidLog && typeof AndroidLog.i == "function");
   do_check_true("w" in AndroidLog && typeof AndroidLog.w == "function");
   do_check_true("e" in AndroidLog && typeof AndroidLog.e == "function");
 
-  // I don't know how to check that these messages actually make it to the log,
-  // but at least we can ensure that they don't cause the test process to crash
+  // Ensure that the functions don't cause the test process to crash
   // (because of some change to the native object being accessed via ctypes)
-  // and return the right values (the number of bytes--not characters--logged).
-  do_check_eq(48, AndroidLog.v("AndroidLogTest", "This is a verbose message."));
-  do_check_eq(46, AndroidLog.d("AndroidLogTest", "This is a debug message."));
-  do_check_eq(46, AndroidLog.i("AndroidLogTest", "This is an info message."));
-  do_check_eq(48, AndroidLog.w("AndroidLogTest", "This is a warning message."));
-  do_check_eq(47, AndroidLog.e("AndroidLogTest", "This is an error message."));
+  // and return the right values (the number of bytes logged).
+  // XXX Ensure that these messages actually make it to the log (bug 1046096).
+  do_check_eq(VERBOSE_BYTES, AndroidLog.v(TAG, VERBOSE_MESSAGE));
+  do_check_eq(DEBUG_BYTES, AndroidLog.d(TAG, DEBUG_MESSAGE));
+  do_check_eq(INFO_BYTES, AndroidLog.i(TAG, INFO_MESSAGE));
+  do_check_eq(WARNING_BYTES, AndroidLog.w(TAG, WARNING_MESSAGE));
+  do_check_eq(ERROR_BYTES, AndroidLog.e(TAG, ERROR_MESSAGE));
 
   // Ensure the functions work when bound with null value for thisArg parameter.
-  do_check_eq(48, AndroidLog.v.bind(null, "AndroidLogTest")("This is a verbose message."));
-  do_check_eq(46, AndroidLog.d.bind(null, "AndroidLogTest")("This is a debug message."));
-  do_check_eq(46, AndroidLog.i.bind(null, "AndroidLogTest")("This is an info message."));
-  do_check_eq(48, AndroidLog.w.bind(null, "AndroidLogTest")("This is a warning message."));
-  do_check_eq(47, AndroidLog.e.bind(null, "AndroidLogTest")("This is an error message."));
+  do_check_eq(VERBOSE_BYTES, AndroidLog.v.bind(null, TAG)(VERBOSE_MESSAGE));
+  do_check_eq(DEBUG_BYTES, AndroidLog.d.bind(null, TAG)(DEBUG_MESSAGE));
+  do_check_eq(INFO_BYTES, AndroidLog.i.bind(null, TAG)(INFO_MESSAGE));
+  do_check_eq(WARNING_BYTES, AndroidLog.w.bind(null, TAG)(WARNING_MESSAGE));
+  do_check_eq(ERROR_BYTES, AndroidLog.e.bind(null, TAG)(ERROR_MESSAGE));
+
+  // Ensure the functions work when the module object is "bound" to a tag.
+  let Log = AndroidLog.bind(TAG);
+  do_check_eq(VERBOSE_BYTES, Log.v(VERBOSE_MESSAGE));
+  do_check_eq(DEBUG_BYTES, Log.d(DEBUG_MESSAGE));
+  do_check_eq(INFO_BYTES, Log.i(INFO_MESSAGE));
+  do_check_eq(WARNING_BYTES, Log.w(WARNING_MESSAGE));
+  do_check_eq(ERROR_BYTES, Log.e(ERROR_MESSAGE));
 
   // Ensure the functions work when the tag length is greater than the maximum
   // tag length.
   let tag = "X".repeat(AndroidLog.MAX_TAG_LENGTH + 1);
   do_check_eq(AndroidLog.MAX_TAG_LENGTH + 54, AndroidLog.v(tag, "This is a verbose message with a too-long tag."));
   do_check_eq(AndroidLog.MAX_TAG_LENGTH + 52, AndroidLog.d(tag, "This is a debug message with a too-long tag."));
   do_check_eq(AndroidLog.MAX_TAG_LENGTH + 52, AndroidLog.i(tag, "This is an info message with a too-long tag."));
   do_check_eq(AndroidLog.MAX_TAG_LENGTH + 54, AndroidLog.w(tag, "This is a warning message with a too-long tag."));
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko.toolbar;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.List;
 
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
@@ -34,26 +35,22 @@ import org.mozilla.gecko.util.GeckoEvent
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.widget.ThemedImageButton;
 import org.mozilla.gecko.widget.ThemedImageView;
 import org.mozilla.gecko.widget.ThemedRelativeLayout;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -214,17 +211,17 @@ public class BrowserToolbar extends Them
         // This will clip the translating edge's image at 60% of its width
         urlBarTranslatingEdge = (ImageView) findViewById(R.id.url_bar_translating_edge);
         if (urlBarTranslatingEdge != null) {
             urlBarTranslatingEdge.getDrawable().setLevel(6000);
         }
 
         tabsButton = (ShapedButton) findViewById(R.id.tabs);
         tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
-        if (Build.VERSION.SDK_INT >= 11) {
+        if (Versions.feature11Plus) {
             tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
         }
 
         backButton = (ImageButton) findViewById(R.id.back);
         setButtonEnabled(backButton, false);
         forwardButton = (ImageButton) findViewById(R.id.forward);
         setButtonEnabled(forwardButton, false);
 
@@ -999,17 +996,17 @@ public class BrowserToolbar extends Them
 
             final int curveTranslation = getUrlBarCurveTranslation();
             final int entryTranslation = getUrlBarEntryTranslation();
             shouldShrinkURLBar = (entryTranslation < 0);
             if (shouldShrinkURLBar) {
                 urlBarEntry.setLayoutParams(urlBarEntryShrunkenLayoutParams);
             }
 
-            if (Build.VERSION.SDK_INT < 11) {
+            if (Versions.preHC) {
                 showEditingOnPreHoneycomb(entryTranslation, curveTranslation);
             } else {
                 showEditingWithPhoneAnimation(animator, entryTranslation, curveTranslation);
             }
         }
     }
 
     private void showEditingOnPreHoneycomb(final int entryTranslation,
@@ -1128,17 +1125,17 @@ public class BrowserToolbar extends Them
         }
 
         updateProgressVisibility();
 
         // The animation looks cleaner if the text in the URL bar is
         // not selected so clear the selection by clearing focus.
         urlEditLayout.clearFocus();
 
-        if (Build.VERSION.SDK_INT < 11) {
+        if (Versions.preHC) {
             stopEditingOnPreHoneycomb();
         } else if (HardwareUtils.isTablet()) {
             stopEditingOnTablet();
         } else {
             stopEditingWithPhoneAnimation();
         }
 
         return url;
--- a/mobile/android/base/toolbar/CanvasDelegate.java
+++ b/mobile/android/base/toolbar/CanvasDelegate.java
@@ -1,22 +1,23 @@
 /* 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.toolbar;
 
+import org.mozilla.gecko.AppConstants.Versions;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Shader;
-import android.os.Build;
 
 class CanvasDelegate {
     Paint mPaint;
     PorterDuffXfermode mMode;
     DrawManager mDrawManager;
 
     // DrawManager would do a default draw of the background.
     static interface DrawManager {
@@ -45,17 +46,17 @@ class CanvasDelegate {
                                      Canvas.CLIP_TO_LAYER_SAVE_FLAG);
 
         // Do a default draw.
         mDrawManager.defaultDraw(canvas);
 
         if (path != null && !path.isEmpty()) {
             // ICS added double-buffering, which made it easier for drawing the Path directly over the DST.
             // In pre-ICS, drawPath() doesn't seem to use ARGB_8888 mode for performance, hence transparency is not preserved.
-            if (Build.VERSION.SDK_INT >= 14) {
+            if (Versions.feature14Plus) {
                 mPaint.setXfermode(mMode);
                 canvas.drawPath(path, mPaint);
             } else {
                 // Allocate a bitmap and draw the masking/clipping path.
                 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                 (new Canvas(bitmap)).drawPath(path, mPaint);
 
                 mPaint.setXfermode(mMode);
--- a/mobile/android/base/toolbar/TabCounter.java
+++ b/mobile/android/base/toolbar/TabCounter.java
@@ -1,28 +1,27 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.toolbar;
 
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.Rotate3DAnimation;
-import org.mozilla.gecko.R;
 import org.mozilla.gecko.widget.ThemedTextSwitcher;
 
 import android.content.Context;
-import android.os.Build;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AlphaAnimation;
+import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.AnimationSet;
 import android.widget.ViewSwitcher;
 
 public class TabCounter extends ThemedTextSwitcher
                         implements ViewSwitcher.ViewFactory {
 
     private static final float CENTER_X = 0.5f;
     private static final float CENTER_Y = 1.25f;
     private static final int DURATION = 500;
@@ -48,17 +47,17 @@ public class TabCounter extends ThemedTe
         mFlipInForward = createAnimation(-90, 0, FadeMode.FADE_IN, -1 * Z_DISTANCE, false);
         mFlipInBackward = createAnimation(90, 0, FadeMode.FADE_IN, Z_DISTANCE, false);
         mFlipOutForward = createAnimation(0, -90, FadeMode.FADE_OUT, -1 * Z_DISTANCE, true);
         mFlipOutBackward = createAnimation(0, 90, FadeMode.FADE_OUT, Z_DISTANCE, true);
 
         removeAllViews();
         setFactory(this);
 
-        if (Build.VERSION.SDK_INT >= 16) {
+        if (Versions.feature16Plus) {
             // This adds the TextSwitcher to the a11y node tree, where we in turn
             // could make it return an empty info node. If we don't do this the
             // TextSwitcher's child TextViews get picked up, and we don't want
             // that since the tabs ImageButton is already properly labeled for
             // accessibility.
             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             setAccessibilityDelegate(new View.AccessibilityDelegate() {
                     @Override
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -1,56 +1,50 @@
 /* -*- 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.toolbar;
 
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
 import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.toolbar.BrowserToolbar.ForwardButtonAnimation;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.widget.ThemedLinearLayout;
 import org.mozilla.gecko.widget.ThemedTextView;
 
-import org.json.JSONObject;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.os.Build;
 import android.os.SystemClock;
-import android.text.style.ForegroundColorSpan;
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
-import android.text.Spanned;
 import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.AlphaAnimation;
 import android.view.animation.TranslateAnimation;
 import android.widget.Button;
 import android.widget.ImageButton;
-import android.widget.LinearLayout.LayoutParams;
-
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.List;
 
 /**
 * {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
 * display state. It's used to display the state of the currently selected
 * tab. It should always be updated through a single entry point
 * (updateFromTab) and should never track any tab events or gecko messages
 * on its own to keep it as dumb as possible.
 *
@@ -147,17 +141,17 @@ public class ToolbarDisplayLayout extend
         final Resources res = getResources();
 
         mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext));
         mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext));
         mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
         mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
 
         mFavicon = (ImageButton) findViewById(R.id.favicon);
-        if (Build.VERSION.SDK_INT >= 16) {
+        if (Versions.feature16Plus) {
             mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         }
         mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
 
         mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
         mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
 
         mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -1,40 +1,37 @@
 /* -*- 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.toolbar;
 
-import org.mozilla.gecko.R;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.CustomEditText;
+import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
-import org.mozilla.gecko.CustomEditText;
-import org.mozilla.gecko.CustomEditText.OnKeyPreImeListener;
-import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Build;
 import android.text.Editable;
 import android.text.NoCopySpan;
 import android.text.Selection;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.text.style.BackgroundColorSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.View.OnKeyListener;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
 import android.view.inputmethod.InputMethodManager;
 
 /**
 * {@code ToolbarEditText} is the text entry used when the toolbar
@@ -542,18 +539,18 @@ public class ToolbarEditText extends Cus
                 if (mDismissListener != null) {
                     mDismissListener.onDismiss();
                 }
 
                 return true;
             }
 
             if ((keyCode == KeyEvent.KEYCODE_DEL ||
-                (Build.VERSION.SDK_INT >= 11 &&
-                    keyCode == KeyEvent.KEYCODE_FORWARD_DEL)) &&
+                (Versions.feature11Plus &&
+                 keyCode == KeyEvent.KEYCODE_FORWARD_DEL)) &&
                 removeAutocomplete(getText())) {
                 // Delete autocomplete text when backspacing or forward deleting.
                 return true;
             }
 
             return false;
         }
     }
--- a/mobile/android/base/toolbar/ToolbarProgressView.java
+++ b/mobile/android/base/toolbar/ToolbarProgressView.java
@@ -11,44 +11,43 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package org.mozilla.gecko.toolbar;
 
+import org.mozilla.gecko.AppConstants.Versions;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
-import android.widget.ImageView;
 import android.view.View;
 import android.view.animation.Animation;
+import android.widget.ImageView;
 
 /**
  * Progress view used for page loads.
  *
  * Because we're given limited information about the page load progress, the
  * bar also includes incremental animation between each step to improve
  * perceived performance.
  */
 public class ToolbarProgressView extends ImageView {
     private static final int MAX_PROGRESS = 10000;
     private static final int MSG_UPDATE = 0;
     private static final int MSG_HIDE = 1;
     private static final int STEPS = 10;
     private static final int DELAY = 40;
 
-    private static final boolean PRE_HONEYCOMB = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;
-
     private int mTargetProgress;
     private int mIncrement;
     private Rect mBounds;
     private Handler mHandler;
     private int mCurrentProgress;
 
     public ToolbarProgressView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -94,29 +93,29 @@ public class ToolbarProgressView extends
 
         };
     }
 
     @Override
     public void setVisibility(int visibility) {
         // On GB/Froyo, setting the visibility to GONE/HIDDEN alone does not
         // work with translations. Calling clearAnimation acts as a workaround.
-        if (PRE_HONEYCOMB && visibility != VISIBLE) {
+        if (Versions.preHC && visibility != VISIBLE) {
             clearAnimation();
         }
 
         super.setVisibility(visibility);
     }
 
     @Override
     public void setAnimation(Animation animation) {
         // On GB/Froyo, setting the animation after hiding the view causes it
         // to reappear. As a workaround, disallow setAnimation from being
         // called if the view is not shown.
-        if (PRE_HONEYCOMB && isShown()) {
+        if (Versions.preHC && isShown()) {
             super.setAnimation(animation);
         }
     }
 
     @Override
     public void onLayout(boolean f, int l, int t, int r, int b) {
         mBounds.left = 0;
         mBounds.right = (r - l) * mCurrentProgress / MAX_PROGRESS;
--- a/mobile/android/base/webapp/EventListener.java
+++ b/mobile/android/base/webapp/EventListener.java
@@ -9,16 +9,17 @@ import java.io.File;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.ActivityHandlerHelper;
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -26,18 +27,16 @@ import org.mozilla.gecko.util.ThreadUtil
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
 import android.util.Log;
 
 public class EventListener implements NativeEventListener  {
 
     private static final String LOGTAG = "GeckoWebappEventListener";
 
     public void registerEvents() {
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
@@ -199,21 +198,21 @@ public class EventListener implements Na
                 if (file.delete()) {
                     Log.i(LOGTAG, "Downloaded APK file deleted");
                 }
             }
         });
     }
 
     public static void uninstallApk(final Activity context, NativeJSObject message) {
-        String packageName = message.getString("apkPackageName");
-        Uri packageUri = Uri.parse("package:" + packageName);
+        final String packageName = message.getString("apkPackageName");
+        final Uri packageUri = Uri.parse("package:" + packageName);
 
-        Intent intent;
-        if (Build.VERSION.SDK_INT < 14) {
+        final Intent intent;
+        if (Versions.preICS) {
             intent = new Intent(Intent.ACTION_DELETE, packageUri);
         } else {
             intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
         }
 
         context.startActivity(intent);
     }
 
--- a/mobile/android/base/widget/ArrowPopup.java
+++ b/mobile/android/base/widget/ArrowPopup.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.widget;
 
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.BitmapDrawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
@@ -122,19 +122,19 @@ public abstract class ArrowPopup extends
             mAnchor.getLocationInWindow(anchorLocation);
         }
 
         // If the anchor is null or out of the window bounds, just show the popup at the top of the
         // root view, keeping the correct X coordinate.
         if (mAnchor == null || anchorLocation[1] < 0) {
             final View decorView = ((Activity) mContext).getWindow().getDecorView();
 
-            // Bug in android code causes the window layout parameters to be ignored
+            // Bug in Android code causes the window layout parameters to be ignored
             // when using showAtLocation() in Gingerbread phones.
-            if (Build.VERSION.SDK_INT < 11) {
+            if (Versions.preHC) {
                 setWidth(decorView.getWidth());
                 setHeight(decorView.getHeight());
             }
 
             showAtLocation(decorView, Gravity.NO_GRAVITY, anchorLocation[0] - mArrowWidth, 0);
             return;
         }
 
--- a/mobile/android/base/widget/DateTimePicker.java
+++ b/mobile/android/base/widget/DateTimePicker.java
@@ -11,45 +11,43 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package org.mozilla.gecko.widget;
 
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Locale;
+
+import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
-import android.os.Build;
 import android.text.format.DateFormat;
 import android.text.format.DateUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CalendarView;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.NumberPicker;
 
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Locale;
-
 public class DateTimePicker extends FrameLayout {
-
     private static final boolean DEBUG = true;
     private static final String LOGTAG = "GeckoDateTimePicker";
-    private static final String DATE_FORMAT = "MM/dd/yyyy";
     private static final int DEFAULT_START_YEAR = 1;
     private static final int DEFAULT_END_YEAR = 9999;
     // Minimal screen width (in inches) for which we can show the calendar;
     private static final int SCREEN_SIZE_THRESHOLD = 5;
     private boolean mYearEnabled = true;
     private boolean mMonthEnabled = true;
     private boolean mWeekEnabled;
     private boolean mDayEnabled = true;
@@ -92,38 +90,41 @@ public class DateTimePicker extends Fram
 
     public static enum PickersState { DATE, MONTH, WEEK, TIME, DATETIME };
 
     public class OnValueChangeListener implements NumberPicker.OnValueChangeListener {
         @Override
         public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
             updateInputState();
             mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
-            boolean newBehavior = (Build.VERSION.SDK_INT > 10);
+            final boolean newBehavior = Versions.feature11Plus;
             if (newBehavior) {
-                if (DEBUG) Log.d(LOGTAG, "Sdk version > 10, using new behavior");
-                //The native date picker widget on these sdks increment
-                //the next field when one field reach the maximum
+                if (DEBUG) {
+                    Log.d(LOGTAG, "SDK version > 10, using new behavior");
+                }
+
+                // The native date picker widget on these SDKs increments
+                // the next field when one field reaches the maximum.
                 if (picker == mDaySpinner && mDayEnabled) {
                     int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
                     int old = mTempDate.get(Calendar.DAY_OF_MONTH);
                     setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
                 } else if (picker == mMonthSpinner && mMonthEnabled) {
                     int old = mTempDate.get(Calendar.MONTH);
                     setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER);
                 } else if (picker == mWeekSpinner) {
                     int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
                     int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
                     setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
                 } else if (picker == mYearSpinner && mYearEnabled) {
                     int month = mTempDate.get(Calendar.MONTH);
                     mTempDate.set(Calendar.YEAR,newVal);
                     // Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
                     // change the day instead;
-                    if (month != mTempDate.get(Calendar.MONTH)){
+                    if (month != mTempDate.get(Calendar.MONTH)) {
                         mTempDate.set(Calendar.MONTH, month);
                         mTempDate.set(Calendar.DAY_OF_MONTH,
                         mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
                     }
                 } else if (picker == mHourSpinner && mHourEnabled) {
                     if (mIs12HourMode) {
                         setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12);
                     } else {
@@ -179,17 +180,17 @@ public class DateTimePicker extends Fram
                 mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
             }
             updateCalendar();
             updateSpinners();
             notifyDateChanged();
         }
 
         private void setTempDate(int field, int oldVal, int newVal, int min, int max) {
-            if (oldVal == max && newVal == min ) {
+            if (oldVal == max && newVal == min) {
                 mTempDate.add(field, 1);
             } else if (oldVal == min && newVal == max) {
                 mTempDate.add(field, -1);
             } else {
                 mTempDate.add(field, newVal - oldVal);
             }
         }
     }
@@ -211,16 +212,17 @@ public class DateTimePicker extends Fram
     };
 
     private void displayPickers() {
         setWeekShown(false);
         set12HourShown(mIs12HourMode);
         if (mState == PickersState.DATETIME) {
             return;
         }
+
         setHourShown(false);
         setMinuteShown(false);
         if (mState == PickersState.WEEK) {
             setDayShown(false);
             setMonthShown(false);
             setWeekShown(true);
         } else if (mState == PickersState.MONTH) {
             setDayShown(false);
@@ -228,19 +230,20 @@ public class DateTimePicker extends Fram
     }
 
     public DateTimePicker(Context context) {
         this(context, "", "", PickersState.DATE);
     }
 
     public DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state) {
         super(context);
-        if (Build.VERSION.SDK_INT < 11) {
+        if (Versions.preHC) {
             throw new UnsupportedOperationException("Custom DateTimePicker is only available for SDK > 10");
         }
+
         setCurrentLocale(Locale.getDefault());
         mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
         mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
         mState = state;
         LayoutInflater inflater = LayoutInflater.from(context);
         inflater.inflate(R.layout.datetime_picker, this, true);
 
         mOnChangeListener = new OnValueChangeListener();
@@ -252,23 +255,32 @@ public class DateTimePicker extends Fram
 
         // We will display differently according to the screen size width.
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
         DisplayMetrics dm = new DisplayMetrics();
         display.getMetrics(dm);
         mScreenWidth = display.getWidth() / dm.densityDpi;
         mScreenHeight = display.getHeight() / dm.densityDpi;
-        if (DEBUG) Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
 
-        // If we're displaying a date, the screen is wide enought (and if we're using a sdk where the calendar view exists)
+        if (DEBUG) {
+            Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
+        }
+
+        // If we're displaying a date, the screen is wide enough
+        // (and if we're using an SDK where the calendar view exists)
         // then display a calendar.
-        if ((mState == PickersState.DATE || mState == PickersState.DATETIME) &&
-            Build.VERSION.SDK_INT > 10 && mScreenWidth >= SCREEN_SIZE_THRESHOLD) {
-            if (DEBUG) Log.d(LOGTAG,"SDK > 10 and screen wide enough, displaying calendar");
+        if (Versions.feature11Plus &&
+            (mState == PickersState.DATE || mState == PickersState.DATETIME) &&
+            mScreenWidth >= SCREEN_SIZE_THRESHOLD) {
+
+            if (DEBUG) {
+                Log.d(LOGTAG,"SDK > 10 and screen wide enough, displaying calendar");
+            }
+
             mCalendar = new CalendarView(context);
             mCalendar.setVisibility(GONE);
 
             LayoutParams layoutParams = new LayoutParams(250,280);
             mCalendar.setLayoutParams(layoutParams);
             mCalendar.setFocusable(true);
             mCalendar.setFocusableInTouchMode(true);
             mCalendar.setMaxDate(mMaxDate.getTimeInMillis());
@@ -281,19 +293,19 @@ public class DateTimePicker extends Fram
                     mTempDate.set(year, month, monthDay);
                     setDate(mTempDate);
                     notifyDateChanged();
                 }
             });
 
             mPickers.addView(mCalendar);
         } else {
-          // If the screen is more wide than high, we are displaying daye and time spinners,
-          // and if there is no calendar displayed,
-          // we should display the fi