Merge m-c to inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 25 Jan 2016 14:06:08 -0800
changeset 281542 2ecefc09d9af7ad20ed0deef0544eceab4d1cd2c
parent 281513 7e32d3531b96def705f7e5a84045365d29716ce1 (current diff)
parent 281541 aa90f482e16db77cdb7dea84564ea1cbd8f7f6b3 (diff)
child 281543 d19d71fdf85999caf68cd02c918e137d99ac8e2d
push id70835
push userkwierso@gmail.com
push dateMon, 25 Jan 2016 22:06:18 +0000
treeherdermozilla-inbound@2ecefc09d9af [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 m-c to inbound a=merge
CLOBBER
browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive.png
browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive@2x.png
browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg
browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg
browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive.png
browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive@2x.png
browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive.png
browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive@2x.png
devtools/client/themes/floating-scrollbars-light.css
devtools/client/themes/floating-scrollbars.css
mobile/android/base/java/org/mozilla/gecko/util/ScreenshotObserver.java
--- a/.hgtags
+++ b/.hgtags
@@ -118,8 +118,9 @@ 2c951493eef5b50b8085ef78ffe0d7902ff3d593
 98086da94ccdc88f6de86774ce3d1fa258dc7c44 FIREFOX_AURORA_38_BASE
 1b6bf6612c0f4d4fee81d18bf18016e692f874e1 FIREFOX_AURORA_39_BASE
 66a95a483d2c77dfc387019336d18093acd6aac2 FIREFOX_AURORA_40_BASE
 312c68b16549de9cea1557f461d5d234bd5e0a7d FIREFOX_AURORA_41_BASE
 7a19194812eb767bee7cdf8fc36ba9a383c1bead FIREFOX_AURORA_42_BASE
 fcef8ded82219c89298b4e376cfbdfba79a1d35a FIREFOX_AURORA_43_BASE
 67a788db9f07822cfef52351bbbe3745dff8bd7f FIREFOX_AURORA_44_BASE
 99137d6d4061f408ae0869122649d8bdf489cc30 FIREFOX_AURORA_45_BASE
+67c66c2878aed17ae3096d7db483ddbb2293c503 FIREFOX_AURORA_46_BASE
--- a/browser/base/content/browser-trackingprotection.js
+++ b/browser/base/content/browser-trackingprotection.js
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var TrackingProtection = {
+  // If the user ignores the doorhanger, we stop showing it after some time.
   MAX_INTROS: 0,
   PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
   PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
   enabledGlobally: false,
   enabledInPrivateWindows: false,
   container: null,
   content: null,
   icon: null,
@@ -101,21 +102,23 @@ var TrackingProtection = {
     let isAllowing = state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT;
 
     if (isBlocking) {
       this.icon.setAttribute("tooltiptext", this.activeTooltipText);
       this.icon.setAttribute("state", "blocked-tracking-content");
       this.content.setAttribute("state", "blocked-tracking-content");
 
       // Open the tracking protection introduction panel, if applicable.
-      let introCount = gPrefService.getIntPref("privacy.trackingprotection.introCount");
-      if (introCount < TrackingProtection.MAX_INTROS) {
-        gPrefService.setIntPref("privacy.trackingprotection.introCount", ++introCount);
-        gPrefService.savePrefFile(null);
-        this.showIntroPanel();
+      if (this.enabledGlobally) {
+        let introCount = gPrefService.getIntPref("privacy.trackingprotection.introCount");
+        if (introCount < TrackingProtection.MAX_INTROS) {
+          gPrefService.setIntPref("privacy.trackingprotection.introCount", ++introCount);
+          gPrefService.savePrefFile(null);
+          this.showIntroPanel();
+        }
       }
 
       this.shieldHistogramAdd(2);
     } else if (isAllowing) {
       this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
       this.icon.setAttribute("state", "loaded-tracking-content");
       this.content.setAttribute("state", "loaded-tracking-content");
 
@@ -178,29 +181,37 @@ var TrackingProtection = {
     this.eventsHistogramAdd(2);
 
     // Hide the control center.
     document.getElementById("identity-popup").hidePopup();
 
     BrowserReload();
   },
 
+  dontShowIntroPanelAgain() {
+    // This function may be called in private windows, but it does not change
+    // any preference unless Tracking Protection is enabled globally.
+    if (this.enabledGlobally) {
+      gPrefService.setIntPref("privacy.trackingprotection.introCount",
+                              this.MAX_INTROS);
+      gPrefService.savePrefFile(null);
+    }
+  },
+
   showIntroPanel: Task.async(function*() {
     let brandBundle = document.getElementById("bundle_brand");
     let brandShortName = brandBundle.getString("brandShortName");
 
     let openStep2 = () => {
       // When the user proceeds in the tour, adjust the counter to indicate that
       // the user doesn't need to see the intro anymore.
-      gPrefService.setIntPref("privacy.trackingprotection.introCount",
-                              this.MAX_INTROS);
-      gPrefService.savePrefFile(null);
+      this.dontShowIntroPanelAgain();
 
       let nextURL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
-                    "#step2";
+                    "?step=2&newtab=true";
       switchToTabHavingURI(nextURL, true, {
         // Ignore the fragment in case the intro is shown on the tour page
         // (e.g. if the user manually visited the tour or clicked the link from
         // about:privatebrowsing) so we can avoid a reload.
         ignoreFragment: true,
       });
     };
 
@@ -215,13 +226,14 @@ var TrackingProtection = {
         style: "primary",
       },
     ];
 
     let panelTarget = yield UITour.getTarget(window, "trackingProtection");
     UITour.initForBrowser(gBrowser.selectedBrowser, window);
     UITour.showInfo(window, panelTarget,
                     gNavigatorBundle.getString("trackingProtection.intro.title"),
-                    gNavigatorBundle.getFormattedString("trackingProtection.intro.description",
+                    gNavigatorBundle.getFormattedString("trackingProtection.intro.description2",
                                                         [brandShortName]),
-                    undefined, buttons);
+                    undefined, buttons,
+                    { closeButtonCallback: () => this.dontShowIntroPanelAgain() });
   }),
 };
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7877,16 +7877,21 @@ var AboutPrivateBrowsingListener = {
         OpenBrowserWindow({private: true});
     });
     window.messageManager.addMessageListener(
       "AboutPrivateBrowsing:ToggleTrackingProtection",
       msg => {
         const PREF = "privacy.trackingprotection.pbmode.enabled";
         Services.prefs.setBoolPref(PREF, !Services.prefs.getBoolPref(PREF));
     });
+    window.messageManager.addMessageListener(
+      "AboutPrivateBrowsing:DontShowIntroPanelAgain",
+      msg => {
+        TrackingProtection.dontShowIntroPanelAgain();
+    });
   }
 };
 
 function TabModalPromptBox(browser) {
   this._weakBrowserRef = Cu.getWeakReference(browser);
 }
 
 TabModalPromptBox.prototype = {
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -224,16 +224,18 @@ var AboutHomeListener = {
 AboutHomeListener.init(this);
 
 var AboutPrivateBrowsingListener = {
   init(chromeGlobal) {
     chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
                                   false, true);
     chromeGlobal.addEventListener("AboutPrivateBrowsingToggleTrackingProtection", this,
                                   false, true);
+    chromeGlobal.addEventListener("AboutPrivateBrowsingDontShowIntroPanelAgain", this,
+                                  false, true);
   },
 
   get isAboutPrivateBrowsing() {
     return content.document.documentURI.toLowerCase() == "about:privatebrowsing";
   },
 
   handleEvent(aEvent) {
     if (!this.isAboutPrivateBrowsing) {
@@ -241,16 +243,19 @@ var AboutPrivateBrowsingListener = {
     }
     switch (aEvent.type) {
       case "AboutPrivateBrowsingOpenWindow":
         sendAsyncMessage("AboutPrivateBrowsing:OpenPrivateWindow");
         break;
       case "AboutPrivateBrowsingToggleTrackingProtection":
         sendAsyncMessage("AboutPrivateBrowsing:ToggleTrackingProtection");
         break;
+      case "AboutPrivateBrowsingDontShowIntroPanelAgain":
+        sendAsyncMessage("AboutPrivateBrowsing:DontShowIntroPanelAgain");
+        break;
     }
   },
 };
 AboutPrivateBrowsingListener.init(this);
 
 var AboutReaderListener = {
 
   _articlePromise: null,
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
@@ -52,16 +52,18 @@ document.addEventListener("DOMContentLoa
 
   document.title = stringBundle.GetStringFromName("title");
   document.getElementById("favicon")
           .setAttribute("href", FAVICON_PRIVACY);
   document.getElementById("enableTrackingProtection")
           .addEventListener("click", toggleTrackingProtection);
   document.getElementById("disableTrackingProtection")
           .addEventListener("click", toggleTrackingProtection);
+  document.getElementById("startTour")
+          .addEventListener("click", dontShowIntroPanelAgain);
 
   let formatURLPref = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
                         .getService(Ci.nsIURLFormatter).formatURLPref;
   document.getElementById("startTour").setAttribute("href",
                      formatURLPref("privacy.trackingprotection.introURL"));
   document.getElementById("learnMore").setAttribute("href",
                      formatURLPref("app.support.baseURL") + "private-browsing");
 
@@ -76,8 +78,15 @@ function openPrivateWindow() {
 }
 
 function toggleTrackingProtection() {
   // Ask chrome to enable tracking protection
   document.dispatchEvent(
     new CustomEvent("AboutPrivateBrowsingToggleTrackingProtection",
                     {bubbles:true}));
 }
+
+function dontShowIntroPanelAgain() {
+  // Ask chrome to disable the doorhanger
+  document.dispatchEvent(
+    new CustomEvent("AboutPrivateBrowsingDontShowIntroPanelAgain",
+                    {bubbles:true}));
+}
--- a/browser/components/uitour/test/browser_trackingProtection.js
+++ b/browser/components/uitour/test/browser_trackingProtection.js
@@ -47,17 +47,17 @@ add_task(function* test_trackingPages() 
     yield new Promise((resolve, reject) => {
       waitForPopupAtAnchor(TOOLTIP_PANEL, TOOLTIP_ANCHOR, resolve,
                            "Intro panel should appear");
     });
 
     is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS, "Check intro count increased");
 
     let step2URL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
-                    "#step2";
+                   "?step=2&newtab=true";
     let buttons = document.getElementById("UITourTooltipButtons");
 
     info("Click the step text and nothing should happen");
     let tabCount = gBrowser.tabs.length;
     yield EventUtils.synthesizeMouseAtCenter(buttons.children[0], {});
     is(gBrowser.tabs.length, tabCount, "Same number of tabs should be open");
 
     info("Resetting count to test that viewing the tour prevents future panels");
--- a/browser/config/version.txt
+++ b/browser/config/version.txt
@@ -1,1 +1,1 @@
-46.0a1
+47.0a1
--- a/browser/config/version_display.txt
+++ b/browser/config/version_display.txt
@@ -1,1 +1,1 @@
-46.0a1
+47.0a1
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -327,18 +327,20 @@ offlineApps.manageUsageAccessKey=S
 
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
 identity.unknown.tooltip=This website does not supply identity information.
 
 trackingProtection.intro.title=How Tracking Protection works
-# LOCALIZATION NOTE (trackingProtection.intro.description): %S is brandShortName
-trackingProtection.intro.description=When the shield is visible, that means Firefox is actively blocking content that tracks you.
+# LOCALIZATION NOTE (trackingProtection.intro.description2):
+# %S is brandShortName. This string should match the one from Step 1 of the tour
+# when it starts from the button shown when a new private window is opened.
+trackingProtection.intro.description2=When you see the shield, %S is blocking some parts of the page that could track your browsing activity.
 # LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
 trackingProtection.intro.step1of3=1 of 3
 trackingProtection.intro.nextButton.label=Next
 
 trackingProtection.icon.activeTooltip=Tracking attempts blocked
 trackingProtection.icon.disabledTooltip=Tracking content detected
 
 # Edit Bookmark UI
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -59,22 +59,16 @@
   content: "";
   display: -moz-box;
   height: 2px;
   margin-top: -2px;
   position: relative;
   z-index: 2; /* navbar is at 1 */
 }
 
-@media (-moz-mac-yosemite-theme) {
-  #navigator-toolbox::after {
-    background-image: linear-gradient(to top, hsla(0,0%,0%,.1), hsla(0,0%,0%,.1) 1px, hsla(0,0%,100%,0) 1px, hsla(0,0%,100%,0) 2px, transparent 3px);
-  }
-}
-
 #navigator-toolbox toolbarbutton:-moz-lwtheme {
   color: inherit;
   text-shadow: inherit;
 }
 
 #main-window {
   -moz-appearance: none;
   background-color: #eeeeee;
@@ -151,20 +145,16 @@ toolbarseparator {
   background: url(chrome://browser/skin/Toolbar-background-noise.png) hsl(0,0%,83%);
 }
 
 /* remove noise texture on Yosemite */
 @media (-moz-mac-yosemite-theme) {
   #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
     background-image: none;
   }
-
-  #navigator-toolbox > toolbar:-moz-window-inactive:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
-    background-color: hsl(0,0%,95%);
-  }
 }
 
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar) {
   overflow: -moz-hidden-unscrollable;
   max-height: 4em;
   transition: min-height 170ms ease-out, max-height 170ms ease-out;
 }
 
