merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 12 Feb 2016 12:40:31 +0100
changeset 320273 3eca89d56c0252b12f8c5dadade8e8e76d852258
parent 320243 576a6dcde5b68c2ea45324ed5ce1dabb7d833d09 (current diff)
parent 320272 8ef5ae38d41074ec2dbc73cd4a6ca8d1a695468c (diff)
child 320274 d992fbeb2b2696cb3854cb3a1f0f89ae09b483e2
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
browser/base/content/test/general/browser_bug408415.js
browser/base/content/test/general/file_bug550565_favicon.ico
browser/base/content/test/general/file_bug550565_popup.html
browser/base/content/test/general/file_generic_favicon.ico
browser/base/content/test/general/file_with_favicon.html
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1097,16 +1097,22 @@ var gBrowserInit = {
           gBrowser.loadTabs(specs, false, true);
         } catch (e) {}
       }
       else if (uriToLoad instanceof XULElement) {
         // swap the given tab with the default about:blank tab and then close
         // the original tab in the other window.
         let tabToOpen = uriToLoad;
 
+        // If this tab was passed as a window argument, clear the
+        // reference to it from the arguments array.
+        if (window.arguments[0] == tabToOpen) {
+          window.arguments[0] = null;
+        }
+
         // Stop the about:blank load
         gBrowser.stop();
         // make sure it has a docshell
         gBrowser.docShell;
 
         // If the browser that we're swapping in was remote, then we'd better
         // be able to support remote browsers, and then make our selectedTab
         // remote.
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -689,30 +689,31 @@ Sanitizer.showUI = function(aParentWindo
 Sanitizer.sanitize = function(aParentWindow)
 {
   Sanitizer.showUI(aParentWindow);
 };
 
 Sanitizer.onStartup = Task.async(function*() {
   // Make sure that we are triggered during shutdown, at the right time,
   // and only once.
-  let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"]     .getService(Ci.nsPIPlacesDatabase)
-     .shutdownClient
-     .jsclient;
+  let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"]
+                       .getService(Ci.nsPIPlacesDatabase)
+                       .shutdownClient
+                       .jsclient;
 
   let deferredSanitization = PromiseUtils.defer();
   let sanitizationInProgress = false;
   let doSanitize = function() {
-    if (sanitizationInProgress) {
-      return deferredSanitization.promise;
+    if (!sanitizationInProgress) {
+      sanitizationInProgress = true;
+      Sanitizer.onShutdown().catch(er => {Promise.reject(er) /* Do not return rejected promise */;}).then(() =>
+        deferredSanitization.resolve()
+      );
     }
-    sanitizationInProgress = true;
-    Sanitizer.onShutdown().catch(er => {Promise.reject(er) /* Do not return rejected promise */;}).then(() =>
-      deferredSanitization.resolve()
-    );
+    return deferredSanitization.promise;
   }
   placesClient.addBlocker("sanitize.js: Sanitize on shutdown", doSanitize);
 
   // Handle incomplete sanitizations
   if (Preferences.has(Sanitizer.PREF_SANITIZE_IN_PROGRESS)) {
     // Firefox crashed during sanitization.
     let s = new Sanitizer();
     let json = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -742,35 +742,35 @@
 
             onLocationChange: function (aWebProgress, aRequest, aLocation,
                                         aFlags) {
               // OnLocationChange is called for both the top-level content
               // and the subframes.
               let topLevel = aWebProgress.isTopLevel;
 
               if (topLevel) {
+                let isSameDocument =
+                  !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
                 // If userTypedClear > 0, the document loaded correctly and we should be
                 // clearing the user typed value. We also need to clear the typed value
                 // if the document failed to load, to make sure the urlbar reflects the
                 // failed URI (particularly for SSL errors). However, don't clear the value
                 // if the error page's URI is about:blank, because that causes complete
                 // loss of urlbar contents for invalid URI errors (see bug 867957).
                 // Another reason to clear the userTypedValue is if this was an anchor
                 // navigation.
                 if (this.mBrowser.userTypedClear > 0 ||
                     ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
                      aLocation.spec != "about:blank") ||
-                     aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+                     isSameDocument) {
                   this.mBrowser.userTypedValue = null;
                 }
 
                 // If the browser was playing audio, we should remove the playing state.
-                if (this.mTab.hasAttribute("soundplaying") &&
-                    (!this.mBrowser.lastURI ||
-                     this.mBrowser.lastURI.spec != aLocation.spec)) {
+                if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
                   this.mTab.removeAttribute("soundplaying");
                   this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
                 }
 
                 // If the browser was previously muted, we should restore the muted state.
                 if (this.mTab.hasAttribute("muted")) {
                   this.mTab.linkedBrowser.mute();
                 }
@@ -783,19 +783,19 @@
                     findBar.close();
                   }
 
                   // fix bug 253793 - turn off highlight when page changes
                   findBar.getElement("highlight").checked = false;
                 }
 
                 // Don't clear the favicon if this onLocationChange was
-                // triggered by a pushState or a replaceState.  See bug 550565.
-                if (aWebProgress.isLoadingDocument &&
-                    !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
+                // triggered by a pushState or a replaceState (bug 550565) or
+                // a hash change (bug 408415).
+                if (aWebProgress.isLoadingDocument && !isSameDocument) {
                   this.mBrowser.mIconURL = null;
                 }
 
                 let autocomplete = this.mTabBrowser._placesAutocomplete;
                 let unifiedComplete = this.mTabBrowser._unifiedComplete;
                 if (this.mBrowser.registeredOpenURI) {
                   autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
                   unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
@@ -1753,16 +1753,17 @@
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
             var aSkipAnimation;
             var aAllowMixedContent;
             var aForceNotRemote;
             var aNoReferrer;
             var aUserContextId;
+            var aEventDetail;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aReferrerURI          = params.referrerURI;
               aReferrerPolicy       = params.referrerPolicy;
               aCharset              = params.charset;
               aPostData             = params.postData;
@@ -1770,16 +1771,17 @@
               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
               aFromExternal         = params.fromExternal;
               aRelatedToCurrent     = params.relatedToCurrent;
               aSkipAnimation        = params.skipAnimation;
               aAllowMixedContent    = params.allowMixedContent;
               aForceNotRemote       = params.forceNotRemote;
               aNoReferrer           = params.noReferrer;
               aUserContextId        = params.userContextId;
+              aEventDetail          = params.eventDetail;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -1888,18 +1890,18 @@
             // Swap in a preloaded customize tab, if available.
             if (aURI == "about:customizing") {
               usingPreloadedContent = gCustomizationTabPreloader.newTab(t);
             }
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
-            var evt = document.createEvent("Events");
-            evt.initEvent("TabOpen", true, false);
+            var detail = aEventDetail || {};
+            var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
             t.dispatchEvent(evt);
 
             // If we didn't swap docShells with a preloaded browser
             // then let's just continue loading the page normally.
             if (!usingPreloadedContent && !uriIsAboutBlank) {
               // pretend the user typed this so it'll be available till
               // the document successfully loads
               if (aURI && gInitialPages.indexOf(aURI) == -1)
@@ -2123,17 +2125,17 @@
             if (!animate &&
                 aTab.closing) {
               this._endRemoveTab(aTab);
               return;
             }
 
             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
 
-            if (!this._beginRemoveTab(aTab, false, null, true, skipPermitUnload))
+            if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload))
               return;
 
             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
               this.tabContainer._lockTabSizing(aTab);
             else
               this.tabContainer._unlockTabSizing();
 
             if (!animate /* the caller didn't opt in */ ||
@@ -2168,17 +2170,17 @@
       <!-- Tab close requests are ignored if the window is closing anyway,
            e.g. when holding Ctrl+W. -->
       <field name="_windowIsClosing">
         false
       </field>
 
       <method name="_beginRemoveTab">
         <parameter name="aTab"/>
-        <parameter name="aTabWillBeMoved"/>
+        <parameter name="aAdoptedByTab"/>
         <parameter name="aCloseWindowWithLastTab"/>
         <parameter name="aCloseWindowFastpath"/>
         <parameter name="aSkipPermitUnload"/>
         <body>
           <![CDATA[
             if (aTab.closing ||
                 this._windowIsClosing)
               return false;
@@ -2202,17 +2204,17 @@
                 // cancels the operation.  We are finished here in both cases.
                 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
                 return null;
               }
 
               newTab = true;
             }
 
-            if (!aTab._pendingPermitUnload && !aTabWillBeMoved && !aSkipPermitUnload) {
+            if (!aTab._pendingPermitUnload && !aAdoptedByTab && !aSkipPermitUnload) {
               // We need to block while calling permitUnload() because it
               // processes the event queue and may lead to another removeTab()
               // call before permitUnload() returns.
               aTab._pendingPermitUnload = true;
               let {permitUnload} = browser.permitUnload();
               delete aTab._pendingPermitUnload;
               // If we were closed during onbeforeunload, we return false now
               // so we don't (try to) close the same tab again. Of course, we
@@ -2236,37 +2238,36 @@
               this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
             else
               this.tabContainer.updateVisibility();
 
             // We're committed to closing the tab now.
             // Dispatch a notification.
             // We dispatch it before any teardown so that event listeners can
             // inspect the tab that's about to close.
-            var evt = document.createEvent("UIEvent");
-            evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
+            var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
             aTab.dispatchEvent(evt);
 
-            if (!aTabWillBeMoved && !gMultiProcessBrowser) {
+            if (!aAdoptedByTab && !gMultiProcessBrowser) {
               // Prevent this tab from showing further dialogs, since we're closing it
               var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                                 getInterface(Ci.nsIDOMWindowUtils);
               windowUtils.disableDialogs();
             }
 
             // Remove the tab's filter and progress listener.
             const filter = this._tabFilters.get(aTab);
 
             browser.webProgress.removeProgressListener(filter);
 
             const listener = this._tabListeners.get(aTab);
             filter.removeProgressListener(listener);
             listener.destroy();
 
-            if (browser.registeredOpenURI && !aTabWillBeMoved) {
+            if (browser.registeredOpenURI && !aAdoptedByTab) {
               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
               delete browser.registeredOpenURI;
             }
 
             // We are no longer the primary content area.
             browser.setAttribute("type", "content-targetable");
 
@@ -2490,17 +2491,17 @@
 
             // That's gBrowser for the other window, not the tab's browser!
             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
             var isPending = aOtherTab.hasAttribute("pending");
 
             // First, start teardown of the other browser.  Make sure to not
             // fire the beforeunload event in the process.  Close the other
             // window if this was its last tab.
-            if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
+            if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
               return;
 
             let modifiedAttrs = [];
             if (aOtherTab.hasAttribute("muted")) {
               aOurTab.setAttribute("muted", "true");
               aOurTab.muteReason = aOtherTab.muteReason;
               ourBrowser.mute();
               modifiedAttrs.push("muted");
@@ -2941,17 +2942,17 @@
         <parameter name="aTab"/>
         <parameter name="aIndex"/>
         <parameter name="aSelectTab"/>
         <body>
         <![CDATA[
           // Swap the dropped tab with a new one we create and then close
           // it in the other window (making it seem to have moved between
           // windows).
-          let newTab = this.addTab("about:blank");
+          let newTab = this.addTab("about:blank", { eventDetail: { adoptedTab: aTab } });
           let newBrowser = this.getBrowserForTab(newTab);
           let newURL = aTab.linkedBrowser.currentURI.spec;
 
           // If we're an e10s browser window, an exception will be thrown
           // if we attempt to drag a non-remote browser in, so we need to
           // ensure that the remoteness of the newly created browser is
           // appropriate for the URL of the tab being dragged in.
           this.updateBrowserRemotenessByURL(newBrowser, newURL);
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -24,18 +24,18 @@ support-files =
   bug792517.html
   bug792517.sjs
   bug839103.css
   discovery.html
   domplate_test.js
   download_page.html
   dummy_page.html
   feed_tab.html
-  file_bug550565_favicon.ico
-  file_bug550565_popup.html
+  file_generic_favicon.ico
+  file_with_favicon.html
   file_bug822367_1.html
   file_bug822367_1.js
   file_bug822367_2.html
   file_bug822367_3.html
   file_bug822367_4.html
   file_bug822367_4.js
   file_bug822367_4B.html
   file_bug822367_5.html
@@ -160,16 +160,17 @@ skip-if = true # browser_bug321000.js is
 [browser_bug329212.js]
 skip-if = e10s # Bug 1236991 - Update or remove tests that use fillInPageTooltip
 [browser_bug331772_xul_tooltiptext_in_html.js]
 skip-if = e10s # Bug 1236991 - Update or remove tests that use fillInPageTooltip
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
 [browser_bug406216.js]
+[browser_bug408415.js]
 [browser_bug409481.js]
 [browser_bug409624.js]
 [browser_bug413915.js]
 [browser_bug416661.js]
 [browser_bug417483.js]
 [browser_bug419612.js]
 [browser_bug422590.js]
 [browser_bug423833.js]
copy from browser/base/content/test/general/browser_bug550565.js
copy to browser/base/content/test/general/browser_bug408415.js
--- a/browser/base/content/test/general/browser_bug550565.js
+++ b/browser/base/content/test/general/browser_bug408415.js
@@ -1,21 +1,21 @@
 function test() {
   waitForExplicitFinish();
 
   let testPath = getRootDirectory(gTestPath);
 
-  let tab = gBrowser.addTab(testPath + "file_bug550565_popup.html");
+  let tab = gBrowser.addTab(testPath + "file_with_favicon.html");
 
   tab.linkedBrowser.addEventListener("DOMContentLoaded", function() {
     tab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
 
-    let expectedIcon = testPath + "file_bug550565_favicon.ico";
+    let expectedIcon = testPath + "file_generic_favicon.ico";
 
-    is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before pushState.");
-    tab.linkedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
-    is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after pushState.");
+    is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before hash change.");
+    tab.linkedBrowser.contentWindow.location.href += "#foo";
+    is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after hash change.");
 
     gBrowser.removeTab(tab);
 
     finish();
   }, true);
 }
--- a/browser/base/content/test/general/browser_bug491431.js
+++ b/browser/base/content/test/general/browser_bug491431.js
@@ -8,24 +8,24 @@ function test() {
   waitForExplicitFinish();
 
   let newWin, tabA, tabB;
 
   // test normal close
   tabA = gBrowser.addTab(testPage);
   gBrowser.tabContainer.addEventListener("TabClose", function(aEvent) {
     gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
-    ok(!aEvent.detail, "This was a normal tab close");
+    ok(!aEvent.detail.adoptedBy, "This was a normal tab close");
 
     // test tab close by moving
     tabB = gBrowser.addTab(testPage);
     gBrowser.tabContainer.addEventListener("TabClose", function(aEvent) {
       gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
       executeSoon(function() {
-        ok(aEvent.detail, "This was a tab closed by moving");
+        ok(aEvent.detail.adoptedBy, "This was a tab closed by moving");
 
         // cleanup
         newWin.close();
         executeSoon(finish);
       });
     }, true);
     newWin = gBrowser.replaceTabWithWindow(tabB);
   }, true);
--- a/browser/base/content/test/general/browser_bug550565.js
+++ b/browser/base/content/test/general/browser_bug550565.js
@@ -1,19 +1,19 @@
 function test() {
   waitForExplicitFinish();
 
   let testPath = getRootDirectory(gTestPath);
 
-  let tab = gBrowser.addTab(testPath + "file_bug550565_popup.html");
+  let tab = gBrowser.addTab(testPath + "file_with_favicon.html");
 
   tab.linkedBrowser.addEventListener("DOMContentLoaded", function() {
     tab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
 
-    let expectedIcon = testPath + "file_bug550565_favicon.ico";
+    let expectedIcon = testPath + "file_generic_favicon.ico";
 
     is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before pushState.");
     tab.linkedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
     is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after pushState.");
 
     gBrowser.removeTab(tab);
 
     finish();
--- a/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
+++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
@@ -142,8 +142,38 @@ add_task(function* test_dragging_blackli
      "The browser we just dragged in should not be remote.");
 
   is(draggedBrowser.currentURI.spec, BLACKLISTED_URL,
      `Expected the URL of the dragged in tab to be ${BLACKLISTED_URL}`);
 
   yield BrowserTestUtils.closeWindow(remoteWin1);
   yield BrowserTestUtils.closeWindow(remoteWin2);
 });
+
+
+/**
+ * Tests that tabs dragged between windows dispatch TabOpen and TabClose
+ * events with the appropriate adoption details.
+ */
+add_task(function* test_dragging_adoption_events() {
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser);
+
+  let awaitCloseEvent = BrowserTestUtils.waitForEvent(tab1, "TabClose");
+  let awaitOpenEvent = BrowserTestUtils.waitForEvent(win2, "TabOpen");
+
+  let effect = ChromeUtils.synthesizeDrop(tab1, tab2,
+    [[{type: TAB_DROP_TYPE, data: tab1}]],
+    null, win1, win2);
+  is(effect, "move", "Tab should be moved from win1 to win2.");
+
+  let closeEvent = yield awaitCloseEvent;
+  let openEvent = yield awaitOpenEvent;
+
+  is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab");
+  is(closeEvent.detail.adoptedBy, openEvent.target, "Old tab adopted by new tab");
+
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+});
rename from browser/base/content/test/general/file_bug550565_favicon.ico
rename to browser/base/content/test/general/file_generic_favicon.ico
rename from browser/base/content/test/general/file_bug550565_popup.html
rename to browser/base/content/test/general/file_with_favicon.html
--- a/browser/base/content/test/general/file_bug550565_popup.html
+++ b/browser/base/content/test/general/file_with_favicon.html
@@ -1,12 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Test file for bug 550565.</title>
+  <title>Test file for bugs with favicons</title>
 
   <!--Set a favicon; that's the whole point of this file.-->
-  <link rel="icon" href="file_bug550565_favicon.ico">
+  <link rel="icon" href="file_generic_favicon.ico">
 </head>
 <body>
-  Test file for bug 550565.
+  Test file for bugs with favicons
 </body>
 </html>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -886,18 +886,19 @@ var SessionStoreInternal = {
   handleEvent: function ssi_handleEvent(aEvent) {
     let win = aEvent.currentTarget.ownerDocument.defaultView;
     let target = aEvent.originalTarget;
     switch (aEvent.type) {
       case "TabOpen":
         this.onTabAdd(win, target);
         break;
       case "TabClose":
-        // aEvent.detail determines if the tab was closed by moving to a different window
-        if (!aEvent.detail)
+        // `adoptedBy` will be set if the tab was closed because it is being
+        // moved to a new window.
+        if (!aEvent.detail.adoptedBy)
           this.onTabClose(win, target);
         this.onTabRemove(win, target);
         break;
       case "TabSelect":
         this.onTabSelect(win);
         break;
       case "TabShow":
         this.onTabShow(win, target);
--- a/configure.in
+++ b/configure.in
@@ -3753,16 +3753,17 @@ MOZ_PEERCONNECTION=
 MOZ_SRTP=
 MOZ_WEBRTC_SIGNALING=
 MOZ_WEBRTC_ASSERT_ALWAYS=1
 MOZ_WEBRTC_HARDWARE_AEC_NS=
 MOZ_SCTP=
 MOZ_ANDROID_OMX=
 MOZ_MEDIA_NAVIGATOR=
 MOZ_OMX_PLUGIN=
+MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=
 MOZ_VPX_ERROR_CONCEALMENT=
 MOZ_WEBSPEECH=1
 MOZ_WEBSPEECH_MODELS=
 MOZ_WEBSPEECH_POCKETSPHINX=
 MOZ_WEBSPEECH_TEST_BACKEND=1
 VPX_AS=
 VPX_ASFLAGS=
 VPX_AS_CONVERSION=
@@ -5361,18 +5362,36 @@ MOZ_ARG_ENABLE_BOOL(omx-plugin,
     MOZ_OMX_PLUGIN=1,
     MOZ_OMX_PLUGIN=)
 
 if test -n "$MOZ_OMX_PLUGIN"; then
     if test "$OS_TARGET" = "Android"; then
         dnl Only allow building OMX plugin on Gonk (B2G) or Android
         AC_DEFINE(MOZ_OMX_PLUGIN)
     else
-       dnl fail if we're not building on Gonk or Android
-       AC_MSG_ERROR([OMX media plugin can only be built on B2G or Android])
+        dnl fail if we're not building on Gonk or Android
+        AC_MSG_ERROR([OMX media plugin can only be built on B2G or Android])
+    fi
+fi
+
+dnl ========================================================
+dnl = Enable building mobile/android with Gradle
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(gradle-mobile-android-builds,
+[  --enable-gradle-mobile-android-builds      Enable building mobile/android with Gradle],
+    MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=1,
+    MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=)
+
+if test -n "$MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE"; then
+    if test "$OS_TARGET" = "Android" -a x"$MOZ_WIDGET_TOOLKIT" != x"gonk"; then
+        dnl Only allow building mobile/android with Gradle.
+        AC_DEFINE(MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE)
+    else
+        dnl fail if we're not building mobile/android.
+        AC_MSG_ERROR([Can only build mobile/android with Gradle])
     fi
 fi
 
 dnl system libvpx Support
 dnl ========================================================
 MOZ_ARG_WITH_BOOL(system-libvpx,
 [  --with-system-libvpx    Use system libvpx (located with pkgconfig)],
     MOZ_NATIVE_LIBVPX=1)
@@ -8902,16 +8921,17 @@ AC_SUBST(MOZ_FFVPX)
 AC_SUBST(FFVPX_AS)
 AC_SUBST_LIST(FFVPX_ASFLAGS)
 AC_SUBST(MOZ_FMP4)
 AC_SUBST(MOZ_EME)
 AC_SUBST(MOZ_DIRECTSHOW)
 AC_SUBST(MOZ_ANDROID_OMX)
 AC_SUBST(MOZ_APPLEMEDIA)
 AC_SUBST(MOZ_OMX_PLUGIN)
+AC_SUBST(MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE)
 AC_SUBST(MOZ_VPX_ERROR_CONCEALMENT)
 AC_SUBST(VPX_AS)
 AC_SUBST_LIST(VPX_ASFLAGS)
 AC_SUBST(VPX_AS_CONVERSION)
 AC_SUBST(VPX_ASM_SUFFIX)
 AC_SUBST(VPX_X86_ASM)
 AC_SUBST(VPX_ARM_ASM)
 AC_SUBST(VPX_NEED_OBJ_INT_EXTRACT)
--- a/devtools/bootstrap.js
+++ b/devtools/bootstrap.js
@@ -77,17 +77,17 @@ function reload(event) {
   // Invalidate xul cache in order to see changes made to chrome:// files
   Services.obs.notifyObservers(null, "startupcache-invalidate", null);
 
   // Ask the loader to update itself and reopen the toolbox if needed
   const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
   devtools.reload(reloadToolbox);
 
   // Also tells gDevTools to reload its dependencies
-  const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+  const {gDevTools} = devtools.require("devtools/client/framework/devtools");
   gDevTools.reload();
 }
 
 let listener;
 function startup() {
   dump("DevTools addon started.\n");
   listener = new MultiWindowKeyListener({
     keyCode: Ci.nsIDOMKeyEvent.DOM_VK_R, ctrlKey: true, altKey: true,
--- a/devtools/client/aboutdebugging/components/target.js
+++ b/devtools/client/aboutdebugging/components/target.js
@@ -12,18 +12,18 @@ loader.lazyRequireGetter(this, "React",
 loader.lazyRequireGetter(this, "TargetFactory",
   "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox",
   "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "Services");
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
-loader.lazyImporter(this, "gDevTools",
-  "resource://devtools/client/framework/gDevTools.jsm");
+loader.lazyRequireGetter(this, "gDevTools",
+  "devtools/client/framework/devtools", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 exports.TargetComponent = React.createClass({
   displayName: "TargetComponent",
 
   debug() {
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -143,18 +143,29 @@ AnimationTimeBlock.prototype = {
   onClick: function(e) {
     e.stopPropagation();
     this.emit("selected", this.animation);
   }
 };
 
 /**
  * Get a formatted title for this animation. This will be either:
- * "some-name", "some-name : CSS Transition", or "some-name : CSS Animation",
- * depending if the server provides the type, and what type it is.
+ * "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
+ * "some-name : Script Animation", or "Script Animation", depending
+ * if the server provides the type, what type it is and if the animation
+ * has a name
  * @param {AnimationPlayerFront} animation
  */
 function getFormattedAnimationTitle({state}) {
-  // Older servers don't send the type.
-  return state.type
-    ? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
-    : state.name;
+  // Older servers don't send a type, and only know about
+  // CSSAnimations and CSSTransitions, so it's safe to use
+  // just the name.
+  if (!state.type) {
+    return state.name;
+  }
+
+  // Script-generated animations may not have a name.
+  if (state.type === "scriptanimation" && !state.name) {
+    return L10N.getStr("timeline.scriptanimation.unnamedLabel");
+  }
+
+  return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
 }
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -3,16 +3,17 @@ tags = devtools
 subsuite = devtools
 support-files =
   doc_body_animation.html
   doc_frame_script.js
   doc_keyframes.html
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_simple_animation.html
+  doc_multiple_animation_types.html
   head.js
 
 [browser_animation_animated_properties_displayed.js]
 [browser_animation_click_selects_animation.js]
 [browser_animation_controller_exposes_document_currentTime.js]
 skip-if = os == "linux" && !debug # Bug 1234567
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_keyframe_click_to_set_time.js]
--- a/devtools/client/animationinspector/test/browser_animation_playerWidgets_appear_on_panel_init.js
+++ b/devtools/client/animationinspector/test/browser_animation_playerWidgets_appear_on_panel_init.js
@@ -2,16 +2,46 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that player widgets are displayed right when the animation panel is
 // initialized, if the selected node (<body> by default) is animated.
 
+const { ANIMATION_TYPES } = require("devtools/server/actors/animation");
+
 add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
+  yield new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.animations-api.core.enabled", true]
+    ]}, resolve);
+  });
+
+  yield addTab(TEST_URL_ROOT + "doc_multiple_animation_types.html");
 
   let {panel} = yield openAnimationInspector();
-  is(panel.animationsTimelineComponent.animations.length, 1,
-    "One animation is handled by the timeline after init");
-  assertAnimationsDisplayed(panel, 1, "One animation is displayed after init");
+  is(panel.animationsTimelineComponent.animations.length, 3,
+    "Three animations are handled by the timeline after init");
+  assertAnimationsDisplayed(panel, 3,
+    "Three animations are displayed after init");
+  is(
+    panel.animationsTimelineComponent
+         .animationsEl
+         .querySelectorAll(`.animation.${ANIMATION_TYPES.SCRIPT_ANIMATION}`)
+         .length,
+    1,
+    "One script-generated animation is displayed");
+  is(
+    panel.animationsTimelineComponent
+         .animationsEl
+         .querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_ANIMATION}`)
+         .length,
+    1,
+    "One CSS animation is displayed");
+  is(
+    panel.animationsTimelineComponent
+         .animationsEl
+         .querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_TRANSITION}`)
+         .length,
+    1,
+    "One CSS transition is displayed");
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_multiple_animation_types.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <style>
+    .ball {
+      width: 80px;
+      height: 80px;
+      border-radius: 50%;
+    }
+
+    .script-animation {
+      background: #f06;
+    }
+
+    .css-transition {
+      background: #006;
+      transition: background-color 20s;
+    }
+
+    .css-animation {
+      background: #a06;
+      animation: flash 10s forwards;
+    }
+
+    @keyframes flash {
+      0% {
+        opacity: 1;
+      }
+      50% {
+        opacity: 0;
+      }
+      100% {
+        opacity: 1;
+      }
+    }
+  </style>
+</head>
+<body>
+  <div class="ball script-animation"></div>
+  <div class="ball css-animation"></div>
+  <div class="ball css-transition"></div>
+
+  <script>
+    setTimeout(function(){
+      document.querySelector(".css-transition").style.backgroundColor = "yellow";
+    }, 0);
+
+    document.querySelector(".script-animation").animate([
+      {  opacity: 1, offset: 0 },
+      {  opacity: .1, offset: 1 }
+    ], {
+      duration: 10000,
+      fill: "forwards"
+    });
+  </script>
+</body>
+</html>
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -1,17 +1,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
-const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
 const promise = require("promise");
 const {TargetFactory} = require("devtools/client/framework/target");
 const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 const {ViewHelpers} = Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm", {});
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 // All tests are asynchronous
 waitForExplicitFinish();
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -8,20 +8,20 @@ var { Services } = Cu.import("resource:/
 
 // Disable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 var { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var promise = require("promise");
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var { DebuggerServer } = require("devtools/server/main");
 var { CallWatcherFront } = require("devtools/server/actors/call-watcher");
 var { CanvasFront } = require("devtools/server/actors/canvas");
 var { setTimeout } = require("sdk/timers");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var TiltGL = require("devtools/client/tilt/tilt-gl");
 var { TargetFactory } = require("devtools/client/framework/target");
--- a/devtools/client/debugger/debugger-commands.js
+++ b/devtools/client/debugger/debugger-commands.js
@@ -3,18 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const l10n = require("gcli/l10n");
-
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 /**
  * The commands and converters that are exported to GCLI
  */
 exports.items = [];
 
 /**
  * Utility to get access to the current breakpoint list.
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js
@@ -96,17 +96,17 @@ function testTabB() {
     is(tabActors.size, 3, "gTabB opened: three tabs in list");
   });
 }
 
 function removeTabA() {
   let deferred = promise.defer();
 
   once(gBrowser.tabContainer, "TabClose").then(aEvent => {
-    ok(!aEvent.detail, "This was a normal tab close");
+    ok(!aEvent.detail.adoptedBy, "This was a normal tab close");
 
     // Let the actor's TabClose handler finish first.
     executeSoon(deferred.resolve);
   }, false);
 
   removeTab(gTabA);
   return deferred.promise;
 }
@@ -140,17 +140,17 @@ function testTabC() {
     is(tabActors.size, 3, "gTabC opened: three tabs in list");
   });
 }
 
 function removeTabC() {
   let deferred = promise.defer();
 
   once(gBrowser.tabContainer, "TabClose").then(aEvent => {
-    ok(aEvent.detail, "This was a tab closed by moving");
+    ok(aEvent.detail.adoptedBy, "This was a tab closed by moving");
 
     // Let the actor's TabClose handler finish first.
     executeSoon(deferred.resolve);
   }, false);
 
   gNewWindow = gBrowser.replaceTabWithWindow(gTabC);
   return deferred.promise;
 }
@@ -198,17 +198,17 @@ function testWindowClosed() {
     is(gActorA.title, "JS Debugger BrowserTabList test page", "gNewWindow closed: new tab title");
   });
 }
 
 function removeTabB() {
   let deferred = promise.defer();
 
   once(gBrowser.tabContainer, "TabClose").then(aEvent => {
-    ok(!aEvent.detail, "This was a normal tab close");
+    ok(!aEvent.detail.adoptedBy, "This was a normal tab close");
 
     // Let the actor's TabClose handler finish first.
     executeSoon(deferred.resolve);
   }, false);
 
   removeTab(gTabB);
   return deferred.promise;
 }
--- a/devtools/client/devtools-clhandler.js
+++ b/devtools/client/devtools-clhandler.js
@@ -46,18 +46,23 @@ devtoolsCommandlineHandler.prototype = {
       this.handleDebuggerServerFlag(cmdLine, debuggerServerFlag);
     }
   },
 
   handleConsoleFlag: function(cmdLine) {
     let window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
       let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-      // Load the browser devtools main module as the loader's main module.
-      Cu.import("resource://devtools/client/framework/gDevTools.jsm");
+      // Ensure loading main devtools module that hooks up into browser UI
+      // and initialize all devtools machinery.
+      // browser.xul or main top-level document used to load this module,
+      // but this code may be called without/before it.
+      // Bug 1247203 should ease handling this.
+      require("devtools/client/framework/devtools-browser");
+
       let hudservice = require("devtools/client/webconsole/hudservice");
       let { console } = Cu.import("resource://gre/modules/Console.jsm", {});
       hudservice.toggleBrowserConsole().then(null, console.error);
     } else {
       // the Browser Console was already open
       window.focus();
     }
 
@@ -66,19 +71,20 @@ devtoolsCommandlineHandler.prototype = {
     }
   },
 
   // Open the toolbox on the selected tab once the browser starts up.
   handleDevToolsFlag: function() {
     Services.obs.addObserver(function onStartup(window) {
       Services.obs.removeObserver(onStartup,
                                   "browser-delayed-startup-finished");
-      const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
-      const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-      let target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
+      const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+      const {gDevTools} = require("devtools/client/framework/devtools");
+      const {TargetFactory} = require("devtools/client/framework/target");
+      let target = TargetFactory.forTab(window.gBrowser.selectedTab);
       gDevTools.showToolbox(target);
     }, "browser-delayed-startup-finished", false);
   },
 
   _isRemoteDebuggingEnabled() {
     let remoteDebuggingEnabled = false;
     try {
       remoteDebuggingEnabled = kDebuggerPrefs.every(pref => {
--- a/devtools/client/framework/connect/connect.js
+++ b/devtools/client/framework/connect/connect.js
@@ -5,18 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var Cu = Components.utils;
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-var {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var {gDevTools} = require("devtools/client/framework/devtools");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Toolbox} = require("devtools/client/framework/toolbox")
 var promise = require("promise");
 var {DebuggerClient} = require("devtools/shared/client/main");
 
 var gClient;
 var gConnectionTimeout;
 
--- a/devtools/client/framework/gDevTools.jsm
+++ b/devtools/client/framework/gDevTools.jsm
@@ -4,28 +4,22 @@
 
 "use strict";
 
 /**
  * This JSM is here to keep some compatibility with existing add-ons.
  * Please now use the modules:
  * - devtools/client/framework/devtools for gDevTools
  * - devtools/client/framework/devtools-browser for gDevToolsBrowser
- *
- * We still do use gDevTools.jsm in our codebase,
- * bug 1245462 is going to ensure we no longer do that.
  */
 
 this.EXPORTED_SYMBOLS = [ "gDevTools", "gDevToolsBrowser" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
 const { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 /**
  * Do not directly map to the commonjs modules so that callsites of
  * gDevTools.jsm do not have to do anything to access to the very last version
  * of the module. The `devtools` and `browser` getter are always going to
  * retrieve the very last version of the modules.
  */
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -12,21 +12,21 @@ var {classes: Cc, interfaces: Ci, utils:
 
 function scopedCuImport(path) {
   const scope = {};
   Cu.import(path, scope);
   return scope;
 }
 
 const {Services} = scopedCuImport("resource://gre/modules/Services.jsm");
-const {gDevTools} = scopedCuImport("resource://devtools/client/framework/gDevTools.jsm");
 const {console} = scopedCuImport("resource://gre/modules/Console.jsm");
 const {ScratchpadManager} = scopedCuImport("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 const {require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
 
+const {gDevTools} = require("devtools/client/framework/devtools");
 const {TargetFactory} = require("devtools/client/framework/target");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 let promise = require("promise");
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
 const URL_ROOT = CHROME_URL_ROOT.replace("chrome://mochitests/content/",
                                          "http://example.com/");
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
 const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
   let l10n = function(aName, ...aArgs) {
     try {
       if (aArgs.length == 0) {
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -11,24 +11,24 @@ const SPLITCONSOLE_HEIGHT_PREF = "devtoo
 const MIN_ZOOM = 0.5;
 const MAX_ZOOM = 2;
 const OS_HISTOGRAM = "DEVTOOLS_OS_ENUMERATED_PER_USER";
 const OS_IS_64_BITS = "DEVTOOLS_OS_IS_64_BITS_PER_USER";
 const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
 
 var {Cc, Ci, Cu} = require("chrome");
 var promise = require("promise");
+var {gDevTools} = require("devtools/client/framework/devtools");
 var EventEmitter = require("devtools/shared/event-emitter");
 var Telemetry = require("devtools/client/shared/telemetry");
 var HUDService = require("devtools/client/webconsole/hudservice");
 var viewSource = require("devtools/client/shared/view-source");
 var { attachThread, detachThread } = require("./attach-thread");
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 Cu.import("resource://devtools/client/shared/DOMHelpers.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 loader.lazyImporter(this, "CommandUtils",
   "resource://devtools/client/shared/DeveloperToolbar.jsm");
 loader.lazyGetter(this, "toolboxStrings", () => {
   const properties = "chrome://devtools/locale/toolbox.properties";
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -13,17 +13,17 @@ const {Cc, Ci, Cu} = require("chrome");
 const ToolDefinitions = require("devtools/client/main").Tools;
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 const promise = require("promise");
 const {setTimeout, clearTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
 const {OutputParser} = require("devtools/client/shared/output-parser");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
 const {createChild} = require("devtools/client/inspector/shared/utils");
-const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 loader.lazyRequireGetter(this, "overlays",
   "devtools/client/inspector/shared/style-inspector-overlays");
 loader.lazyRequireGetter(this, "StyleInspectorMenu",
   "devtools/client/inspector/shared/style-inspector-menu");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cu} = require("chrome");
 const {setTimeout, clearTimeout} =
       Cu.import("resource://gre/modules/Timer.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 const DEFAULT_PREVIEW_TEXT = "Abc";
 const PREVIEW_UPDATE_DELAY = 150;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
--- a/devtools/client/inspector/inspector-commands.js
+++ b/devtools/client/inspector/inspector-commands.js
@@ -17,15 +17,15 @@ exports.items = [{
       name: "selector",
       type: "node",
       description: l10n.lookup("inspectNodeDesc"),
       manual: l10n.lookup("inspectNodeManual")
     }
   ],
   exec: function(args, context) {
     let target = context.environment.target;
-    let gDevTools = require("resource://devtools/client/framework/gDevTools.jsm").gDevTools;
+    let {gDevTools} = require("devtools/client/framework/devtools");
 
     return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
     });
   }
 }];
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -19,21 +19,20 @@ const {PrefObserver, PREF_ORIG_SOURCES} 
       require("devtools/client/styleeditor/utils");
 const {ElementStyle} =
       require("devtools/client/inspector/rules/models/element-style");
 const {Rule} = require("devtools/client/inspector/rules/models/rule");
 const {RuleEditor} =
       require("devtools/client/inspector/rules/views/rule-editor");
 const {createChild, promiseWarn} =
       require("devtools/client/inspector/shared/utils");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-loader.lazyGetter(this, "gDevTools", () =>
-  Cu.import("resource://devtools/client/framework/gDevTools.jsm", {}).gDevTools);
 loader.lazyRequireGetter(this, "overlays",
   "devtools/client/inspector/shared/style-inspector-overlays");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "StyleInspectorMenu",
   "devtools/client/inspector/shared/style-inspector-menu");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 
--- a/devtools/client/inspector/shared/test/head.js
+++ b/devtools/client/inspector/shared/test/head.js
@@ -1,26 +1,25 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
-var {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var {gDevTools} = require("devtools/client/framework/devtools");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {CssRuleView, _ElementStyle} = require("devtools/client/inspector/rules/rules");
 var {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var promise = require("promise");
 var {editableField, getInplaceEditorForSpan: inplaceEditor} =
   require("devtools/client/shared/inplace-editor");
-var {console} =
-  Components.utils.import("resource://gre/modules/Console.jsm", {});
+var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 
 // All tests are asynchronous
 waitForExplicitFinish();
 
 const TEST_URL_ROOT =
   "http://example.com/browser/devtools/client/inspector/shared/test/";
 const TEST_URL_ROOT_SSL =
   "https://example.com/browser/devtools/client/inspector/shared/test/";
--- a/devtools/client/jsonview/main.js
+++ b/devtools/client/jsonview/main.js
@@ -13,17 +13,17 @@ const {Services} = Cu.import("resource:/
 
 XPCOMUtils.defineLazyGetter(this, "JsonViewService", function() {
   return require("devtools/client/jsonview/utils");
 });
 
 /**
  * Singleton object that represents the JSON View in-content tool.
  * It has the same lifetime as the browser. Initialization done by
- * DevTools() object from gDevTools.jsm
+ * DevTools() object from devtools/client/framework/devtools.js
  */
 var JsonView = {
   initialize: function() {
     // Load JSON converter module. This converter is responsible
     // for handling 'application/json' documents and converting
     // them into a simple web-app that allows easy inspection
     // of the JSON data.
     Services.ppmm.loadProcessScript(
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -103,14 +103,25 @@ timeline.timeGraduationLabel=%Sms
 timeline.cssanimation.nameLabel=%S - CSS Animation
 
 # LOCALIZATION NOTE (timeline.csstransition.nameLabel):
 # This string is displayed in a tooltip of the animation panel that is shown
 # when hovering over the name of a CSS Transition in the timeline UI.
 # %S will be replaced by the name of the transition at run-time.
 timeline.csstransition.nameLabel=%S - CSS Transition
 
+# LOCALIZATION NOTE (timeline.scriptanimation.nameLabel):
+# This string is displayed in a tooltip of the animation panel that is shown
+# when hovering over the name of a script-generated animation in the timeline UI.
+# %S will be replaced by the name of the animation at run-time.
+timeline.scriptanimation.nameLabel=%S - Script Animation
+
+# LOCALIZATION NOTE (timeline.scriptanimation.unnamedLabel):
+# This string is displayed in a tooltip of the animation panel that is shown
+# when hovering over an unnamed script-generated animation in the timeline UI.
+timeline.scriptanimation.unnamedLabel=Script Animation
+
 # LOCALIZATION NOTE (timeline.unknown.nameLabel):
 # This string is displayed in a tooltip of the animation panel that is shown
 # when hovering over the name of an unknown animation type in the timeline UI.
 # This can happen if devtools couldn't figure out the type of the animation.
 # %S will be replaced by the name of the transition at run-time.
 timeline.unknown.nameLabel=%S
--- a/devtools/client/main.js
+++ b/devtools/client/main.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cu } = require("chrome");
 Cu.import("resource://gre/modules/Services.jsm");
-const { gDevTools } = require("resource://devtools/client/framework/gDevTools.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 const { defaultTools, defaultThemes } = require("devtools/client/definitions");
 
 defaultTools.forEach(definition => gDevTools.registerTool(definition));
 defaultThemes.forEach(definition => gDevTools.registerTheme(definition));
 
 // Re-export for backwards compatibility, but we should probably the
 // definitions from require("devtools/client/definitions") in the future
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -9,14 +9,15 @@ support-files =
 [browser_memory_allocationStackBreakdown_01.js]
     skip-if = debug # bug 1219554
 [browser_memory_breakdowns_01.js]
 [browser_memory_clear_snapshots.js]
 [browser_memory_diff_01.js]
 [browser_memory_dominator_trees_01.js]
 [browser_memory_dominator_trees_02.js]
 [browser_memory_filter_01.js]
+[browser_memory_keyboard.js]
 [browser_memory_no_allocation_stacks.js]
 [browser_memory_no_auto_expand.js]
     skip-if = debug # bug 1219554
 [browser_memory_percents_01.js]
 [browser_memory_simple_01.js]
 [browser_memory_transferHeapSnapshot_e10s_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/browser/browser_memory_keyboard.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 1246570 - Check that when pressing on LEFT arrow, the parent tree node
+// gets focused.
+
+"use strict";
+
+const {
+  snapshotState
+} = require("devtools/client/memory/constants");
+const {
+  takeSnapshotAndCensus
+} = require("devtools/client/memory/actions/snapshot");
+
+const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
+
+function waitUntilFocused(store, node) {
+  return waitUntilState(store, state =>
+      state.snapshots.length === 1 &&
+      state.snapshots[0].state === snapshotState.SAVED_CENSUS &&
+      state.snapshots[0].census &&
+      state.snapshots[0].census.focused &&
+      state.snapshots[0].census.focused === node
+  );
+}
+
+function waitUntilExpanded(store, node) {
+  return waitUntilState(store, state =>
+    state.snapshots[0] &&
+    state.snapshots[0].census &&
+    state.snapshots[0].census.expanded.has(node.id));
+}
+
+this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
+  const heapWorker = panel.panelWin.gHeapAnalysesClient;
+  const front = panel.panelWin.gFront;
+  const store = panel.panelWin.gStore;
+  const { getState, dispatch } = store;
+  const doc = panel.panelWin.document;
+
+  is(getState().breakdown.by, "coarseType");
+  yield dispatch(takeSnapshotAndCensus(front, heapWorker));
+  let census = getState().snapshots[0].census;
+  let root1 = census.report.children[0];
+  let root2 = census.report.children[0];
+  let root3 = census.report.children[0];
+  let root4 = census.report.children[0];
+  let child1 = root1.children[0];
+
+  info("Click on first node.");
+  let firstNode = doc.querySelector(".tree .heap-tree-item-name");
+  EventUtils.synthesizeMouseAtCenter(firstNode, {}, panel.panelWin);
+  yield waitUntilFocused(store, root1);
+  ok(true, "First root is selected after click.");
+
+  info("Press DOWN key, expect second root focused.");
+  EventUtils.synthesizeKey("VK_DOWN", {}, panel.panelWin);
+  yield waitUntilFocused(store, root2);
+  ok(true, "Second root is selected after pressing DOWN arrow.");
+
+  info("Press DOWN key, expect third root focused.");
+  EventUtils.synthesizeKey("VK_DOWN", {}, panel.panelWin);
+  yield waitUntilFocused(store, root3);
+  ok(true, "Third root is selected after pressing DOWN arrow.");
+
+  info("Press DOWN key, expect fourth root focused.");
+  EventUtils.synthesizeKey("VK_DOWN", {}, panel.panelWin);
+  yield waitUntilFocused(store, root4);
+  ok(true, "Fourth root is selected after pressing DOWN arrow.");
+
+  info("Press UP key, expect third root focused.");
+  EventUtils.synthesizeKey("VK_UP", {}, panel.panelWin);
+  yield waitUntilFocused(store, root3);
+  ok(true, "Third root is selected after pressing UP arrow.");
+
+  info("Press UP key, expect second root focused.");
+  EventUtils.synthesizeKey("VK_UP", {}, panel.panelWin);
+  yield waitUntilFocused(store, root2);
+  ok(true, "Second root is selected after pressing UP arrow.");
+
+  info("Press UP key, expect first root focused.");
+  EventUtils.synthesizeKey("VK_UP", {}, panel.panelWin);
+  yield waitUntilFocused(store, root1);
+  ok(true, "First root is selected after pressing UP arrow.");
+
+  info("Press RIGHT key");
+  EventUtils.synthesizeKey("VK_RIGHT", {}, panel.panelWin);
+  yield waitUntilExpanded(store, root1);
+  ok(true, "Root node is expanded.");
+
+  info("Press RIGHT key, expect first child focused.");
+  EventUtils.synthesizeKey("VK_RIGHT", {}, panel.panelWin);
+  yield waitUntilFocused(store, child1);
+  ok(true, "First child is selected after pressing RIGHT arrow.");
+
+  info("Press LEFT key, expect first root focused.");
+  EventUtils.synthesizeKey("VK_LEFT", {}, panel.panelWin);
+  yield waitUntilFocused(store, root1);
+  ok(true, "First root is selected after pressing LEFT arrow.");
+});
--- a/devtools/client/memory/test/unit/head.js
+++ b/devtools/client/memory/test/unit/head.js
@@ -1,23 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { console } = Cu.import("resource://gre/modules/Console.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 DevToolsUtils.testing = true;
 DevToolsUtils.dumpn.wantLogging = true;
-DevToolsUtils.dumpv.wantLogging = true;
+DevToolsUtils.dumpv.wantVerbose = false;
 
 var { OS } = require("resource://gre/modules/osfile.jsm");
 var { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
 var { TargetFactory } = require("devtools/client/framework/target");
 var promise = require("promise");
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 var { expectState } = require("devtools/server/actors/common");
 var HeapSnapshotFileUtils = require("devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { CurlUtils } = Cu.import("resource://devtools/client/shared/Curl.jsm", {});
 var promise = require("promise");
 var NetworkHelper = require("devtools/shared/webconsole/network-helper");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
--- a/devtools/client/performance/performance-controller.js
+++ b/devtools/client/performance/performance-controller.js
@@ -4,16 +4,17 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 var BrowserLoaderModule = {};
 Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
 var { loader, require } = BrowserLoaderModule.BrowserLoader("resource://devtools/client/performance/", this);
 var { Task } = require("resource://gre/modules/Task.jsm");
 var { Heritage, ViewHelpers, WidgetMethods } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
+var {gDevTools} = require("devtools/client/framework/devtools");
 
 // Events emitted by various objects in the panel.
 var EVENTS = require("devtools/client/performance/events");
 Object.defineProperty(this, "EVENTS", {
   value: EVENTS,
   enumerable: true,
   writable: false
 });
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -3,17 +3,17 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 var { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { console } = require("resource://gre/modules/Console.jsm");
 var { TargetFactory } = require("devtools/client/framework/target");
 var Promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { DebuggerServer } = require("devtools/server/main");
 var { merge } = require("sdk/util/object");
 var { createPerformanceFront } = require("devtools/server/actors/performance");
 var RecordingUtils = require("devtools/shared/performance/recording-utils");
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -12,16 +12,19 @@ createEnum([
 
   // The location of the page has changed.  This may be triggered by the user
   // directly entering a new URL, navigating with links, etc.
   "CHANGE_LOCATION",
 
   // Add an additional viewport to display the document.
   "ADD_VIEWPORT",
 
+  // Resize the viewport.
+  "RESIZE_VIEWPORT",
+
   // Rotate the viewport.
   "ROTATE_VIEWPORT",
 
 ], module.exports);
 
 /**
  * Create a simple enum-like object with keys mirrored to values from an array.
  * This makes comparison to a specfic value simpler without having to repeat and
--- a/devtools/client/responsive.html/actions/viewports.js
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -1,28 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("./index");
+const {
+  ADD_VIEWPORT,
+  RESIZE_VIEWPORT,
+  ROTATE_VIEWPORT
+} = require("./index");
 
 module.exports = {
 
   /**
    * Add an additional viewport to display the document.
    */
   addViewport() {
     return {
       type: ADD_VIEWPORT,
     };
   },
 
   /**
+   * Resize the viewport.
+   */
+  resizeViewport(id, width, height) {
+    return {
+      type: RESIZE_VIEWPORT,
+      id,
+      width,
+      height,
+    };
+  },
+
+  /**
    * Rotate the viewport.
    */
   rotateViewport(id) {
     return {
       type: ROTATE_VIEWPORT,
       id,
     };
   },
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createClass, createFactory, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
-const { rotateViewport } = require("./actions/viewports");
+const { resizeViewport, rotateViewport } = require("./actions/viewports");
 const Types = require("./types");
 const Viewports = createFactory(require("./components/viewports"));
 
 let App = createClass({
 
   displayName: "App",
 
   propTypes: {
@@ -29,14 +29,16 @@ let App = createClass({
     } = this.props;
 
     // For the moment, the app is just the viewports.  This seems likely to
     // change assuming we add a global toolbar or something similar.
     return Viewports({
       location,
       viewports,
       onRotateViewport: id => dispatch(rotateViewport(id)),
+      onResizeViewport: (id, width, height) =>
+        dispatch(resizeViewport(id, width, height)),
     });
   },
 
 });
 
 module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/browser.js
+++ b/devtools/client/responsive.html/components/browser.js
@@ -1,39 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass } =
+const { DOM: dom, createClass, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
 const Types = require("../types");
 
 module.exports = createClass({
 
   displayName: "Browser",
 
   propTypes: {
     location: Types.location.isRequired,
     width: Types.viewport.width.isRequired,
     height: Types.viewport.height.isRequired,
+    isResizing: PropTypes.bool.isRequired,
   },
 
   render() {
     let {
       location,
       width,
       height,
+      isResizing,
     } = this.props;
 
+    let className = "browser";
+    if (isResizing) {
+      className += " resizing";
+    }
+
     return dom.iframe(
       {
-        className: "browser",
+        className,
         src: location,
         width,
         height,
       }
     );
   },
 
 });
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -1,12 +1,13 @@
 # -*- 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/.
 
 DevToolsModules(
     'browser.js',
+    'resizable-viewport.js',
     'viewport-toolbar.js',
     'viewport.js',
     'viewports.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/resizable-viewport.js
@@ -0,0 +1,143 @@
+/* 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/. */
+
+/* global window */
+
+"use strict";
+
+const { DOM: dom, createClass, createFactory, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+
+const Types = require("../types");
+const Browser = createFactory(require("./browser"));
+const ViewportToolbar = createFactory(require("./viewport-toolbar"));
+
+const VIEWPORT_MIN_WIDTH = 280;
+const VIEWPORT_MIN_HEIGHT = 280;
+
+module.exports = createClass({
+
+  displayName: "ResizableViewport",
+
+  propTypes: {
+    location: Types.location.isRequired,
+    viewport: PropTypes.shape(Types.viewport).isRequired,
+    onResizeViewport: PropTypes.func.isRequired,
+    onRotateViewport: PropTypes.func.isRequired,
+  },
+
+  getInitialState() {
+    return {
+      isResizing: false,
+      lastClientX: 0,
+      lastClientY: 0,
+      ignoreX: false,
+      ignoreY: false,
+    };
+  },
+
+  onResizeStart({ target, clientX, clientY }) {
+    window.addEventListener("mousemove", this.onResizeDrag, true);
+    window.addEventListener("mouseup", this.onResizeStop, true);
+
+    this.setState({
+      isResizing: true,
+      lastClientX: clientX,
+      lastClientY: clientY,
+      ignoreX: target === this.refs.resizeBarY,
+      ignoreY: target === this.refs.resizeBarX,
+    });
+  },
+
+  onResizeStop() {
+    window.removeEventListener("mousemove", this.onResizeDrag, true);
+    window.removeEventListener("mouseup", this.onResizeStop, true);
+
+    this.setState({
+      isResizing: false,
+      lastClientX: 0,
+      lastClientY: 0,
+      ignoreX: false,
+      ignoreY: false,
+    });
+  },
+
+  onResizeDrag({ clientX, clientY }) {
+    if (!this.state.isResizing) {
+      return;
+    }
+
+    let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state;
+    let deltaX = clientX - lastClientX;
+    let deltaY = clientY - lastClientY;
+
+    if (ignoreX) {
+      deltaX = 0;
+    }
+    if (ignoreY) {
+      deltaY = 0;
+    }
+
+    let width = this.props.viewport.width + deltaX;
+    let height = this.props.viewport.height + deltaY;
+
+    if (width < VIEWPORT_MIN_WIDTH) {
+      width = VIEWPORT_MIN_WIDTH;
+    } else {
+      lastClientX = clientX;
+    }
+
+    if (height < VIEWPORT_MIN_HEIGHT) {
+      height = VIEWPORT_MIN_HEIGHT;
+    } else {
+      lastClientY = clientY;
+    }
+
+    // Update the viewport store with the new width and height.
+    this.props.onResizeViewport(width, height);
+
+    this.setState({
+      lastClientX,
+      lastClientY
+    });
+  },
+
+  render() {
+    let {
+      location,
+      viewport,
+      onRotateViewport,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "resizable-viewport",
+      },
+      ViewportToolbar({
+        onRotateViewport,
+      }),
+      Browser({
+        location,
+        width: viewport.width,
+        height: viewport.height,
+        isResizing: this.state.isResizing
+      }),
+      dom.div({
+        className: "viewport-resize-handle",
+        onMouseDown: this.onResizeStart,
+      }),
+      dom.div({
+        ref: "resizeBarX",
+        className: "viewport-horizontal-resize-handle",
+        onMouseDown: this.onResizeStart,
+      }),
+      dom.div({
+        ref: "resizeBarY",
+        className: "viewport-vertical-resize-handle",
+        onMouseDown: this.onResizeStart,
+      })
+    );
+  },
+
+});
--- a/devtools/client/responsive.html/components/viewport.js
+++ b/devtools/client/responsive.html/components/viewport.js
@@ -3,44 +3,43 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { DOM: dom, createClass, createFactory, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
 const Types = require("../types");
-const Browser = createFactory(require("./browser"));
-const ViewportToolbar = createFactory(require("./viewport-toolbar"));
+const ResizableViewport = createFactory(require("./resizable-viewport"));
 
 module.exports = createClass({
 
   displayName: "Viewport",
 
   propTypes: {
     location: Types.location.isRequired,
     viewport: PropTypes.shape(Types.viewport).isRequired,
+    onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
   },
 
   render() {
     let {
       location,
       viewport,
+      onResizeViewport,
       onRotateViewport,
     } = this.props;
 
     return dom.div(
       {
-        className: "viewport"
+        className: "viewport",
       },
-      ViewportToolbar({
+      ResizableViewport({
+        location,
+        viewport,
+        onResizeViewport,
         onRotateViewport,
-      }),
-      Browser({
-        location,
-        width: viewport.width,
-        height: viewport.height,
       })
     );
   },
 
 });
--- a/devtools/client/responsive.html/components/viewports.js
+++ b/devtools/client/responsive.html/components/viewports.js
@@ -12,34 +12,38 @@ const Viewport = createFactory(require("
 
 module.exports = createClass({
 
   displayName: "Viewports",
 
   propTypes: {
     location: Types.location.isRequired,
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
+    onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
   },
 
   render() {
     let {
       location,
       viewports,
+      onResizeViewport,
       onRotateViewport,
     } = this.props;
 
     return dom.div(
       {
         id: "viewports",
       },
       viewports.map(viewport => {
         return Viewport({
           key: viewport.id,
           location,
           viewport,
+          onResizeViewport: (width, height) =>
+            onResizeViewport(viewport.id, width, height),
           onRotateViewport: () => onRotateViewport(viewport.id),
         });
       })
     );
   },
 
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/images/grippers.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#A5A5A5">
+  <path d="M16 3.2L3.1 16h1.7L16 4.9zM16 7.2L7.1 16h1.8L16 8.9zM16 11.1L11.1 16h1.8l3.1-3.1z" />
+</svg>
--- a/devtools/client/responsive.html/images/moz.build
+++ b/devtools/client/responsive.html/images/moz.build
@@ -1,9 +1,10 @@
 # -*- 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/.
 
 DevToolsModules(
+    'grippers.svg',
     'rotate-viewport.svg',
 )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -8,16 +8,20 @@
 .theme-light {
   --viewport-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
 }
 
 .theme-dark {
   --viewport-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
 }
 
+* {
+  box-sizing: border-box;
+}
+
 html, body {
   margin: 0;
   height: 100%;
 }
 
 body {
   /* Only allow horizontal scrolling when more viewports are added */
   overflow-y: hidden;
@@ -52,16 +56,20 @@ body {
 .viewport {
   display: inline-block;
   /* Align all viewports to the top */
   vertical-align: top;
   border: 1px solid var(--theme-splitter-color);
   box-shadow: var(--viewport-box-shadow);
 }
 
+.resizable-viewport {
+  position: relative;
+}
+
 /**
  * Viewport Toolbar
  */
 
 .viewport-toolbar {
   background-color: var(--theme-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   color: var(--theme-body-color);
@@ -91,12 +99,56 @@ body {
   background-color: var(--theme-selection-background);
   opacity: 1;
 }
 
 .viewport-rotate-button {
   mask-image: url("./images/rotate-viewport.svg");
 }
 
+/**
+ * Viewport Browser
+ */
+
 .browser {
   display: block;
   border: 0;
 }
+
+.browser.resizing {
+  pointer-events: none;
+}
+
+/**
+ * Viewport Resize Handles
+ */
+
+.viewport-resize-handle {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  bottom: 0;
+  right: 0;
+  background-image: url("./images/grippers.svg");
+  background-position: bottom right;
+  padding: 0 1px 1px 0;
+  background-repeat: no-repeat;
+  background-origin: content-box;
+  cursor: se-resize;
+}
+
+.viewport-horizontal-resize-handle {
+  position: absolute;
+  width: 5px;
+  height: calc(100% - 16px);
+  right: -4px;
+  top: 0;
+  cursor: e-resize;
+}
+
+.viewport-vertical-resize-handle {
+  position: absolute;
+  width: calc(100% - 16px);
+  height: 5px;
+  left: 0;
+  bottom: -4px;
+  cursor: s-resize;
+}
--- a/devtools/client/responsive.html/reducers/viewports.js
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -1,15 +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/. */
 
 "use strict";
 
-const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("../actions/index");
+const {
+  ADD_VIEWPORT,
+  RESIZE_VIEWPORT,
+  ROTATE_VIEWPORT,
+} = require("../actions/index");
 
 let nextViewportId = 0;
 
 const INITIAL_VIEWPORTS = [];
 const INITIAL_VIEWPORT = {
   id: nextViewportId++,
   width: 320,
   height: 480,
@@ -20,16 +24,29 @@ let reducers = {
   [ADD_VIEWPORT](viewports) {
     // For the moment, there can be at most one viewport.
     if (viewports.length === 1) {
       return viewports;
     }
     return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
   },
 
+  [RESIZE_VIEWPORT](viewports, { id, width, height }) {
+    return viewports.map(viewport => {
+      if (viewport.id !== id) {
+        return viewport;
+      }
+
+      return Object.assign({}, viewport, {
+        width,
+        height,
+      });
+    });
+  },
+
   [ROTATE_VIEWPORT](viewports, { id }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
       }
 
       return Object.assign({}, viewport, {
         width: viewport.height,
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_resize_viewport.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test resizing the viewport.
+
+const { addViewport, resizeViewport } =
+  require("devtools/client/responsive.html/actions/viewports");
+
+add_task(function*() {
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  dispatch(addViewport());
+  dispatch(resizeViewport(0, 500, 500));
+
+  let viewport = getState().viewports[0];
+  equal(viewport.width, 500, "Resized width of 500");
+  equal(viewport.height, 500, "Resized height of 500");
+});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini
+++ b/devtools/client/responsive.html/test/unit/xpcshell.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 tags = devtools
 head = head.js ../../../framework/test/shared-redux-head.js
 tail =
 firefox-appdir = browser
 
 [test_add_viewport.js]
 [test_change_location.js]
+[test_resize_viewport.js]
 [test_rotate_viewport.js]
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -2,31 +2,28 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
-Cu.import("resource://devtools/shared/event-emitter.js");
-Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
-                                  "resource://gre/modules/SystemAppProxy.jsm");
-
-var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var Telemetry = require("devtools/client/shared/telemetry");
 var { showDoorhanger } = require("devtools/client/shared/doorhanger");
 var { TouchEventSimulator } = require("devtools/shared/touch/simulator");
 var { Task } = require("resource://gre/modules/Task.jsm");
 var promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var Services = require("Services");
+var EventEmitter = require("devtools/shared/event-emitter");
+var { ViewHelpers } = require("devtools/client/shared/widgets/ViewHelpers.jsm");
+loader.lazyImporter(this, "SystemAppProxy",
+                    "resource://gre/modules/SystemAppProxy.jsm");
 
 this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
 
 const MIN_WIDTH = 50;
 const MIN_HEIGHT = 50;
 
 const MAX_WIDTH = 10000;
 const MAX_HEIGHT = 10000;
@@ -1087,11 +1084,11 @@ ResponsiveUI.prototype = {
     let registeredPresets = this.presets.filter(function (aPreset) {
       return !aPreset.custom;
     });
 
     Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets));
   },
 }
 
-XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () {
+loader.lazyGetter(ResponsiveUI.prototype, "strings", function () {
   return Services.strings.createBundle("chrome://devtools/locale/responsiveUI.properties");
 });
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -47,23 +47,23 @@ const {require, loader} = Cu.import("res
 
 const Telemetry = require("devtools/client/shared/telemetry");
 const Editor    = require("devtools/client/sourceeditor/editor");
 const TargetFactory = require("devtools/client/framework/target").TargetFactory;
 const EventEmitter = require("devtools/shared/event-emitter");
 const {DevToolsWorker} = require("devtools/shared/worker/worker");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const promise = require("promise");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 Cu.import("resource://gre/modules/jsdebugger.jsm");
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 Cu.import("resource://gre/modules/reflect.jsm");
 
 XPCOMUtils.defineConstant(this, "SCRATCHPAD_CONTEXT_CONTENT", SCRATCHPAD_CONTEXT_CONTENT);
 XPCOMUtils.defineConstant(this, "SCRATCHPAD_CONTEXT_BROWSER", SCRATCHPAD_CONTEXT_BROWSER);
 XPCOMUtils.defineConstant(this, "BUTTON_POSITION_SAVE", BUTTON_POSITION_SAVE);
 XPCOMUtils.defineConstant(this, "BUTTON_POSITION_CANCEL", BUTTON_POSITION_CANCEL);
--- a/devtools/client/shadereditor/test/head.js
+++ b/devtools/client/shadereditor/test/head.js
@@ -6,20 +6,20 @@ var { classes: Cc, interfaces: Ci, utils
 
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var promise = require("promise");
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var { DebuggerServer } = require("devtools/server/main");
 var { WebGLFront } = require("devtools/server/actors/webgl");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var TiltGL = require("devtools/client/tilt/tilt-gl");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Toolbox} = require("devtools/client/framework/toolbox");
 var mm = null;
--- a/devtools/client/shared/autocomplete-popup.js
+++ b/devtools/client/shared/autocomplete-popup.js
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
+const {gDevTools} = require("devtools/client/framework/devtools");
 const events  = require("devtools/shared/event-emitter");
 
 /**
  * Autocomplete popup UI implementation.
  *
  * @constructor
  * @param nsIDOMDocument aDocument
  *        The document you want the popup attached to.
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -3,20 +3,20 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://testing-common/Assert.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
-var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+var promise = require("promise");
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 DevToolsUtils.testing = true;
 var { require: browserRequire } = BrowserLoader("resource://devtools/client/shared/", this);
--- a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -1,19 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the developer toolbar errors count works properly.
 
 function test() {
   const TEST_URI = TEST_URI_ROOT + "browser_toolbar_webconsole_errors_count.html";
 
-  let gDevTools = Cu.import("resource://devtools/client/framework/gDevTools.jsm",
-                             {}).gDevTools;
-
   let webconsole = document.getElementById("developer-toolbar-toolbox-button");
   let tab1, tab2;
 
   Services.prefs.setBoolPref("javascript.options.strict", true);
 
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref("javascript.options.strict");
   });
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -154,18 +154,18 @@
   function handlePrefChange(event, data) {
     if (data.pref == "devtools.theme") {
       switchTheme(data.newValue, data.oldValue);
     }
   }
 
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
   Cu.import("resource://gre/modules/Services.jsm");
-  Cu.import("resource://devtools/client/framework/gDevTools.jsm");
   const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+  const {gDevTools} = require("devtools/client/framework/devtools");
   const StylesheetUtils = require("sdk/stylesheet/utils");
 
   if (documentElement.hasAttribute("force-theme")) {
     switchTheme(documentElement.getAttribute("force-theme"));
   } else {
     switchTheme(Services.prefs.getCharPref("devtools.theme"));
 
     gDevTools.on("pref-changed", handlePrefChange);
--- a/devtools/client/shared/theme.js
+++ b/devtools/client/shared/theme.js
@@ -7,17 +7,17 @@
 /**
  * Colors for themes taken from:
  * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
  */
 
 const { Ci, Cu } = require("chrome");
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 loader.lazyRequireGetter(this, "Services");
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 const VARIABLES_URI = "chrome://devtools/skin/variables.css";
 const THEME_SELECTOR_STRINGS = {
   light: ":root.theme-light {",
   dark: ":root.theme-dark {"
 }
 
 let variableFileContents;
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 loader.lazyRequireGetter(this, "Services");
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
 loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
 
+var {gDevTools} = require("devtools/client/framework/devtools");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 /**
  * Tries to open a Stylesheet file in the Style Editor. If the file is not found,
  * it is opened in source view instead.
  * Returns a promise resolving to a boolean indicating whether or not
  * the source was able to be displayed in the StyleEditor, as the built-in Firefox
  * View Source is the fallback.
--- a/devtools/client/shims/gDevTools.jsm
+++ b/devtools/client/shims/gDevTools.jsm
@@ -19,17 +19,16 @@ if (Services.prefs.getBoolPref(WARNING_P
   Deprecated.warning("This path to gDevTools.jsm is deprecated.  Please use " +
                      "Cu.import(\"resource://devtools/client/" +
                      "framework/gDevTools.jsm\") to load this module.",
                      "https://bugzil.la/912121");
 }
 
 this.EXPORTED_SYMBOLS = [
   "gDevTools",
-  "DevTools",
   "gDevToolsBrowser"
 ];
 
 const module =
   Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 
 for (let symbol of this.EXPORTED_SYMBOLS) {
   this[symbol] = module[symbol];
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -11,17 +11,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Services = require("Services");
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const EventEmitter = require("devtools/shared/event-emitter");
-const {gDevTools} = require("resource://devtools/client/framework/gDevTools.jsm");
+const {gDevTools} = require("devtools/client/framework/devtools");
 /* import-globals-from StyleEditorUtil.jsm */
 Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
 const {SplitView} = Cu.import("resource://devtools/client/shared/SplitView.jsm", {});
 const {StyleSheetEditor} = Cu.import("resource://devtools/client/styleeditor/StyleSheetEditor.jsm");
 loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
 const {PrefObserver, PREF_ORIG_SOURCES} =
       require("devtools/client/styleeditor/utils");
 const csscoverage = require("devtools/server/actors/csscoverage");
--- a/devtools/client/styleeditor/styleeditor-commands.js
+++ b/devtools/client/styleeditor/styleeditor-commands.js
@@ -53,16 +53,16 @@ exports.items = [{
     return { href: args.resource.name, line: args.line };
   }
 }, {
   item: "converter",
   from: "editArgs",
   to: "dom",
   exec: function(args, context) {
     let target = context.environment.target;
-    let gDevTools = require("resource://devtools/client/framework/gDevTools.jsm").gDevTools;
+    let {gDevTools} = require("devtools/client/framework/devtools");
     return gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
       let styleEditor = toolbox.getCurrentPanel();
       styleEditor.selectStyleSheet(args.href, args.line);
       return null;
     });
   }
 }];
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -37,16 +37,21 @@
   --timeline-background-color: var(--theme-contrast-background);
 }
 
 .animation.csstransition {
   --timeline-border-color: var(--theme-highlight-bluegrey);
   --timeline-background-color: var(--theme-highlight-blue);
 }
 
+.animation.scriptanimation {
+  --timeline-border-color: var(--theme-highlight-green);
+  --timeline-background-color: var(--theme-graphs-green);
+}
+
 html {
   height: 100%;
 }
 
 body {
   margin: 0;
   padding: 0;
   display : flex;
@@ -524,16 +529,20 @@ body {
 .keyframes.cssanimation {
   background-color: var(--theme-contrast-background);
 }
 
 .keyframes.csstransition {
   background-color: var(--theme-highlight-blue);
 }
 
+.keyframes.scriptanimation {
+  background-color: var(--theme-graphs-green);
+}
+
 .keyframes .frame {
   position: absolute;
   top: 0;
   width: 0;
   height: 0;
   background-color: inherit;
   cursor: pointer;
 }
--- a/devtools/client/themes/images/diff.svg
+++ b/devtools/client/themes/images/diff.svg
@@ -1,7 +1,7 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#babec3">
-    <path d="M10.2 4.1c-.6 0-1.2.2-1.8.4-.6-.4-1.4-.6-2.2-.6-2.5 0-4.6 2.1-4.6 4.6s2.1 4.6 4.6 4.6c.9 0 1.6-.2 2.3-.7.5.2 1.1.4 1.7.4 2.4 0 4.3-1.9 4.3-4.3.1-2.4-1.9-4.4-4.3-4.4zm-4 7.9c-1.9 0-3.5-1.6-3.5-3.5S4.2 5 6.2 5c.3 0 .7 0 1 .1-.8.9-1.4 2.1-1.4 3.4 0 1.3.6 2.5 1.5 3.3-.4.1-.8.2-1.1.2zm2.1-.7c-.9-.6-1.4-1.6-1.4-2.8 0-1.1.6-2.1 1.4-2.8.8.6 1.4 1.6 1.4 2.8 0 1.1-.6 2.1-1.4 2.8z"/>
-    <path d="M7.6 8c-.2 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.5c0 .1-.1.1-.1.1zM7.6 9.1c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.3-.1.5 0 .6.2.1.2 0 .4-.2.5l-1.1.6h-.2zM7.8 10.3c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5L8.8 9c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.6h-.2z"/>
+  <path d="M10.2 4.1c-.6 0-1.2.2-1.8.4-.6-.4-1.4-.6-2.2-.6-2.5 0-4.6 2.1-4.6 4.6s2.1 4.6 4.6 4.6c.9 0 1.6-.2 2.3-.7.5.2 1.1.4 1.7.4 2.4 0 4.3-1.9 4.3-4.3.1-2.4-1.9-4.4-4.3-4.4zm-4 7.9c-1.9 0-3.5-1.6-3.5-3.5S4.2 5 6.2 5c.3 0 .7 0 1 .1-.8.9-1.4 2.1-1.4 3.4 0 1.3.6 2.5 1.5 3.3-.4.1-.8.2-1.1.2zm2.1-.7c-.9-.6-1.4-1.6-1.4-2.8 0-1.1.6-2.1 1.4-2.8.8.6 1.4 1.6 1.4 2.8 0 1.1-.6 2.1-1.4 2.8z"/>
+  <path d="M7.6 8c-.2 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.5c0 .1-.1.1-.1.1zM7.6 9.1c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.3-.1.5 0 .6.2.1.2 0 .4-.2.5l-1.1.6h-.2zM7.8 10.3c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5L8.8 9c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.6h-.2z"/>
 </svg>
--- a/devtools/client/tilt/tilt-visualizer.js
+++ b/devtools/client/tilt/tilt-visualizer.js
@@ -9,17 +9,17 @@ const {Cu, Ci, ChromeWorker} = require("
 
 var TiltGL = require("devtools/client/tilt/tilt-gl");
 var TiltUtils = require("devtools/client/tilt/tilt-utils");
 var TiltVisualizerStyle = require("devtools/client/tilt/tilt-visualizer-style");
 var {EPSILON, TiltMath, vec3, mat4, quat4} = require("devtools/client/tilt/tilt-math");
 var {TargetFactory} = require("devtools/client/framework/target");
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
+var {gDevTools} = require("devtools/client/framework/devtools");
 
 const ELEMENT_MIN_SIZE = 4;
 const INVISIBLE_ELEMENTS = {
   "head": true,
   "base": true,
   "basefont": true,
   "isindex": true,
   "link": true,
--- a/devtools/client/webaudioeditor/includes.js
+++ b/devtools/client/webaudioeditor/includes.js
@@ -3,31 +3,31 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 
 const { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var { console } = Cu.import("resource://gre/modules/Console.jsm", {});
 var { EventTarget } = require("sdk/event/target");
 
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const { Class } = require("sdk/core/heritage");
 const EventEmitter = require("devtools/shared/event-emitter");
 const STRINGS_URI = "chrome://devtools/locale/webaudioeditor.properties"
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const Telemetry = require("devtools/client/shared/telemetry");
 const telemetry = new Telemetry();
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 loader.lazyRequireGetter(this, "LineGraphWidget",
   "devtools/client/shared/widgets/LineGraphWidget");
 
 // `AUDIO_NODE_DEFINITION` defined in the controller's initialization,
 // which describes all the properties of an AudioNode
 var AUDIO_NODE_DEFINITION;
 
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -7,18 +7,18 @@ var { classes: Cc, interfaces: Ci, utils
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Enable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { DebuggerServer } = require("devtools/server/main");
 var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 var Promise = require("promise");
 var { WebAudioFront } = require("devtools/server/actors/webaudio");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var audioNodes = require("devtools/server/actors/utils/audionodes.json");
--- a/devtools/client/webconsole/console-commands.js
+++ b/devtools/client/webconsole/console-commands.js
@@ -2,17 +2,17 @@
 /* vim: set ft= javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const l10n = require("gcli/l10n");
-loader.lazyGetter(this, "gDevTools", () => require("resource://devtools/client/framework/gDevTools.jsm").gDevTools);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 
 exports.items = [
   {
     item: "command",
     runAt: "client",
     name: 'splitconsole',
     hidden: true,
     buttonId: "command-button-splitconsole",
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -7,21 +7,21 @@
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 const { Services } = require("resource://gre/modules/Services.jsm");
 
 loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "escapeHTML", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
 loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
 loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
 
 loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "TableWidget", "devtools/client/shared/widgets/TableWidget", true);
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
 
 const Heritage = require("sdk/core/heritage");
 const URI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -9,23 +9,23 @@
 const {Cc, Ci, Cu} = require("chrome");
 
 var WebConsoleUtils = require("devtools/shared/webconsole/utils").Utils;
 var Heritage = require("sdk/core/heritage");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Tools} = require("devtools/client/definitions");
 var promise = require("promise");
 
-loader.lazyGetter(this, "Telemetry", () => require("devtools/client/shared/telemetry"));
-loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/client/webconsole/webconsole").WebConsoleFrame);
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
+loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
+loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
-loader.lazyGetter(this, "showDoorhanger", () => require("devtools/client/shared/doorhanger").showDoorhanger);
+loader.lazyRequireGetter(this, "showDoorhanger", "devtools/client/shared/doorhanger", true);
 loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source");
 
 const STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
 var l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 // The preference prefix for all of the Browser Console filters.
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -24,18 +24,18 @@ loader.lazyRequireGetter(this, "ConsoleO
 loader.lazyRequireGetter(this, "Messages", "devtools/client/webconsole/console-output", true);
 loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
 loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "system", "devtools/shared/system");
 loader.lazyRequireGetter(this, "Timers", "sdk/timers");
 loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
 
 const STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
 var l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Security/MixedContent";
 
--- a/devtools/client/webide/content/details.js
+++ b/devtools/client/webide/content/details.js
@@ -1,14 +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/. */
 
 var Cu = Components.utils;
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
 const {ProjectBuilding} = require("devtools/client/webide/modules/build");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.addEventListener("visibilitychange", updateUI, true);
--- a/devtools/client/webide/content/logs.js
+++ b/devtools/client/webide/content/logs.js
@@ -1,14 +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/. */
 
 var Cu = Components.utils;
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
 
   Logs.init();
 });
--- a/devtools/client/webide/content/monitor.js
+++ b/devtools/client/webide/content/monitor.js
@@ -1,14 +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/. */
 
 var Cu = Components.utils;
-Cu.import('resource://devtools/client/framework/gDevTools.jsm');
 const {require} = Cu.import('resource://devtools/shared/Loader.jsm', {});
 const {Services} = Cu.import('resource://gre/modules/Services.jsm');
 const {AppManager} = require('devtools/client/webide/modules/app-manager');
 const {AppActorFront} = require('devtools/shared/apps/app-actor-front');
 const {Connection} = require('devtools/shared/client/connection-manager');
 const EventEmitter = require('devtools/shared/event-emitter');
 
 window.addEventListener('load', function onLoad() {
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -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/. */
 
 var Cc = Components.classes;
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 
-Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
 const {Toolbox} = require("devtools/client/framework/toolbox");
 const Services = require("Services");
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const {Connection} = require("devtools/shared/client/connection-manager");
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
--- a/devtools/client/webide/test/head.js
+++ b/devtools/client/webide/test/head.js
@@ -5,17 +5,17 @@
 
 var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+const {gDevTools} = require("devtools/client/framework/devtools");
 const promise = require("promise");
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 DevToolsUtils.testing = true;
 
 var TEST_BASE;
 if (window.location === "chrome://browser/content/browser.xul") {
   TEST_BASE = "chrome://mochitests/content/browser/devtools/client/webide/test/";
--- a/devtools/client/webide/test/test_basic.html
+++ b/devtools/client/webide/test/test_basic.html
@@ -16,17 +16,17 @@
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
             let win = yield openWebIDE();
 
-            const {gDevToolsBrowser} = Cu.import("resource://devtools/client/framework/gDevTools.jsm");
+            const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
             yield gDevToolsBrowser.isWebIDEInitialized.promise;
             ok(true, "WebIDE was initialized");
 
             ok(win, "Found a window");
             ok(win.AppManager, "App Manager accessible");
             let appmgr = win.AppManager;
             ok(appmgr.connection, "App Manager connection ready");
             ok(appmgr.runtimeList, "Runtime list ready");
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -34,16 +34,17 @@ const {ActorClass, Actor, FrontClass, Fr
 // Make sure the nodeActor type is know here.
 const {NodeActor} = require("devtools/server/actors/inspector");
 const events = require("sdk/event/core");
 
 // Types of animations.
 const ANIMATION_TYPES = {
   CSS_ANIMATION: "cssanimation",
   CSS_TRANSITION: "csstransition",
+  SCRIPT_ANIMATION: "scriptanimation",
   UNKNOWN: "unknown"
 };
 exports.ANIMATION_TYPES = ANIMATION_TYPES;
 
 /**
  * The AnimationPlayerActor provides information about a given animation: its
  * startTime, currentTime, current state, etc.
  *
@@ -114,46 +115,55 @@ var AnimationPlayerActor = ActorClass({
     // return its corresponding NodeActor ID too.
     if (this.walker && this.walker.hasNode(this.node)) {
       data.animationTargetNodeActorID = this.walker.getNode(this.node).actorID;
     }
 
     return data;
   },
 
-  isAnimation: function(player = this.player) {
+  isCssAnimation: function(player = this.player) {
     return player instanceof this.window.CSSAnimation;
   },
 
-  isTransition: function(player = this.player) {
+  isCssTransition: function(player = this.player) {
     return player instanceof this.window.CSSTransition;
   },
 
+  isScriptAnimation: function(player = this.player) {
+    return player instanceof this.window.Animation && !(
+      player instanceof this.window.CSSAnimation ||
+      player instanceof this.window.CSSTransition
+    );
+  },
+
   getType: function() {
-    if (this.isAnimation()) {
+    if (this.isCssAnimation()) {
       return ANIMATION_TYPES.CSS_ANIMATION;
-    } else if (this.isTransition()) {
+    } else if (this.isCssTransition()) {
       return ANIMATION_TYPES.CSS_TRANSITION;
+    } else if (this.isScriptAnimation()) {
+      return ANIMATION_TYPES.SCRIPT_ANIMATION;
     }
 
     return ANIMATION_TYPES.UNKNOWN;
   },
 
   /**
    * Get the name of this animation. This can be either the animation.id
    * property if it was set, or the keyframe rule name or the transition
    * property.
    * @return {String}
    */
   getName: function() {
     if (this.player.id) {
       return this.player.id;
-    } else if (this.isAnimation()) {
+    } else if (this.isCssAnimation()) {
       return this.player.animationName;
-    } else if (this.isTransition()) {
+    } else if (this.isCssTransition()) {
       return this.player.transitionProperty;
     }
 
     return "";
   },
 
   /**
    * Get the animation duration from this player, in milliseconds.
@@ -621,19 +631,19 @@ var AnimationsActor = exports.Animations
         if (this.actors.find(a => a.player === player)) {
           continue;
         }
         // If the added player has the same name and target node as a player we
         // already have, it means it's a transition that's re-starting. So send
         // a "removed" event for the one we already have.
         let index = this.actors.findIndex(a => {
           let isSameType = a.player.constructor === player.constructor;
-          let isSameName = (a.isAnimation() &&
+          let isSameName = (a.isCssAnimation() &&
                             a.player.animationName === player.animationName) ||
-                           (a.isTransition() &&
+                           (a.isCssTransition() &&
                             a.player.transitionProperty === player.transitionProperty);
           let isSameNode = a.player.effect.target === player.effect.target;
 
           return isSameType && isSameNode && isSameName;
         });
         if (index !== -1) {
           eventData.push({
             type: "removed",
--- a/devtools/server/actors/csscoverage.js
+++ b/devtools/server/actors/csscoverage.js
@@ -7,20 +7,18 @@
 const { Cc, Ci, Cu } = require("chrome");
 
 const Services = require("Services");
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 const events = require("sdk/event/core");
 const protocol = require("devtools/server/protocol");
 const { method, custom, RetVal, Arg } = protocol;
+const {gDevTools} = require("devtools/client/framework/devtools");
 
-loader.lazyGetter(this, "gDevTools", () => {
-  return require("resource://devtools/client/framework/gDevTools.jsm").gDevTools;
-});
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
 });
 loader.lazyGetter(this, "stylesheets", () => {
   return require("devtools/server/actors/stylesheets");
 });
 loader.lazyGetter(this, "CssLogic", () => {
   return require("devtools/shared/inspector/css-logic").CssLogic;
--- a/devtools/server/tests/unit/test_animation_name.js
+++ b/devtools/server/tests/unit/test_animation_name.js
@@ -19,16 +19,19 @@ function run_test() {
     CSSAnimation: function() {
       this.effect = {target: getMockNode()};
     },
     CSSTransition: function() {
       this.effect = {target: getMockNode()};
     }
   };
 
+  window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+  window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
   // Helper to get a mock DOM node.
   function getMockNode() {
     return {
       ownerDocument: {
         defaultView: window
       }
     };
   }
@@ -42,16 +45,21 @@ function run_test() {
   // - expectedName {String} The expected name returned by
   //   AnimationPlayerActor.getName.
   const TEST_DATA = [{
     desc: "Animation with an id",
     animation: new window.Animation(),
     props: { id: "animation-id" },
     expectedName: "animation-id"
   }, {
+    desc: "Animation without an id",
+    animation: new window.Animation(),
+    props: {},
+    expectedName: ""
+  }, {
     desc: "CSSTransition with an id",
     animation: new window.CSSTransition(),
     props: { id: "transition-with-id", transitionProperty: "width" },
     expectedName: "transition-with-id"
   }, {
     desc: "CSSAnimation with an id",
     animation: new window.CSSAnimation(),
     props: { id: "animation-with-id", animationName: "move" },
--- a/devtools/server/tests/unit/test_animation_type.js
+++ b/devtools/server/tests/unit/test_animation_type.js
@@ -19,16 +19,19 @@ function run_test() {
     CSSAnimation: function() {
       this.effect = {target: getMockNode()};
     },
     CSSTransition: function() {
       this.effect = {target: getMockNode()};
     }
   };
 
+  window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+  window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
   // Helper to get a mock DOM node.
   function getMockNode() {
     return {
       ownerDocument: {
         defaultView: window
       }
     };
   }
@@ -43,16 +46,20 @@ function run_test() {
     desc: "Test CSSAnimation type",
     animation: new window.CSSAnimation(),
     expectedType: ANIMATION_TYPES.CSS_ANIMATION
   }, {
     desc: "Test CSSTransition type",
     animation: new window.CSSTransition(),
     expectedType: ANIMATION_TYPES.CSS_TRANSITION
   }, {
+    desc: "Test ScriptAnimation type",
+    animation: new window.Animation(),
+    expectedType: ANIMATION_TYPES.SCRIPT_ANIMATION
+  }, {
     desc: "Test unknown type",
     animation: {effect: {target: getMockNode()}},
     expectedType: ANIMATION_TYPES.UNKNOWN
   }];
 
   for (let { desc, animation, expectedType } of TEST_DATA) {
     do_print(desc);
     let actor = AnimationPlayerActor({}, animation);
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -439,17 +439,17 @@ DevToolsLoader.prototype = {
       // Reopen the toolbox automatically if we are reloading from toolbox shortcut
       // and are on a browser window.
       // Wait for a second before opening the toolbox to avoid races
       // between the old and the new one.
       let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
       setTimeout(() => {
         let { gBrowser } = window;
         let target = this.TargetFactory.forTab(gBrowser.selectedTab);
-        const { gDevTools } = this.require("resource://devtools/client/framework/gDevTools.jsm");
+        const { gDevTools } = require("devtools/client/framework/devtools");
         gDevTools.showToolbox(target);
       }, 1000);
     } else if (location.includes("/webide.xul")) {
       window.location.reload();
     }
   },
 
   /**
--- a/devtools/shared/gcli/commands/calllog.js
+++ b/devtools/shared/gcli/commands/calllog.js
@@ -2,28 +2,21 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const l10n = require("gcli/l10n");
 const gcli = require("gcli/index");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const Debugger = require("Debugger");
 
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 
-loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm");
-
-loader.lazyGetter(this, "Debugger", () => {
-  let global = Cu.getGlobalForObject({});
-  let JsDebugger = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
-  JsDebugger.addDebuggerToGlobal(global);
-  return global.Debugger;
-});
-
 var debuggers = [];
 var chromeDebuggers = [];
 var sandboxes = [];
 
 exports.items = [
   {
     name: "calllog",
     description: l10n.lookup("calllogDesc")
--- a/devtools/shared/gcli/commands/csscoverage.js
+++ b/devtools/shared/gcli/commands/csscoverage.js
@@ -1,18 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci } = require("chrome");
 
-loader.lazyGetter(this, "gDevTools", () => require("resource://devtools/client/framework/gDevTools.jsm").gDevTools);
-
+const { gDevTools } = require("devtools/client/framework/devtools");
 const domtemplate = require("gcli/util/domtemplate");
 const csscoverage = require("devtools/server/actors/csscoverage");
 const l10n = csscoverage.l10n;
 
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart", "resource://devtools/client/shared/widgets/Chart.jsm");
 
--- a/devtools/shared/gcli/source/lib/gcli/l10n.js
+++ b/devtools/shared/gcli/source/lib/gcli/l10n.js
@@ -42,17 +42,17 @@ exports.lookup = function(name) {
 
 /**
  * An alternative to lookup().
  * <code>l10n.lookup('BLAH') === l10n.propertyLookup.BLAH</code>
  * This is particularly nice for templates because you can pass
  * <code>l10n:l10n.propertyLookup</code> in the template data and use it
  * like <code>${l10n.BLAH}</code>
  */
-exports.propertyLookup = Proxy.create({
+exports.propertyLookup = new Proxy({}, {
   get: function(rcvr, name) {
     return exports.lookup(name);
   }
 });
 
 /**
  * Lookup a string in the GCLI string bundle
  */
--- a/devtools/shared/gcli/source/lib/gcli/util/l10n.js
+++ b/devtools/shared/gcli/source/lib/gcli/util/l10n.js
@@ -66,17 +66,17 @@ exports.lookup = function(key) {
   }
   catch (ex) {
     console.error('Failed to lookup ', key, ex);
     return key;
   }
 };
 
 /** @see propertyLookup in lib/gcli/util/l10n.js */
-exports.propertyLookup = Proxy.create({
+exports.propertyLookup = new Proxy({}, {
   get: function(rcvr, name) {
     return exports.lookup(name);
   }
 });
 
 /** @see lookupFormat in lib/gcli/util/l10n.js */
 exports.lookupFormat = function(key, swaps) {
   try {
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -370,15 +370,15 @@ exports.diff = diff
  * @return {Object<number, TreeNode>}
  */
 const createParentMap = exports.createParentMap = function (node,
                                                             getId = node => node.id,
                                                             aggregator = Object.create(null)) {
   if (node.children) {
     for (let i = 0, length = node.children.length; i < length; i++) {
       const child = node.children[i];
-      aggregator[getId(child)] = getId(node);
+      aggregator[getId(child)] = node;
       createParentMap(child, getId, aggregator);
     }
   }
 
   return aggregator;
 };
--- a/devtools/shared/heapsnapshot/HeapAnalysesClient.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -16,17 +16,17 @@ var workerCounter = 0;
  * interacting with a HeapAnalysesWorker. This enables users to be ignorant of
  * the message passing protocol used to communicate with the worker. The
  * HeapAnalysesClient owns the worker, and terminating the worker is done by
  * terminating the client (see the `destroy` method).
  */
 const HeapAnalysesClient = module.exports = function () {
   this._worker = new DevToolsWorker(WORKER_URL, {
     name: `HeapAnalyses-${workerCounter++}`,
-    verbose: DevToolsUtils.dumpv.wantLogging
+    verbose: DevToolsUtils.dumpv.wantVerbose
   });
 };
 
 /**
  * Destroy the worker, causing it to release its resources (such as heap
  * snapshots it has deserialized and read into memory). The client is no longer
  * usable after calling this method.
  */
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
 #Mon Nov 02 13:44:39 GMT 2015
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip
+distributionSha256Sum=2ba0aaa11a3e96ec0af31d532d808e1f09cc6dcad0954e637902a1ab544b9e60
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -1,12 +1,18 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+# We call mach -> Make -> gradle -> mach, which races to find and
+# create .mozconfig files and to generate targets.
+ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+.NOTPARALLEL:
+endif
+
 MOZ_BUILDID := $(shell cat $(DEPTH)/config/buildid)
 
 # Set the appropriate version code, based on the existance of the
 # MOZ_APP_ANDROID_VERSION_CODE variable.
 ifdef MOZ_APP_ANDROID_VERSION_CODE
     ANDROID_VERSION_CODE:=$(MOZ_APP_ANDROID_VERSION_CODE)
 else
     ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
@@ -196,19 +202,31 @@ library_jars := \
 # See https://bugzilla.mozilla.org/show_bug.cgi?id=1233238#c19 for symptoms and
 # more discussion.
 ifdef MOZ_INSTALL_TRACKING
 library_jars += $(ANDROID_SDK)/optional/org.apache.http.legacy.jar
 endif # MOZ_INSTALL_TRACKING
 
 library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
 
+gradle_dir := $(topobjdir)/gradle/build/mobile/android
+
+ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+.gradle.deps: .aapt.deps FORCE
+	@$(TOUCH) $@
+	$(topsrcdir)/mach gradle --no-daemon --offline app:dexAutomationDebug app:assembleAutomationDebugAndroidTest -x lint
+
+classes.dex: .gradle.deps
+	$(REPORT_BUILD)
+	cp $(gradle_dir)/app/intermediates/dex/automation/debug/classes.dex $@
+else
 classes.dex: .proguard.deps
 	$(REPORT_BUILD)
 	$(DX) --dex --output=classes.dex jars-proguarded
+endif
 
 ifdef MOZ_DISABLE_PROGUARD
   PROGUARD_PASSES=0
 else
   ifdef MOZ_DEBUG
     PROGUARD_PASSES=1
   else
     ifndef MOZILLA_OFFICIAL
@@ -496,18 +514,23 @@ endef
 # packaging.  It doesn't write the normal ap_, or R.java, since we
 # don't want the packaging step to write anything that would make a
 # further no-op build do work.  See also
 # toolkit/mozapps/installer/packager.mk.
 
 # .aapt.deps: $(all_resources)
 $(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,generated/,./))
 
-# .aapt.nodeps: $(abspath $(CURDIR)/AndroidManifest.xml) FORCE
-$(eval $(call aapt_command,.aapt.nodeps,$(abspath $(CURDIR)/AndroidManifest.xml) FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
+ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+.aapt.nodeps: FORCE
+	cp $(gradle_dir)/app/intermediates/res/resources-automation-debug.ap_ gecko-nodeps.ap_
+else
+# .aapt.nodeps: $(CURDIR)/AndroidManifest.xml FORCE
+$(eval $(call aapt_command,.aapt.nodeps,$(CURDIR)/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
+endif
 
 # Override the Java settings with some specific android settings
 include $(topsrcdir)/config/android-common.mk
 
 update-generated-wrappers:
 	@cp $(CURDIR)/jni-stubs.inc $(topsrcdir)/mozglue/android
 	@cp $(CURDIR)/GeneratedJNIWrappers.cpp $(CURDIR)/GeneratedJNIWrappers.h $(CURDIR)/GeneratedJNINatives.h $(topsrcdir)/widget/android
 	@echo Updated generated JNI code
@@ -527,34 +550,47 @@ update-generated-wrappers:
 	rsync --update $(DIST)/fennec/$(notdir $(OMNIJAR_NAME)) $@
 	$(RM) $(DIST)/fennec/$(notdir $(OMNIJAR_NAME))
 
 # Targets built very early during a Gradle build.
 gradle-targets: $(foreach f,$(constants_PP_JAVAFILES),$(f))
 gradle-targets: $(abspath AndroidManifest.xml)
 gradle-targets: $(ANDROID_GENERATED_RESFILES)
 
-gradle-omnijar: $(ABS_DIST)/fennec/$(OMNIJAR_NAME)
+ifndef MOZILLA_OFFICIAL
+# Local developers update omni.ja during their builds.  There's a
+# chicken-and-egg problem here.
+gradle-omnijar: $(abspath $(DIST)/fennec/$(OMNIJAR_NAME))
+else
+# In automation, omni.ja is built only during packaging.
+gradle-omnijar:
+endif
 
 .PHONY: gradle-targets gradle-omnijar
 
 ifndef MOZ_DISABLE_GECKOVIEW
 libs:: geckoview_resources.zip
 	$(INSTALL) geckoview_resources.zip $(FINAL_TARGET)
 endif
 
 # GeneratedJNIWrappers.cpp target also generates
 # GeneratedJNIWrappers.h and GeneratedJNINatives.h
-libs:: classes.dex jni-stubs.inc GeneratedJNIWrappers.cpp $(CURDIR)/fennec_ids.txt
-	$(INSTALL) classes.dex $(FINAL_TARGET)
+ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+libs:: jni-stubs.inc GeneratedJNIWrappers.cpp
 	@(diff jni-stubs.inc $(topsrcdir)/mozglue/android/jni-stubs.inc >/dev/null && \
 	  diff GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp >/dev/null && \
 	  diff GeneratedJNIWrappers.h $(topsrcdir)/widget/android/GeneratedJNIWrappers.h >/dev/null && \
 	  diff GeneratedJNINatives.h $(topsrcdir)/widget/android/GeneratedJNINatives.h >/dev/null) || \
 	 (echo '*****************************************************' && \
 	  echo '***   Error: The generated JNI code has changed   ***' && \
 	  echo '* To update generated code in the tree, please run  *' && \
 	  echo && \
 	  echo '  make -C $(CURDIR) update-generated-wrappers' && \
 	  echo && \
 	  echo '* Repeat the build, and check in any changes.       *' && \
 	  echo '*****************************************************' && \
 	  exit 1)
+endif
+
+libs:: $(CURDIR)/fennec_ids.txt
+
+libs:: classes.dex
+	$(INSTALL) classes.dex $(FINAL_TARGET)
--- a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
@@ -52,16 +52,17 @@ public class URLMetadataTable extends Ba
                    " ADD COLUMN " + TOUCH_ICON_COLUMN + " STRING");
     }
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         // This table was added in v21 of the db. Force its creation if we're coming from an earlier version
         if (newVersion >= 21 && oldVersion < 21) {
             onCreate(db);
+            return;
         }
 
         // Removed the redundant metadata_url_idx index in version 26
         if (newVersion >= 26 && oldVersion < 26) {
             db.execSQL("DROP INDEX IF EXISTS metadata_url_idx");
         }
         if (newVersion >= 27 && oldVersion < 27) {
             upgradeDatabaseFrom26To27(db);
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java
@@ -264,16 +264,67 @@ public class DateTimePicker extends Fram
         display.getMetrics(dm);
         mScreenWidth = display.getWidth() / dm.densityDpi;
         mScreenHeight = display.getHeight() / dm.densityDpi;
 
         if (DEBUG) {
             Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
         }
 
+        // Set the min / max attribute.
+        try {
+            if (minDateValue != null && !minDateValue.equals("")) {
+                mMinDate.setTime(new SimpleDateFormat(dateFormat).parse(minDateValue));
+            } else {
+                mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
+            }
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Error parsing format sting: " + ex);
+            mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
+        }
+
+        try {
+            if (maxDateValue != null && !maxDateValue.equals("")) {
+                mMaxDate.setTime(new SimpleDateFormat(dateFormat).parse(maxDateValue));
+            } else {
+                mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
+            }
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Error parsing format string: " + ex);
+            mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
+        }
+
+        // Find the initial date from the constructor arguments.
+        try {
+            if (!dateTimeValue.equals("")) {
+                mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
+            } else {
+                mTempDate.setTimeInMillis(System.currentTimeMillis());
+            }
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Error parsing format string: " + ex);
+            mTempDate.setTimeInMillis(System.currentTimeMillis());
+        }
+
+        if (mMaxDate.before(mMinDate)) {
+            // If the input date range is illogical/garbage, we should not restrict the input range (i.e. allow the
+            // user to select any date). If we try to make any assumptions based on the illogical min/max date we could
+            // potentially prevent the user from selecting dates that are in the developers intended range, so it's best
+            // to allow anything.
+            mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
+            mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
+        }
+
+        // mTempDate will either be a site-supplied value, or today's date otherwise. CalendarView implementations can
+        // crash if they're supplied an invalid date (i.e. a date not in the specified range), hence we need to set
+        // a sensible default date here.
+        if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+            mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
+        }
+
         // 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 (Versions.feature11Plus &&
             (mState == PickersState.DATE || mState == PickersState.DATETIME) &&
             mScreenWidth >= SCREEN_SIZE_THRESHOLD) {
 
             if (DEBUG) {
@@ -307,51 +358,16 @@ public class DateTimePicker extends Fram
             // time spinners, and if there is no calendar displayed, we should
             // display the fields in one row.
             if (mScreenWidth > mScreenHeight && mState == PickersState.DATETIME) {
                 mPickers.setOrientation(LinearLayout.HORIZONTAL);
             }
             mCalendar = null;
         }
 
-        // Find the initial date from the constructor arguments.
-        try {
-            if (!dateTimeValue.equals("")) {
-                mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
-            } else {
-                mTempDate.setTimeInMillis(System.currentTimeMillis());
-            }
-        } catch (Exception ex) {
-            Log.e(LOGTAG, "Error parsing format string: " + ex);
-            mTempDate.setTimeInMillis(System.currentTimeMillis());
-        }
-
-	// Set the min / max attribute.
-	try {
-	    if (minDateValue != null && !minDateValue.equals("")) {
-		mMinDate.setTime(new SimpleDateFormat(dateFormat).parse(minDateValue));
-	    } else {
-		mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
-	    }
-	} catch (Exception ex) {
-	    Log.e(LOGTAG, "Error parsing format sting: " + ex);
-	    mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
-	}
-
-	try {
-	    if (maxDateValue != null && !maxDateValue.equals("")) {
-		mMaxDate.setTime(new SimpleDateFormat(dateFormat).parse(maxDateValue));
-	    } else {
-		mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
-	    }
-	} catch (Exception ex) {
-	    Log.e(LOGTAG, "Error parsing format string: " + ex);
-	    mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
-	}
-
         // Initialize all spinners.
         mDaySpinner = setupSpinner(R.id.day, 1,
                                    mTempDate.get(Calendar.DAY_OF_MONTH));
         mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER);
         mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1);
 
         mMonthSpinner = setupSpinner(R.id.month, 1,
                                      mTempDate.get(Calendar.MONTH) + 1); // Month is 0-based
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -3,82 +3,29 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- Don't localize these. They're here until they have
      a better place to live. -->
 <!ENTITY syncBrand.fullName.label "Firefox Sync">
 <!ENTITY syncBrand.shortName.label "Sync">
 
 <!-- Main titles. -->
-<!ENTITY sync.app.name.label '&syncBrand.fullName.label;'>
 <!ENTITY sync.title.connect.label 'Connect to &syncBrand.shortName.label;'>
-<!ENTITY sync.title.adddevice.label 'Add a &syncBrand.fullName.label; Account'>
-<!ENTITY sync.title.pair.label 'Pair a Device'>
 
 <!-- J-PAKE Key Screen -->
 <!ENTITY sync.subtitle.connect.label 'To activate your new device, select “Set up &syncBrand.shortName.label;” on the device.'>
-<!ENTITY sync.subtitle.header2.label 'Enter this code on your other device'>
-<!ENTITY sync.subtitle.connectlocation2.label 'Select “&sync.title.pair.label;” in the &syncBrand.shortName.label; section of your other device\&apos;s Firefox options.'>
 <!ENTITY sync.subtitle.pair.label 'To activate, select “Pair a device” on your other device.'>
 <!ENTITY sync.pin.default.label '...\n...\n...\n'>
-<!ENTITY sync.pin.oneline.label '...'>
-<!ENTITY sync.link.show.label 'Show me how.'>
-<!ENTITY sync.link.advancedsetup.label 'Advanced setup…'>
 <!ENTITY sync.link.nodevice.label 'I don\&apos;t have the device with me…'>
 
-<!-- J-PAKE Waiting Screen -->
-<!ENTITY sync.jpake.subtitle.waiting.label 'Waiting for other device…'>
-
-<!-- Account Login Screen -->
-<!ENTITY sync.subtitle.account.label 'Enter your &syncBrand.fullName.label; account information'>
-<!ENTITY sync.input.username.label 'Account Name'>
-<!ENTITY sync.input.password.label 'Password'>
-<!ENTITY sync.input.key.label 'Recovery Key'>
-<!ENTITY sync.checkbox.server.label 'Use custom server'>
-<!ENTITY sync.input.server.label 'Server URL'>
-
-<!-- Setup Fail -->
-<!ENTITY sync.title.fail.label 'Cannot Set Up &syncBrand.shortName.label;'>
-<!ENTITY sync.subtitle.fail.label '&syncBrand.fullName.label; could not connect to the server. Would you like to try again?'>
-<!ENTITY sync.button.tryagain.label 'Try again'>
-<!ENTITY sync.button.manual.label 'Manual Setup'>
-<!ENTITY sync.subtitle.nointernet.label 'No internet connection available.'>
-<!ENTITY sync.subtitle.failaccount.label 'Account creation on your device failed.'>
-<!ENTITY sync.subtitle.failmultiple.label 'Do you have more than one Firefox installed? Currently, &syncBrand.fullName.label; only supports one Firefox installation at a time. Please uninstall other instances to use &syncBrand.shortName.label;.'>
-
-<!-- Setup Success -->
-<!ENTITY sync.title.success.label 'Setup Complete'>
-<!ENTITY sync.subtitle.success.label1 'Your data is now being downloaded in the background. You can go to Settings to manage your account, or start browsing with &brandShortName;.'>
-<!ENTITY sync.settings.label 'Settings'>
-<!ENTITY sync.subtitle.manage.label1 'Your &syncBrand.fullName.label; account is already set up. You can go to Settings to manage your account, or launch &brandShortName;.'>
-
-<!-- Pair Device -->
-<!ENTITY sync.pair.tryagain.label 'Please try again.'>
-<!ENTITY sync.pair.connectlocation.label 'To activate your new device, select “Set up &syncBrand.shortName.label;” on the device, and then select “I Have an Account.”'>
-
-<!-- Firefox SyncAdapter Settings Screen -->
-<!ENTITY sync.settings.options.label 'Options'>
-<!ENTITY sync.summary.pair.label 'Link another device to your &syncBrand.shortName.label; account'>
-
 <!-- Configure Engines -->
-<!ENTITY sync.configure.engines.title.label 'What to sync'>
-<!ENTITY sync.configure.engines.sync.my.title.label 'Sync your…'>
-<!ENTITY sync.configure.engines.title.bookmarks 'Bookmarks'>
 <!ENTITY sync.configure.engines.title.passwords2 'Logins'>
 <!ENTITY sync.configure.engines.title.history 'History'>
 <!ENTITY sync.configure.engines.title.tabs 'Tabs'>
 
-<!-- Common text -->
-<!ENTITY sync.button.cancel.label 'Cancel'>
-<!ENTITY sync.button.connect.label 'Connect'>
-<!ENTITY sync.button.ok.label 'OK'>
-
-<!-- Account strings -->
-<!ENTITY sync.account.label '&syncBrand.fullName.label; (deprecated)'>
-
 <!-- Localization note (sync.default.client.name): Default string of the "Device
      name" menu item upon setting up Firefox Sync.  The placeholder &formatS1
      will be replaced by the name of the Firefox release channel and &formatS2
      by the model name of the Android device. Examples look like "Aurora on
      GT-I1950" and "Fennec on MI 2S". -->
 <!ENTITY sync.default.client.name '&formatS1; on &formatS2;'>
 
 <!-- Bookmark folder strings -->
@@ -88,30 +35,17 @@
 <!ENTITY bookmarks.folder.toolbar.label 'Bookmarks Toolbar'>
 <!ENTITY bookmarks.folder.unfiled.label 'Unsorted Bookmarks'>
 <!ENTITY bookmarks.folder.desktop.label 'Desktop Bookmarks'>
 <!ENTITY bookmarks.folder.mobile.label 'Mobile Bookmarks'>
 <!ENTITY bookmarks.folder.readinglist.label 'Reading List'>
 <!-- Pinned sites on about:home. This folder should never be shown to the user, but we have to give it a string name -->
 <!ENTITY bookmarks.folder.pinned.label 'Pinned'>
 
-<!-- Notification strings -->
-<!ENTITY sync.notification.oneaccount.label 'Only one &syncBrand.fullName.label; account is supported.'>
-<!ENTITY sync.notification.configure.saved 'Your selections have been saved.'>
-
-<!-- Incorrect settings and changing credentials. -->
-<!ENTITY sync.invalidcreds.label 'Incorrect account name or password.'>
-<!ENTITY sync.invalidserver.label 'Please enter a valid server URL'>
-<!ENTITY sync.verifying.label 'Verifying…'>
-<!ENTITY sync.new.recoverykey.status.incorrect 'Recovery Key incorrect. Please try again.'>
-
 <!-- Send tab to device. -->
-<!ENTITY sync.title.send.tab.label 'Send Tab To Devices'>
-<!ENTITY sync.button.send.label 'Send'>
-<!ENTITY sync.button.set.up.sync.label 'Set up &syncBrand.shortName.label;'>
 <!ENTITY sync.title.redirect.to.set.up.sync.label 'Set up &syncBrand.shortName.label; to send tabs'>
 <!ENTITY sync.text.redirect.to.set.up.sync.label 'Set up &syncBrand.fullName.label; on your device to send tabs to other devices.'>
 <!ENTITY sync.text.tab.sent.label 'Your tab was sent!'>
 <!ENTITY sync.text.tab.not.sent.label 'There was a problem sending your tab.'>
 
 <!-- Firefox Account strings. -->
 
 <!ENTITY fxaccount_full_label 'Firefox Accounts'>
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -16,26 +16,16 @@ from mozbuild.base import (
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-SUCCESS = '''
-You should be ready to build with Gradle and import into IntelliJ!  Test with
-
-    ./mach gradle build
-
-and in IntelliJ select File > Import project... and choose
-
-    {topobjdir}/mobile/android/gradle
-'''
-
 
 # NOTE python/mach/mach/commands/commandinfo.py references this function
 #      by name. If this function is renamed or removed, that file should
 #      be updated accordingly as well.
 def REMOVED(cls):
     """Command no longer exists! Use the Gradle configuration rooted in the top source directory instead.
 
     See https://developer.mozilla.org/en-US/docs/Simple_Firefox_for_Android_build#Developing_Firefox_for_Android_in_Android_Studio_or_IDEA_IntelliJ.
--- a/mobile/android/moz.build
+++ b/mobile/android/moz.build
@@ -26,13 +26,14 @@ DIRS += [
     'geckoview_library',
 ]
 
 if CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
     DIRS += ['bouncer'] # No ordering implied with respect to base.
 
 DIRS += ['../../xulrunner/tools/redit']
 
-TEST_DIRS += [
-    'tests',
-]
+if not CONFIG['MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE']:
+    TEST_DIRS += [
+        'tests',
+    ]
 
 SPHINX_TREES['fennec'] = 'docs'
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java
@@ -208,16 +208,19 @@ public class FxAccountStatusFragment
 
     syncServerPreference = ensureFindPreference("sync_server");
     morePreference = ensureFindPreference("more");
     morePreference.setOnPreferenceClickListener(this);
 
     syncNowPreference = ensureFindPreference("sync_now");
     syncNowPreference.setEnabled(true);
     syncNowPreference.setOnPreferenceClickListener(this);
+
+    ensureFindPreference("linktos").setOnPreferenceClickListener(this);
+    ensureFindPreference("linkprivacy").setOnPreferenceClickListener(this);
   }
 
   /**
    * We intentionally don't refresh here. Our owning activity is responsible for
    * providing an AndroidFxAccount to our refresh method in its onResume method.
    */
   @Override
   public void onResume() {
@@ -289,16 +292,26 @@ public class FxAccountStatusFragment
 
     if (preference == syncNowPreference) {
       if (fxAccount != null) {
         fxAccount.requestImmediateSync(null, null);
       }
       return true;
     }
 
+    if (TextUtils.equals("linktos", preference.getKey())) {
+      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_tos));
+      return true;
+    }
+
+    if (TextUtils.equals("linkprivacy", preference.getKey())) {
+      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_pn));
+      return true;
+    }
+
     return false;
   }
 
   protected void setCheckboxesEnabled(boolean enabled) {
     bookmarksPreference.setEnabled(enabled);
     historyPreference.setEnabled(enabled);
     tabsPreference.setEnabled(enabled);
     passwordsPreference.setEnabled(enabled);
--- a/mobile/android/services/src/main/res/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/services/src/main/res/xml/fxaccount_status_prefscreen.xml
@@ -109,30 +109,26 @@
             android:key="more"
             android:persistent="false"
             android:title="@string/fxaccount_status_more" />
 
     </PreferenceCategory>
     <PreferenceCategory
         android:key="legal_category"
         android:title="@string/fxaccount_status_legal" >
-        <Preference android:title="@string/fxaccount_status_linktos" >
-            <intent
-                android:action="android.intent.action.VIEW"
-                android:data="@string/fxaccount_link_tos"
-                android:targetClass="@string/browser_intent_class"
-                android:targetPackage="@string/browser_intent_package" />
-        </Preference>
-        <Preference android:title="@string/fxaccount_status_linkprivacy" >
-            <intent
-                android:action="android.intent.action.VIEW"
-                android:data="@string/fxaccount_link_pn"
-                android:targetClass="@string/browser_intent_class"
-                android:targetPackage="@string/browser_intent_package" />
-        </Preference>
+        <Preference
+            android:editable="false"
+            android:key="linktos"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_linktos" />
+        <Preference
+            android:editable="false"
+            android:key="linkprivacy"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_linkprivacy" />
     </PreferenceCategory>
     <PreferenceCategory
         android:key="debug_category" >
         <Preference android:key="debug_refresh" />
         <Preference android:key="debug_dump" />
         <Preference android:key="debug_force_sync" />
         <Preference android:key="debug_invalidate_certificate" />
         <Preference android:key="debug_forget_certificate" />
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -1,111 +1,37 @@
 <!-- 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/. -->
 
-  <!-- Used for setup success. -->
-  <string name="brand_short_name">&brandShortName;</string>
-
-  <string name="sync_app_name">&sync.app.name.label;</string>
-  <string name="sync_title_connect">&sync.title.adddevice.label;</string>
-  <string name="sync_title_pair">&sync.title.pair.label;</string>
-
-  <!-- J-PAKE PIN Screen -->
-  <string name="sync_subtitle_header">&sync.subtitle.header2.label;</string>
-  <string name="sync_subtitle_connect">&sync.subtitle.connectlocation2.label;</string>
-  <string name="sync_subtitle_pair">&sync.pair.connectlocation.label;</string>
-  <string name="sync_pin_default">&sync.pin.oneline.label;</string>
-  <string name="sync_link_show"><u>&sync.link.show.label;</u></string>
-  <string name="sync_link_advancedsetup"><u>&sync.link.advancedsetup.label;</u></string>
-
-  <!-- J-PAKE Waiting Screen -->
-
-  <string name="sync_jpake_subtitle_waiting">&sync.jpake.subtitle.waiting.label;</string>
+<!-- Used for setup success. -->
+<string name="brand_short_name">&brandShortName;</string>
 
-  <!-- Account Login Screen -->
-  <string name="sync_subtitle_account">&sync.subtitle.account.label;</string>
-  <string name="sync_input_username">&sync.input.username.label;</string>
-  <string name="sync_input_password">&sync.input.password.label;</string>
-  <string name="sync_input_key">&sync.input.key.label;</string>
-  <string name="sync_checkbox_server">&sync.checkbox.server.label;</string>
-  <string name="sync_input_server">&sync.input.server.label;</string>
-
-  <!-- Setup Fail -->
-  <string name="sync_title_fail">&sync.title.fail.label;</string>
-  <string name="sync_subtitle_fail">&sync.subtitle.fail.label;</string>
-  <string name="sync_button_tryagain">&sync.button.tryagain.label;</string>
-  <string name="sync_button_manual">&sync.button.manual.label;</string>
-  <string name="sync_subtitle_nointernet">&sync.subtitle.nointernet.label;</string>
-  <string name="sync_subtitle_failaccount">&sync.subtitle.failaccount.label;</string>
-  <string name="sync_subtitle_failmultiple">&sync.subtitle.failmultiple.label;</string>
-
-  <!-- Setup Success -->
-  <string name="sync_title_success">&sync.title.success.label;</string>
-  <string name="sync_subtitle_success">&sync.subtitle.success.label1;</string>
-  <string name="sync_settings">&sync.settings.label;</string>
-  <string name="sync_subtitle_manage">&sync.subtitle.manage.label1;</string>
-
-  <!-- Pair Device -->
-  <string name="sync_pair_tryagain">&sync.pair.tryagain.label;</string>
-
-  <!-- Firefox SyncAdapter Settings Screen -->
-  <string name="sync_settings_options">&sync.settings.options.label;</string>
-  <string name="sync_settings_summary_pair">&sync.summary.pair.label;</string>
+<!-- Configure Engines -->
+<string name="sync_configure_engines_title_passwords">&sync.configure.engines.title.passwords2;</string>
+<string name="sync_configure_engines_title_history">&sync.configure.engines.title.history;</string>
+<string name="sync_configure_engines_title_tabs">&sync.configure.engines.title.tabs;</string>
 
-  <!-- Configure Engines -->
-  <string name="sync_configure_engines_title">&sync.configure.engines.title.label;</string>
-  <string name="sync_configure_engines_sync_my_title">&sync.configure.engines.sync.my.title.label;</string>
-  <string name="sync_configure_engines_title_bookmarks">&sync.configure.engines.title.bookmarks;</string>
-  <string name="sync_configure_engines_title_passwords">&sync.configure.engines.title.passwords2;</string>
-  <string name="sync_configure_engines_title_history">&sync.configure.engines.title.history;</string>
-  <string name="sync_configure_engines_title_tabs">&sync.configure.engines.title.tabs;</string>
-
-  <!-- Common text -->
-  <string name="sync_button_cancel">&sync.button.cancel.label;</string>
-  <string name="sync_button_connect">&sync.button.connect.label;</string>
-  <string name="sync_button_ok">&sync.button.ok.label;</string>
-
-  <!-- Account strings -->
-  <string name="sync_account_label">&sync.account.label;</string>
+<!-- Bookmark folder strings -->
+<string name="bookmarks_folder_menu">&bookmarks.folder.menu.label;</string>
+<string name="bookmarks_folder_places">&bookmarks.folder.places.label;</string>
+<string name="bookmarks_folder_tags">&bookmarks.folder.tags.label;</string>
+<string name="bookmarks_folder_toolbar">&bookmarks.folder.toolbar.label;</string>
+<string name="bookmarks_folder_unfiled">&bookmarks.folder.unfiled.label;</string>
+<string name="bookmarks_folder_desktop">&bookmarks.folder.desktop.label;</string>
+<string name="bookmarks_folder_mobile">&bookmarks.folder.mobile.label;</string>
+<string name="bookmarks_folder_reading_list">&bookmarks.folder.readinglist.label;</string>
+<string name="bookmarks_folder_pinned">&bookmarks.folder.pinned.label;</string>
 
-  <!-- Bookmark folder strings -->
-  <string name="bookmarks_folder_menu">&bookmarks.folder.menu.label;</string>
-  <string name="bookmarks_folder_places">&bookmarks.folder.places.label;</string>
-  <string name="bookmarks_folder_tags">&bookmarks.folder.tags.label;</string>
-  <string name="bookmarks_folder_toolbar">&bookmarks.folder.toolbar.label;</string>
-  <string name="bookmarks_folder_unfiled">&bookmarks.folder.unfiled.label;</string>
-  <string name="bookmarks_folder_desktop">&bookmarks.folder.desktop.label;</string>
-  <string name="bookmarks_folder_mobile">&bookmarks.folder.mobile.label;</string>
-  <string name="bookmarks_folder_reading_list">&bookmarks.folder.readinglist.label;</string>
-  <string name="bookmarks_folder_pinned">&bookmarks.folder.pinned.label;</string>
-
-  <!-- Notification strings -->
-  <string name="sync_notification_oneaccount">&sync.notification.oneaccount.label;</string>
-  <string name="sync_notification_savedprefs">&sync.notification.configure.saved;</string>
-
-  <!-- Incorrect settings and changing credentials. -->
-  <string name="sync_invalidcreds_label">&sync.invalidcreds.label;</string>
-  <string name="sync_invalidserver_label">&sync.invalidserver.label;</string>
-  <string name="sync_verifying_label">&sync.verifying.label;</string>
-  <string name="sync_new_recoverykey_status_incorrect">&sync.new.recoverykey.status.incorrect;</string>
-
-  <!-- Send tab to device. -->
-  <string name="sync_new_tab">&new_tab;</string>
-  <string name="sync_title_send_tab">&sync.title.send.tab.label;</string>
-  <string name="sync_button_send">&sync.button.send.label;</string>
-  <string name="sync_button_set_up_sync">&sync.button.set.up.sync.label;</string>
-  <string name="sync_title_redirect_to_set_up_sync">&sync.title.redirect.to.set.up.sync.label;</string>
-  <string name="sync_text_redirect_to_set_up_sync">&sync.text.redirect.to.set.up.sync.label;</string>
-  <string name="sync_text_tab_sent">&sync.text.tab.sent.label;</string>
-  <string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
-  <string name="sync_default_client_name">&sync.default.client.name;</string>
-
-<string name="browser_intent_package">@ANDROID_PACKAGE_NAME@</string>
-<string name="browser_intent_class">@ANDROID_PACKAGE_NAME@.App</string>
+<!-- Send tab to device. -->
+<string name="sync_title_redirect_to_set_up_sync">&sync.title.redirect.to.set.up.sync.label;</string>
+<string name="sync_text_redirect_to_set_up_sync">&sync.text.redirect.to.set.up.sync.label;</string>
+<string name="sync_text_tab_sent">&sync.text.tab.sent.label;</string>
+<string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
+<string name="sync_default_client_name">&sync.default.client.name;</string>
 
 <!-- Firefox Account strings. -->
 
 <string name="fxaccount_full_label">&fxaccount_full_label;</string>
 
 <!-- Firefox Account links. -->
 <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/old-sync -->
 <string name="fxaccount_link_old_firefox">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/old-sync</string>
--- a/python/mozbuild/mozbuild/mozconfig.py
+++ b/python/mozbuild/mozbuild/mozconfig.py
@@ -102,17 +102,17 @@ class MozconfigLoader(ProcessExecutionMi
         MozconfigFindException will be raised if there is a bad state,
         including conditions from #3 above.
         """
         # Check for legacy methods first.
 
         if 'MOZ_MYCONFIG' in env:
             raise MozconfigFindException(MOZ_MYCONFIG_ERROR)
 
-        env_path = env.get('MOZCONFIG', None)
+        env_path = env.get('MOZCONFIG', None) or None
         if env_path is not None:
             if not os.path.isabs(env_path):
                 potential_roots = [self.topsrcdir, os.getcwd()]
                 # Attempt to eliminate duplicates for e.g.
                 # self.topsrcdir == os.curdir.
                 potential_roots = set(os.path.abspath(p) for p in potential_roots)
                 existing = [root for root in potential_roots
                             if os.path.exists(os.path.join(root, env_path))]
--- a/testing/instrumentation/Makefile.in
+++ b/testing/instrumentation/Makefile.in
@@ -9,15 +9,17 @@ include $(topsrcdir)/config/rules.mk
 
 # Fennec and all instrumentation tests need to be signed with the same
 # key, which means release signing them all.
 
 include $(topsrcdir)/config/android-common.mk
 
 stage-package:
 	$(NSINSTALL) -D $(_DEST_DIR)
+ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
 	$(call RELEASE_SIGN_ANDROID_APK,\
 		$(DEPTH)/mobile/android/tests/background/junit3/background-junit3-debug-unsigned-unaligned.apk,\
 		$(_DEST_DIR)/background-junit3.apk)
 	$(call RELEASE_SIGN_ANDROID_APK,\
 		$(DEPTH)/mobile/android/tests/browser/junit3/browser-junit3-debug-unsigned-unaligned.apk,\
 		$(_DEST_DIR)/browser-junit3.apk)
+endif
 	@(cd $(DEPTH)/_tests/ && tar $(TAR_CREATE_FLAGS) - instrumentation) | (cd $(PKG_STAGE) && tar -xf -)
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -102,21 +102,25 @@ GMP_TEST_PLUGIN_DIRS := \
 
 $(_DEST_DIR):
 	$(NSINSTALL) -D $@
 
 # On Android only, include a release signed Robocop APK in the test package.
 ifeq ($(MOZ_BUILD_APP),mobile/android)
 include $(topsrcdir)/config/android-common.mk
 
+ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+robocop_apk := $(topobjdir)/mobile/android/tests/browser/robocop/robocop-debug-unsigned-unaligned.apk
+else
+robocop_apk := $(topobjdir)/gradle/build/mobile/android/app/outputs/apk/app-automation-debug-androidTest-unaligned.apk
+endif
+
 stage-package-android:
 	$(NSINSTALL) -D $(_DEST_DIR)
-	$(call RELEASE_SIGN_ANDROID_APK,\
-		$(DEPTH)/mobile/android/tests/browser/robocop/robocop-debug-unsigned-unaligned.apk,\
-		$(_DEST_DIR)/robocop.apk)
+	$(call RELEASE_SIGN_ANDROID_APK,$(robocop_apk),$(_DEST_DIR)/robocop.apk)
 
 stage-package: stage-package-android
 endif
 
 stage-package:
 	$(NSINSTALL) -D $(PKG_STAGE)/mochitest && $(NSINSTALL) -D $(PKG_STAGE)/bin/plugins && $(NSINSTALL) -D $(DIST)/plugins
 	cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/mochitest
 	@cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/mochitest
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -36,16 +36,20 @@ function WebRequestEventManager(context,
         url: data.url,
         method: data.method,
         type: data.type,
         timeStamp: Date.now(),
         frameId: ExtensionManagement.getFrameId(data.windowId),
         parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
       };
 
+      if ("ip" in data) {
+        data2.ip = data.ip;
+      }
+
       // Fills in tabId typically.
       let result = {};
       extensions.emit("fill-browser-data", data.browser, data2, result);
       if (result.cancel) {
         return;
       }
 
       let optional = ["requestHeaders", "responseHeaders", "statusCode", "redirectUrl"];
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
@@ -82,16 +82,17 @@ function compareLists(list1, list2, kind
   list2.sort();
   removeDupes(list2);
   is(String(list1), String(list2), `${kind} URLs correct`);
 }
 
 function backgroundScript() {
   const BASE = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest";
 
+  let checkCompleted = true;
   let savedTabId = -1;
 
   function checkType(details) {
     let expected_type = "???";
     if (details.url.indexOf("style") != -1) {
       expected_type = "stylesheet";
     } else if (details.url.indexOf("image") != -1) {
       expected_type = "image";
@@ -192,47 +193,73 @@ function backgroundScript() {
 
   function onRecord(kind, details) {
     checkResourceType(details.type);
     if (details.url.startsWith(BASE)) {
       recorded[kind].push(details.url);
     }
   }
 
+  let completedUrls = {
+    responseStarted: new Set(),
+    completed: new Set(),
+  };
+
+  function checkIpAndRecord(kind, details) {
+    onRecord(kind, details);
+
+    // When resources are cached, the ip property is not present,
+    // so only check for the ip property the first time around.
+    if (checkCompleted && !completedUrls[kind].has(details.url)) {
+      browser.test.assertEq(details.ip, "127.0.0.1", "correct ip");
+      completedUrls[kind].add(details.url);
+    }
+  }
+
   browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ["<all_urls>"]}, ["blocking"]);
   browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {urls: ["<all_urls>"]}, ["blocking"]);
   browser.webRequest.onSendHeaders.addListener(onRecord.bind(null, "sendHeaders"), {urls: ["<all_urls>"]});
   browser.webRequest.onBeforeRedirect.addListener(onBeforeRedirect, {urls: ["<all_urls>"]});
-  browser.webRequest.onResponseStarted.addListener(onRecord.bind(null, "responseStarted"), {urls: ["<all_urls>"]});
-  browser.webRequest.onCompleted.addListener(onRecord.bind(null, "completed"), {urls: ["<all_urls>"]});
+  browser.webRequest.onResponseStarted.addListener(checkIpAndRecord.bind(null, "responseStarted"), {urls: ["<all_urls>"]});
+  browser.webRequest.onCompleted.addListener(checkIpAndRecord.bind(null, "completed"), {urls: ["<all_urls>"]});
 
-  function onTestMessage() {
-    browser.test.sendMessage("results", recorded);
+  function onTestMessage(msg) {
+    if (msg == "skipCompleted") {
+      checkCompleted = false;
+      browser.test.sendMessage("ackSkipCompleted");
+    } else {
+      browser.test.sendMessage("results", recorded);
+    }
   }
 
   browser.test.onMessage.addListener(onTestMessage);
 
   browser.test.sendMessage("ready", browser.webRequest.ResourceType);
 }
 
-function* test_once() {
+function* test_once(skipCompleted) {
   let extensionData = {
     manifest: {
       permissions: [
         "webRequest",
         "webRequestBlocking",
       ],
     },
     background: "(" + backgroundScript.toString() + ")()",
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   let [, resourceTypes] = yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
   info("webrequest extension loaded");
 
+  if (skipCompleted) {
+    extension.sendMessage("skipCompleted");
+    yield extension.awaitMessage("ackSkipCompleted");
+  }
+
   for (let key in resourceTypes) {
     let value = resourceTypes[key];
     is(key, value.toUpperCase());
   }
 
   // Check a few Firefox-specific types.
   is(resourceTypes.XBL, "xbl", "XBL resource type supported");
   is(resourceTypes.FONT, "font", "Font resource type supported");
@@ -273,14 +300,14 @@ function* test_once() {
   compareLists(recorded.responseStarted, expected_complete, "responseStarted");
   compareLists(recorded.completed, expected_complete, "completed");
 
   yield extension.unload();
   info("webrequest extension unloaded");
 }
 
 // Run the test twice to make sure it works with caching.
-add_task(test_once);
-add_task(test_once);
+add_task(function*() { yield test_once(false); });
+add_task(function*() { yield test_once(true); });
 </script>
 
 </body>
 </html>
--- a/toolkit/components/places/PlacesDBUtils.jsm
+++ b/toolkit/components/places/PlacesDBUtils.jsm
@@ -849,66 +849,50 @@ this.PlacesDBUtils = {
       tasks.log("Trigger " + stmt.getString(0));
     }
     stmt.finalize();
 
     PlacesDBUtils._executeTasks(tasks);
   },
 
   /**
-   * Collects telemetry data.
-   *
-   * There are essentially two modes of collection and the mode is
-   * determined by the presence of aHealthReportCallback. If
-   * aHealthReportCallback is not defined (the default) then we are in
-   * "Telemetry" mode. Results will be reported to Telemetry. If we are
-   * in "Health Report" mode only the probes with a true healthreport
-   * flag will be collected and the results will be reported to the
-   * aHealthReportCallback.
+   * Collects telemetry data and reports it to Telemetry.
    *
    * @param [optional] aTasks
    *        Tasks object to execute.
-   * @param [optional] aHealthReportCallback
-   *        Function to receive data relevant for Firefox Health Report.
    */
-  telemetry: function PDBU_telemetry(aTasks, aHealthReportCallback=null)
+  telemetry: function PDBU_telemetry(aTasks)
   {
     let tasks = new Tasks(aTasks);
 
-    let isTelemetry = !aHealthReportCallback;
-
     // This will be populated with one integer property for each probe result,
     // using the histogram name as key.
     let probeValues = {};
 
     // The following array contains an ordered list of entries that are
     // processed to collect telemetry data.  Each entry has these properties:
     //
     //  histogram: Name of the telemetry histogram to update.
     //  query:     This is optional.  If present, contains a database command
     //             that will be executed asynchronously, and whose result will
     //             be added to the telemetry histogram.
     //  callback:  This is optional.  If present, contains a function that must
     //             return the value that will be added to the telemetry
     //             histogram. If a query is also present, its result is passed
     //             as the first argument of the function.  If the function
     //             raises an exception, no data is added to the histogram.
-    //  healthreport: Boolean indicating whether this probe is relevant
-    //                to Firefox Health Report.
     //
     // Since all queries are executed in order by the database backend, the
     // callbacks can also use the result of previous queries stored in the
     // probeValues object.
     let probes = [
       { histogram: "PLACES_PAGES_COUNT",
-        healthreport: true,
         query:     "SELECT count(*) FROM moz_places" },
 
       { histogram: "PLACES_BOOKMARKS_COUNT",
-        healthreport: true,
         query:     `SELECT count(*) FROM moz_bookmarks b
                     JOIN moz_bookmarks t ON t.id = b.parent
                     AND t.parent <> :tags_folder
                     WHERE b.type = :type_bookmark` },
 
       { histogram: "PLACES_TAGS_COUNT",
         query:     `SELECT count(*) FROM moz_bookmarks
                     WHERE parent = :tags_folder` },
@@ -984,25 +968,19 @@ this.PlacesDBUtils = {
 
     let params = {
       tags_folder: PlacesUtils.tagsFolderId,
       type_folder: PlacesUtils.bookmarks.TYPE_FOLDER,
       type_bookmark: PlacesUtils.bookmarks.TYPE_BOOKMARK,
       places_root: PlacesUtils.placesRootId
     };
 
-    let outstandingProbes = [];
-
     for (let i = 0; i < probes.length; i++) {
       let probe = probes[i];
 
-      if (!isTelemetry && !probe.healthreport) {
-        continue;
-      }
-
       let promiseDone = new Promise((resolve, reject) => {
         if (!("query" in probe)) {
           resolve([probe]);
           return;
         }
 
         let stmt = DBConn.createAsyncStatement(probe.query);
         for (let param in params) {
@@ -1022,17 +1000,17 @@ this.PlacesDBUtils = {
           });
         } finally{
           stmt.finalize();
         }
       });
 
       // Report the result of the probe through Telemetry.
       // The resulting promise cannot reject.
-      promiseDone = promiseDone.then(
+      promiseDone.then(
         // On success
         ([aProbe, aValue]) => {
           let value = aValue;
           try {
             if ("callback" in aProbe) {
               value = aProbe.callback(value);
             }
             probeValues[aProbe.histogram] = value;
@@ -1040,24 +1018,16 @@ this.PlacesDBUtils = {
           } catch (ex) {
             Components.utils.reportError("Error adding value " + value +
                                          " to histogram " + aProbe.histogram +
                                          ": " + ex);
           }
         },
         // On failure
         this._handleError);
-
-      outstandingProbes.push(promiseDone);
-    }
-
-    if (aHealthReportCallback) {
-      Promise.all(outstandingProbes).then(() =>
-        aHealthReportCallback(probeValues)
-      );
     }
 
     PlacesDBUtils._executeTasks(tasks);
   },
 
   /**
    * Runs a list of tasks, notifying log messages to the callback.
    *
--- a/toolkit/components/places/tests/unit/test_telemetry.js
+++ b/toolkit/components/places/tests/unit/test_telemetry.js
@@ -119,23 +119,8 @@ add_task(function* test_execute()
   for (let histogramId in histograms) {
     do_print("checking histogram " + histogramId);
     let validate = histograms[histogramId];
     let snapshot = Services.telemetry.getHistogramById(histogramId).snapshot();
     validate(snapshot.sum);
     do_check_true(snapshot.counts.reduce((a, b) => a + b) > 0);
   }
 });
-
-add_test(function test_healthreport_callback() {
-  Services.prefs.clearUserPref("places.database.lastMaintenance");
-  PlacesDBUtils.telemetry(null, function onResult(data) {
-    do_check_neq(data, null);
-
-    do_check_eq(Object.keys(data).length, 2);
-    do_check_eq(data.PLACES_PAGES_COUNT, 1);
-    do_check_eq(data.PLACES_BOOKMARKS_COUNT, 1);
-
-    do_check_true(!Services.prefs.prefHasUserValue("places.database.lastMaintenance"));
-    run_next_test();
-  });
-});
-
--- a/toolkit/components/thumbnails/test/browser.ini
+++ b/toolkit/components/thumbnails/test/browser.ini
@@ -8,36 +8,33 @@ support-files =
   thumbnails_background.sjs
   thumbnails_crash_content_helper.js
   thumbnails_update.sjs
 
 [browser_thumbnails_bg_bad_url.js]
 [browser_thumbnails_bg_crash_during_capture.js]
 skip-if = buildapp == 'mulet' || !crashreporter || e10s # crashing the remote thumbnailer crashes the remote test tab
 [browser_thumbnails_bg_crash_while_idle.js]
-skip-if = buildapp == 'mulet' || !crashreporter || e10s
+skip-if = buildapp == 'mulet' || !crashreporter
 [browser_thumbnails_bg_basic.js]
 [browser_thumbnails_bg_queueing.js]
 [browser_thumbnails_bg_timeout.js]
 [browser_thumbnails_bg_redirect.js]
 [browser_thumbnails_bg_destroy_browser.js]
 [browser_thumbnails_bg_no_cookies_sent.js]
-skip-if = e10s # e10s cookie problems
 [browser_thumbnails_bg_no_cookies_stored.js]
 [browser_thumbnails_bg_no_auth_prompt.js]
 [browser_thumbnails_bg_no_alert.js]
 [browser_thumbnails_bg_no_duplicates.js]
 [browser_thumbnails_bg_captureIfMissing.js]
 [browser_thumbnails_bug726727.js]
 skip-if = buildapp == 'mulet'
 [browser_thumbnails_bug727765.js]
-skip-if = e10s # tries to open crypto/local file from the child
 [browser_thumbnails_bug818225.js]
 [browser_thumbnails_capture.js]
-skip-if = e10s # tries to call drawWindow with a remote browser.
 [browser_thumbnails_expiration.js]
 [browser_thumbnails_privacy.js]
 [browser_thumbnails_redirect.js]
 skip-if = e10s # bug 1050869
 [browser_thumbnails_storage.js]
 [browser_thumbnails_storage_migrate3.js]
 skip-if = buildapp == 'mulet'
 [browser_thumbnails_update.js]
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -32,16 +32,17 @@ window.addEventListener("load", function
 // Each property in this object corresponds to a property in Troubleshoot.jsm's
 // snapshot data.  Each function is passed its property's corresponding data,
 // and it's the function's job to update the page with it.
 var snapshotFormatters = {
 
   application: function application(data) {
     $("application-box").textContent = data.name;
     $("useragent-box").textContent = data.userAgent;
+    $("osarch-box").textContent = data.osVersion + " " + data.arch;
     $("supportLink").href = data.supportURL;
     let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
     if (data.vendor)
       version += " (" + data.vendor + ")";
     $("version-box").textContent = version;
     $("buildid-box").textContent = data.buildID;
     if (data.updateChannel)
       $("updatechannel-box").textContent = data.updateChannel;
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -125,16 +125,25 @@
             <th class="column">
               &aboutSupport.appBasicsUserAgent;
             </th>
 
             <td id="useragent-box">
             </td>
           </tr>
 
+          <tr>
+            <th class="column">
+              &aboutSupport.appBasicsOS;
+            </th>
+
+            <td id="osarch-box">
+            </td>
+          </tr>
+
           <tr id="profile-row" class="no-copy">
             <th class="column">
 #ifdef XP_WIN
               &aboutSupport.appBasicsProfileDirWinMac;
 #else
 #ifdef XP_MACOSX
               &aboutSupport.appBasicsProfileDirWinMac;
 #else
--- a/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
@@ -49,16 +49,17 @@ This is likely the same like id.heading 
 <!-- LOCALIZATION NOTE (aboutSupport.appBasicsProfileDirWinMac):
 This is the Windows- and Mac-specific variant of aboutSupport.appBasicsProfileDir.
 Windows/Mac use the term "Folder" instead of "Directory" -->
 <!ENTITY aboutSupport.appBasicsProfileDirWinMac "Profile Folder">
 
 <!ENTITY aboutSupport.appBasicsEnabledPlugins "Enabled Plugins">
 <!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
 <!ENTITY aboutSupport.appBasicsUserAgent "User Agent">
+<!ENTITY aboutSupport.appBasicsOS "OS">
 <!ENTITY aboutSupport.appBasicsMemoryUse "Memory Use">
 
 <!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
 <!ENTITY aboutSupport.appBasicsServiceWorkers "Registered Service Workers">
 
 <!ENTITY aboutSupport.appBasicsProfiles "Profiles">
 
 <!ENTITY aboutSupport.appBasicsMultiProcessSupport "Multiprocess Windows">
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -173,18 +173,24 @@ this.Troubleshoot = {
 // Each data provider is a name => function mapping.  When a snapshot is
 // captured, each provider's function is called, and it's the function's job to
 // generate the provider's data.  The function is passed a "done" callback, and
 // when done, it must pass its data to the callback.  The resulting snapshot
 // object will contain a name => data entry for each provider.
 var dataProviders = {
 
   application: function application(done) {
+
+    let sysInfo = Cc["@mozilla.org/system-info;1"].
+                  getService(Ci.nsIPropertyBag2);
+
     let data = {
       name: Services.appinfo.name,
+      osVersion: sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"),
+      arch: sysInfo.getProperty("arch"),
       version: AppConstants.MOZ_APP_VERSION_DISPLAY,
       buildID: Services.appinfo.appBuildID,
       userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].
                  getService(Ci.nsIHttpProtocolHandler).
                  userAgent,
       safeMode: Services.appinfo.inSafeMode,
     };
 
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -328,16 +328,25 @@ HttpObserverManager = {
       let data = {
         url: channel.URI.spec,
         method: channel.requestMethod,
         browser: browser,
         type: WebRequestCommon.typeForPolicyType(policyType),
         windowId: loadInfo ? loadInfo.outerWindowID : 0,
         parentWindowId: loadInfo ? loadInfo.parentOuterWindowID : 0,
       };
+
+      let httpChannel = channel.QueryInterface(Ci.nsIHttpChannelInternal);
+      try {
+        data.ip = httpChannel.remoteAddress;
+      } catch (e) {
+        // The remoteAddress getter throws if the address is unavailable,
+        // but ip is an optional property so just ignore the exception.
+      }
+
       if (extraData) {
         Object.assign(data, extraData);
       }
       if (opts.requestHeaders) {
         if (!requestHeaders) {
           requestHeaders = this.getHeaders(channel, "visitRequestHeaders");
         }
         data.requestHeaders = requestHeaders;
--- a/toolkit/modules/tests/browser/browser_Troubleshoot.js
+++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js
@@ -107,16 +107,24 @@ const SNAPSHOT_SCHEMA = {
         buildID: {
           required: true,
           type: "string",
         },
         userAgent: {
           required: true,
           type: "string",
         },
+        osVersion: {
+          required: true,
+          type: "string",
+        },
+        arch: {
+          required: true,
+          type: "string",
+        },
         vendor: {
           type: "string",
         },
         updateChannel: {
           type: "string",
         },
         supportURL: {
           type: "string",
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4208,25 +4208,31 @@ this.XPIProvider = {
   /**
    * Determine if an add-on should be blocking e10s if enabled.
    *
    * @param  aAddon
    *         The add-on to test
    * @return true if enabling the add-on should block e10s
    */
   isBlockingE10s: function(aAddon) {
-    // Only extensions change behaviour
-    if (aAddon.type != "extension")
+    if (aAddon.type != "extension" &&
+        aAddon.type != "webextension" &&
+        aAddon.type != "theme")
       return false;
 
     // The hotfix is exempt
     let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
     if (hotfixID && hotfixID == aAddon.id)
       return false;
 
+    // The default theme is exempt
+    if (aAddon.type == "theme" &&
+        aAddon.internalName == XPIProvider.defaultSkin)
+      return false;
+
     // System add-ons are exempt
     let locName = aAddon._installLocation ? aAddon._installLocation.name
                                           : undefined;
     if (locName == KEY_APP_SYSTEM_DEFAULTS ||
         locName == KEY_APP_SYSTEM_ADDONS)
       return false;
 
     return true;
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -1399,18 +1399,20 @@ this.XPIDatabase = {
     this.saveChanges();
   },
 
   updateAddonsBlockingE10s: function() {
     let blockE10s = false;
     for (let [, addon] of this.addonDB) {
       let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);
 
-      if (active && XPIProvider.isBlockingE10s(addon))
+      if (active && XPIProvider.isBlockingE10s(addon)) {
         blockE10s = true;
+        break;
+      }
     }
     Preferences.set(PREF_E10S_BLOCKED_BY_ADDONS, blockE10s);
   },
 
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons: function() {
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -320,23 +320,28 @@ ifeq ($(MOZ_BUILD_APP),mobile/android)
 UPLOAD_EXTRA_FILES += robocop.apk
 UPLOAD_EXTRA_FILES += fennec_ids.txt
 UPLOAD_EXTRA_FILES += geckoview_library/geckoview_library.zip
 UPLOAD_EXTRA_FILES += geckoview_library/geckoview_assets.zip
 
 # Robocop/Robotium tests, Android Background tests, and Fennec need to
 # be signed with the same key, which means release signing them all.
 
-ROBOCOP_PATH = $(topobjdir)/mobile/android/tests/browser/robocop
+ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+robocop_apk := $(topobjdir)/mobile/android/tests/browser/robocop/robocop-debug-unsigned-unaligned.apk
+else
+robocop_apk := $(topobjdir)/gradle/build/mobile/android/app/outputs/apk/app-automation-debug-androidTest-unaligned.apk
+endif
+
 # Normally, $(NSINSTALL) would be used instead of cp, but INNER_ROBOCOP_PACKAGE
 # is used in a series of commands that run under a "cd something", while
 # $(NSINSTALL) is relative.
 INNER_ROBOCOP_PACKAGE= \
   cp $(GECKO_APP_AP_PATH)/fennec_ids.txt $(ABS_DIST) && \
-  $(call RELEASE_SIGN_ANDROID_APK,$(ROBOCOP_PATH)/robocop-debug-unsigned-unaligned.apk,$(ABS_DIST)/robocop.apk)
+  $(call RELEASE_SIGN_ANDROID_APK,$(robocop_apk),$(ABS_DIST)/robocop.apk)
 endif
 else
 INNER_ROBOCOP_PACKAGE=echo 'Testing is disabled - No Android Robocop for you'
 endif
 
 ifdef MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER
 UPLOAD_EXTRA_FILES += bouncer.apk
 
@@ -465,16 +470,25 @@ OMNIJAR_NAME := $(notdir $(OMNIJAR_NAME)
 # and the res/ directory is taken from the ap_ as part of the regular
 # packaging.
 
 PKG_SUFFIX = .apk
 
 INNER_SZIP_LIBRARIES = \
   $(if $(ALREADY_SZIPPED),,$(foreach lib,$(SZIP_LIBRARIES),host/bin/szip $(MOZ_SZIP_FLAGS) $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/$(lib) && )) true
 
+ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+INNER_CHECK_R_TXT=echo 'No R.txt checking for you!'
+else
+INNER_CHECK_R_TXT=\
+  ((test ! -f $(GECKO_APP_AP_PATH)/R.txt && echo "*** Warning: The R.txt that is being packaged might not agree with the R.txt that was built. This is normal during l10n repacks.") || \
+    diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \
+    (echo "*** Error: The R.txt that was built and the R.txt that is being packaged are not the same. Rebuild mobile/android/base and re-package." && exit 1))
+endif
+
 # Insert $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/classes.dex into
 # $(ABS_DIST)/gecko.ap_, producing $(ABS_DIST)/gecko.apk.
 INNER_MAKE_APK = \
   ( cd $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH) && \
     unzip -o $(ABS_DIST)/gecko.ap_ && \
     rm $(ABS_DIST)/gecko.ap_ && \
     $(ZIP) -r9D $(ABS_DIST)/gecko.ap_ assets && \
     $(ZIP) $(if $(ALREADY_SZIPPED),-0 ,$(if $(MOZ_ENABLE_SZIP),-0 ))$(ABS_DIST)/gecko.ap_ $(ASSET_SO_LIBRARIES) && \
@@ -485,32 +499,30 @@ INNER_MAKE_APK = \
   rm -f $(ABS_DIST)/gecko.apk && \
   cp $(ABS_DIST)/gecko.ap_ $(ABS_DIST)/gecko.apk && \
   $(ZIP) -j0 $(ABS_DIST)/gecko.apk $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/classes.dex && \
   cp $(ABS_DIST)/gecko.apk $(ABS_DIST)/gecko-unsigned-unaligned.apk && \
   $(RELEASE_JARSIGNER) $(ABS_DIST)/gecko.apk && \
   $(ZIPALIGN) -f -v 4 $(ABS_DIST)/gecko.apk $(PACKAGE)
 
 ifeq ($(MOZ_BUILD_APP),mobile/android)
-INNER_MAKE_PACKAGE	= \
+INNER_MAKE_PACKAGE = \
   $(INNER_SZIP_LIBRARIES) && \
   make -C $(GECKO_APP_AP_PATH) gecko-nodeps.ap_ && \
   cp $(GECKO_APP_AP_PATH)/gecko-nodeps.ap_ $(ABS_DIST)/gecko.ap_ && \
-  ( (test ! -f $(GECKO_APP_AP_PATH)/R.txt && echo "*** Warning: The R.txt that is being packaged might not agree with the R.txt that was built. This is normal during l10n repacks.") || \
-    diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \
-    (echo "*** Error: The R.txt that was built and the R.txt that is being packaged are not the same. Rebuild mobile/android/base and re-package." && exit 1)) && \
+  $(INNER_CHECK_R_TXT) && \
   $(INNER_MAKE_APK) && \
   $(INNER_ROBOCOP_PACKAGE) && \
   $(INNER_INSTALL_BOUNCER_PACKAGE) && \
   $(INNER_MAKE_GECKOLIBS_AAR) && \
   $(INNER_MAKE_GECKOVIEW_LIBRARY)
 endif
 
 ifeq ($(MOZ_BUILD_APP),mobile/android/b2gdroid)
-INNER_MAKE_PACKAGE	= \
+INNER_MAKE_PACKAGE = \
   $(INNER_SZIP_LIBRARIES) && \
   cp $(topobjdir)/mobile/android/b2gdroid/app/classes.dex $(ABS_DIST)/classes.dex && \
   cp $(topobjdir)/mobile/android/b2gdroid/app/b2gdroid-unsigned-unaligned.apk $(ABS_DIST)/gecko.ap_ && \
   $(INNER_MAKE_APK)
 endif
 
 # Language repacks root the resources contained in assets/omni.ja
 # under assets/, but the repacks expect them to be rooted at /.