@@ -197,20 +187,16 @@ toolbarseparator {
   }
 }
 
 /* remove noise texture on Yosemite */
 @media (-moz-mac-yosemite-theme) {
   #nav-bar {
     background: linear-gradient(hsl(0,0%,93%), hsl(0,0%,83%));
   }
-
-  #nav-bar:-moz-window-inactive {
-    background: linear-gradient(hsl(0,0%,97%), hsl(0,0%,95%));
-  }
 }
 
 /* Draw the bottom border of the tabs toolbar when it's not using
    -moz-appearance: toolbar. */
 #main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
 #main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
 #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
   border-top: 1px solid hsla(0,0%,0%,.3);
@@ -227,20 +213,16 @@ toolbarseparator {
   #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) {
     border-top: 1px solid hsla(0,0%,0%,.2);
     background-clip: padding-box;
     margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
     /* Position the toolbar above the bottom of background tabs */
     position: relative;
     z-index: 1;
   }
-
-  #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-window-inactive:not(:-moz-lwtheme) {
-    border-top-color: hsla(0,0%,0%,.05);
-  }
 }
 
 #nav-bar-customization-target {
   padding: 4px;
 }
 
 #PersonalToolbar {
   padding: 0 4px 4px;
@@ -2485,64 +2467,16 @@ toolbarbutton.chevron > .toolbarbutton-m
     list-style-image: url("chrome://browser/skin/tabbrowser/connecting@2x.png");
   }
 
   .tab-throbber[progress] {
     list-style-image: url("chrome://browser/skin/tabbrowser/loading@2x.png");
   }
 }
 
-@media (-moz-mac-yosemite-theme) {
-  .tab-background-middle[visuallyselected=true]:-moz-window-inactive {
-    background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive.png),
-                      @fgTabTextureYosemiteInactive@,
-                      none;
-  }
-
-  .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
-  .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
-    background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive.png);
-  }
-
-  .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
-  .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
-    background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive.png);
-  }
-
-  .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
-  .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
-    background-image: url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg);
-    background-size: 100% 100%;
-  }
-
-  .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
-  .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
-    background-image: url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg);
-    background-size: 100% 100%;
-  }
-
-  @media (min-resolution: 2dppx) {
-    .tab-background-middle[visuallyselected=true]:-moz-window-inactive {
-      background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive@2x.png),
-                        @fgTabTextureYosemiteInactive@,
-                        none;
-    }
-
-    .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
-    .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
-      background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive@2x.png);
-    }
-
-    .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
-    .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
-      background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive@2x.png);
-    }
-  }
-}
-
 .tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([visuallyselected="true"]) {
   opacity: .9;
 }
 
 /*
  * Force the overlay to create a new stacking context so it always appears on
  * top of the icon.
  */
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -233,24 +233,16 @@ browser.jar:
   skin/classic/browser/yosemite/menuPanel-help.png                     (menuPanel-help-yosemite.png)
   skin/classic/browser/yosemite/menuPanel-help@2x.png                  (menuPanel-help-yosemite@2x.png)
   skin/classic/browser/yosemite/menuPanel-small.png                    (menuPanel-small-yosemite.png)
   skin/classic/browser/yosemite/menuPanel-small@2x.png                 (menuPanel-small-yosemite@2x.png)
   skin/classic/browser/yosemite/reload-stop-go.png                     (reload-stop-go-yosemite.png)
   skin/classic/browser/yosemite/reload-stop-go@2x.png                  (reload-stop-go-yosemite@2x.png)
   skin/classic/browser/yosemite/sync-horizontalbar.png                 (sync-horizontalbar-yosemite.png)
   skin/classic/browser/yosemite/sync-horizontalbar@2x.png              (sync-horizontalbar-yosemite@2x.png)
-  skin/classic/browser/yosemite/tab-selected-end-inactive.svg          (tabbrowser/tab-selected-end-yosemite-inactive.svg)
-  skin/classic/browser/yosemite/tab-selected-start-inactive.svg        (tabbrowser/tab-selected-start-yosemite-inactive.svg)
-  skin/classic/browser/yosemite/tab-active-middle-inactive.png         (tabbrowser/tab-active-middle-yosemite-inactive.png)
-  skin/classic/browser/yosemite/tab-active-middle-inactive@2x.png      (tabbrowser/tab-active-middle-yosemite-inactive@2x.png)
-  skin/classic/browser/yosemite/tab-stroke-end-inactive.png            (tabbrowser/tab-stroke-end-yosemite-inactive.png)
-  skin/classic/browser/yosemite/tab-stroke-end-inactive@2x.png         (tabbrowser/tab-stroke-end-yosemite-inactive@2x.png)
-  skin/classic/browser/yosemite/tab-stroke-start-inactive.png          (tabbrowser/tab-stroke-start-yosemite-inactive.png)
-  skin/classic/browser/yosemite/tab-stroke-start-inactive@2x.png       (tabbrowser/tab-stroke-start-yosemite-inactive@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
@@ -269,10 +261,10 @@ browser.jar:
 % override chrome://browser/skin/menuPanel-help.png                        chrome://browser/skin/yosemite/menuPanel-help.png                       os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-help@2x.png                     chrome://browser/skin/yosemite/menuPanel-help@2x.png                    os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-small.png                       chrome://browser/skin/yosemite/menuPanel-small.png                      os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-small@2x.png                    chrome://browser/skin/yosemite/menuPanel-small@2x.png                   os=Darwin osversion>=10.10
 % override chrome://browser/skin/preferences/checkbox.png                  chrome://browser/skin/yosemite/preferences/checkbox.png                 os=Darwin osversion>=10.10
 % override chrome://browser/skin/preferences/checkbox@2x.png               chrome://browser/skin/yosemite/preferences/checkbox@2x.png              os=Darwin osversion>=10.10
 % override chrome://browser/skin/reload-stop-go.png                        chrome://browser/skin/yosemite/reload-stop-go.png                       os=Darwin osversion>=10.10
 % override chrome://browser/skin/reload-stop-go@2x.png                     chrome://browser/skin/yosemite/reload-stop-go@2x.png                    os=Darwin osversion>=10.10
-% override chrome://browser/skin/sync-horizontalbar.png                    chrome://browser/skin/yosemite/sync-horizontalbar.png               os=Darwin osversion>=10.10
-% override chrome://browser/skin/sync-horizontalbar@2x.png                 chrome://browser/skin/yosemite/sync-horizontalbar@2x.png            os=Darwin osversion>=10.10
+% override chrome://browser/skin/sync-horizontalbar.png                    chrome://browser/skin/yosemite/sync-horizontalbar.png                   os=Darwin osversion>=10.10
+% override chrome://browser/skin/sync-horizontalbar@2x.png                 chrome://browser/skin/yosemite/sync-horizontalbar@2x.png                os=Darwin osversion>=10.10
--- a/browser/themes/osx/shared.inc
+++ b/browser/themes/osx/shared.inc
@@ -1,13 +1,12 @@
 %include ../../../toolkit/themes/osx/global/shared.inc
 %include ../shared/browser.inc
 
 %filter substitution
 
 %define fgTabTexture linear-gradient(transparent 2px, hsla(0,0%,100%,.6) 2px, hsla(0,0%,100%,.6) 3px, hsl(0,0%,99%) 3px, hsl(0,0%,93%))
-%define fgTabTextureYosemiteInactive linear-gradient(transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%))
 %define toolbarColorLWT rgba(253,253,253,0.45)
 %define fgTabTextureLWT linear-gradient(transparent 2px, rgba(254,254,254,.72) 2px, @toolbarColorLWT@)
 %define fgTabBackgroundColor transparent
 %define hudButton -moz-appearance: none; color: #434343; border-radius: 4px; border: 1px solid #b5b5b5; background: linear-gradient(#fff, #f2f2f2); box-shadow: inset 0 1px rgba(255,255,255,.8), inset 0 0 1px rgba(255,255, 255,.25), 0 1px rgba(255,255,255,.3); background-clip: padding-box; background-origin: padding-box; padding: 2px 6px;
 %define hudButtonPressed box-shadow: inset 0 1px 4px -3px #000, 0 1px rgba(255,255,255,.3);
 %define hudButtonFocused box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
deleted file mode 100644
index 9f74c4e7654d18ca9b811c778ebf8586ba525dcd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 50961db7ec08f990d349ab94ebe4296cddd72368..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none">
-  <defs>
-    <style>
-      #tab-background-fill {
-        background-color: transparent;
-        background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%));
-        background-repeat: no-repeat;
-        height: 100%;
-        width: 100%;
-      }
-    </style>
-
-    <clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox">
-      <path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/>
-    </clipPath>
-
-    <clipPath id="tab-hover-clip-path" clipPathUnits="objectBoundingBox">
-      <path d="M 0,0.2 0,1 1,1, 1,0.2 z"/>
-    </clipPath>
-  </defs>
-
-  <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-end)">
-    <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div>
-  </foreignObject>
-</svg>
deleted file mode 100644
--- a/browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none">
-  <defs>
-    <style>
-      #tab-background-fill {
-        background-color: transparent;
-        background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%));
-        background-repeat: no-repeat;
-        height: 100%;
-        width: 100%;
-      }
-    </style>
-
-    <clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox">
-      <path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/>
-    </clipPath>
-
-    <clipPath id="tab-hover-clip-path" clipPathUnits="objectBoundingBox">
-      <path d="M 0,0.2 0,1 1,1, 1,0.2 z"/>
-    </clipPath>
-  </defs>
-
-  <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-start)">
-    <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div>
-  </foreignObject>
-</svg>
deleted file mode 100644
index 4206f0c7dca0188d886a6a10f4a9ba0a6c7767dc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 21ac892703cffc3bb16e4c63e7735984b598f23c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 2acbac438c6aea4c4f3c4b0a2efb859a5f8945d7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index a12fc39a7b10829cffefb0f736eebd8a545ef19f..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.py.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-46.0a1
+47.0a1
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -44,21 +44,34 @@ add_task(function *() {
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
   yield waitForWorkersUpdate(document);
 
   assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
 
-  // XXX: race, the WorkerDebugger is ready whereas ServiceWorkerInfo
-  // doesn't has the worker registered yet on its side
+  // Ensure that the registration resolved before trying to connect to the sw
+  let frameScript = function () {
+    // Retrieve the `sw` promise created in the html page
+    let { sw } = content.wrappedJSObject;
+    sw.then(function (registration) {
+      sendAsyncMessage("sw-registered");
+    });
+  };
+  let mm = swTab.linkedBrowser.messageManager;
+  mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
+
   yield new Promise(done => {
-    require("sdk/timers").setTimeout(done, 250);
+    mm.addMessageListener("sw-registered", function listener() {
+      mm.removeMessageListener("sw-registered", listener);
+      done();
+    });
   });
+  ok(true, "Service worker registration resolved");
 
   // Retrieve the DEBUG button for the worker
   let names = [...document.querySelectorAll("#service-workers .target-name")];
   let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
   ok(name, "Found the service worker in the list");
   let debugBtn = name.parentNode.parentNode.querySelector("button");
   ok(debugBtn, "Found its debug button");
 
@@ -88,29 +101,29 @@ add_task(function *() {
   // after we destroy the toolbox.
   // The list should update once it get destroyed.
   yield waitForWorkersUpdate(document);
 
   assertHasWorker(false, document, "service-workers", SERVICE_WORKER);
 
   // Finally, unregister the service worker itself
   // Use message manager to work with e10s
-  let frameScript = function () {
+  frameScript = function () {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
     sw.then(function (registration) {
       registration.unregister().then(function (success) {
         sendAsyncMessage("sw-unregistered");
       },
       function (e) {
         dump("SW not unregistered; " + e + "\n");
       });
     });
   };
-  let mm = swTab.linkedBrowser.messageManager;
+  mm = swTab.linkedBrowser.messageManager;
   mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
 
   yield new Promise(done => {
     mm.addMessageListener("sw-unregistered", function listener() {
       mm.removeMessageListener("sw-unregistered", listener);
       done();
     });
   });
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -103,19 +103,20 @@ OptionsPanel.prototype = {
     } else {
       targetPromise = promise.resolve(this.target);
     }
 
     return targetPromise.then(() => {
       this.setupToolsList();
       this.setupToolbarButtonsList();
       this.setupThemeList();
-      this.populatePreferences();
       this.updateDefaultTheme();
     }).then(() => {
+      return this.populatePreferences();
+    }).then(() => {
       this.isReady = true;
       this.emit("ready");
       return this;
     }).then(null, function onError(aReason) {
       Cu.reportError("OptionsPanel open failed. " +
                      aReason.error + ": " + aReason.message);
     });
   },
@@ -310,17 +311,17 @@ OptionsPanel.prototype = {
         }
       }
       menulist.addEventListener("command", function() {
         setPrefAndEmit(this.getAttribute("data-pref"), this.value);
       }.bind(menulist));
     }
 
     if (this.target.activeTab) {
-      this.target.client.attachTab(this.target.activeTab._actor, (response) => {
+      return this.target.client.attachTab(this.target.activeTab._actor).then(([response,client]) => {
         this._origJavascriptEnabled = !response.javascriptEnabled;
         this.disableJSNode.checked = this._origJavascriptEnabled;
         this.disableJSNode.addEventListener("click", this._disableJSClicked, false);
       });
     } else {
       this.disableJSNode.hidden = true;
     }
   },
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js
@@ -45,10 +45,10 @@ function* testColorPickerAppearsOnColorS
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   ok(true, "The color picker was shown on click of the color swatch");
   ok(!inplaceEditor(swatch.parentNode),
     "The inplace editor wasn't shown as a result of the color swatch click");
 
-  yield hideTooltipAndWaitForRuleviewChanged(cPicker, view);
+  yield hideTooltipAndWaitForRuleViewChanged(cPicker, view);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
@@ -67,10 +67,10 @@ function* testPickingNewColor(view) {
   is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)",
     "The color swatch's background was updated");
   is(colorEl.textContent, "#010101", "The color text was updated");
   is(content.getComputedStyle(content.document.body).backgroundImage,
     "linear-gradient(to left, rgb(1, 1, 1) 25%, rgb(51, 51, 51) 95%, " +
       "rgb(0, 0, 0) 100%)",
     "The gradient has been updated correctly");
 
-  yield hideTooltipAndWaitForRuleviewChanged(cPicker, view);
+  yield hideTooltipAndWaitForRuleViewChanged(cPicker, view);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
@@ -85,17 +85,17 @@ function* testComplexMultipleColorChange
     yield simulateColorPickerChange(ruleView, picker, rgba, {
       element: content.document.body,
       name: "backgroundColor",
       value: computed
     });
   }
 
   info("Closing the color picker");
-  yield hideTooltipAndWaitForRuleviewChanged(picker.tooltip, ruleView);
+  yield hideTooltipAndWaitForRuleViewChanged(picker.tooltip, ruleView);
 }
 
 function* testOverriddenMultipleColorChanges(inspector, ruleView) {
   yield selectNode("p", inspector);
 
   info("Getting the <body> tag's color property");
   let swatch = getRuleViewProperty(ruleView, "body", "color").valueSpan
     .querySelector(".ruleview-colorswatch");
--- a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js
@@ -61,10 +61,10 @@ function* testAppears(view, swatch) {
 
   let onShown = bezier.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   ok(true, "The cubic-bezier tooltip was shown on click of the cibuc swatch");
   ok(!inplaceEditor(swatch.parentNode),
     "The inplace editor wasn't shown as a result of the cibuc swatch click");
-  yield hideTooltipAndWaitForRuleviewChanged(bezier, view);
+  yield hideTooltipAndWaitForRuleViewChanged(bezier, view);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js
+++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js
@@ -24,10 +24,10 @@ add_task(function*() {
   swatch.click();
   yield onRuleViewChanged;
 
   ok(true, "The shown event was emitted after clicking on swatch");
   ok(!inplaceEditor(swatch.parentNode),
   "The inplace editor wasn't shown as a result of the filter swatch click");
 
   yield filterTooltip.widget;
-  yield hideTooltipAndWaitForRuleviewChanged(filterTooltip, view);
+  yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view);
 });
--- a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js
@@ -8,26 +8,28 @@
 // unfinished properties/values in inplace-editors
 
 const TEST_URI = "<div>Test Element</div>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
-  yield testCreateNewMultiDuplicates(inspector, view);
-});
 
-function* testCreateNewMultiDuplicates(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 0);
+  // Note that we wait for a markup mutation here because this new rule will end
+  // up creating a style attribute on the node shown in the markup-view.
+  // (we also wait for the rule-view to refresh).
   let onMutation = inspector.once("markupmutation");
+  let onRuleViewChanged = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor,
     "color:red;color:orange;color:yellow;color:green;color:blue;color:indigo;" +
     "color:violet;");
   yield onMutation;
+  yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 7,
     "Should have created new text properties.");
   is(ruleEditor.propertyList.children.length, 8,
     "Should have created new property editors.");
 
   is(ruleEditor.rule.textProps[0].name, "color",
     "Should have correct property name");
@@ -58,9 +60,9 @@ function* testCreateNewMultiDuplicates(i
     "Should have correct property name");
   is(ruleEditor.rule.textProps[5].value, "indigo",
     "Should have correct property value");
 
   is(ruleEditor.rule.textProps[6].name, "color",
     "Should have correct property name");
   is(ruleEditor.rule.textProps[6].value, "violet",
     "Should have correct property value");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js
@@ -8,25 +8,27 @@
 // unfinished properties/values in inplace-editors.
 
 const TEST_URI = "<div>Test Element</div>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
-  yield testCreateNewMultiPriority(inspector, view);
-});
 
-function* testCreateNewMultiPriority(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 0);
+  // Note that we wait for a markup mutation here because this new rule will end
+  // up creating a style attribute on the node shown in the markup-view.
+  // (we also wait for the rule-view to refresh).
   let onMutation = inspector.once("markupmutation");
+  let onRuleViewChanged = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor,
     "color:red;width:100px;height: 100px;");
   yield onMutation;
+  yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 3,
     "Should have created new text properties.");
   is(ruleEditor.propertyList.children.length, 4,
     "Should have created new property editors.");
 
   is(ruleEditor.rule.textProps[0].name, "color",
     "Should have correct property name");
@@ -37,9 +39,9 @@ function* testCreateNewMultiPriority(ins
     "Should have correct property name");
   is(ruleEditor.rule.textProps[1].value, "100px",
     "Should have correct property value");
 
   is(ruleEditor.rule.textProps[2].name, "height",
     "Should have correct property name");
   is(ruleEditor.rule.textProps[2].value, "100px",
     "Should have correct property value");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
@@ -8,36 +8,40 @@
 // unfinished properties/values in inplace-editors
 
 const TEST_URI = "<div>Test Element</div>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
-  yield testCreateNewMultiPartialUnfinished(inspector, view);
-});
 
-function* testCreateNewMultiPartialUnfinished(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 0);
+  // Note that we wait for a markup mutation here because this new rule will end
+  // up creating a style attribute on the node shown in the markup-view.
+  // (we also wait for the rule-view to refresh).
   let onMutation = inspector.once("markupmutation");
+  let onRuleViewChanged = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor, "width: 100px; heig");
   yield onMutation;
+  yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 2,
     "Should have created a new text property.");
   is(ruleEditor.propertyList.children.length, 2,
     "Should have created a property editor.");
 
   // Value is focused, lets add multiple rules here and make sure they get added
   onMutation = inspector.once("markupmutation");
+  onRuleViewChanged = view.once("ruleview-changed");
   let valueEditor = ruleEditor.propertyList.children[1].querySelector("input");
   valueEditor.value = "10px;background:orangered;color: black;";
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
   yield onMutation;
+  yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 4,
     "Should have added the changed value.");
   is(ruleEditor.propertyList.children.length, 5,
     "Should have added the changed value editor.");
 
   is(ruleEditor.rule.textProps[0].name, "width",
     "Should have correct property name");
@@ -53,9 +57,9 @@ function* testCreateNewMultiPartialUnfin
     "Should have correct property name");
   is(ruleEditor.rule.textProps[2].value, "orangered",
     "Should have correct property value");
 
   is(ruleEditor.rule.textProps[3].name, "color",
     "Should have correct property name");
   is(ruleEditor.rule.textProps[3].value, "black",
     "Should have correct property value");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js
@@ -8,26 +8,28 @@
 // unfinished properties/values in inplace-editors.
 
 const TEST_URI = "<div>Test Element</div>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
-  yield testCreateNewMulti(inspector, view);
-});
 
-function* testCreateNewMulti(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 0);
+  // Note that we wait for a markup mutation here because this new rule will end
+  // up creating a style attribute on the node shown in the markup-view.
+  // (we also wait for the rule-view to refresh).
   let onMutation = inspector.once("markupmutation");
+  let onRuleViewChanged = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor,
     "color:blue;background : orange   ; text-align:center; " +
     "border-color: green;");
   yield onMutation;
+  yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 4,
     "Should have created a new text property.");
   is(ruleEditor.propertyList.children.length, 5,
     "Should have created a new property editor.");
 
   is(ruleEditor.rule.textProps[0].name, "color",
     "Should have correct property name");
@@ -43,9 +45,9 @@ function* testCreateNewMulti(inspector, 
     "Should have correct property name");
   is(ruleEditor.rule.textProps[2].value, "center",
     "Should have correct property value");
 
   is(ruleEditor.rule.textProps[3].name, "border-color",
     "Should have correct property name");
   is(ruleEditor.rule.textProps[3].value, "green",
     "Should have correct property value");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js
@@ -18,38 +18,45 @@ const TEST_URI = `
   </style>
   <h1 id='testid'>Styled Node</h1>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testModifyPropertyNameFilter(inspector, view);
-});
 
-function* testModifyPropertyNameFilter(inspector, view) {
+  info("Enter the test value in the search filter");
   yield setSearchFilter(view, SEARCH);
 
+  info("Focus the width property name");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let rule = ruleEditor.rule;
   let propEditor = rule.textProps[0].editor;
-  let editor = yield focusEditableField(view, propEditor.nameSpan);
+  yield focusEditableField(view, propEditor.nameSpan);
 
   info("Check that the correct rules are visible");
   is(view.element.children.length, 2, "Should have 2 rules.");
   is(rule.selectorText, "#testid", "Second rule is #testid.");
   ok(!propEditor.container.classList.contains("ruleview-highlight"),
     "width text property is not highlighted.");
   ok(rule.textProps[1].editor.container.classList
     .contains("ruleview-highlight"),
     "height text property is correctly highlighted.");
 
-  let onBlur = once(editor.input, "blur");
-  let onModification = rule._applyingModifications;
+  info("Change the width property to margin-left");
   EventUtils.sendString("margin-left", view.styleWindow);
+
+  info("Submit the change");
+  let onRuleViewChanged = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_RETURN", {});
-  yield onBlur;
-  yield onModification;
+  yield onRuleViewChanged;
 
   ok(propEditor.container.classList.contains("ruleview-highlight"),
     "margin-left text property is correctly highlighted.");
-}
+
+  // After pressing return on the property name, the value has been focused
+  // automatically. Blur it now and wait for the rule-view to refresh to avoid
+  // pending requests.
+  onRuleViewChanged = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield onRuleViewChanged;
+});
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
@@ -18,38 +18,36 @@ const TEST_URI = `
   </style>
   <h1 id='testid'>Styled Node</h1>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testModifyPropertyValueFilter(inspector, view);
-});
 
-function* testModifyPropertyValueFilter(inspector, view) {
+  info("Enter the test value in the search filter");
   yield setSearchFilter(view, SEARCH);
 
+  info("Focus the height property value");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let rule = ruleEditor.rule;
   let propEditor = rule.textProps[1].editor;
   let editor = yield focusEditableField(view, propEditor.valueSpan);
 
   info("Check that the correct rules are visible");
   is(view.element.children.length, 2, "Should have 2 rules.");
   is(rule.selectorText, "#testid", "Second rule is #testid.");
   ok(rule.textProps[0].editor.container.classList
     .contains("ruleview-highlight"),
     "width text property is correctly highlighted.");
   ok(!propEditor.container.classList.contains("ruleview-highlight"),
     "height text property is not highlighted.");
 
-  let onBlur = once(editor.input, "blur");
-  let onModification = rule._applyingModifications;
+  info("Change the height property value to 100%");
+  let onRuleViewChanged = view.once("ruleview-changed");
   EventUtils.sendString("100%", view.styleWindow);
   EventUtils.synthesizeKey("VK_RETURN", {});
-  yield onBlur;
-  yield onModification;
+  yield onRuleViewChanged;
 
   ok(propEditor.container.classList.contains("ruleview-highlight"),
     "height text property is correctly highlighted.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
@@ -18,22 +18,21 @@ const TEST_URI = `
   </style>
   <h1 id='testid'>Styled Node</h1>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testNewPropertyFilter(inspector, view);
-});
 
-function* testNewPropertyFilter(inspector, view) {
+  info("Enter the test value in the search filter");
   yield setSearchFilter(view, SEARCH);
 
+  info("Start entering a new property in the rule");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let rule = ruleEditor.rule;
   let editor = yield focusEditableField(view, ruleEditor.closeBrace);
 
   info("Check that the correct rules are visible");
   is(view.element.children.length, 2, "Should have 2 rules.");
   is(rule.selectorText, "#testid", "Second rule is #testid.");
   ok(rule.textProps[0].editor.container.classList
@@ -41,32 +40,33 @@ function* testNewPropertyFilter(inspecto
     "width text property is correctly highlighted.");
   ok(!rule.textProps[1].editor.container.classList
     .contains("ruleview-highlight"),
     "height text property is not highlighted.");
 
   info("Test creating a new property");
 
   info("Entering margin-left in the property name editor");
+  // Changing the value doesn't cause a rule-view refresh, no need to wait for
+  // ruleview-changed here.
   editor.input.value = "margin-left";
 
   info("Pressing return to commit and focus the new value field");
-  let onValueFocus = once(ruleEditor.element, "focus", true);
-  let onModifications = ruleEditor.rule._applyingModifications;
+  let onRuleViewChanged = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onValueFocus;
-  yield onModifications;
+  yield onRuleViewChanged;
 
   // Getting the new value editor after focus
   editor = inplaceEditor(view.styleDocument.activeElement);
   let propEditor = ruleEditor.rule.textProps[2].editor;
 
   info("Entering a value and bluring the field to expect a rule change");
+  onRuleViewChanged = view.once("ruleview-changed");
   editor.input.value = "100%";
-  let onBlur = once(editor.input, "blur");
-  onModifications = ruleEditor.rule._applyingModifications;
+  yield onRuleViewChanged;
+
+  onRuleViewChanged = view.once("ruleview-changed");
   editor.input.blur();
-  yield onBlur;
-  yield onModifications;
+  yield onRuleViewChanged;
 
   ok(propEditor.container.classList.contains("ruleview-highlight"),
     "margin-left text property is correctly highlighted.");
-}
+});
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -284,17 +284,17 @@ function assertHoverTooltipOn(tooltip, e
  * When a tooltip is closed, this ends up "commiting" the value changed within
  * the tooltip (e.g. the color in case of a colorpicker) which, in turn, ends up
  * setting the value of the corresponding css property in the rule-view.
  * Use this function to close the tooltip and make sure the test waits for the
  * ruleview-changed event.
  * @param {Tooltip} tooltip
  * @param {CSSRuleView} view
  */
-function* hideTooltipAndWaitForRuleviewChanged(tooltip, view) {
+function* hideTooltipAndWaitForRuleViewChanged(tooltip, view) {
   let onModified = view.once("ruleview-changed");
   tooltip.hide();
   yield onModified;
 }
 
 /**
  * Listen for a new tab to open and return a promise that resolves when one
  * does and completes the load event.
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -274,18 +274,18 @@ devtools.jar:
     skin/images/responsivemode/responsiveui-screenshot@2x.png (themes/images/responsivemode/responsiveui-screenshot@2x.png)
     skin/images/responsivemode/responsiveui-home.png (themes/images/responsivemode/responsiveui-home.png)
     skin/images/toggle-tools.png (themes/images/toggle-tools.png)
     skin/images/toggle-tools@2x.png (themes/images/toggle-tools@2x.png)
     skin/images/dock-bottom@2x.png (themes/images/dock-bottom@2x.png)
     skin/images/dock-bottom-minimize@2x.png (themes/images/dock-bottom-minimize@2x.png)
     skin/images/dock-bottom-maximize@2x.png (themes/images/dock-bottom-maximize@2x.png)
     skin/images/dock-side@2x.png (themes/images/dock-side@2x.png)
-*   skin/floating-scrollbars.css (themes/floating-scrollbars.css)
-    skin/floating-scrollbars-light.css (themes/floating-scrollbars-light.css)
+    skin/floating-scrollbars-dark-theme.css (themes/floating-scrollbars-dark-theme.css)
+    skin/floating-scrollbars-responsive-design.css (themes/floating-scrollbars-responsive-design.css)
     skin/inspector.css (themes/inspector.css)
     skin/images/profiler-stopwatch.svg (themes/images/profiler-stopwatch.svg)
     skin/images/emojis/emoji-command-pick.svg (themes/images/emojis/emoji-command-pick.svg)
     skin/images/emojis/emoji-tool-webconsole.svg (themes/images/emojis/emoji-tool-webconsole.svg)
     skin/images/emojis/emoji-tool-canvas.svg (themes/images/emojis/emoji-tool-canvas.svg)
     skin/images/emojis/emoji-tool-debugger.svg (themes/images/emojis/emoji-tool-debugger.svg)
     skin/images/emojis/emoji-tool-inspector.svg (themes/images/emojis/emoji-tool-inspector.svg)
     skin/images/emojis/emoji-tool-shadereditor.svg (themes/images/emojis/emoji-tool-shadereditor.svg)
--- a/devtools/client/responsivedesign/responsivedesign-child.js
+++ b/devtools/client/responsivedesign/responsivedesign-child.js
@@ -1,15 +1,15 @@
 /* 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 Ci = Components.interfaces;
 const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
-const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars.css", null, null);
+const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars-responsive-design.css", null, null);
 var gRequiresFloatingScrollbars;
 
 var active = false;
 
 addMessageListener("ResponsiveMode:Start", startResponsiveMode);
 addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
 
 function startResponsiveMode({data:data}) {
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 (function() {
-  const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-light.css";
+  const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-dark-theme.css";
   let documentElement = document.documentElement;
 
   let os;
   let platform = navigator.platform;
   if (platform.startsWith("Win")) {
     os = "win";
   } else if (platform.startsWith("Mac")) {
     os = "mac";
rename from devtools/client/themes/floating-scrollbars.css
rename to devtools/client/themes/floating-scrollbars-dark-theme.css
--- a/devtools/client/themes/floating-scrollbars.css
+++ b/devtools/client/themes/floating-scrollbars-dark-theme.css
@@ -1,49 +1,55 @@
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
-scrollbar {
+xul|scrollbar {
   -moz-appearance: none !important;
   position: relative;
   background-color: transparent;
   background-image: none;
   z-index: 2147483647;
   padding: 2px;
-%ifdef XP_MACOSX
-  border: 0px solid transparent;
-%endif
+}
+
+:root[platform="mac"] xul|scrollbar {
+  border: none;
 }
 
 /* Scrollbar code will reset the margin to the correct side depending on
    where layout actually puts the scrollbar */
-scrollbar[orient="vertical"] {
+xul|scrollbar[orient="vertical"] {
   margin-left: -10px;
   min-width: 10px;
   max-width: 10px;
 }
 
-scrollbar[orient="horizontal"] {
+xul|scrollbar[orient="horizontal"] {
   margin-top: -10px;
   min-height: 10px;
   max-height: 10px;
 }
 
-%ifdef XP_MACOSX
-slider {
+:root[platform="mac"] xul|slider {
   -moz-appearance: none !important;
 }
 
-thumb {
+:root[platform="mac"] xul|thumb {
   -moz-appearance: none !important;
-  background-color: rgba(0,0,0,0.2);
   border-radius: 3px;
 }
-%else
-scrollbar thumb {
+
+xul|scrollbar xul|thumb {
+  background-color: rgba(170,170,170,0.2) !important;
+}
+
+:root[platform="win"] xul|thumb,
+:root[platform="linux"] xul|thumb {
   -moz-appearance: none !important;
   border-width: 0px !important;
-  background-color: rgba(170,170,170,0.2) !important;
   border-radius: 3px !important;
 }
-scrollbar scrollbarbutton, scrollbar gripper {
+
+:root[platform="win"] xul|scrollbar xul|scrollbarbutton,
+:root[platform="linux"] xul|scrollbar xul|scrollbarbutton,
+:root[platform="win"] xul|scrollbar xul|gripper,
+:root[platform="linux"] xul|scrollbar xul|gripper {
   display: none;
 }
-%endif
deleted file mode 100644
--- a/devtools/client/themes/floating-scrollbars-light.css
+++ /dev/null
@@ -1,10 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-@import url("floating-scrollbars.css");
-
-scrollbar thumb {
-  background-color: rgba(170,170,170,0.2) !important;
-}
copy from devtools/client/themes/floating-scrollbars.css
copy to devtools/client/themes/floating-scrollbars-responsive-design.css
--- a/devtools/client/themes/floating-scrollbars.css
+++ b/devtools/client/themes/floating-scrollbars-responsive-design.css
@@ -2,48 +2,41 @@
 
 scrollbar {
   -moz-appearance: none !important;
   position: relative;
   background-color: transparent;
   background-image: none;
   z-index: 2147483647;
   padding: 2px;
-%ifdef XP_MACOSX
-  border: 0px solid transparent;
-%endif
+  border: none;
 }
 
 /* Scrollbar code will reset the margin to the correct side depending on
    where layout actually puts the scrollbar */
 scrollbar[orient="vertical"] {
   margin-left: -10px;
   min-width: 10px;
   max-width: 10px;
 }
 
 scrollbar[orient="horizontal"] {
   margin-top: -10px;
   min-height: 10px;
   max-height: 10px;
 }
 
-%ifdef XP_MACOSX
 slider {
   -moz-appearance: none !important;
 }
 
 thumb {
   -moz-appearance: none !important;
   background-color: rgba(0,0,0,0.2);
   border-radius: 3px;
-}
-%else
-scrollbar thumb {
-  -moz-appearance: none !important;
   border-width: 0px !important;
-  background-color: rgba(170,170,170,0.2) !important;
   border-radius: 3px !important;
 }
-scrollbar scrollbarbutton, scrollbar gripper {
+
+scrollbar scrollbarbutton,
+scrollbar gripper {
   display: none;
 }
-%endif
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -435,17 +435,17 @@ DebuggerClient.prototype = {
     if (this._clients.has(aTabActor)) {
       let cachedTab = this._clients.get(aTabActor);
       let cachedResponse = {
         cacheDisabled: cachedTab.cacheDisabled,
         javascriptEnabled: cachedTab.javascriptEnabled,
         traits: cachedTab.traits,
       };
       DevToolsUtils.executeSoon(() => aOnResponse(cachedResponse, cachedTab));
-      return;
+      return promise.resolve([cachedResponse, cachedTab]);
     }
 
     let packet = {
       to: aTabActor,
       type: "attach"
     };
     return this.request(packet).then(aResponse => {
       let tabClient;
@@ -456,22 +456,23 @@ DebuggerClient.prototype = {
       aOnResponse(aResponse, tabClient);
       return [aResponse, tabClient];
     });
   },
 
   attachWorker: function DC_attachWorker(aWorkerActor, aOnResponse = noop) {
     let workerClient = this._clients.get(aWorkerActor);
     if (workerClient !== undefined) {
-      DevToolsUtils.executeSoon(() => aOnResponse({
+      let response = {
         from: workerClient.actor,
         type: "attached",
         url: workerClient.url
-      }, workerClient));
-      return;
+      };
+      DevToolsUtils.executeSoon(() => aOnResponse(response, workerClient));
+      return promise.resolve([response, workerClient]);
     }
 
     return this.request({ to: aWorkerActor, type: "attach" }).then(aResponse => {
       if (aResponse.error) {
         aOnResponse(aResponse, null);
         return [aResponse, null];
       }
 
@@ -551,18 +552,19 @@ DebuggerClient.prototype = {
    *        Called with the response packet and a ThreadClient
    *        (which will be undefined on error).
    * @param object aOptions
    *        Configuration options.
    *        - useSourceMaps: whether to use source maps or not.
    */
   attachThread: function (aThreadActor, aOnResponse = noop, aOptions={}) {
     if (this._clients.has(aThreadActor)) {
-      DevToolsUtils.executeSoon(() => aOnResponse({}, this._clients.get(aThreadActor)));
-      return;
+      let client = this._clients.get(aThreadActor);
+      DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+      return promise.resolve([{}, client]);
     }
 
     let packet = {
       to: aThreadActor,
       type: "attach",
       options: aOptions
     };
     return this.request(packet).then(aResponse => {
@@ -581,18 +583,19 @@ DebuggerClient.prototype = {
    * @param string aTraceActor
    *        The actor ID for the tracer to attach.
    * @param function aOnResponse
    *        Called with the response packet and a TraceClient
    *        (which will be undefined on error).
    */
   attachTracer: function (aTraceActor, aOnResponse = noop) {
     if (this._clients.has(aTraceActor)) {
-      DevToolsUtils.executeSoon(() => aOnResponse({}, this._clients.get(aTraceActor)));
-      return;
+      let client = this._clients.get(aTraceActor);
+      DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+      return promise.resolve([{}, client]);
     }
 
     let packet = {
       to: aTraceActor,
       type: "attach"
     };
     return this.request(packet).then(aResponse => {
       if (!aResponse.error) {
@@ -1256,17 +1259,17 @@ TabClient.prototype = {
    *        - useSourceMaps: whether to use source maps or not.
    * @param function aOnResponse
    *        Called with the response packet and a ThreadClient
    *        (which will be undefined on error).
    */
   attachThread: function(aOptions={}, aOnResponse = noop) {
     if (this.thread) {
       DevToolsUtils.executeSoon(() => aOnResponse({}, this.thread));
-      return;
+      return promise.resolve([{}, this.thread]);
     }
 
     let packet = {
       to: this._threadActor,
       type: "attach",
       options: aOptions
     };
     return this.request(packet).then(aResponse => {
@@ -1822,21 +1825,20 @@ ThreadClient.prototype = {
                                aIgnoreCaughtExceptions,
                                aOnResponse = noop) {
     this._pauseOnExceptions = aPauseOnExceptions;
     this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
 
     // If the debuggee is paused, we have to send the flag via a reconfigure
     // request.
     if (this.paused) {
-      this.reconfigure({
+      return this.reconfigure({
         pauseOnExceptions: aPauseOnExceptions,
         ignoreCaughtExceptions: aIgnoreCaughtExceptions
       }, aOnResponse);
-      return;
     }
     // Otherwise send the flag using a standard resume request.
     return this.interrupt(aResponse => {
       if (aResponse.error) {
         // Can't continue if pausing failed.
         aOnResponse(aResponse);
         return aResponse;
       }
--- a/mobile/android/app/base/build.gradle
+++ b/mobile/android/app/base/build.gradle
@@ -117,16 +117,20 @@ dependencies {
     }
 
     if (mozconfig.substs.MOZ_ANDROID_GCM) {
         compile 'com.google.android.gms:play-services-basement:8.1.0'
         compile 'com.google.android.gms:play-services-base:8.1.0'
         compile 'com.google.android.gms:play-services-gcm:8.1.0'
     }
 
+    // Gradle based builds include LeakCanary. Mach based builds only include the no-op version of
+    // this library.
+    compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
+
     compile project(':thirdparty')
 
     testCompile 'junit:junit:4.12'
     testCompile 'org.robolectric:robolectric:3.0'
     testCompile 'org.simpleframework:simple-http:6.0.1'
     testCompile 'org.mockito:mockito-core:1.10.19'
 }
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -63,25 +63,25 @@ import org.mozilla.gecko.tabs.TabsPanel;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.toolbar.ToolbarProgressView;
 import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.Experiments;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.ScreenshotObserver;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UIAsyncTask;
 import org.mozilla.gecko.widget.AnchoredPopup;
 import org.mozilla.gecko.widget.ButtonToast;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.app.Activity;
@@ -3045,16 +3045,18 @@ public class BrowserApp extends GeckoApp
 
         Tab tab = Tabs.getInstance().getSelectedTab();
         final MenuItem bookmark = aMenu.findItem(R.id.bookmark);
         final MenuItem reader = aMenu.findItem(R.id.reading_list);
         final MenuItem back = aMenu.findItem(R.id.back);
         final MenuItem forward = aMenu.findItem(R.id.forward);
         final MenuItem share = aMenu.findItem(R.id.share);
         final MenuItem quickShare = aMenu.findItem(R.id.quickshare);
+        final MenuItem bookmarksList = aMenu.findItem(R.id.bookmarks_list);
+        final MenuItem historyList = aMenu.findItem(R.id.history_list);
         final MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         final MenuItem print = aMenu.findItem(R.id.print);
         final MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
         final MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
         final MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
         final MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
         final MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
 
@@ -3254,16 +3256,21 @@ public class BrowserApp extends GeckoApp
         if (!Restrictions.isAllowed(this, Restrictable.GUEST_BROWSING)) {
             MenuUtils.safeSetVisible(aMenu, R.id.new_guest_session, false);
         }
 
         if (!Restrictions.isAllowed(this, Restrictable.INSTALL_EXTENSION)) {
             MenuUtils.safeSetVisible(aMenu, R.id.addons, false);
         }
 
+        if (!SwitchBoard.isInExperiment(this, Experiments.BOOKMARKS_HISTORY_MENU)) {
+            bookmarksList.setVisible(false);
+            historyList.setVisible(false);
+        }
+
         return true;
     }
 
     private int resolveBookmarkIconID(final boolean isBookmark) {
         if (isBookmark) {
             return R.drawable.ic_menu_bookmark_remove;
         } else {
             return R.drawable.ic_menu_bookmark_add;
@@ -3382,16 +3389,30 @@ public class BrowserApp extends GeckoApp
 
         if (itemId == R.id.forward) {
             tab = Tabs.getInstance().getSelectedTab();
             if (tab != null)
                 tab.doForward();
             return true;
         }
 
+        if (itemId == R.id.bookmarks_list) {
+            final String url = AboutPages.getURLForBuiltinPanelType(PanelType.BOOKMARKS);
+            Tabs.getInstance().loadUrl(url);
+            Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.BOOKMARKS_HISTORY_MENU);
+            return true;
+        }
+
+        if (itemId == R.id.history_list) {
+            final String url = AboutPages.getURLForBuiltinPanelType(PanelType.HISTORY);
+            Tabs.getInstance().loadUrl(url);
+            Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.BOOKMARKS_HISTORY_MENU);
+            return true;
+        }
+
         if (itemId == R.id.save_as_pdf) {
             Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "pdf");
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SaveAs:PDF", null));
             return true;
         }
 
         if (itemId == R.id.print) {
             Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "print");
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -19,16 +19,18 @@ import org.mozilla.gecko.util.ThreadUtil
 
 import android.app.Application;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.squareup.leakcanary.LeakCanary;
+
 import java.io.File;
 
 public class GeckoApplication extends Application 
     implements ContextGetter {
     private static final String LOG_TAG = "GeckoApplication";
 
     private static volatile GeckoApplication instance;
 
@@ -122,16 +124,18 @@ public class GeckoApplication extends Ap
 
         mInBackground = false;
     }
 
     @Override
     public void onCreate() {
         Log.i(LOG_TAG, "zerdatime " + SystemClock.uptimeMillis() + " - Fennec application start");
 
+        LeakCanary.install(this);
+
         final Context context = getApplicationContext();
         HardwareUtils.init(context);
         Clipboard.init(context);
         FilePicker.init(context);
         DownloadsIntegration.init();
         HomePanelsManager.getInstance().init(context);
 
         // This getInstance call will force initialization of the NotificationHelper, but does nothing with the result
rename from mobile/android/base/java/org/mozilla/gecko/util/ScreenshotObserver.java
rename to mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java
--- a/mobile/android/base/java/org/mozilla/gecko/util/ScreenshotObserver.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java
@@ -1,18 +1,20 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-package org.mozilla.gecko.util;
+package org.mozilla.gecko;
 
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.Manifest;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore;
 import android.util.Log;
 
@@ -44,36 +46,56 @@ public class ScreenshotObserver {
     private String[] mediaProjections = new String[] {
                     MediaStore.Images.ImageColumns.DATA,
                     MediaStore.Images.ImageColumns.DISPLAY_NAME,
                     MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
                     MediaStore.Images.ImageColumns.DATE_TAKEN,
                     MediaStore.Images.ImageColumns.TITLE
     };
 
+    /**
+     * Start ScreenshotObserver if this device is supported and all required runtime permissions
+     * have been granted by the user. Calling this method will not prompt for permissions.
+     */
     public void start() {
         if (!Versions.feature14Plus) {
             return;
         }
 
-        try {
-            if (mediaObserver == null) {
-                mediaObserver = new MediaObserver();
-                context.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mediaObserver);
+        Permissions.from(context)
+                   .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                   .doNotPrompt()
+                   .run(startObserverRunnable());
+    }
+
+    private Runnable startObserverRunnable() {
+        return new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (mediaObserver == null) {
+                        mediaObserver = new MediaObserver();
+                        context.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mediaObserver);
+                    }
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Failure to start watching media: ", e);
+                }
             }
-        } catch (Exception e) {
-            Log.e(LOGTAG, "Failure to start watching media: ", e);
-        }
+        };
     }
 
     public void stop() {
         if (!Versions.feature14Plus) {
             return;
         }
 
+        if (mediaObserver == null) {
+            return;
+        }
+
         try {
             context.getContentResolver().unregisterContentObserver(mediaObserver);
             mediaObserver = null;
         } catch (Exception e) {
             Log.e(LOGTAG, "Failure to stop watching media: ", e);
         }
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
@@ -12,16 +12,17 @@ import java.util.List;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+import org.mozilla.gecko.util.Experiments;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
@@ -282,16 +283,19 @@ public class HomePager extends ViewPager
     }
 
     /**
      * Shows a home panel. If the given panelId is null,
      * the default panel will be shown. No action will be taken if:
      *  * HomePager has not loaded yet
      *  * Panel with the given panelId cannot be found
      *
+     * If you're trying to open a built-in panel, consider loading the panel url directly with
+     * {@link org.mozilla.gecko.AboutPages#getURLForBuiltinPanelType(HomeConfig.PanelType)}.
+     *
      * @param panelId of the home panel to be shown.
      */
     public void showPanel(String panelId) {
         if (!mVisible) {
             return;
         }
 
         switch (mLoadState) {
@@ -520,11 +524,12 @@ public class HomePager extends ViewPager
     /**
      * Stop the current panel telemetry session if one exists.
      */
     private void stopCurrentPanelTelemetrySession() {
         if (mCurrentPanelSession != null) {
             Telemetry.stopUISession(mCurrentPanelSession, mCurrentPanelSessionSuffix);
             mCurrentPanelSession = null;
             mCurrentPanelSessionSuffix = null;
+            Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.BOOKMARKS_HISTORY_MENU);
         }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/util/Experiments.java
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+/**
+ * This class should reflect the experiment names found in the Switchboard experiments config here:
+ * https://github.com/mozilla-services/switchboard-experiments
+ */
+public class Experiments {
+    public static final String BOOKMARKS_HISTORY_MENU = "bookmark-history-menu";
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -92,16 +92,17 @@ gujar.sources += ['java/org/mozilla/geck
     'util/ActivityResultHandler.java',
     'util/ActivityResultHandlerMap.java',
     'util/ActivityUtils.java',
     'util/BundleEventListener.java',
     'util/Clipboard.java',
     'util/ColorUtils.java',
     'util/DrawableUtil.java',
     'util/EventCallback.java',
+    'util/Experiments.java',
     'util/FileUtils.java',
     'util/FloatUtils.java',
     'util/GamepadUtils.java',
     'util/GeckoBackgroundThread.java',
     'util/GeckoEventListener.java',
     'util/GeckoJarReader.java',
     'util/GeckoRequest.java',
     'util/HardwareCodecCapabilityUtils.java',
@@ -114,17 +115,16 @@ gujar.sources += ['java/org/mozilla/geck
     'util/MenuUtils.java',
     'util/NativeEventListener.java',
     'util/NativeJSContainer.java',
     'util/NativeJSObject.java',
     'util/NonEvictingLruCache.java',
     'util/PrefUtils.java',
     'util/ProxySelector.java',
     'util/RawResource.java',
-    'util/ScreenshotObserver.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UIAsyncTask.java',
     'util/WeakReferenceHandler.java',
     'util/WebActivityMapper.java',
     'util/WindowUtils.java',
 ]]
 gujar.extra_jars = [
@@ -501,16 +501,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'Restarter.java',
     'Restrictions.java',
     'restrictions/DefaultConfiguration.java',
     'restrictions/GuestProfileConfiguration.java',
     'restrictions/Restrictable.java',
     'restrictions/RestrictedProfileConfiguration.java',
     'restrictions/RestrictionConfiguration.java',
     'restrictions/RestrictionProvider.java',
+    'ScreenshotObserver.java',
     'ServiceNotificationClient.java',
     'SessionParser.java',
     'SharedPreferencesHelper.java',
     'SiteIdentity.java',
     'SmsManager.java',
     'SnackbarHelper.java',
     'sqlite/ByteBufferInputStream.java',
     'sqlite/MatrixBlobCursor.java',
@@ -773,16 +774,18 @@ gtjar.sources += [ thirdparty_source_dir
     'com/nineoldandroids/util/Property.java',
     'com/nineoldandroids/util/ReflectiveProperty.java',
     'com/nineoldandroids/view/animation/AnimatorProxy.java',
     'com/nineoldandroids/view/ViewHelper.java',
     'com/nineoldandroids/view/ViewPropertyAnimator.java',
     'com/nineoldandroids/view/ViewPropertyAnimatorHC.java',
     'com/nineoldandroids/view/ViewPropertyAnimatorICS.java',
     'com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java',
+    'com/squareup/leakcanary/LeakCanary.java',
+    'com/squareup/leakcanary/RefWatcher.java',
     'com/squareup/picasso/Action.java',
     'com/squareup/picasso/AssetBitmapHunter.java',
     'com/squareup/picasso/BitmapHunter.java',
     'com/squareup/picasso/Cache.java',
     'com/squareup/picasso/Callback.java',
     'com/squareup/picasso/ContactsPhotoBitmapHunter.java',
     'com/squareup/picasso/ContentStreamBitmapHunter.java',
     'com/squareup/picasso/DeferredRequestCreator.java',
--- a/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
@@ -49,16 +49,22 @@
           android:showAsAction="collapseActionView"/>
 
     <item android:id="@+id/new_tab"
           android:title="@string/new_tab"/>
 
     <item android:id="@+id/new_private_tab"
           android:title="@string/new_private_tab"/>
 
+    <item android:id="@+id/bookmarks_list"
+          android:title="@string/bookmarks_title"/>
+
+    <item android:id="@+id/history_list"
+          android:title="@string/history_title"/>
+
     <item android:id="@+id/find_in_page"
           android:title="@string/find_in_page" />
 
     <item android:id="@+id/desktop_mode"
           android:title="@string/desktop_mode"
           android:checkable="true" />
 
     <item android:id="@+id/page"
--- a/mobile/android/base/resources/menu-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-v11/browser_app_menu.xml
@@ -49,16 +49,22 @@
           android:showAsAction="collapseActionView"/>
 
     <item android:id="@+id/new_tab"
           android:title="@string/new_tab"/>
 
     <item android:id="@+id/new_private_tab"
           android:title="@string/new_private_tab"/>
 
+    <item android:id="@+id/bookmarks_list"
+          android:title="@string/bookmarks_title"/>
+
+    <item android:id="@+id/history_list"
+          android:title="@string/history_title"/>
+
     <item android:id="@+id/find_in_page"
           android:title="@string/find_in_page" />
 
     <item android:id="@+id/desktop_mode"
           android:title="@string/desktop_mode"
           android:checkable="true" />
 
     <item android:id="@+id/page"
--- a/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
@@ -49,16 +49,22 @@
           android:showAsAction="collapseActionView"/>
 
     <item android:id="@+id/new_tab"
           android:title="@string/new_tab"/>
 
     <item android:id="@+id/new_private_tab"
           android:title="@string/new_private_tab"/>
 
+    <item android:id="@+id/bookmarks_list"
+          android:title="@string/bookmarks_title"/>
+
+    <item android:id="@+id/history_list"
+          android:title="@string/history_title"/>
+
     <item android:id="@+id/find_in_page"
           android:title="@string/find_in_page" />
 
     <item android:id="@+id/desktop_mode"
           android:title="@string/desktop_mode"
           android:checkable="true" />
 
 
--- a/mobile/android/base/resources/menu/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu/browser_app_menu.xml
@@ -34,16 +34,22 @@
 
     <item android:id="@+id/find_in_page"
           android:title="@string/find_in_page" />
 
     <item android:id="@+id/desktop_mode"
           android:title="@string/desktop_mode"
           android:checkable="true" />
 
+    <item android:id="@+id/bookmarks_list"
+          android:title="@string/bookmarks_title"/>
+
+    <item android:id="@+id/history_list"
+          android:title="@string/history_title"/>
+
     <item android:id="@+id/page"
           android:title="@string/page">
 
         <menu>
             <item android:id="@+id/subscribe"
                   android:title="@string/contextmenu_subscribe"/>
 
             <item android:id="@+id/save_as_pdf"
new file mode 100644
--- /dev/null
+++ b/mobile/android/config/proguard/leakcanary-keeps.cfg
@@ -0,0 +1,7 @@
+# LeakCanary
+-keep class org.eclipse.mat.** { *; }
+-keep class com.squareup.leakcanary.** { *; }
+-keep class com.squareup.haha.** { *; }
+
+# With LeakCanary 1.4-beta1 this creates a pile of warnings
+-dontwarn com.squareup.haha.**
--- a/mobile/android/config/proguard/proguard.cfg
+++ b/mobile/android/config/proguard/proguard.cfg
@@ -242,8 +242,11 @@
 
 -include "play-services-keeps.cfg"
 
 # Don't print spurious warnings from the support library.
 # See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl
 -dontnote android.support.**
 
 -include "adjust-keeps.cfg"
+
+-include "leakcanary-keeps.cfg"
+
--- a/mobile/android/thirdparty/build.gradle
+++ b/mobile/android/thirdparty/build.gradle
@@ -23,16 +23,20 @@ android {
     sourceSets {
         main {
             manifest.srcFile 'AndroidManifest.xml'
             java {
                 srcDir '.'
                 if (!mozconfig.substs.MOZ_INSTALL_TRACKING) {
                     exclude 'com/adjust/**'
                 }
+
+                // Exclude LeakCanary: It will be added again via a gradle dependency. This version
+                // here is only the no-op library for mach-based builds.
+                exclude 'com/squareup/leakcanary/**'
             }
         }
     }
 }
 
 dependencies {
     compile 'com.android.support:support-v4:23.0.1'
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/squareup/leakcanary/LeakCanary.java
@@ -0,0 +1,21 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package com.squareup.leakcanary;
+
+import android.app.Application;
+
+/**
+ * A no-op version of {@link LeakCanary} that can be used in release builds.
+ */
+public final class LeakCanary {
+    public static RefWatcher install(Application application) {
+        return RefWatcher.DISABLED;
+    }
+
+    private LeakCanary() {
+        throw new AssertionError();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/squareup/leakcanary/RefWatcher.java
@@ -0,0 +1,20 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package com.squareup.leakcanary;
+
+/**
+ * No-op implementation of {@link RefWatcher} for release builds. Please use {@link
+ * RefWatcher#DISABLED}.
+ */
+public final class RefWatcher {
+    public static final RefWatcher DISABLED = new RefWatcher();
+
+    private RefWatcher() {}
+
+    public void watch(Object watchedReference) {}
+
+    public void watch(Object watchedReference, String referenceName) {}
+}
--- a/services/sync/moz.build
+++ b/services/sync/moz.build
@@ -37,17 +37,17 @@ EXTRA_JS_MODULES['services-sync'] += [
     'modules/util.js',
 ]
 
 EXTRA_PP_JS_MODULES['services-sync'] += [
     'modules/constants.js',
 ]
 
 # Definitions used by constants.js
-DEFINES['weave_version'] = '1.48.0'
+DEFINES['weave_version'] = '1.49.0'
 DEFINES['weave_id'] = '{340c2bbc-ce74-4362-90b5-7c26312808ef}'
 
 EXTRA_JS_MODULES['services-sync'].engines += [
     'modules/engines/addons.js',
     'modules/engines/bookmarks.js',
     'modules/engines/clients.js',
     'modules/engines/forms.js',
     'modules/engines/history.js',
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8289,72 +8289,72 @@
   },
   "LOOP_CLIENT_CALL_URL_SHARED": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Stores 1 every time the URL is copied or shared."
   },
   "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1": {
     "alert_emails": ["firefox-dev@mozilla.org", "dmose@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 8,
     "releaseChannelCollection": "opt-out",
     "description": "Connection length for bi-directionally connected media (0=SHORTER_THAN_10S, 1=BETWEEN_10S_AND_30S, 2=BETWEEN_30S_AND_5M, 3=MORE_THAN_5M)"
   },
   "LOOP_SHARING_STATE_CHANGE_1": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "48",
     "kind": "enumerated",
     "n_values": 8,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times the sharing feature has been enabled and disabled (0=WINDOW_ENABLED, 1=WINDOW_DISABLED, 2=BROWSER_ENABLED, 3=BROWSER_DISABLED)"
   },
   "LOOP_SHARING_ROOM_URL": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 8,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times a room URL is shared (0=COPY_FROM_PANEL, 1=COPY_FROM_CONVERSATION, 2=EMAIL_FROM_CALLFAILED, 3=EMAIL_FROM_CONVERSATION, 4=FACEBOOK_FROM_CONVERSATION)"
   },
   "LOOP_ROOM_CREATE": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 4,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times a room create action is performed (0=CREATE_SUCCESS, 1=CREATE_FAIL)"
   },
   "LOOP_ROOM_DELETE": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 4,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times a room delete action is performed (0=DELETE_SUCCESS, 2=DELETE_FAIL)"
   },
   "LOOP_ROOM_CONTEXT_ADD": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "48",
     "kind": "enumerated",
     "n_values": 8,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times a room context action is performed (0=ADD_FROM_PANEL, 1=ADD_FROM_CONVERSATION)"
   },
   "LOOP_ROOM_CONTEXT_CLICK": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "48",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "description": "Number times room context is clicked to visit the attached URL"
   },
   "LOOP_ROOM_SESSION_WITHCHAT": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
-    "expires_in_version": "47",
+    "expires_in_version": "50",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "description": "Number of sessions where at least one chat message was exchanged"
   },
   "E10S_STATUS": {
     "alert_emails": ["firefox-dev@mozilla.org"],
     "expires_in_version": "never",
     "kind": "enumerated",
--- a/toolkit/mozapps/extensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini
@@ -5,16 +5,17 @@ support-files =
 [browser_about.js]
 skip-if = os == 'linux' || os == 'win' # bug 632290
 [browser_bug523784.js]
 [browser_bug557943.js]
 [browser_bug562797.js]
 skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_bug562854.js]
 [browser_bug562890.js]
+skip-if = os == 'win' && !debug # Disabled on Windows opt/PGO builds due to intermittent failures (bug 1135866)
 [browser_bug562899.js]
 skip-if = buildapp == 'mulet'
 [browser_bug562992.js]
 [browser_bug567127.js]
 [browser_bug567137.js]
 [browser_bug570760.js]
 [browser_bug572561.js]
 [browser_bug577990.js]
@@ -53,16 +54,17 @@ skip-if = e10s # TypeError: Promise.defe
 [browser_manualupdates.js]
 [browser_globalwarnings.js]
 [browser_globalinformations.js]
 [browser_eula.js]
 skip-if = buildapp == 'mulet'
 [browser_updateid.js]
 [browser_purchase.js]
 [browser_openDialog.js]
+skip-if = os == 'win' && !debug # Disabled on Windows opt/PGO builds due to intermittent failures (bug 1135866)
 [browser_types.js]
 [browser_inlinesettings.js]
 [browser_inlinesettings_custom.js]
 [browser_inlinesettings_info.js]
 [browser_tabsettings.js]
 [browser_pluginprefs.js]
 skip-if = buildapp == 'mulet'
 [browser_CTP_plugins.js]
--- a/xpcom/components/Module.h
+++ b/xpcom/components/Module.h
@@ -17,17 +17,17 @@ namespace mozilla {
 /**
  * A module implements one or more XPCOM components. This structure is used
  * for both binary and script modules, but the registration members
  * (cids/contractids/categoryentries) are unused for modules which are loaded
  * via a module loader.
  */
 struct Module
 {
-  static const unsigned int kVersion = 46;
+  static const unsigned int kVersion = 47;
 
   struct CIDEntry;
 
   typedef already_AddRefed<nsIFactory> (*GetFactoryProcPtr)(
     const Module& module, const CIDEntry& entry);
 
   typedef nsresult (*ConstructorProcPtr)(nsISupports* aOuter,
                                          const nsIID& aIID,