Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 07 Aug 2015 13:35:38 +0200
changeset 288435 d098f59caf971c3f5429353674ffce6bf8c85cfe
parent 288434 db738b3975bdeaba564f83a4f772c2ba91257ce8 (current diff)
parent 288423 3e51753a099f8014b3dc37ed3fa27b887842735b (diff)
child 288436 c9984832a2b5633821398155d34060defa9586f0
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to b2g-inbound
build/autoconf/gcc-pr49911.m4
dom/workers/test/serviceworkers/client_focus_worker.js
dom/workers/test/serviceworkers/sw_clients/focus_stealing_client.html
memory/jemalloc/Makefile.in
mobile/android/base/home/RecyclerViewItemClickListener.java
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -1127,16 +1127,20 @@ a11y::ProxyDestroyed(ProxyAccessible* aP
 }
 
 nsresult
 AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   nsresult rv = Accessible::HandleAccEvent(aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (IPCAccessibilityActive()) {
+    return NS_OK;
+  }
+
     Accessible* accessible = aEvent->GetAccessible();
     NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
 
     // The accessible can become defunct if we have an xpcom event listener
     // which decides it would be fun to change the DOM and flush layout.
     if (accessible->IsDefunct())
         return NS_OK;
 
--- a/accessible/mac/AccessibleWrap.mm
+++ b/accessible/mac/AccessibleWrap.mm
@@ -89,16 +89,20 @@ AccessibleWrap::Shutdown ()
 nsresult
 AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   nsresult rv = Accessible::HandleAccEvent(aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (IPCAccessibilityActive()) {
+    return NS_OK;
+  }
+
   uint32_t eventType = aEvent->GetEventType();
 
   // ignore everything but focus-changed, value-changed, caret and selection
   // events for now.
   if (eventType != nsIAccessibleEvent::EVENT_FOCUS &&
       eventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
       eventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
       eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -1203,16 +1203,20 @@ AccessibleWrap::GetNativeInterface(void*
 // Accessible
 
 nsresult
 AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   nsresult rv = Accessible::HandleAccEvent(aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (IPCAccessibilityActive()) {
+    return NS_OK;
+  }
+
   uint32_t eventType = aEvent->GetEventType();
 
   static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY,
                 "MSAA event map skewed");
 
   NS_ENSURE_TRUE(eventType > 0 && eventType < ArrayLength(gWinEventMap), NS_ERROR_FAILURE);
 
   uint32_t winEvent = gWinEventMap[eventType];
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -14,17 +14,16 @@ builtin(include, build/autoconf/nspr-bui
 builtin(include, build/autoconf/nss.m4)dnl
 builtin(include, build/autoconf/pkg.m4)dnl
 builtin(include, build/autoconf/codeset.m4)dnl
 builtin(include, build/autoconf/altoptions.m4)dnl
 builtin(include, build/autoconf/mozprog.m4)dnl
 builtin(include, build/autoconf/mozheader.m4)dnl
 builtin(include, build/autoconf/mozcommonheader.m4)dnl
 builtin(include, build/autoconf/lto.m4)dnl
-builtin(include, build/autoconf/gcc-pr49911.m4)dnl
 builtin(include, build/autoconf/llvm-pr8927.m4)dnl
 builtin(include, build/autoconf/frameptr.m4)dnl
 builtin(include, build/autoconf/compiler-opts.m4)dnl
 builtin(include, build/autoconf/expandlibs.m4)dnl
 builtin(include, build/autoconf/arch.m4)dnl
 builtin(include, build/autoconf/android.m4)dnl
 builtin(include, build/autoconf/zlib.m4)dnl
 builtin(include, build/autoconf/linux.m4)dnl
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1148,18 +1148,19 @@ pref("dom.requestSync.enabled", true);
 // Resample touch events on b2g
 pref("gfx.touch.resample", true);
 
 // Comma separated list of activity names that can only be provided by
 // the system app in dev mode.
 pref("dom.activities.developer_mode_only", "import-app");
 
 // mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
-// disable serviceworkers here to get them disabled in mulet.
+// disable serviceworkers and push here to get them disabled in mulet.
 pref("dom.serviceWorkers.enabled", false);
+pref("dom.push.enabled", false);
 
 // Retain at most 10 processes' layers buffers
 pref("layers.compositor-lru-size", 10);
 
 // Enable Cardboard VR on mobile, assuming VR at all is enabled
 pref("dom.vr.cardboard.enabled", true);
 
 // In B2G by deafult any AudioChannelAgent is muted when created.
--- a/b2g/components/MailtoProtocolHandler.js
+++ b/b2g/components/MailtoProtocolHandler.js
@@ -35,16 +35,16 @@ MailtoProtocolHandler.prototype = {
     cpmm.sendAsyncMessage("mail-handler", {
       URI: aURI.spec,
       type: "mail" });
 
     throw Components.results.NS_ERROR_ILLEGAL_VALUE;
   },
 
   newChannel: function Proto_newChannel(aURI) {
-    return newChannel2(aURI, null);
+    return this.newChannel2(aURI, null);
   },
 
   classID: Components.ID("{50777e53-0331-4366-a191-900999be386c}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MailtoProtocolHandler]);
--- a/b2g/components/SmsProtocolHandler.js
+++ b/b2g/components/SmsProtocolHandler.js
@@ -61,16 +61,16 @@ SmsProtocolHandler.prototype = {
         type: "websms/sms",
         body: body });
     }
 
     throw Components.results.NS_ERROR_ILLEGAL_VALUE;
   },
 
   newChannel: function Proto_newChannel(aURI) {
-    return newChannel2(aURI, null);
+    return this.newChannel2(aURI, null);
   },
 
   classID: Components.ID("{81ca20cb-0dad-4e32-8566-979c8998bd73}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SmsProtocolHandler]);
--- a/b2g/components/TelProtocolHandler.js
+++ b/b2g/components/TelProtocolHandler.js
@@ -47,16 +47,16 @@ TelProtocolHandler.prototype = {
         number: number,
         type: "webtelephony/number" });
     }
 
     throw Components.results.NS_ERROR_ILLEGAL_VALUE;
   },
 
   newChannel: function Proto_newChannel(aURI) {
-    return newChannel2(aURI, null);
+    return this.newChannel2(aURI, null);
   },
 
   classID: Components.ID("{782775dd-7351-45ea-aff1-0ffa872cfdd2}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelProtocolHandler]);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -305,17 +305,18 @@ pref("browser.urlbar.restrict.searches",
 pref("browser.urlbar.match.title", "#");
 pref("browser.urlbar.match.url", "@");
 
 // The default behavior for the urlbar can be configured to use any combination
 // of the match filters with each additional filter adding more results (union).
 pref("browser.urlbar.suggest.history",              true);
 pref("browser.urlbar.suggest.bookmark",             true);
 pref("browser.urlbar.suggest.openpage",             true);
-pref("browser.urlbar.suggest.searches",             true);
+pref("browser.urlbar.suggest.searches",             false);
+pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
 
 // Limit the number of characters sent to the current search engine to fetch
 // suggestions.
 pref("browser.urlbar.maxCharsForSearchSuggestions", 20);
 
 // Restrictions to current suggestions can also be applied (intersection).
 // Typed suggestion works only if history is set to true.
 pref("browser.urlbar.suggest.history.onlyTyped",    false);
@@ -1938,13 +1939,14 @@ pref("browser.pocket.enabled", true);
 pref("browser.pocket.api", "api.getpocket.com");
 pref("browser.pocket.site", "getpocket.com");
 pref("browser.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4");
 pref("browser.pocket.useLocaleList", true);
 pref("browser.pocket.enabledLocales", "cs de en-GB en-US en-ZA es-ES es-MX fr hu it ja ja-JP-mac ko nl pl pt-BR pt-PT ru zh-CN zh-TW");
 
 pref("view_source.tab", true);
 
-// Enable Service Workers for desktop on non-release builds
-#ifndef RELEASE_BUILD
+// Enable ServiceWorkers for Push API consumers.
+// Interception is still disabled.
 pref("dom.serviceWorkers.enabled", true);
-pref("dom.serviceWorkers.interception.enabled", true);
-#endif
+
+// Enable Push API.
+pref("dom.push.enabled", true);
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -27,16 +27,17 @@
     <command id="Browser:SendLink"
              oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
 
     <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
     <command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
     <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
     <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()" reserved="true"/>
     <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()" reserved="true"/>
+    <command id="cmd_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
     <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
     <command id="cmd_quitApplication" oncommand="goQuitApplication()" reserved="true"/>
 
 
     <commandset id="editMenuCommands"/>
 
     <command id="View:PageSource" oncommand="BrowserViewSource(window.gBrowser.selectedBrowser);" observes="isImage"/>
     <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
@@ -311,16 +312,17 @@
 
     <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift"
          keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
     <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile"  modifiers="accel"/>
     <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
     <key id="printKb" key="&printCmd.commandkey;" command="cmd_print"  modifiers="accel"/>
     <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
+    <key id="key_toggleMute" key="&toggleMuteCmd.key;" command="cmd_toggleMute" modifiers="alt,shift"/>
     <key id="key_undo"
          key="&undoCmd.key;"
          modifiers="accel"/>
 #ifdef XP_UNIX
     <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
 #else
     <key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
 #endif
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -472,16 +472,37 @@ panel[noactions] > richlistbox > richlis
 searchbar[oneoffui] {
   -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-flare") !important;
 }
 
 #PopupAutoCompleteRichResult {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
 }
 
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification {
+  transition: height 100ms;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
+  visibility: collapse;
+  transition: margin-top 100ms;
+}
+
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > hbox[anonid="search-suggestions-notification"] {
+  visibility: visible;
+}
+
+#PopupAutoCompleteRichResult > richlistbox {
+  transition: height 100ms;
+}
+
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
+  transition: none;
+}
+
 #urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
 #urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
 #urlbar[pageproxystate="valid"] > #urlbar-go-button,
 #urlbar:not([focused="true"]) > #urlbar-go-button {
   visibility: collapse;
 }
 
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -515,16 +515,20 @@ ContentLinkHandler.init(this);
 
 // TODO: Load this lazily so the JSM is run only if a relevant event/message fires.
 let pluginContent = new PluginContent(global);
 
 addEventListener("DOMWebNotificationClicked", function(event) {
   sendAsyncMessage("DOMWebNotificationClicked", {});
 }, false);
 
+addEventListener("DOMServiceWorkerFocusClient", function(event) {
+  sendAsyncMessage("DOMServiceWorkerFocusClient", {});
+}, false);
+
 ContentWebRTC.init();
 addMessageListener("webrtc:Allow", ContentWebRTC);
 addMessageListener("webrtc:Deny", ContentWebRTC);
 addMessageListener("webrtc:StopSharing", ContentWebRTC);
 addMessageListener("webrtc:StartBrowserSharing", () => {
   let windowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   sendAsyncMessage("webrtc:response:StartBrowserSharing", {
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -641,16 +641,22 @@ let DOMFullscreenHandler = {
       }
       case "MozDOMFullscreen:Exit": {
         sendAsyncMessage("DOMFullscreen:Exit");
         break;
       }
       case "MozDOMFullscreen:Entered":
       case "MozDOMFullscreen:Exited": {
         addEventListener("MozAfterPaint", this);
+        if (!content || !content.document.mozFullScreen) {
+          // If we receive any fullscreen change event, and find we are
+          // actually not in fullscreen, also ask the parent to exit to
+          // ensure that the parent always exits fullscreen when we do.
+          sendAsyncMessage("DOMFullscreen:Exit");
+        }
         break;
       }
       case "MozAfterPaint": {
         removeEventListener("MozAfterPaint", this);
         sendAsyncMessage("DOMFullscreen:Painted");
         break;
       }
     }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3897,30 +3897,30 @@
         <parameter name="event"/>
         <body><![CDATA[
           event.stopPropagation();
           var tab = document.tooltipNode;
           if (tab.localName != "tab") {
             event.preventDefault();
             return;
           }
-          var stringID, label;
+          var label;
           if (tab.mOverCloseButton) {
-            stringID = "tabs.closeTab.tooltip";
+            label = this.mStringBundle.getString("tabs.closeTab.tooltip");
           } else if (tab._overPlayingIcon) {
-            stringID = tab.linkedBrowser.audioMuted ?
-              "tabs.mutedAudio.tooltip" :
-              "tabs.playingAudio.tooltip";
+            var stringID = tab.linkedBrowser.audioMuted ?
+              "tabs.unmuteAudio.tooltip" :
+              "tabs.muteAudio.tooltip";
+            var key = document.getElementById("key_toggleMute");
+            var shortcut = ShortcutUtils.prettifyShortcut(key);
+            label = this.mStringBundle.getFormattedString(stringID, [shortcut]);
           } else {
             label = tab.getAttribute("label") +
                       (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
           }
-          if (stringID && !label) {
-            label = this.mStringBundle.getString(stringID);
-          }
           event.target.setAttribute("label", label);
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
@@ -3995,16 +3995,17 @@
                                           selectionInfo: aMessage.data.selectionInfo,
                                           disableSetDesktopBackground: aMessage.data.disableSetDesktopBg,
                                         };
               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
               let event = gContextMenuContentData.event;
               popup.openPopupAtScreen(event.screenX, event.screenY, true);
               break;
             }
+            case "DOMServiceWorkerFocusClient":
             case "DOMWebNotificationClicked": {
               let tab = this.getTabForBrowser(browser);
               if (!tab)
                 return;
               this.selectedTab = tab;
               window.focus();
               break;
             }
@@ -4091,16 +4092,17 @@
             // If this window has remote tabs, switch to our tabpanels fork
             // which does asynchronous tab switching.
             this.mPanelContainer.classList.add("tabbrowser-tabpanels");
           } else {
             this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
                                               this.mCurrentBrowser);
           }
           messageManager.addMessageListener("DOMWebNotificationClicked", this);
+          messageManager.addMessageListener("DOMServiceWorkerFocusClient", this);
           messageManager.addMessageListener("Findbar:Keypress", this);
         ]]>
       </constructor>
 
       <method name="_generateUniquePanelID">
         <body><![CDATA[
           if (!this._uniquePanelIDCounter) {
             this._uniquePanelIDCounter = 0;
@@ -5896,17 +5898,17 @@
             tabContainer._afterHoveredTab.removeAttribute("afterhovered");
             tabContainer._afterHoveredTab = null;
           }
 
           tabContainer._hoveredTab = null;
         ]]></body>
       </method>
 
-      <method name="_toggleMuteAudio">
+      <method name="toggleMuteAudio">
         <body>
         <![CDATA[
           let tabContainer = this.parentNode;
           let browser = this.linkedBrowser;
           if (browser.audioMuted) {
             browser.unmute();
             this.removeAttribute("muted");
           } else {
@@ -5968,17 +5970,18 @@
           return;
         }
 
         let anonid = event.originalTarget.getAttribute("anonid");
         let iconVisible = this.hasAttribute("soundplaying") ||
                           this.hasAttribute("muted");
         if ((anonid == "soundplaying-icon") ||
             ((anonid == "overlay-icon") && iconVisible)) {
-          this._toggleMuteAudio();
+          this.toggleMuteAudio();
+          this._overPlayingIcon = false;
         }
       ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-alltabs-popup"
            extends="chrome://global/content/bindings/popup.xml#popup">
@@ -6077,16 +6080,23 @@
             aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
           else
             aMenuitem.removeAttribute("pending");
 
           if (aTab.selected)
             aMenuitem.setAttribute("selected", "true");
           else
             aMenuitem.removeAttribute("selected");
+
+          if (aTab.hasAttribute("muted"))
+            aMenuitem.setAttribute("endimage", "chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
+          else if (aTab.hasAttribute("soundplaying"))
+            aMenuitem.setAttribute("endimage", "chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
+          else
+            aMenuitem.removeAttribute("endimage");
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="popupshowing">
       <![CDATA[
         document.getElementById("alltabs_undoCloseTab").disabled =
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -453,16 +453,17 @@ skip-if = e10s # Bug 1100700 - test reli
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
+[browser_urlbarSearchSuggestionsNotification.js]
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_autoFill_backspaced.js]
 [browser_urlbar_search_healthreport.js]
 [browser_urlbar_searchsettings.js]
 [browser_utilityOverlay.js]
 [browser_visibleFindSelection.js]
 [browser_visibleLabel.js]
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -35,17 +35,17 @@ function leave_icon(icon) {
 
   disable_non_test_mouse(false);
 }
 
 function* test_tooltip(icon, expectedTooltip) {
   let tooltip = document.getElementById("tabbrowser-tab-tooltip");
 
   yield hover_icon(icon, tooltip);
-  is(tooltip.getAttribute("label"), expectedTooltip, "Correct tooltip expected");
+  is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
   leave_icon(icon);
 }
 
 function* test_mute_tab(tab, icon, expectMuted) {
   let mutedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
     if (event.detail.changed.indexOf("muted") >= 0) {
       is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
       return true;
@@ -72,38 +72,38 @@ function* test_playing_icon_on_tab(tab, 
 
   yield ContentTask.spawn(browser, {}, function* () {
     let audio = content.document.querySelector("audio");
     audio.play();
   });
 
   yield wait_for_tab_playing_event(tab, true);
 
-  yield test_tooltip(icon, "This tab is playing audio");
+  yield test_tooltip(icon, "Mute tab");
 
   yield test_mute_tab(tab, icon, true);
 
-  yield test_tooltip(icon, "This tab has been muted");
+  yield test_tooltip(icon, "Unmute tab");
 
   yield test_mute_tab(tab, icon, false);
 
-  yield test_tooltip(icon, "This tab is playing audio");
+  yield test_tooltip(icon, "Mute tab");
 
   yield test_mute_tab(tab, icon, true);
 
   yield ContentTask.spawn(browser, {}, function* () {
     let audio = content.document.querySelector("audio");
     audio.pause();
   });
   yield wait_for_tab_playing_event(tab, false);
 
   ok(tab.hasAttribute("muted") &&
      !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing");
 
-  yield test_tooltip(icon, "This tab has been muted");
+  yield test_tooltip(icon, "Unmute tab");
 
   yield test_mute_tab(tab, icon, false);
 
   ok(!tab.hasAttribute("muted") &&
      !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing");
 }
 
 function* test_swapped_browser(oldTab, newBrowser, isPlaying) {
@@ -154,16 +154,68 @@ function* test_browser_swapping(tab, bro
 
     yield BrowserTestUtils.withNewTab({
       gBrowser,
       url: "about:blank",
     }, newBrowser => test_swapped_browser(tab, newBrowser, false));
   });
 }
 
+function* test_click_on_pinned_tab_after_mute() {
+  function* test_on_browser(browser) {
+    let tab = gBrowser.getTabForBrowser(browser);
+
+    gBrowser.selectedTab = originallySelectedTab;
+    isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!");
+
+    // Steps to reproduce the bug:
+    //   Pin the tab.
+    gBrowser.pinTab(tab);
+
+    //   Start playbak.
+    yield ContentTask.spawn(browser, {}, function* () {
+      let audio = content.document.querySelector("audio");
+      audio.play();
+    });
+
+    //   Wait for playback to start.
+    yield wait_for_tab_playing_event(tab, true);
+
+    //   Mute the tab.
+    let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
+    yield test_mute_tab(tab, icon, true);
+
+    //   Stop playback
+    yield ContentTask.spawn(browser, {}, function* () {
+      let audio = content.document.querySelector("audio");
+      audio.pause();
+    });
+
+    // Unmute tab.
+    yield test_mute_tab(tab, icon, false);
+
+    // Now click on the tab.
+    let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
+    EventUtils.synthesizeMouseAtCenter(image, {button: 0});
+
+    is(tab, gBrowser.selectedTab, "Tab switch should be successful");
+
+    // Cleanup.
+    gBrowser.unpinTab(tab);
+    gBrowser.selectedTab = originallySelectedTab;
+  }
+
+  let originallySelectedTab = gBrowser.selectedTab;
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE
+  }, test_on_browser);
+}
+
 function* test_on_browser(browser) {
   let tab = gBrowser.getTabForBrowser(browser);
 
   // Test the icon in a normal tab.
   yield test_playing_icon_on_tab(tab, browser, false);
 
   gBrowser.pinTab(tab);
 
@@ -175,16 +227,18 @@ function* test_on_browser(browser) {
   // Retest with another browser in the foreground tab
   if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
     yield BrowserTestUtils.withNewTab({
       gBrowser,
       url: "data:text/html,test"
     }, () => test_on_browser(browser));
   } else {
     yield test_browser_swapping(tab, browser);
+
+    yield test_click_on_pinned_tab_after_mute();
   }
 }
 
 add_task(function*() {
   yield new Promise((resolve) => {
     SpecialPowers.pushPrefEnv({"set": [
                                 ["media.useAudioChannelService", true],
                                 ["browser.tabs.showAudioPlayingIcon", true],
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -50,30 +50,48 @@ function ignoredError(aErrorObject) {
     }
     if (matches) {
       return true;
     }
   }
   return false;
 }
 
+function once(target, name) {
+  return new Promise((resolve, reject) => {
+    let cb = () => {
+      target.removeEventListener(name, cb);
+      resolve();
+    };
+    target.addEventListener(name, cb);
+  });
+}
+
 add_task(function checkAllTheCSS() {
   let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
   let uris = yield generateURIsFromDirTree(appDir, ".css");
 
-  // Create a clean iframe to load all the files into:
-  let hiddenWin = Services.appShell.hiddenDOMWindow;
-  let iframe = hiddenWin.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
-  hiddenWin.document.documentElement.appendChild(iframe);
+  // Create a clean iframe to load all the files into. This needs to live at a
+  // file or jar URI (depending on whether we're using a packaged build or not)
+  // so that it's allowed to load other same-scheme URIs (i.e. the browser css).
+  let resHandler = Services.io.getProtocolHandler("resource")
+                           .QueryInterface(Ci.nsISubstitutingProtocolHandler);
+  let resURI = Services.io.newURI('resource://testing-common/resource_test_file.html', null, null);
+  let testFile = resHandler.resolveURI(resURI);
+  let windowless = Services.appShell.createWindowlessBrowser();
+  let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+  windowless.document.documentElement.appendChild(iframe);
+  let iframeLoaded = once(iframe, 'load');
+  iframe.contentWindow.location = testFile;
+  yield iframeLoaded;
   let doc = iframe.contentWindow.document;
 
-
   // Listen for errors caused by the CSS:
   let errorListener = {
     observe: function(aMessage) {
       if (!aMessage || !(aMessage instanceof Ci.nsIScriptError)) {
         return;
       }
       // Only care about CSS errors generated by our iframe:
       if (aMessage.category.includes("CSS") && aMessage.innerWindowID === 0 && aMessage.outerWindowID === 0) {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_urlbarSearchSuggestionsNotification.js
@@ -0,0 +1,192 @@
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+  let engine = yield promiseNewEngine(TEST_ENGINE_BASENAME);
+  let oldCurrentEngine = Services.search.currentEngine;
+  Services.search.currentEngine = engine;
+  registerCleanupFunction(function () {
+    Services.search.currentEngine = oldCurrentEngine;
+    Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+    Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+
+    // Disable the notification for future tests so it doesn't interfere with
+    // them.  clearUserPref() won't work because by default the pref is false.
+    Services.prefs.setBoolPref(CHOICE_PREF, true);
+
+    // Make sure the popup is closed for the next test.
+    gURLBar.blur();
+    Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  });
+});
+
+add_task(function* focus_allSuggestionsDisabled() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, false);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  yield promiseAutocompleteResultPopup("foo");
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(false);
+});
+
+add_task(function* focus_noChoiceMade() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(true);
+  gURLBar.blur();
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  gURLBar.focus();
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open again");
+  assertVisible(true);
+});
+
+add_task(function* dismissWithoutResults() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(true);
+  Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
+  let disableButton = document.getAnonymousElementByAttribute(
+    gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+  );
+  let transitionPromise = promiseTransition();
+  disableButton.click();
+  yield transitionPromise;
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+  yield promiseAutocompleteResultPopup("foo");
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(false);
+});
+
+add_task(function* dismissWithResults() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(true);
+  yield promiseAutocompleteResultPopup("foo");
+  Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results");
+  let disableButton = document.getAnonymousElementByAttribute(
+    gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+  );
+  let transitionPromise = promiseTransition();
+  disableButton.click();
+  yield transitionPromise;
+  Assert.ok(gURLBar.popup.popupOpen, "popup should remain open");
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+  yield promiseAutocompleteResultPopup("foo");
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(false);
+});
+
+add_task(function* disable() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+  assertVisible(true);
+  let disableButton = document.getAnonymousElementByAttribute(
+    gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+  );
+  let transitionPromise = promiseTransition();
+  disableButton.click();
+  yield transitionPromise;
+  gURLBar.blur();
+  yield promiseAutocompleteResultPopup("foo");
+  assertSuggestionsPresent(false);
+});
+
+add_task(function* enable() {
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+  Services.prefs.setBoolPref(CHOICE_PREF, false);
+  gURLBar.blur();
+  gURLBar.focus();
+  yield promiseAutocompleteResultPopup("foo");
+  assertVisible(true);
+  assertSuggestionsPresent(false);
+  let enableButton = document.getAnonymousElementByAttribute(
+    gURLBar.popup, "anonid", "search-suggestions-notification-enable"
+  );
+  let searchPromise = promiseSearchComplete();
+  enableButton.click();
+  yield searchPromise;
+  // Clicking Yes should trigger a new search so that suggestions appear
+  // immediately.
+  assertSuggestionsPresent(true);
+  gURLBar.blur();
+  gURLBar.focus();
+  // Suggestions should still be present in a new search of course.
+  yield promiseAutocompleteResultPopup("bar");
+  assertSuggestionsPresent(true);
+});
+
+function assertSuggestionsPresent(expectedPresent) {
+  let controller = gURLBar.popup.input.controller;
+  let matchCount = controller.matchCount;
+  let actualPresent = false;
+  for (let i = 0; i < matchCount; i++) {
+    let url = controller.getValueAt(i);
+    let [, type, paramStr] = url.match(/^moz-action:([^,]+),(.*)$/);
+    let params = {};
+    try {
+      params = JSON.parse(paramStr);
+    } catch (err) {}
+    let isSuggestion = type == "searchengine" && "searchSuggestion" in params;
+    actualPresent = actualPresent || isSuggestion;
+  }
+  Assert.equal(actualPresent, expectedPresent);
+}
+
+function assertVisible(visible) {
+  let style =
+    window.getComputedStyle(gURLBar.popup.searchSuggestionsNotification);
+  Assert.equal(style.visibility, visible ? "visible" : "collapse");
+}
+
+function promiseNewEngine(basename) {
+  return new Promise((resolve, reject) => {
+    info("Waiting for engine to be added: " + basename);
+    let url = getRootDirectory(gTestPath) + basename;
+    Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "",
+                              false, {
+      onSuccess: function (engine) {
+        info("Search engine added: " + basename);
+        registerCleanupFunction(() => Services.search.removeEngine(engine));
+        resolve(engine);
+      },
+      onError: function (errCode) {
+        Assert.ok(false, "addEngine failed with error code " + errCode);
+        reject();
+      },
+    });
+  });
+}
+
+function promiseTransition() {
+  return new Promise(resolve => {
+    gURLBar.popup.addEventListener("transitionend", function onEnd() {
+      gURLBar.popup.removeEventListener("transitionend", onEnd, true);
+      // The urlbar needs to handle the transitionend first, but that happens
+      // naturally since promises are resolved at the end of the current tick.
+      resolve();
+    }, true);
+  });
+}
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -61,16 +61,18 @@ file, You can obtain one at http://mozil
 
         this._prefs.addObserver("", this, false);
         this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
         this.timeout = this._prefs.getIntPref("delay");
         this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
+        this._userMadeSearchSuggestionsChoice =
+          this._prefs.getBoolPref("userMadeSearchSuggestionsChoice");
         this._ignoreNextSelect = false;
 
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
         this.inputField.addEventListener("paste", this, false);
         this.inputField.addEventListener("mousedown", this, false);
         this.inputField.addEventListener("mousemove", this, false);
         this.inputField.addEventListener("mouseout", this, false);
         this.inputField.addEventListener("overflow", this, false);
@@ -655,16 +657,20 @@ file, You can obtain one at http://mozil
                 this.completeDefaultIndex = this._prefs.getBoolPref(aData);
                 break;
               case "delay":
                 this.timeout = this._prefs.getIntPref(aData);
                 break;
               case "formatting.enabled":
                 this._formattingEnabled = this._prefs.getBoolPref(aData);
                 break;
+              case "userMadeSearchSuggestionsChoice":
+                this._userMadeSearchSuggestionsChoice =
+                  this._prefs.getBoolPref(aData);
+                break;
               case "trimURLs":
                 this._mayTrimURLs = this._prefs.getBoolPref(aData);
                 break;
               case "unifiedcomplete":
                 let useUnifiedComplete = false;
                 try {
                   useUnifiedComplete = this._prefs.getBoolPref(aData);
                 } catch (ex) {}
@@ -908,16 +914,35 @@ file, You can obtain one at http://mozil
           if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete") &&
               this.popup.selectedIndex == 0) {
             return this.mController.handleText();
           }
           return this.mController.handleDelete();
         ]]></body>
       </method>
 
+      <field name="_userMadeSearchSuggestionsChoice"><![CDATA[
+        false
+      ]]></field>
+
+      <method name="_maybeShowSearchSuggestionsNotification">
+        <body><![CDATA[
+          let showNotification =
+            !this._userMadeSearchSuggestionsChoice &&
+            // When _urlbarFocused is true, tabbrowser would close the popup if
+            // it's opened here, so don't show the notification.
+            !gBrowser.selectedBrowser._urlbarFocused &&
+            Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
+            this._prefs.getBoolPref("unifiedcomplete");
+          if (showNotification) {
+            this.popup.showSearchSuggestionsNotification(this, this);
+          }
+        ]]></body>
+      </method>
+
     </implementation>
 
     <handlers>
       <handler event="keydown"><![CDATA[
         if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
              event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
             this.popup.selectedIndex >= 0 &&
             !this._noActionsKeys.has(event.keyCode)) {
@@ -938,16 +963,17 @@ file, You can obtain one at http://mozil
             this._clearNoActions();
         }
       ]]></handler>
 
       <handler event="focus"><![CDATA[
         if (event.originalTarget == this.inputField) {
           this._hideURLTooltip();
           this.formatValue();
+          this._maybeShowSearchSuggestionsNotification();
         }
       ]]></handler>
 
       <handler event="blur"><![CDATA[
         if (event.originalTarget == this.inputField) {
           this._clearNoActions();
           this.formatValue();
         }
@@ -1469,25 +1495,81 @@ file, You can obtain one at http://mozil
   <binding id="addengine-icon" extends="xul:box">
     <content>
       <xul:image class="addengine-icon" xbl:inherits="src"/>
       <xul:image class="addengine-badge"/>
     </content>
   </binding>
 
   <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+
+    <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+      <xul:hbox anonid="search-suggestions-notification" align="center">
+        <xul:description flex="1">
+          &urlbar.searchSuggestionsNotification.question;
+          <xul:label anonid="search-suggestions-notification-learn-more"
+                     class="text-link"
+                     value="&urlbar.searchSuggestionsNotification.learnMore;"/>
+        </xul:description>
+        <xul:button anonid="search-suggestions-notification-disable"
+                    label="&urlbar.searchSuggestionsNotification.disable;"
+                    onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(false);"/>
+        <xul:button anonid="search-suggestions-notification-enable"
+                    label="&urlbar.searchSuggestionsNotification.enable;"
+                    onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(true);"/>
+      </xul:hbox>
+      <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
+                       flex="1"/>
+      <xul:hbox anonid="footer">
+        <children/>
+      </xul:hbox>
+    </content>
+
     <implementation>
       <field name="_maxResults">0</field>
 
       <field name="_bundle" readonly="true">
         Cc["@mozilla.org/intl/stringbundle;1"].
           getService(Ci.nsIStringBundleService).
           createBundle("chrome://browser/locale/places/places.properties");
       </field>
 
+      <field name="searchSuggestionsNotification" readonly="true">
+        document.getAnonymousElementByAttribute(
+          this, "anonid", "search-suggestions-notification"
+        );
+      </field>
+
+      <field name="searchSuggestionsNotificationLearnMoreLink" readonly="true">
+        document.getAnonymousElementByAttribute(
+          this, "anonid", "search-suggestions-notification-learn-more"
+        );
+      </field>
+
+      <field name="footer" readonly="true">
+        document.getAnonymousElementByAttribute(this, "anonid", "footer");
+      </field>
+
+      <method name="dismissSearchSuggestionsNotification">
+        <parameter name="enableSuggestions"/>
+        <body><![CDATA[
+          Services.prefs.setBoolPref(
+            "browser.urlbar.userMadeSearchSuggestionsChoice", true
+          );
+          Services.prefs.setBoolPref(
+            "browser.urlbar.suggest.searches", enableSuggestions
+          );
+          this._hideSearchSuggestionsNotification(true);
+          if (enableSuggestions && this.input.textValue) {
+            // Start a new search so that suggestions appear immediately.
+            this.input.controller.startSearch(this.input.textValue);
+          }
+        ]]></body>
+      </method>
+
       <!-- Override this so that when UnifiedComplete is enabled, navigating
            between items results in an item always being selected. This is
            contrary to the old behaviour (UnifiedComplete disabled) where
            if you navigate beyond either end of the list, no item will be
            selected. -->
       <method name="getNextIndex">
         <parameter name="reverse"/>
         <parameter name="amount"/>
@@ -1544,19 +1626,112 @@ file, You can obtain one at http://mozil
         <parameter name="aInput"/>
         <parameter name="aElement"/>
         <body>
           <![CDATA[
           // initially the panel is hidden
           // to avoid impacting startup / new window performance
           aInput.popup.hidden = false;
 
-          // this method is defined on the base binding
+          // The popup may already be open if it's showing the search
+          // suggestions notification.  In that case, its footer visibility
+          // needs to be updated.
+          if (this.popupOpen) {
+            this._updateFooterVisibility();
+          }
+
           this._openAutocompletePopup(aInput, aElement);
-        ]]></body>
+          ]]>
+        </body>
+      </method>
+
+      <method name="_updateFooterVisibility">
+        <body>
+          <![CDATA[
+          this.footer.collapsed = this._matchCount == 0;
+          ]]>
+        </body>
+      </method>
+
+      <method name="showSearchSuggestionsNotification">
+        <parameter name="aInput"/>
+        <parameter name="aElement"/>
+        <body>
+          <![CDATA[
+          // Set the learn-more link href.
+          let link = this.searchSuggestionsNotificationLearnMoreLink;
+          if (!link.hasAttribute("href")) {
+            let url = Services.urlFormatter.formatURL(
+              Services.prefs.getCharPref("app.support.baseURL") + "suggestions"
+            );
+            link.setAttribute("href", url);
+          }
+
+          // With the notification shown, the listbox's height can sometimes be
+          // too small when it's flexed, as it normally is.  Also, it can start
+          // out slightly scrolled down.  Both problems appear together, most
+          // often when the popup is very narrow and the notification's text
+          // must wrap.  Work around them by removing the flex.
+          //
+          // But without flexing the listbox, the listbox's height animation
+          // sometimes fails to complete, leaving the popup too tall.  Work
+          // around that problem by disabling the listbox animation.
+          this.richlistbox.flex = 0;
+          this.setAttribute("dontanimate", "true");
+
+          this.classList.add("showSearchSuggestionsNotification");
+          this._updateFooterVisibility();
+          this.openAutocompletePopup(aInput, aElement);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_hideSearchSuggestionsNotification">
+        <parameter name="animate"/>
+        <body>
+          <![CDATA[
+          if (animate) {
+            this._hideSearchSuggestionsNotificationWithAnimation();
+            return;
+          }
+          this.classList.remove("showSearchSuggestionsNotification");
+          this.richlistbox.flex = 1;
+          this.removeAttribute("dontanimate");
+          if (this._matchCount) {
+            // Update popup height.
+            this._invalidate();
+          } else {
+            this.closePopup();
+          }
+          ]]>
+        </body>
+      </method>
+
+      <method name="_hideSearchSuggestionsNotificationWithAnimation">
+        <body>
+          <![CDATA[
+          let notificationHeight = this.searchSuggestionsNotification
+                                       .getBoundingClientRect()
+                                       .height;
+          this.searchSuggestionsNotification.style.marginTop =
+            "-" + notificationHeight + "px";
+
+          let popupHeightPx =
+            (this.getBoundingClientRect().height - notificationHeight) + "px";
+          this.style.height = popupHeightPx;
+
+          let onTransitionEnd = () => {
+            this.removeEventListener("transitionend", onTransitionEnd, true);
+            this.searchSuggestionsNotification.style.marginTop = "0px";
+            this.style.removeProperty("height");
+            this._hideSearchSuggestionsNotification(false);
+          };
+          this.addEventListener("transitionend", onTransitionEnd, true);
+          ]]>
+        </body>
       </method>
 
       <method name="onPopupClick">
         <parameter name="aEvent"/>
         <body>
           <![CDATA[
           // Ignore right-clicks
           if (aEvent.button == 2)
@@ -1664,16 +1839,25 @@ file, You can obtain one at http://mozil
         // When the user selects one of matches, stop the search to avoid
         // changing the underlying result unexpectedly.
         if (!this._ignoreNextSelect && this.selectedIndex >= 0) {
           let controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
           controller.stopSearch();
         }
       ]]></handler>
 
+      <handler event="mousedown"><![CDATA[
+        // Required to make the xul:label.text-link elements in the search
+        // suggestions notification work correctly when clicked on Linux.
+        // This is copied from the mousedown handler in
+        // browser-search-autocomplete-result-popup, which apparently had a
+        // similar problem.
+        event.preventDefault();
+      ]]></handler>
+
     </handlers>
   </binding>
 
   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <implementation>
       <constructor><![CDATA[
         if (!this.notification)
           return;
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -20,17 +20,17 @@
                        oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
                        wrap="true"
                        hidden="true"/>
         <hbox id="PanelUI-footer-fxa">
           <hbox id="PanelUI-fxa-status"
                 defaultlabel="&fxaSignIn.label;"
                 signedinTooltiptext="&fxaSignedIn.tooltip;"
                 errorlabel="&fxaSignInError.label;"
-                onclick="gFxAccounts.onMenuPanelCommand();">
+                onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
             <image id="PanelUI-fxa-avatar"/>
             <toolbarbutton id="PanelUI-fxa-label"
                            fxabrandname="&syncBrand.fxAccount.label;"/>
           </hbox>
           <toolbarseparator/>
           <toolbarbutton id="PanelUI-fxa-icon"
                          oncommand="gSyncUI.doSync();"
                          closemenu="none"/>
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -13,53 +13,55 @@
 }
 
 .content-area input.contact-filter {
   margin-top: 14px;
   border-radius: 10000px;
 }
 
 .contact-list {
-  border-top: 1px solid #ccc;
   overflow-x: hidden;
   overflow-y: auto;
   /* Space for six contacts, not affected by filtering.  This is enough space
      to show the dropdown menu when there is only one contact. */
   height: 306px;
 }
 
+.contact-list-title {
+  padding: 0 1rem;
+  color: #666;
+  font-weight: 500;
+  font-size: .9em;
+}
+
 .contact,
 .contact-separator {
-  padding: .5rem 1rem;
+  padding: .5rem 15px;
   font-size: 13px;
 }
 
 .contact {
   position: relative;
   display: flex;
   flex-direction: row;
   align-items: center;
   color: #666;
 }
 
 .contact-separator {
   background-color: #eee;
   color: #888;
 }
 
-.contact:not(:first-child) {
-  border-top: 1px solid #ddd;
-}
-
 .contact-separator:not(:first-child) {
   border-top: 1px solid #ccc;
 }
 
 .contact:hover {
-  background-color: #eee;
+  background-color: #E3F7FE;
 }
 
 .contact:hover > .icons {
   display: block;
   z-index: 1;
 }
 
 .contact > .details {
@@ -86,24 +88,111 @@
   box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
   background-image: url("../shared/img/audio-call-avatar.svg");
   background-repeat: no-repeat;
   background-color: #4ba6e7;
   background-size: contain;
   -moz-user-select: none;
 }
 
+/*
+ * Loop through all 12 default avatars.
+ */
+.contact:nth-child(12n + 1) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#blue-avatar");
+  background-color: #4A90E2;
+}
+
+.contact:nth-child(12n + 2) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#orange-avatar");
+  background-color: #F3A35C;
+}
+
+.contact:nth-child(12n + 3) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#mintgreen-avatar");
+  background-color: #50E2C2;
+}
+
+.contact:nth-child(12n + 4) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#lightpink-avatar");
+  background-color: #E364A1;
+}
+
+.contact:nth-child(12n + 5) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#grey-avatar");
+  background-color: #9B9B9B;
+}
+
+.contact:nth-child(12n + 6) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#yellow-avatar");
+  background-color: #F3E968;
+}
+
+.contact:nth-child(12n + 7) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#purple-avatar");
+  background-color: #9C61AF;
+}
+
+.contact:nth-child(12n + 8) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#lightgreen-avatar");
+  background-color: #9AC967;
+}
+
+.contact:nth-child(12n + 9) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#darkblue-avatar");
+  background-color: #607CAE;
+}
+
+.contact:nth-child(12n + 10) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#darkpink-avatar");
+  background-color: #CE4D6E;
+}
+
+.contact:nth-child(12n + 11) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#brown-avatar");
+  background-color: #8A572A;
+}
+
+.contact:nth-child(12n + 12) > .avatar.defaultAvatar {
+  background-image: url("../shared/img/avatars.svg#green-avatar");
+  background-color: #56B397;
+}
+
 .contact > .avatar > img {
   width: 100%;
 }
 
+.contact-list-empty {
+  background-image: url("../shared/img/empty_contacts.svg");
+  background-repeat: no-repeat;
+  background-position: top center;
+  padding-top: 28%;
+  padding-bottom: 5%;
+  text-align: center;
+  color: #4a4a4a;
+  font-weight: lighter;
+}
+
+.panel-text-medium,
+.panel-text-large {
+  margin: 3px;
+}
+
+.panel-text-medium {
+  font-size: 1.6rem;
+}
+
+.panel-text-large {
+  font-size: 2.2rem;
+}
+
 .contact > .details > .username {
-  font-size: 12px;
+  font-size: 1.3rem;
   line-height: 20px;
-  color: #222;
+  color: #000;
 }
 
 .contact.blocked > .details > .username {
   color: #d74345;
 }
 
 .contact > .details > .username > strong {
   font-weight: bold;
@@ -115,135 +204,88 @@
   height: 20px;
   -moz-margin-start: 3px;
   background-image: url("../shared/img/icons-16x16.svg#block-red");
   background-position: center;
   background-size: 10px 10px;
   background-repeat: no-repeat;
 }
 
-.contact > .details > .username > i.icon-google {
-  position: absolute;
-  right: 1rem;
-  top: 35%;
-  width: 14px;
-  height: 14px;
-  border-radius: 50%;
-  background-image: url("../shared/img/icons-16x16.svg#google");
-  background-position: center;
-  background-size: 16px 16px;
-  background-repeat: no-repeat;
-  background-color: #fff;
-}
-
-html[dir="rtl"] .contact > .details > .username > i.icon-google {
-  left: 1rem;
-  right: auto;
-}
-
 .contact > .details > .email {
-  color: #999;
+  color: #4a4a4a;
   font-size: 11px;
-  line-height: 16px;
+  line-height: 14px;
 }
 
 .icons {
   cursor: pointer;
   display: none;
   -moz-margin-start: auto;
-  padding: 10px;
-  border-radius: 2px;
-  background-color: #5bc0a4;
   color: #fff;
   -moz-user-select: none;
 }
 
 .icons:hover {
-  background-color: #47b396;
-}
-
-.icons:hover:active {
-  background-color: #3aa689;
+  display: block;
 }
 
 .icons i {
-  margin: 0 5px;
   display: inline-block;
   background-position: center;
   background-repeat: no-repeat;
 }
 
-.icons i.icon-video {
-  background-image: url("../shared/img/icons-14x14.svg#video-white");
-  background-size: 14px 14px;
+.icon-contact-video-call {
+  padding: 15px;
   width: 16px;
   height: 16px;
+  border-radius: 50%;
+  background-color: #5bc0a4;
+  background-image: url("../shared/img/icons-14x14.svg#video-white");
+  background-size: 16px 16px;
+}
+
+.icon-contact-video-call:hover {
+  background-color: #47b396;
 }
 
-.icons i.icon-caret-down {
-  background-image: url("../shared/img/icons-10x10.svg#dropdown-white");
-  background-size: 10px 10px;
-  width: 10px;
-  height: 16px;
+.icon-contact-video-call:active {
+  background-color: #3aa689;
+}
+
+.icon-vertical-ellipsis {
+  /* Added padding for a larger click area. */
+  padding: 0 10px;
+  margin: 6px 0;
+  -moz-margin-start: 5px;
+  -moz-margin-end: -8px;
+  width: 4px;
+  height: 20px;
+  background-image: url("../shared/img/ellipsis-v.svg");
+  background-size: contain;
 }
 
 .contact > .dropdown-menu {
   z-index: 2;
-  top: 10px;
+  top: 37px;
+  right: 22px;
   bottom: auto;
-  right: 3em;
   left: auto;
 }
 
 html[dir="rtl"] .contact > .dropdown-menu {
   right: auto;
-  left: 3em;
+  left: 22px;
 }
 
 .contact > .dropdown-menu-up {
-  bottom: 10px;
+  bottom: 25px;
   top: auto;
 }
 
-.contact > .dropdown-menu > .dropdown-menu-item > .icon {
-  width: 20px;
-  height: 10px;
-  background-position: center left;
-  background-size: 10px 10px;
-  margin-top: 3px;
-}
-
-html[dir="rtl"] .contact > .dropdown-menu > .dropdown-menu-item > .icon {
-  background-position: center right;
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-audio-call {
-  background-image: url("../shared/img/icons-16x16.svg#audio");
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-video-call {
-  background-image: url("../shared/img/icons-16x16.svg#video");
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-edit {
-  background-image: url("../shared/img/icons-16x16.svg#contacts");
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-block {
-  background-image: url("../shared/img/icons-16x16.svg#block");
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-unblock {
-  background-image: url("../shared/img/icons-16x16.svg#unblock");
-}
-
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-remove {
-  background-image: url("../shared/img/icons-16x16.svg#delete");
-}
-
 .contact-form > .button-group {
   margin-top: 1rem;
 }
 
 .contacts-gravatar-promo {
   position: relative;
   border: 1px dashed #c1c1c1;
   border-radius: 2px;
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -61,16 +61,17 @@ body {
   padding: .5rem 1rem;
   border-radius: 3px;
 }
 
 /* Tabs and tab selection buttons */
 
 .tab-view-container {
   background-image: url("../shared/img/beta-ribbon.svg#beta-ribbon");
+  background-color: #fbfbfb;
   background-size: 36px 36px;
   background-repeat: no-repeat;
 }
 
 .tab-view {
   position: relative;
   width: 100%;
   height: 4rem;
@@ -717,16 +718,21 @@ html[dir="rtl"] .generate-url-spinner {
   height: 16px;
   vertical-align: bottom;
   background-repeat: no-repeat;
   background-size: cover;
   -moz-margin-end: .2rem;
   margin-bottom: -2px;
 }
 
+.dropdown-menu-item.status-available:before,
+.dropdown-menu-item.status-unavailable:before {
+  margin-bottom: 2px;
+}
+
 html[dir="rtl"] .dropdown-menu-item.status-available:before,
 html[dir="rtl"] .dropdown-menu-item.status-unavailable:before {
   margin-right: -3px;
 }
 
 .status-available:before {
   background-image: url("../shared/img/icons-16x16.svg#status-available");
 }
@@ -837,17 +843,17 @@ html[dir="rtl"] .settings-menu .dropdown
 .footer {
   display: flex;
   flex-direction: row;
   flex-wrap: nowrap;
   justify-content: space-between;
   align-content: stretch;
   align-items: center;
   font-size: 1rem;
-  background-color: #fff;
+  background-color: #fbfbfb;
   color: #666666;
   padding: .5rem 15px;
 }
 
 .footer .signin-details {
   align-items: center;
   display: flex;
 }
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -180,57 +180,39 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
-
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
         React.createElement("ul", {className: cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}, 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
-                              "disabled": this.props.blocked }), 
-              "data-action": "video-call", 
-              onClick: this.onItemClick}, 
-            React.createElement("i", {className: "icon icon-video-call"}), 
-            mozL10n.get("video_call_menu_button")
-          ), 
-          React.createElement("li", {className: cx({ "dropdown-menu-item": true,
-                              "disabled": this.props.blocked }), 
-              "data-action": "audio-call", 
-              onClick: this.onItemClick}, 
-            React.createElement("i", {className: "icon icon-audio-call"}), 
-            mozL10n.get("audio_call_menu_button")
-          ), 
-          React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
               "data-action": "edit", 
               onClick: this.onItemClick}, 
-            React.createElement("i", {className: "icon icon-edit"}), 
-            mozL10n.get("edit_contact_menu_button")
+            mozL10n.get("edit_contact_title")
           ), 
           React.createElement("li", {className: "dropdown-menu-item", 
               "data-action": blockAction, 
               onClick: this.onItemClick}, 
-            React.createElement("i", {className: "icon icon-" + blockAction}), 
             mozL10n.get(blockLabel)
           ), 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
-               "data-action": "remove", 
-               onClick: this.onItemClick}, 
-            React.createElement("i", {className: "icon icon-remove"}), 
-            mozL10n.get("remove_contact_menu_button2")
+              "data-action": "remove", 
+              onClick: this.onItemClick}, 
+            mozL10n.get("confirm_delete_contact_remove_button")
           )
         )
       );
     }
   });
 
   const ContactDetail = React.createClass({displayName: "ContactDetail",
     getInitialState: function() {
@@ -290,38 +272,42 @@ loop.contacts = (function(_, mozL10n) {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
+      let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
+      let avatarCSSClass = cx({
+        avatar: true,
+        defaultAvatar: !avatarSrc
+      });
 
       return (
         React.createElement("li", {className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, 
-          React.createElement("div", {className: "avatar"}, 
-            React.createElement("img", {src: navigator.mozLoop.getUserAvatar(email.value)})
+          React.createElement("div", {className: avatarCSSClass}, 
+            avatarSrc ? React.createElement("img", {src: avatarSrc}) : null
           ), 
           React.createElement("div", {className: "details"}, 
             React.createElement("div", {className: "username"}, React.createElement("strong", null, names.firstName), " ", names.lastName, 
-              React.createElement("i", {className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), 
               React.createElement("i", {className: cx({"icon icon-blocked": this.props.contact.blocked})})
             ), 
             React.createElement("div", {className: "email"}, email.value)
           ), 
           React.createElement("div", {className: "icons"}, 
-            React.createElement("i", {className: "icon icon-video", 
+            React.createElement("i", {className: "icon icon-contact-video-call", 
                onClick: this.handleAction.bind(null, "video-call")}), 
-            React.createElement("i", {className: "icon icon-caret-down", 
+            React.createElement("i", {className: "icon icon-vertical-ellipsis", 
                onClick: this.showDropdownMenu})
           ), 
           this.state.showMenu
             ? React.createElement(ContactDropdown, {blocked: this.props.contact.blocked, 
                                canEdit: this.canEdit(), 
                                handleAction: this.handleAction})
             : null
           
@@ -332,20 +318,21 @@ loop.contacts = (function(_, mozL10n) {
 
   const ContactsList = React.createClass({displayName: "ContactsList",
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
+      mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.instanceOf(
-        loop.shared.models.NotificationCollection).isRequired,
-        // Callback to handle entry to the add/edit contact form.
-        startForm: React.PropTypes.func.isRequired
+                     loop.shared.models.NotificationCollection).isRequired,
+      // Callback to handle entry to the add/edit contact form.
+      startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -356,17 +343,17 @@ loop.contacts = (function(_, mozL10n) {
     getInitialState: function() {
       return {
         importBusy: false,
         filter: ""
       };
     },
 
     refresh: function(callback = function() {}) {
-      let contactsAPI = navigator.mozLoop.contacts;
+      let contactsAPI = this.props.mozLoop.contacts;
 
       this.handleContactRemoveAll();
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
           callback(err);
           return;
         }
@@ -388,28 +375,28 @@ loop.contacts = (function(_, mozL10n) {
         addContactsInChunks(contacts);
       });
     },
 
     componentWillMount: function() {
       // Take the time to initialize class variables that are used outside
       // `this.state`.
       this.contacts = {};
-      this._userProfile = navigator.mozLoop.userProfile;
+      this._userProfile = this.props.mozLoop.userProfile;
     },
 
     componentDidMount: function() {
       window.addEventListener("LoopStatusChanged", this._onStatusChanged);
 
       this.refresh(err => {
         if (err) {
           throw err;
         }
 
-        let contactsAPI = navigator.mozLoop.contacts;
+        let contactsAPI = this.props.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -422,17 +409,17 @@ loop.contacts = (function(_, mozL10n) {
       });
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
     },
 
     _onStatusChanged: function() {
-      let profile = navigator.mozLoop.userProfile;
+      let profile = this.props.mozLoop.userProfile;
       let currUid = this._userProfile ? this._userProfile.uid : null;
       let newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), reload all contacts.
         this._userProfile = profile;
         // The following will do a forceUpdate() for us.
         this.refresh();
       }
@@ -460,17 +447,17 @@ loop.contacts = (function(_, mozL10n) {
     handleContactRemoveAll: function() {
       // Do not allow any race conditions when removing all contacts.
       this.contacts = {};
       this.forceUpdate();
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
-      navigator.mozLoop.startImport({
+      this.props.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
@@ -486,54 +473,54 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
-          navigator.mozLoop.confirm({
+          this.props.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
           }, (error, result) => {
             if (error) {
               throw error;
             }
 
             if (!result) {
               return;
             }
 
-            navigator.mozLoop.contacts.remove(contact._guid, err => {
+            this.props.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
                 throw err;
               }
             });
           });
           break;
         case "block":
         case "unblock":
           // Invoke the API named like the action.
-          navigator.mozLoop.contacts[actionName](contact._guid, err => {
+          this.props.mozLoop.contacts[actionName](contact._guid, err => {
             if (err) {
               throw err;
             }
           });
           break;
         case "video-call":
           if (!contact.blocked) {
-            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
+            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
             this.closeWindow();
           }
           break;
         case "audio-call":
           if (!contact.blocked) {
-            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
+            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
             this.closeWindow();
           }
           break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
@@ -549,17 +536,17 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
-    render: function() {
+    _renderContactsList: function() {
       let cx = React.addons.classSet;
 
       let viewForItem = item => {
         return (
           React.createElement(ContactDetail, {contact: item, 
                          handleContactAction: this.handleContactAction, 
                          key: item._guid})
         );
@@ -582,51 +569,79 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.available = shownContacts.available.filter(filterFn);
           }
           if (shownContacts.blocked) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
+      if (shownContacts.available || shownContacts.blocked) {
+        return (
+          React.createElement("div", null, 
+            React.createElement("div", {className: "contact-list-title"}, 
+              mozL10n.get("contact_list_title")
+            ), 
+            React.createElement("ul", {className: "contact-list"}, 
+              shownContacts.available ?
+                shownContacts.available.sort(this.sortContacts).map(viewForItem) :
+                null, 
+              shownContacts.blocked && shownContacts.blocked.length > 0 ?
+                React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
+                null, 
+              shownContacts.blocked ?
+                shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
+                null
+            )
+          )
+        );
+      }
+
+      return (
+        React.createElement("div", {className: "contact-list-empty"}, 
+          React.createElement("p", {className: "panel-text-large"}, 
+            mozL10n.get("no_contacts_message_heading")
+          ), 
+          React.createElement("p", {className: "panel-text-medium"}, 
+            mozL10n.get("no_contacts_import_or_add")
+          )
+        )
+      );
+    },
+
+    render: function() {
+      let cx = React.addons.classSet;
+      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+                       MIN_CONTACTS_FOR_FILTERING;
+
       return (
         React.createElement("div", null, 
           React.createElement("div", {className: "content-area"}, 
             showFilter ?
             React.createElement("input", {className: "contact-filter", 
                    placeholder: mozL10n.get("contacts_search_placesholder"), 
                    valueLink: this.linkState("filter")})
             : null, 
             React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
           ), 
-          React.createElement("ul", {className: "contact-list"}, 
-            shownContacts.available ?
-              shownContacts.available.sort(this.sortContacts).map(viewForItem) :
-              null, 
-            shownContacts.blocked && shownContacts.blocked.length > 0 ?
-              React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
-              null, 
-            shownContacts.blocked ?
-              shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
-              null
-          ), 
+          this._renderContactsList(), 
           React.createElement(ButtonGroup, {additionalClass: "contact-controls"}, 
             React.createElement(Button, {additionalClass: "secondary", 
-                    caption: this.state.importBusy
-                             ? mozL10n.get("importing_contacts_progress_button")
-                             : mozL10n.get("import_contacts_button3"), 
-                    disabled: this.state.importBusy, 
-                    onClick: this.handleImportButtonClick}, 
-              React.createElement("div", {className: cx({"contact-import-spinner": true,
-                                  spinner: true,
-                                  busy: this.state.importBusy})})
+              caption: this.state.importBusy
+                ? mozL10n.get("importing_contacts_progress_button")
+                : mozL10n.get("import_contacts_button3"), 
+                disabled: this.state.importBusy, 
+                onClick: this.handleImportButtonClick}, 
+                React.createElement("div", {className: cx({"contact-import-spinner": true,
+                                   spinner: true,
+                busy: this.state.importBusy})})
             ), 
             React.createElement(Button, {additionalClass: "primary", 
-                    caption: mozL10n.get("new_contact_button"), 
-                    onClick: this.handleAddContactButtonClick})
+              caption: mozL10n.get("new_contact_button"), 
+              onClick: this.handleAddContactButtonClick})
           )
         )
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({displayName: "ContactDetailsForm",
     mixins: [React.addons.LinkedStateMixin],
@@ -761,14 +776,16 @@ loop.contacts = (function(_, mozL10n) {
                     onClick: this.handleAcceptButtonClick})
           )
         )
       );
     }
   });
 
   return {
+    ContactDropdown: ContactDropdown,
     ContactsList: ContactsList,
+    ContactDetail: ContactDetail,
     ContactDetailsForm: ContactDetailsForm,
     _getPreferred: getPreferred,
     _setPreferred: setPreferred
   };
 })(_, document.mozL10n);
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -180,57 +180,39 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
-
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
         <ul className={cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}>
           <li className={cx({ "dropdown-menu-item": true,
-                              "disabled": this.props.blocked })}
-              data-action="video-call"
-              onClick={this.onItemClick}>
-            <i className="icon icon-video-call" />
-            {mozL10n.get("video_call_menu_button")}
-          </li>
-          <li className={cx({ "dropdown-menu-item": true,
-                              "disabled": this.props.blocked })}
-              data-action="audio-call"
-              onClick={this.onItemClick}>
-            <i className="icon icon-audio-call" />
-            {mozL10n.get("audio_call_menu_button")}
-          </li>
-          <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
               data-action="edit"
               onClick={this.onItemClick}>
-            <i className="icon icon-edit" />
-            {mozL10n.get("edit_contact_menu_button")}
+            {mozL10n.get("edit_contact_title")}
           </li>
           <li className="dropdown-menu-item"
               data-action={blockAction}
               onClick={this.onItemClick}>
-            <i className={"icon icon-" + blockAction} />
             {mozL10n.get(blockLabel)}
           </li>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
-               data-action="remove"
-               onClick={this.onItemClick}>
-            <i className="icon icon-remove" />
-            {mozL10n.get("remove_contact_menu_button2")}
+              data-action="remove"
+              onClick={this.onItemClick}>
+            {mozL10n.get("confirm_delete_contact_remove_button")}
           </li>
         </ul>
       );
     }
   });
 
   const ContactDetail = React.createClass({
     getInitialState: function() {
@@ -290,38 +272,42 @@ loop.contacts = (function(_, mozL10n) {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
+      let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
+      let avatarCSSClass = cx({
+        avatar: true,
+        defaultAvatar: !avatarSrc
+      });
 
       return (
         <li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
-          <div className="avatar">
-            <img src={navigator.mozLoop.getUserAvatar(email.value)} />
+          <div className={avatarCSSClass}>
+            {avatarSrc ? <img src={avatarSrc} /> : null}
           </div>
           <div className="details">
             <div className="username"><strong>{names.firstName}</strong> {names.lastName}
-              <i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
               <i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
             </div>
             <div className="email">{email.value}</div>
           </div>
           <div className="icons">
-            <i className="icon icon-video"
+            <i className="icon icon-contact-video-call"
                onClick={this.handleAction.bind(null, "video-call")} />
-            <i className="icon icon-caret-down"
+            <i className="icon icon-vertical-ellipsis"
                onClick={this.showDropdownMenu} />
           </div>
           {this.state.showMenu
             ? <ContactDropdown blocked={this.props.contact.blocked}
                                canEdit={this.canEdit()}
                                handleAction={this.handleAction} />
             : null
           }
@@ -332,20 +318,21 @@ loop.contacts = (function(_, mozL10n) {
 
   const ContactsList = React.createClass({
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
+      mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.instanceOf(
-        loop.shared.models.NotificationCollection).isRequired,
-        // Callback to handle entry to the add/edit contact form.
-        startForm: React.PropTypes.func.isRequired
+                     loop.shared.models.NotificationCollection).isRequired,
+      // Callback to handle entry to the add/edit contact form.
+      startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -356,17 +343,17 @@ loop.contacts = (function(_, mozL10n) {
     getInitialState: function() {
       return {
         importBusy: false,
         filter: ""
       };
     },
 
     refresh: function(callback = function() {}) {
-      let contactsAPI = navigator.mozLoop.contacts;
+      let contactsAPI = this.props.mozLoop.contacts;
 
       this.handleContactRemoveAll();
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
           callback(err);
           return;
         }
@@ -388,28 +375,28 @@ loop.contacts = (function(_, mozL10n) {
         addContactsInChunks(contacts);
       });
     },
 
     componentWillMount: function() {
       // Take the time to initialize class variables that are used outside
       // `this.state`.
       this.contacts = {};
-      this._userProfile = navigator.mozLoop.userProfile;
+      this._userProfile = this.props.mozLoop.userProfile;
     },
 
     componentDidMount: function() {
       window.addEventListener("LoopStatusChanged", this._onStatusChanged);
 
       this.refresh(err => {
         if (err) {
           throw err;
         }
 
-        let contactsAPI = navigator.mozLoop.contacts;
+        let contactsAPI = this.props.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -422,17 +409,17 @@ loop.contacts = (function(_, mozL10n) {
       });
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
     },
 
     _onStatusChanged: function() {
-      let profile = navigator.mozLoop.userProfile;
+      let profile = this.props.mozLoop.userProfile;
       let currUid = this._userProfile ? this._userProfile.uid : null;
       let newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), reload all contacts.
         this._userProfile = profile;
         // The following will do a forceUpdate() for us.
         this.refresh();
       }
@@ -460,17 +447,17 @@ loop.contacts = (function(_, mozL10n) {
     handleContactRemoveAll: function() {
       // Do not allow any race conditions when removing all contacts.
       this.contacts = {};
       this.forceUpdate();
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
-      navigator.mozLoop.startImport({
+      this.props.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
@@ -486,54 +473,54 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
-          navigator.mozLoop.confirm({
+          this.props.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
           }, (error, result) => {
             if (error) {
               throw error;
             }
 
             if (!result) {
               return;
             }
 
-            navigator.mozLoop.contacts.remove(contact._guid, err => {
+            this.props.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
                 throw err;
               }
             });
           });
           break;
         case "block":
         case "unblock":
           // Invoke the API named like the action.
-          navigator.mozLoop.contacts[actionName](contact._guid, err => {
+          this.props.mozLoop.contacts[actionName](contact._guid, err => {
             if (err) {
               throw err;
             }
           });
           break;
         case "video-call":
           if (!contact.blocked) {
-            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
+            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
             this.closeWindow();
           }
           break;
         case "audio-call":
           if (!contact.blocked) {
-            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
+            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
             this.closeWindow();
           }
           break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
@@ -549,17 +536,17 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
-    render: function() {
+    _renderContactsList: function() {
       let cx = React.addons.classSet;
 
       let viewForItem = item => {
         return (
           <ContactDetail contact={item}
                          handleContactAction={this.handleContactAction}
                          key={item._guid} />
         );
@@ -582,51 +569,79 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.available = shownContacts.available.filter(filterFn);
           }
           if (shownContacts.blocked) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
+      if (shownContacts.available || shownContacts.blocked) {
+        return (
+          <div>
+            <div className="contact-list-title">
+              {mozL10n.get("contact_list_title")}
+            </div>
+            <ul className="contact-list">
+              {shownContacts.available ?
+                shownContacts.available.sort(this.sortContacts).map(viewForItem) :
+                null}
+              {shownContacts.blocked && shownContacts.blocked.length > 0 ?
+                <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
+                null}
+              {shownContacts.blocked ?
+                shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
+                null}
+            </ul>
+          </div>
+        );
+      }
+
+      return (
+        <div className="contact-list-empty">
+          <p className="panel-text-large">
+            {mozL10n.get("no_contacts_message_heading")}
+          </p>
+          <p className="panel-text-medium">
+            {mozL10n.get("no_contacts_import_or_add")}
+          </p>
+        </div>
+      );
+    },
+
+    render: function() {
+      let cx = React.addons.classSet;
+      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+                       MIN_CONTACTS_FOR_FILTERING;
+
       return (
         <div>
           <div className="content-area">
             {showFilter ?
             <input className="contact-filter"
                    placeholder={mozL10n.get("contacts_search_placesholder")}
                    valueLink={this.linkState("filter")} />
             : null }
             <GravatarPromo handleUse={this.handleUseGravatar}/>
           </div>
-          <ul className="contact-list">
-            {shownContacts.available ?
-              shownContacts.available.sort(this.sortContacts).map(viewForItem) :
-              null}
-            {shownContacts.blocked && shownContacts.blocked.length > 0 ?
-              <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
-              null}
-            {shownContacts.blocked ?
-              shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
-              null}
-          </ul>
+          {this._renderContactsList()}
           <ButtonGroup additionalClass="contact-controls">
             <Button additionalClass="secondary"
-                    caption={this.state.importBusy
-                             ? mozL10n.get("importing_contacts_progress_button")
-                             : mozL10n.get("import_contacts_button3")}
-                    disabled={this.state.importBusy}
-                    onClick={this.handleImportButtonClick} >
-              <div className={cx({"contact-import-spinner": true,
-                                  spinner: true,
-                                  busy: this.state.importBusy})} />
+              caption={this.state.importBusy
+                ? mozL10n.get("importing_contacts_progress_button")
+                : mozL10n.get("import_contacts_button3")}
+                disabled={this.state.importBusy}
+                onClick={this.handleImportButtonClick} >
+                <div className={cx({"contact-import-spinner": true,
+                                   spinner: true,
+                busy: this.state.importBusy})} />
             </Button>
             <Button additionalClass="primary"
-                    caption={mozL10n.get("new_contact_button")}
-                    onClick={this.handleAddContactButtonClick} />
+              caption={mozL10n.get("new_contact_button")}
+              onClick={this.handleAddContactButtonClick} />
           </ButtonGroup>
         </div>
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
@@ -761,14 +776,16 @@ loop.contacts = (function(_, mozL10n) {
                     onClick={this.handleAcceptButtonClick} />
           </ButtonGroup>
         </div>
       );
     }
   });
 
   return {
+    ContactDropdown: ContactDropdown,
     ContactsList: ContactsList,
+    ContactDetail: ContactDetail,
     ContactDetailsForm: ContactDetailsForm,
     _getPreferred: getPreferred,
     _setPreferred: setPreferred
   };
 })(_, document.mozL10n);
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -311,35 +311,42 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel settings (gear) menu entry.
    */
   var SettingsDropdownEntry = React.createClass({displayName: "SettingsDropdownEntry",
     propTypes: {
       displayed: React.PropTypes.bool,
-      icon: React.PropTypes.string,
+      extraCSSClass: React.PropTypes.string,
       label: React.PropTypes.string.isRequired,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {displayed: true};
     },
 
     render: function() {
+      var cx = React.addons.classSet;
+
       if (!this.props.displayed) {
         return null;
       }
+
+      var extraCSSClass = {
+        "dropdown-menu-item": true
+      };
+      if (this.props.extraCSSClass) {
+        extraCSSClass[this.props.extraCSSClass] = true;
+      }
+
       return (
-        React.createElement("li", {className: "dropdown-menu-item", onClick: this.props.onClick}, 
-          this.props.icon ?
-            React.createElement("i", {className: "icon icon-" + this.props.icon}) :
-            null, 
-          React.createElement("span", null, this.props.label)
+        React.createElement("li", {className: cx(extraCSSClass), onClick: this.props.onClick}, 
+          this.props.label
         )
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
@@ -380,42 +387,43 @@ loop.panel = (function(_, mozL10n) {
 
     openGettingStartedTour: function() {
       this.props.mozLoop.openGettingStartedTour("settings-menu");
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
+      var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
+                                                      "entry-settings-signin";
 
       return (
         React.createElement("div", {className: "settings-menu dropdown"}, 
           React.createElement("button", {className: "button-settings", 
              onClick: this.toggleDropdownMenu, 
              ref: "menu-button", 
              title: mozL10n.get("settings_menu_button_tooltip")}), 
           React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu})}, 
+            React.createElement(SettingsDropdownEntry, {
+                displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled, 
+                extraCSSClass: "entry-settings-account", 
+                label: mozL10n.get("settings_menu_item_account"), 
+                onClick: this.handleClickAccountEntry}), 
             React.createElement(SettingsDropdownEntry, {displayed: false, 
-                                   icon: "settings", 
                                    label: mozL10n.get("settings_menu_item_settings"), 
                                    onClick: this.handleClickSettingsEntry}), 
-            React.createElement(SettingsDropdownEntry, {displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled, 
-                                   icon: "account", 
-                                   label: mozL10n.get("settings_menu_item_account"), 
-                                   onClick: this.handleClickAccountEntry}), 
-            React.createElement(SettingsDropdownEntry, {icon: "tour", 
-                                   label: mozL10n.get("tour_label"), 
+            React.createElement(SettingsDropdownEntry, {label: mozL10n.get("tour_label"), 
                                    onClick: this.openGettingStartedTour}), 
             React.createElement(SettingsDropdownEntry, {displayed: this.props.mozLoop.fxAEnabled, 
-                                   icon: this._isSignedIn() ? "signout" : "signin", 
+                                   extraCSSClass: accountEntryCSSClass, 
                                    label: this._isSignedIn() ?
                                           mozL10n.get("settings_menu_item_signout") :
                                           mozL10n.get("settings_menu_item_signin"), 
                                    onClick: this.handleClickAuthEntry}), 
-            React.createElement(SettingsDropdownEntry, {icon: "help", 
+            React.createElement(SettingsDropdownEntry, {extraCSSClass: "entry-settings-help", 
                                    label: mozL10n.get("help_label"), 
                                    onClick: this.handleHelpEntry})
           )
         )
       );
     }
   });
 
@@ -947,20 +955,20 @@ loop.panel = (function(_, mozL10n) {
             selectedTab: this.props.selectedTab}, 
             React.createElement(Tab, {name: "rooms"}, 
               React.createElement(RoomList, {dispatcher: this.props.dispatcher, 
                         mozLoop: this.props.mozLoop, 
                         store: this.props.roomStore, 
                         userProfile: this.state.userProfile})
             ), 
             React.createElement(Tab, {name: "contacts"}, 
-              React.createElement(ContactsList, {
-                notifications: this.props.notifications, 
-                selectTab: this.selectTab, 
-                startForm: this.startForm})
+              React.createElement(ContactsList, {mozLoop: this.props.mozLoop, 
+                            notifications: this.props.notifications, 
+                            selectTab: this.selectTab, 
+                            startForm: this.startForm})
             ), 
             React.createElement(Tab, {hidden: true, name: "contacts_add"}, 
               React.createElement(ContactDetailsForm, {
                 mode: "add", 
                 ref: "contacts_add", 
                 selectTab: this.selectTab})
             ), 
             React.createElement(Tab, {hidden: true, name: "contacts_edit"}, 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -311,35 +311,42 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel settings (gear) menu entry.
    */
   var SettingsDropdownEntry = React.createClass({
     propTypes: {
       displayed: React.PropTypes.bool,
-      icon: React.PropTypes.string,
+      extraCSSClass: React.PropTypes.string,
       label: React.PropTypes.string.isRequired,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {displayed: true};
     },
 
     render: function() {
+      var cx = React.addons.classSet;
+
       if (!this.props.displayed) {
         return null;
       }
+
+      var extraCSSClass = {
+        "dropdown-menu-item": true
+      };
+      if (this.props.extraCSSClass) {
+        extraCSSClass[this.props.extraCSSClass] = true;
+      }
+
       return (
-        <li className="dropdown-menu-item" onClick={this.props.onClick}>
-          {this.props.icon ?
-            <i className={"icon icon-" + this.props.icon}></i> :
-            null}
-          <span>{this.props.label}</span>
+        <li className={cx(extraCSSClass)} onClick={this.props.onClick}>
+          {this.props.label}
         </li>
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
@@ -380,42 +387,43 @@ loop.panel = (function(_, mozL10n) {
 
     openGettingStartedTour: function() {
       this.props.mozLoop.openGettingStartedTour("settings-menu");
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
+      var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
+                                                      "entry-settings-signin";
 
       return (
         <div className="settings-menu dropdown">
           <button className="button-settings"
              onClick={this.toggleDropdownMenu}
              ref="menu-button"
              title={mozL10n.get("settings_menu_button_tooltip")} />
           <ul className={cx({"dropdown-menu": true, hide: !this.state.showMenu})}>
+            <SettingsDropdownEntry
+                displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
+                extraCSSClass="entry-settings-account"
+                label={mozL10n.get("settings_menu_item_account")}
+                onClick={this.handleClickAccountEntry} />
             <SettingsDropdownEntry displayed={false}
-                                   icon="settings"
                                    label={mozL10n.get("settings_menu_item_settings")}
                                    onClick={this.handleClickSettingsEntry} />
-            <SettingsDropdownEntry displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
-                                   icon="account"
-                                   label={mozL10n.get("settings_menu_item_account")}
-                                   onClick={this.handleClickAccountEntry} />
-            <SettingsDropdownEntry icon="tour"
-                                   label={mozL10n.get("tour_label")}
+            <SettingsDropdownEntry label={mozL10n.get("tour_label")}
                                    onClick={this.openGettingStartedTour} />
             <SettingsDropdownEntry displayed={this.props.mozLoop.fxAEnabled}
-                                   icon={this._isSignedIn() ? "signout" : "signin"}
+                                   extraCSSClass={accountEntryCSSClass}
                                    label={this._isSignedIn() ?
                                           mozL10n.get("settings_menu_item_signout") :
                                           mozL10n.get("settings_menu_item_signin")}
                                    onClick={this.handleClickAuthEntry} />
-            <SettingsDropdownEntry icon="help"
+            <SettingsDropdownEntry extraCSSClass="entry-settings-help"
                                    label={mozL10n.get("help_label")}
                                    onClick={this.handleHelpEntry} />
           </ul>
         </div>
       );
     }
   });
 
@@ -947,20 +955,20 @@ loop.panel = (function(_, mozL10n) {
             selectedTab={this.props.selectedTab}>
             <Tab name="rooms">
               <RoomList dispatcher={this.props.dispatcher}
                         mozLoop={this.props.mozLoop}
                         store={this.props.roomStore}
                         userProfile={this.state.userProfile} />
             </Tab>
             <Tab name="contacts">
-              <ContactsList
-                notifications={this.props.notifications}
-                selectTab={this.selectTab}
-                startForm={this.startForm} />
+              <ContactsList mozLoop={this.props.mozLoop}
+                            notifications={this.props.notifications}
+                            selectTab={this.selectTab}
+                            startForm={this.startForm} />
             </Tab>
             <Tab hidden={true} name="contacts_add">
               <ContactDetailsForm
                 mode="add"
                 ref="contacts_add"
                 selectTab={this.selectTab} />
             </Tab>
             <Tab hidden={true} name="contacts_edit">
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -331,16 +331,26 @@ p {
 .icon-audio,
 .icon-video {
   background-size: 20px;
   background-repeat: no-repeat;
   vertical-align: top;
   background-position: 80% center;
 }
 
+.pseudo-icon:before {
+  content: "";
+  display: inline-block;
+  background-repeat: no-repeat;
+  width: 14px;
+  height: 14px;
+  vertical-align: top;
+  margin: 0 .7rem;
+}
+
 .icon-small {
   background-size: 10px;
 }
 
 .icon-video {
   background-image: url("../img/video-inverse-14x14.png");
 }
 
@@ -414,46 +424,61 @@ p {
 .dropdown {
   position: relative;
 }
 
 .dropdown-menu {
   position: absolute;
   bottom: 0;
   left: 0;
-  background-color: #fdfdfd;
+  background-color: #fbfbfb;
   box-shadow: 0 1px 3px rgba(0,0,0,.3);
   list-style: none;
   border-radius: 2px;
 }
 
 html[dir="rtl"] .dropdown-menu {
   left: auto;
   right: 0;
 }
 
 .dropdown-menu-item {
   width: 100%;
   text-align: start;
-  padding: .5em 15px;
+  padding: .3rem .8rem;
   cursor: pointer;
   border: 1px solid transparent;
-  font-size: 1em;
+  font-size: 1.2rem;
+  line-height: 22px;
   white-space: nowrap;
+  color: #4a4a4a;
+}
+
+.dropdown-menu-item:first-child {
+  padding-top: .8rem;
+}
+
+.dropdown-menu-item:last-child {
+  padding-bottom: .8rem;
+}
+
+.dropdown-menu-item:first-child:hover {
+  border-top-right-radius: 2px;
+  border-top-left-radius: 2px;
+}
+
+.dropdown-menu-item:last-child {
+  border-bottom-right-radius: 2px;
+  border-bottom-left-radius: 2px;
 }
 
 .dropdown-menu-item:hover {
   background-color: #dbf7ff;
 }
 
-.dropdown-menu-item > .icon {
-  background-repeat: no-repeat;
-  display: inline-block;
-}
-
 .dropdown-menu-separator {
   height: 1px;
   margin: 2px -2px 1px -2px;
   border-top: 1px solid #dedede;
   background-color: #fff;
 }
 
 /* Custom checkbox */
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/avatars.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
+  <style>
+    use:not(:target) {
+      display: none;
+    }
+    use {
+      fill: #ccc;
+    }
+    use[id$="-hover"] {
+      fill: #444;
+    }
+    use[id$="-active"] {
+      fill: #0095dd;
+    }
+    use[id$="-white"] {
+      fill: #fff;
+    }
+  </style>
+  <defs>
+    <g id="blue" transform="translate(-2588 -413) translate(2588 413)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="orange" transform="translate(-2638 -317) translate(2638 317)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="mintgreen" transform="translate(-2588 -317) translate(2588 317)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="lightpink" transform="translate(-2687 -366) translate(2687 366)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="grey" transform="translate(-2736 -366) translate(2736 366)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="yellow" transform="translate(-2732 -317) translate(2732 317)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="purple" transform="translate(-2588 -366) translate(2588 366)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="lightgreen" transform="translate(-2686 -317) translate(2686 317)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="darkblue" transform="translate(-2686 -413) translate(2686 413)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="darkpink" transform="translate(-2638 -413) translate(2638 413)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="brown" transform="translate(-2736 -413) translate(2736 413)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+    <g id="green" transform="translate(-2638 -366) translate(2637.857 366)" fill="none">
+      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
+    </g>
+  </defs>
+  <use id="blue-avatar" xlink:href="#blue"/>
+  <use id="orange-avatar" xlink:href="#orange"/>
+  <use id="mintgreen-avatar" xlink:href="#mintgreen"/>
+  <use id="lightpink-avatar" xlink:href="#lightpink"/>
+  <use id="grey-avatar" xlink:href="#grey"/>
+  <use id="yellow-avatar" xlink:href="#yellow"/>
+  <use id="purple-avatar" xlink:href="#purple"/>
+  <use id="lightgreen-avatar" xlink:href="#lightgreen"/>
+  <use id="darkblue-avatar" xlink:href="#darkblue"/>
+  <use id="darkpink-avatar" xlink:href="#darkpink"/>
+  <use id="brown-avatar" xlink:href="#brown"/>
+  <use id="green-avatar" xlink:href="#green"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/ellipsis-v.svg
@@ -0,0 +1,1 @@
+<svg width="4" height="20" viewBox="0 0 4 20" xmlns="http://www.w3.org/2000/svg"><g fill="#3A99DA"><ellipse cx="2" cy="2" rx="2" ry="2"/><ellipse cx="2" cy="10" rx="2" ry="2"/><ellipse cx="2" cy="18" rx="2" ry="2"/></g></svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/empty_contacts.svg
@@ -0,0 +1,1 @@
+<svg width="117" height="91" viewBox="0 0 117 91" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8"><path d="M116.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311zM38.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311z" id="Mask-Copy-5" fill-opacity=".8"/><path d="M91.495 70.608c-8.418 9.588-20.766 15.639-34.528 15.639-12.684 0-24.167-5.141-32.479-13.453l-.488-.495c7.229-11.312 19.898-18.812 34.318-18.812 13.689 0 25.8 6.759 33.177 17.121zm-33.177-22.367c11.941 0 21.621-9.68 21.621-21.621 0-11.941-9.68-21.621-21.621-21.621-11.941 0-21.621 9.68-21.621 21.621 0 11.941 9.68 21.621 21.621 21.621z" stroke="#FBFBFB" stroke-width="4"/></g></svg>
\ No newline at end of file
--- a/browser/components/loop/content/shared/img/icons-14x14.svg
+++ b/browser/components/loop/content/shared/img/icons-14x14.svg
@@ -30,17 +30,17 @@
     <path id="incoming-shape" fill-rule="evenodd" d="M2.745,7.558l0.637,0.669c0.04,0.041,0.085,0.073,0.134,0.1 l3.249,3.313c0.38,0.393,0.915,0.478,1.197,0.186l0.638-0.676c0.281-0.292,0.2-0.848-0.18-1.244L7.097,8.558h3.566 c0.419,0,0.759-0.34,0.759-0.759V6.28c0-0.419-0.34-0.759-0.759-0.759H7.059l1.42-1.443c0.381-0.392,0.461-0.945,0.18-1.234 l-0.637-0.67C7.74,1.883,7.204,1.966,6.824,2.359L3.55,5.688C3.487,5.717,3.43,5.755,3.381,5.806L2.745,6.482 c-0.131,0.137-0.183,0.332-0.162,0.54C2.562,7.229,2.613,7.423,2.745,7.558z"/>
     <path id="link-shape" fill-rule="evenodd" d="M7.359,6.107c0.757-0.757,0.757-1.995,0-2.752 L5.573,1.568c-0.757-0.757-1.995-0.757-2.752,0L1.568,2.82c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787 c0.757,0.757,1.995,0.757,2.752,0L6.266,7.2L6.8,7.734L6.641,7.893c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787 c0.757,0.757,1.995,0.757,2.752,0l1.253-1.253c0.757-0.757,0.757-1.995,0-2.752l-1.787-1.787c-0.757-0.757-1.995-0.757-2.752,0 L7.734,6.8L7.2,6.266L7.359,6.107z M9.87,7.868l1.335,1.335c0.294,0.294,0.294,0.774,0,1.068l-0.934,0.934 c-0.294,0.294-0.774,0.294-1.068,0L7.868,9.87c-0.294-0.294-0.294-0.774,0-1.068L8.13,9.064c0.294,0.294,0.744,0.324,1.001,0.067 C9.388,8.874,9.358,8.424,9.064,8.13L8.802,7.868C9.096,7.574,9.577,7.574,9.87,7.868z M4.13,6.132L2.795,4.797 c-0.294-0.294-0.294-0.774,0-1.068l0.934-0.934c0.294-0.294,0.774-0.294,1.068,0L6.132,4.13c0.294,0.294,0.294,0.774,0,1.068 L5.86,4.926C5.567,4.632,5.116,4.602,4.859,4.859C4.602,5.116,4.632,5.567,4.926,5.86l0.272,0.272 C4.904,6.426,4.423,6.426,4.13,6.132z"/>
     <g id="mute-shape">
       <path fill-rule="evenodd" d="M5.186,9.492L5.49,9.188l3.822-3.822l2.354-2.354l-0.848-0.848 L9.312,3.669V3.142C9.312,1.959,8.352,1,7.169,1C5.986,1,5.026,1.959,5.026,3.142v4.715c0,0.032,0.001,0.064,0.002,0.096 L4.643,8.338c-0.03-0.156-0.046-0.317-0.046-0.481V6.142H3.741v1.715c0,0.414,0.073,0.81,0.208,1.176l-1.615,1.615l0.848,0.848 l1.398-1.398v0L5.186,9.492z"/>
       <path fill-rule="evenodd" d="M9.312,7.857V6.045L5.829,9.528C6.196,9.824,6.662,10,7.169,10 C8.352,10,9.312,9.04,9.312,7.857z"/>
       <path fill-rule="evenodd" d="M9.741,7.857c0,1.42-1.151,2.572-2.572,2.572 c-0.625,0-1.199-0.223-1.645-0.595l-0.605,0.605c0.395,0.344,0.87,0.599,1.393,0.734v0.97H5.884c-0.56,0-1.034,0.359-1.212,0.858 h4.994c-0.178-0.499-0.652-0.858-1.212-0.858H8.026v-0.97c1.478-0.38,2.572-1.718,2.572-3.316V6.142H9.741V7.857z"/>
     </g>
     <path id="pause-shape" fill-rule="evenodd" d="M4.75,1h-1.5C2.836,1,2.5,1.336,2.5,1.75v10.5 C2.5,12.664,2.836,13,3.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C5.5,1.336,5.164,1,4.75,1z M10.75,1h-1.5 C8.836,1,8.5,1.336,8.5,1.75v10.5C8.5,12.664,8.836,13,9.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C11.5,1.336,11.164,1,10.75,1 z"/>
-    <path id="video-shape" fill-rule="evenodd" d="M12.175,3.347L9.568,5.651V3.905c0-0.657-0.497-1.19-1.111-1.19 H2.111C1.498,2.714,1,3.247,1,3.905v6.191c0,0.658,0.498,1.19,1.111,1.19h6.345c0.614,0,1.111-0.533,1.111-1.19V8.322l2.607,2.305 C12.4,10.867,12.71,10.938,13,10.874V3.099C12.71,3.035,12.4,3.106,12.175,3.347z"/>
+    <path id="video-shape" d="M1.59247473,11.4075253 C1.9956945,11.7983901 2.46237364,12 3.02957694,12 L7.98333957,12 C8.53762636,12 9.01666043,11.7983901 9.40752527,11.4075253 C9.81130663,11.0043055 10,10.5376264 10,9.97042306 L10,8.81074504 L12.8360165,11.6467615 C12.9247473,11.7354923 13.0252714,11.7731187 13.1516286,11.7731187 C13.2145264,11.7731187 13.2650693,11.7607638 13.3279671,11.7354923 C13.517222,11.659678 13.6053912,11.5209659 13.6053912,11.319356 L13.6053912,3.66772744 C13.6053912,3.47903407 13.517222,3.34032198 13.3279671,3.25215275 C13.2650693,3.23923624 13.2145264,3.22688132 13.1516286,3.22688132 C13.0252714,3.22688132 12.9247473,3.26450768 12.8360165,3.3526769 L10,6.17633845 L10,5.01666043 C10,4.46181206 9.81130663,3.98333957 9.40752527,3.59247473 C9.01666043,3.18869337 8.53762636,3 7.98333957,3 L3.02957694,3 C2.46237364,3 1.9956945,3.18869337 1.59247473,3.59247473 C1.20160988,3.98333957 1,4.46181206 1,5.01666043 L1,9.97042306 C1,10.5376264 1.20160988,11.0043055 1.59247473,11.4075253" fill="#fff" fill-rule="evenodd"/>
     <g id="volume-shape">
       <path fill-rule="evenodd" d="M3.513,4.404H1.896c-0.417,0-0.756,0.338-0.756,0.755v3.679 c0,0.417,0.338,0.755,0.756,0.755H3.51l2.575,2.575c0.261,0.261,0.596,0.4,0.938,0.422V1.409C6.682,1.431,6.346,1.57,6.085,1.831 L3.513,4.404z M8.555,5.995C8.619,6.32,8.653,6.656,8.653,7c0,0.344-0.034,0.679-0.098,1.004l0.218,0.142 C8.852,7.777,8.895,7.393,8.895,7c0-0.394-0.043-0.777-0.123-1.147L8.555,5.995z M12.224,3.6l-0.475,0.31 c0.359,0.962,0.557,2.003,0.557,3.09c0,1.087-0.198,2.128-0.557,3.09l0.475,0.31c0.41-1.054,0.635-2.201,0.635-3.4 C12.859,5.8,12.634,4.654,12.224,3.6z M10.061,5.012C10.25,5.642,10.353,6.308,10.353,7c0,0.691-0.103,1.358-0.293,1.987 l0.351,0.229C10.634,8.517,10.756,7.772,10.756,7c0-0.773-0.121-1.517-0.345-2.216L10.061,5.012z"/>
       <path d="M7.164,12.74l-0.15-0.009c-0.389-0.024-0.754-0.189-1.028-0.463L3.452,9.735H1.896 C1.402,9.735,1,9.333,1,8.838V5.16c0-0.494,0.402-0.896,0.896-0.896h1.558l2.531-2.531C6.26,1.458,6.625,1.293,7.014,1.269 l0.15-0.009V12.74z M1.896,4.545c-0.339,0-0.615,0.276-0.615,0.615v3.679c0,0.339,0.276,0.615,0.615,0.615h1.672l2.616,2.616 c0.19,0.19,0.434,0.316,0.697,0.363V1.568C6.619,1.615,6.375,1.741,6.185,1.931L3.571,4.545H1.896z M12.292,10.612l-0.714-0.467 l0.039-0.105C11.981,9.067,12.165,8.044,12.165,7c0-1.044-0.184-2.067-0.548-3.041l-0.039-0.105l0.714-0.467l0.063,0.162 C12.783,4.649,13,5.81,13,7s-0.217,2.351-0.645,3.451L12.292,10.612z M11.92,10.033l0.234,0.153 c0.374-1.019,0.564-2.09,0.564-3.186s-0.19-2.167-0.564-3.186L11.92,3.966C12.27,4.94,12.447,5.96,12.447,7 C12.447,8.04,12.27,9.059,11.92,10.033z M10.489,9.435L9.895,9.047l0.031-0.101C10.116,8.315,10.212,7.66,10.212,7 c0-0.661-0.096-1.316-0.287-1.947L9.895,4.952l0.594-0.388l0.056,0.176C10.779,5.471,10.897,6.231,10.897,7 c0,0.769-0.118,1.529-0.351,2.259L10.489,9.435z M10.225,8.926l0.106,0.069C10.52,8.348,10.615,7.677,10.615,7 c0-0.677-0.095-1.348-0.284-1.996l-0.106,0.07C10.403,5.699,10.494,6.347,10.494,7C10.494,7.652,10.403,8.3,10.225,8.926z M8.867,8.376L8.398,8.07l0.018-0.093C8.48,7.654,8.512,7.325,8.512,7S8.48,6.345,8.417,6.022L8.398,5.929l0.469-0.306l0.043,0.2 C8.994,6.211,9.036,6.607,9.036,7c0,0.393-0.042,0.789-0.126,1.176L8.867,8.376z"/>
     </g>
     <path id="contacts-shape" fill-rule="evenodd" transform="translate(-79.000000, -59.000000)" d="M91.5000066,69.9765672 C91.5000066,68.2109401 91.0859436,65.4999994 88.7968783,65.4999994 C88.5546906,65.4999994 87.5312518,66.5859382 86,66.5859382 C84.4687482,66.5859382 83.4453095,65.4999994 83.2031217,65.4999994 C80.9140564,65.4999994 80.4999935,68.2109401 80.4999935,69.9765672 C80.4999935,71.2421938 81.3437445,72.0000072 82.5859334,72.0000072 L89.4140666,72.0000072 C90.6562555,72.0000072 91.5000066,71.2421938 91.5000066,69.9765672 L91.5000066,69.9765672 L91.5000066,69.9765672 Z M89.0000036,62.9999964 C89.0000036,61.3437444 87.656252,59.9999928 86,59.9999928 C84.343748,59.9999928 82.9999964,61.3437444 82.9999964,62.9999964 C82.9999964,64.6562484 84.343748,66 86,66 C87.656252,66 89.0000036,64.6562484 89.0000036,62.9999964 L89.0000036,62.9999964 L89.0000036,62.9999964 Z" />
     <path id="hello-shape" fill-rule="evenodd" transform="translate(-261.000000, -59.000000)" d="M268.273778,60 C264.809073,60 262,62.4730749 262,65.523237 C262,67.0417726 262.697086,68.4174001 263.822897,69.4155754 C263.627626,70.1061164 263.240356,71.0442922 262.474542,71.959559 C262.605451,72.1919211 264.761073,71.3737446 266.2807,70.7617485 C266.907968,70.946111 267.577782,71.046474 268.274868,71.046474 C271.740664,71.046474 274.549737,68.5733991 274.549737,65.523237 C274.549737,62.4730749 271.739573,60 268.274868,60 L268.273778,60 Z M270.15122,63.3119786 C270.609399,63.3119786 270.980306,63.6850671 270.980306,64.1432459 C270.980306,64.6036066 270.609399,64.9756042 270.15122,64.9756042 C269.693041,64.9756042 269.321044,64.6036066 269.321044,64.1432459 C269.321044,63.6850671 269.693041,63.3119786 270.15122,63.3119786 L270.15122,63.3119786 Z M266.36579,63.3119786 C266.823969,63.3119786 267.195966,63.6850671 267.195966,64.1432459 C267.195966,64.6036066 266.823969,64.9756042 266.36579,64.9756042 C265.907611,64.9756042 265.535613,64.6036066 265.535613,64.1432459 C265.535613,63.6850671 265.907611,63.3119786 266.36579,63.3119786 L266.36579,63.3119786 Z M268.283596,69.3675757 L268.258505,69.3664848 L268.233414,69.3675757 C266.557789,69.3675757 264.685801,68.2777646 264.254894,66.4428674 C265.38616,66.9675913 266.967968,67.1966807 268.258505,67.1966807 C269.549042,67.1966807 271.13085,66.9675913 272.262115,66.4428674 C271.8323,68.2777646 269.959221,69.3675757 268.283596,69.3675757 L268.283596,69.3675757 Z" />
   </defs>
   <use id="audio" xlink:href="#audio-shape"/>
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -67,16 +67,19 @@ browser.jar:
   content/browser/loop/shared/img/movistar.png                  (content/shared/img/movistar.png)
   content/browser/loop/shared/img/movistar@2x.png               (content/shared/img/movistar@2x.png)
   content/browser/loop/shared/img/vivo.png                      (content/shared/img/vivo.png)
   content/browser/loop/shared/img/vivo@2x.png                   (content/shared/img/vivo@2x.png)
   content/browser/loop/shared/img/02.png                        (content/shared/img/02.png)
   content/browser/loop/shared/img/02@2x.png                     (content/shared/img/02@2x.png)
   content/browser/loop/shared/img/telefonica.png                (content/shared/img/telefonica.png)
   content/browser/loop/shared/img/telefonica@2x.png             (content/shared/img/telefonica@2x.png)
+  content/browser/loop/shared/img/ellipsis-v.svg                (content/shared/img/ellipsis-v.svg)
+  content/browser/loop/shared/img/empty_contacts.svg            (content/shared/img/empty_contacts.svg)
+  content/browser/loop/shared/img/avatars.svg                   (content/shared/img/avatars.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js             (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js   (content/shared/js/conversationStore.js)
   content/browser/loop/shared/js/store.js               (content/shared/js/store.js)
   content/browser/loop/shared/js/roomStates.js          (content/shared/js/roomStates.js)
   content/browser/loop/shared/js/fxOSActiveRoomStore.js (content/shared/js/fxOSActiveRoomStore.js)
   content/browser/loop/shared/js/activeRoomStore.js     (content/shared/js/activeRoomStore.js)
--- a/browser/components/loop/modules/MozLoopAPI.jsm
+++ b/browser/components/loop/modules/MozLoopAPI.jsm
@@ -893,31 +893,30 @@ function injectLoopAPI(targetWindow) {
 
         request.send();
       }
     },
 
     /**
      * Compose a URL pointing to the location of an avatar by email address.
      * At the moment we use the Gravatar service to match email addresses with
-     * avatars. This might change in the future as avatars might come from another
-     * source.
+     * avatars. If no email address is found we return null.
      *
      * @param {String} emailAddress Users' email address
      * @param {Number} size         Size of the avatar image to return in pixels.
      *                              Optional. Default value: 40.
-     * @return the URL pointing to an avatar matching the provided email address.
+     * @return the URL pointing to an avatar matching the provided email address
+     *         or null if this is not available.
      */
     getUserAvatar: {
       enumerable: true,
       writable: true,
       value: function(emailAddress, size = 40) {
-        const kEmptyGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
         if (!emailAddress || !MozLoopService.getLoopPref("contacts.gravatars.show")) {
-          return kEmptyGif;
+          return null;
         }
 
         // Do the MD5 dance.
         let hasher = Cc["@mozilla.org/security/hash;1"]
                        .createInstance(Ci.nsICryptoHash);
         hasher.init(Ci.nsICryptoHash.MD5);
         let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
                              .createInstance(Ci.nsIStringInputStream);
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -71,19 +71,23 @@ describe("loop.contacts", function() {
     published: 1406798311748,
     updated: 1406798311748
   }];
   var sandbox;
   var fakeWindow;
   var notifications;
   var listView;
   var oldMozLoop = navigator.mozLoop;
+  var mozL10nGetSpy;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
+
+    mozL10nGetSpy = sandbox.spy(document.mozL10n, "get");
+
     navigator.mozLoop = {
       getStrings: function(entityName) {
         var textContentValue = "fakeText";
         if (entityName == "add_contact_button") {
           textContentValue = fakeAddContactButtonText;
         } else if (entityName == "edit_contact_title") {
           textContentValue = fakeEditContactButtonText;
         } else if (entityName == "edit_contact_done_button") {
@@ -106,16 +110,20 @@ describe("loop.contacts", function() {
         }
         return "gravatarsDisabled";
       },
       contacts: {
         getAll: function(callback) {
           callback(null, [].concat(fakeContacts));
         },
         on: sandbox.stub()
+      },
+      calls: {
+        startDirectCall: function() {},
+        clearCallInProgress: function() {}
       }
     };
 
     fakeWindow = {
       close: sandbox.stub()
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
@@ -141,140 +149,216 @@ describe("loop.contacts", function() {
       // Sanity check the reverse:
       gravatars = node.querySelectorAll(".contact img[src=gravatarsDisabled]");
       expect(gravatars.length).to.equal(enabled ? 0 : fakeContacts.length);
     }
 
     it("should show the gravatars promo box", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.not.equal(null);
 
       checkGravatarContacts(false);
     });
 
     it("should not show the gravatars promo box when the 'contacts.gravatars.promo' pref is set", function() {
-      navigator.mozLoop.getLoopPref = function(pref) {
+      sandbox.stub(navigator.mozLoop, "getLoopPref", function(pref) {
         if (pref == "contacts.gravatars.promo") {
           return false;
         } else if (pref == "contacts.gravatars.show") {
           return true;
         }
         return "";
-      };
+      });
+
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
 
       checkGravatarContacts(true);
     });
 
     it("should hide the gravatars promo box when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should should set the prefs correctly when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.promo", false);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.show", true);
     });
 
     it("should hide the gravatars promo box when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should set prefs correctly when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
+          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       sinon.assert.calledOnce(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref,
         "contacts.gravatars.promo", false);
     });
   });
 
   describe("ContactsList", function () {
+    var node;
     beforeEach(function() {
-      navigator.mozLoop.calls = {
-        startDirectCall: sandbox.stub(),
-        clearCallInProgress: sandbox.stub()
-      };
-      navigator.mozLoop.contacts = {getAll: sandbox.stub()};
+      sandbox.stub(navigator.mozLoop.calls, "startDirectCall");
+      sandbox.stub(navigator.mozLoop.calls, "clearCallInProgress");
+    });
+
+    describe("#RenderNoContacts", function() {
+      beforeEach(function() {
+        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
+          cb(null, []);
+        });
+        listView = TestUtils.renderIntoDocument(
+          React.createElement(loop.contacts.ContactsList, {
+            mozLoop: navigator.mozLoop,
+            notifications: notifications,
+            startForm: function() {}
+          }));
+        node = listView.getDOMNode();
+      });
+
+      it("should not show a contacts title if no contacts", function() {
+        expect(node.querySelector(".contact-list-title")).to.eql(null);
+        sinon.assert.neverCalledWith(mozL10nGetSpy, "contact_list_title");
+      });
+
+      it("should show the no contacts view", function() {
+        expect(node.querySelector(".contact-list-empty")).to.not.eql(null);
+      });
 
-      listView = TestUtils.renderIntoDocument(
-        React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications,
-          startForm: function() {}
-        }));
+      it("should display the no contacts strings", function() {
+        sinon.assert.calledWithExactly(mozL10nGetSpy,
+                                       "no_contacts_message_heading");
+        sinon.assert.calledWithExactly(mozL10nGetSpy,
+                                       "no_contacts_import_or_add");
+      });
+    });
+
+    describe("#RenderWithContacts", function() {
+      beforeEach(function() {
+        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
+          cb(null, [].concat(fakeContacts));
+        });
+        listView = TestUtils.renderIntoDocument(
+          React.createElement(loop.contacts.ContactsList, {
+            mozLoop: navigator.mozLoop,
+            notifications: notifications,
+            startForm: function() {}
+          }));
+        node = listView.getDOMNode();
+      });
+
+      it("should show a contacts title", function() {
+        expect(node.querySelector(".contact-list-title")).not.to.eql(null);
+        sinon.assert.calledWithExactly(mozL10nGetSpy, "contact_list_title");
+      });
     });
 
     describe("#handleContactAction", function() {
+      beforeEach(function() {
+        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
+          cb(null, []);
+        });
+        listView = TestUtils.renderIntoDocument(
+          React.createElement(loop.contacts.ContactsList, {
+            mozLoop: navigator.mozLoop,
+            notifications: notifications,
+            startForm: function() {}
+          }));
+        node = listView.getDOMNode();
+      });
+
       it("should call window.close when called with 'video-call' action",
         function() {
           listView.handleContactAction({}, "video-call");
 
           sinon.assert.calledOnce(fakeWindow.close);
       });
 
       it("should call window.close when called with 'audio-call' action",
         function() {
           listView.handleContactAction({}, "audio-call");
 
           sinon.assert.calledOnce(fakeWindow.close);
         });
     });
 
     describe("#handleImportButtonClick", function() {
+      beforeEach(function() {
+        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
+          cb(null, []);
+        });
+        listView = TestUtils.renderIntoDocument(
+          React.createElement(loop.contacts.ContactsList, {
+            mozLoop: navigator.mozLoop,
+            notifications: notifications,
+            startForm: function() {}
+          }));
+        node = listView.getDOMNode();
+      });
+
       it("should notify the end user from a succesful import", function() {
         sandbox.stub(notifications, "successL10n");
         navigator.mozLoop.startImport = function(opts, cb) {
           cb(null, {success: 42});
         };
 
         listView.handleImportButtonClick();
 
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -8,16 +8,17 @@ describe("loop.panel", function() {
   var expect = chai.expect;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
 
   var sandbox, notifications;
   var fakeXHR, fakeWindow, fakeMozLoop;
   var requests = [];
+  var mozL10nGetSpy;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     fakeXHR = sandbox.useFakeXMLHttpRequest();
     requests = [];
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
@@ -72,29 +73,29 @@ describe("loop.panel", function() {
       logOutFromFxA: sandbox.stub(),
       notifyUITour: sandbox.stub(),
       openURL: sandbox.stub(),
       getSelectedTabMetadata: sandbox.stub(),
       userProfile: null
     };
 
     document.mozL10n.initialize(navigator.mozLoop);
+    sandbox.stub(document.mozL10n, "get").returns("Fake title");
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     loop.shared.mixins.setRootObject(window);
     sandbox.restore();
   });
 
   describe("#init", function() {
     beforeEach(function() {
       sandbox.stub(React, "render");
       sandbox.stub(document.mozL10n, "initialize");
-      sandbox.stub(document.mozL10n, "get").returns("Fake title");
     });
 
     it("should initalize L10n", function() {
       loop.panel.init();
 
       sinon.assert.calledOnce(document.mozL10n.initialize);
       sinon.assert.calledWithExactly(document.mozL10n.initialize,
         navigator.mozLoop);
@@ -344,79 +345,79 @@ describe("loop.panel", function() {
         beforeEach(function() {
           fakeMozLoop.userProfile = null;
         });
 
         it("should show a signin entry when user is not authenticated",
            function() {
              var view = mountTestComponent();
 
-             expect(view.getDOMNode().querySelectorAll(".icon-signout"))
+             expect(view.getDOMNode().querySelectorAll(".entry-settings-signout"))
                .to.have.length.of(0);
-             expect(view.getDOMNode().querySelectorAll(".icon-signin"))
+             expect(view.getDOMNode().querySelectorAll(".entry-settings-signin"))
                .to.have.length.of(1);
            });
 
         it("should hide any account entry when user is not authenticated",
            function() {
              var view = mountTestComponent();
 
              expect(view.getDOMNode().querySelectorAll(".icon-account"))
                .to.have.length.of(0);
            });
 
         it("should sign in the user on click when unauthenticated", function() {
           navigator.mozLoop.loggedInToFxA = false;
           var view = mountTestComponent();
 
           TestUtils.Simulate.click(view.getDOMNode()
-                                     .querySelector(".icon-signin"));
+                                     .querySelector(".entry-settings-signin"));
 
           sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
         });
       });
 
       it("should show a signout entry when user is authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
-        expect(view.getDOMNode().querySelectorAll(".icon-signout"))
-          .to.have.length.of(1);
-        expect(view.getDOMNode().querySelectorAll(".icon-signin"))
-          .to.have.length.of(0);
+        sinon.assert.calledWithExactly(document.mozL10n.get,
+                                       "settings_menu_item_signout");
+        sinon.assert.neverCalledWith(document.mozL10n.get,
+                                     "settings_menu_item_signin");
       });
 
       it("should show an account entry when user is authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
-        expect(view.getDOMNode().querySelectorAll(".icon-account"))
-          .to.have.length.of(1);
+        sinon.assert.calledWithExactly(document.mozL10n.get,
+                                       "settings_menu_item_settings");
       });
 
       it("should open the FxA settings when the account entry is clicked",
          function() {
            navigator.mozLoop.userProfile = {email: "test@example.com"};
 
            var view = mountTestComponent();
 
            TestUtils.Simulate.click(view.getDOMNode()
-                                      .querySelector(".icon-account"));
+                                      .querySelector(".entry-settings-account"));
 
            sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
          });
 
       it("should sign out the user on click when authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
         var view = mountTestComponent();
 
         TestUtils.Simulate.click(view.getDOMNode()
-                                   .querySelector(".icon-signout"));
+                                   .querySelector(".entry-settings-signout"));
 
         sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
       });
     });
 
     describe("Help", function() {
       var view, supportUrl;
 
@@ -437,27 +438,27 @@ describe("loop.panel", function() {
           return "unseen";
         };
       });
 
       it("should open a tab to the support page", function() {
         view = mountTestComponent();
 
         TestUtils.Simulate
-          .click(view.getDOMNode().querySelector(".icon-help"));
+          .click(view.getDOMNode().querySelector(".entry-settings-help"));
 
         sinon.assert.calledOnce(fakeMozLoop.openURL);
         sinon.assert.calledWithExactly(fakeMozLoop.openURL, supportUrl);
       });
 
       it("should close the panel", function() {
         view = mountTestComponent();
 
         TestUtils.Simulate
-          .click(view.getDOMNode().querySelector(".icon-help"));
+          .click(view.getDOMNode().querySelector(".entry-settings-help"));
 
         sinon.assert.calledOnce(fakeWindow.close);
       });
     });
 
     describe("#render", function() {
       it("should not render a ToSView when gettingStarted.seen is true", function() {
         navigator.mozLoop.getLoopPref = function() {
@@ -832,17 +833,17 @@ describe("loop.panel", function() {
        "conversation button",
       function() {
         navigator.mozLoop.userProfile = {email: fakeEmail};
         var view = createTestComponent(false);
 
         TestUtils.Simulate.click(view.getDOMNode().querySelector(".new-room-button"));
 
         sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
-          nameTemplate: "fakeText",
+          nameTemplate: "Fake title",
           roomOwner: fakeEmail
         }));
       });
 
     it("should dispatch a CreateRoom action with context when clicking on the " +
        "Start a conversation button", function() {
       fakeMozLoop.userProfile = {email: fakeEmail};
       var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
@@ -863,17 +864,17 @@ describe("loop.panel", function() {
       var node = view.getDOMNode();
 
       // Select the checkbox
       TestUtils.Simulate.click(node.querySelector(".checkbox-wrapper"));
 
       TestUtils.Simulate.click(node.querySelector(".new-room-button"));
 
       sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
-        nameTemplate: "fakeText",
+        nameTemplate: "Fake title",
         roomOwner: fakeEmail,
         urls: [{
           location: "http://invalid.com",
           description: "fakeSite",
           thumbnail: favicon
         }]
       }));
     });
--- a/browser/components/loop/ui/fake-mozLoop.js
+++ b/browser/components/loop/ui/fake-mozLoop.js
@@ -135,18 +135,18 @@ var fakeContacts = [{
           return false;
       }
     },
     hasEncryptionKey: true,
     setLoopPref: function(){},
     releaseCallData: function() {},
     copyString: function() {},
     getUserAvatar: function(emailAddress) {
-      return "http://www.gravatar.com/avatar/" + (Math.ceil(Math.random() * 3) === 2 ?
-        "0a996f0fe2727ef1668bdb11897e4459" : "foo") + ".jpg?default=blank&s=40";
+      var avatarUrl = "http://www.gravatar.com/avatar/0a996f0fe2727ef1668bdb11897e4459.jpg?default=blank&s=40";
+      return Math.ceil(Math.random() * 3) === 2 ? avatarUrl : null;
     },
     getSelectedTabMetadata: function(callback) {
       callback({
         previews: ["chrome://branding/content/about-logo.png"],
         description: "sample webpage description",
         url: "https://www.example.com"
       });
     },
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -53,16 +53,18 @@ body {
 }
 
 .showcase > section > h1 {
   margin: 1em 0;
   border-bottom: 1px solid #aaa;
 }
 
 .showcase > section .comp {
+  /* contain absolute positioned elements such as dropdowns */
+  position: relative;
   margin: 0 auto; /* width is usually set programmatically */
 }
 
 .showcase > section .comp.dashed,
 .showcase > section .comp > iframe.dashed
 {
   border: 1px dashed #ccc;
 }
@@ -162,12 +164,12 @@ body {
 }
 
 /* Temporary until bug 1168829 is completed */
 .standalone.text-chat-example .text-chat-view {
   height: 400px;
 }
 
 /* Force dropdown menus to display. */
-.force-menu-show * {
+.force-menu-show .icons,
+.force-menu-show .dropdown-menu {
   display: inline-block !important;
 }
-
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1,28 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global Frame:false uncaughtError:true */
+/* global Frame:false uncaughtError:true fakeContacts:true */
 
 (function() {
   "use strict";
 
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
   var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
+  var ContactDropdown = loop.contacts.ContactDropdown;
+  var ContactDetail = loop.contacts.ContactDetail;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
@@ -446,16 +448,27 @@
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedInLongEmail.userProfile = {
     email: "reallyreallylongtext@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
+  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
+
+  var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
+  mozLoopNoContacts.userProfile = {
+    email: "reallyreallylongtext@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
+  mozLoopNoContacts.contacts.getAll = function(callback) {
+    callback(null, []);
+  };
+
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
@@ -734,16 +747,24 @@
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab long email"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: mockMozLoopLoggedInLongEmail, 
                          notifications: notifications, 
                          roomStore: roomStore, 
                          selectedTab: "contacts"})
             ), 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab (no contacts)"}, 
+              React.createElement(PanelView, {client: mockClient, 
+                         dispatcher: dispatcher, 
+                         mozLoop: mozLoopNoContacts, 
+                         notifications: notifications, 
+                         roomStore: roomStore, 
+                         selectedTab: "contacts"})
+            ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: navigator.mozLoop, 
                          notifications: errNotifications, 
                          roomStore: roomStore})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification - authenticated"}, 
@@ -779,16 +800,40 @@
             ), 
             React.createElement(Example, {cssClass: "force-menu-show", dashed: true, 
                      style: {width: "332px", height: "200px"}, 
                      summary: "AvailabilityDropdown Expanded"}, 
               React.createElement(AvailabilityDropdown, null)
             )
           ), 
 
+          React.createElement(Section, {name: "ContactDetail"}, 
+            React.createElement(Example, {cssClass: "force-menu-show", dashed: true, 
+                     style: {width: "300px", height: "272px"}, 
+                     summary: "ContactDetail"}, 
+              React.createElement(ContactDetail, {contact: fakeContacts[0], 
+                handleContactAction: function() {}})
+            )
+          ), 
+
+          React.createElement(Section, {name: "ContactDropdown"}, 
+            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
+                     summary: "ContactDropdown not blocked can edit"}, 
+              React.createElement(ContactDropdown, {blocked: false, 
+                               canEdit: true, 
+                               handleAction: function () {}})
+            ), 
+            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
+                     summary: "ContactDropdown blocked can't edit"}, 
+              React.createElement(ContactDropdown, {blocked: true, 
+                               canEdit: false, 
+                               handleAction: function () {}})
+            )
+          ), 
+
           React.createElement(Section, {name: "AcceptCallView"}, 
             React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming video call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
                                 mozLoop: mockMozLoopLoggedIn})
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1,28 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global Frame:false uncaughtError:true */
+/* global Frame:false uncaughtError:true fakeContacts:true */
 
 (function() {
   "use strict";
 
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
   var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
+  var ContactDropdown = loop.contacts.ContactDropdown;
+  var ContactDetail = loop.contacts.ContactDetail;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
@@ -446,16 +448,27 @@
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedInLongEmail.userProfile = {
     email: "reallyreallylongtext@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
+  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
+
+  var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
+  mozLoopNoContacts.userProfile = {
+    email: "reallyreallylongtext@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
+  mozLoopNoContacts.contacts.getAll = function(callback) {
+    callback(null, []);
+  };
+
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
@@ -734,16 +747,24 @@
             <Example dashed={true} style={{width: "332px"}} summary="Contact list tab long email">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={mockMozLoopLoggedInLongEmail}
                          notifications={notifications}
                          roomStore={roomStore}
                          selectedTab="contacts" />
             </Example>
+            <Example dashed={true} style={{width: "332px"}} summary="Contact list tab (no contacts)">
+              <PanelView client={mockClient}
+                         dispatcher={dispatcher}
+                         mozLoop={mozLoopNoContacts}
+                         notifications={notifications}
+                         roomStore={roomStore}
+                         selectedTab="contacts" />
+            </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={navigator.mozLoop}
                          notifications={errNotifications}
                          roomStore={roomStore} />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification - authenticated">
@@ -779,16 +800,40 @@
             </Example>
             <Example cssClass="force-menu-show" dashed={true}
                      style={{width: "332px", height: "200px"}}
                      summary="AvailabilityDropdown Expanded">
               <AvailabilityDropdown />
             </Example>
           </Section>
 
+          <Section name="ContactDetail">
+            <Example cssClass="force-menu-show" dashed={true}
+                     style={{width: "300px", height: "272px"}}
+                     summary="ContactDetail">
+              <ContactDetail contact={fakeContacts[0]}
+                handleContactAction={function() {}} />
+            </Example>
+          </Section>
+
+          <Section name="ContactDropdown">
+            <Example dashed={true} style={{width: "300px", height: "272px"}}
+                     summary="ContactDropdown not blocked can edit">
+              <ContactDropdown blocked={false}
+                               canEdit={true}
+                               handleAction={function () {}} />
+            </Example>
+            <Example dashed={true} style={{width: "300px", height: "272px"}}
+                     summary="ContactDropdown blocked can't edit">
+              <ContactDropdown blocked={true}
+                               canEdit={false}
+                               handleAction={function () {}} />
+            </Example>
+          </Section>
+
           <Section name="AcceptCallView">
             <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming video call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
                                 mozLoop={mockMozLoopLoggedIn} />
--- a/browser/components/preferences/handlers.xml
+++ b/browser/components/preferences/handlers.xml
@@ -67,15 +67,15 @@
     </implementation>
 
   </binding>
 
   <binding id="offlineapp"
 	   extends="chrome://global/content/bindings/listbox.xml#listitem">
     <content>
       <children>
-	<xul:listcell xbl:inherits="label=host"/>
+	<xul:listcell xbl:inherits="label=origin"/>
 	<xul:listcell xbl:inherits="label=usage"/>
       </children>
     </content>
   </binding>
 
 </bindings>
--- a/browser/components/translation/test/browser_translation_exceptions.js
+++ b/browser/components/translation/test/browser_translation_exceptions.js
@@ -53,17 +53,29 @@ function getDomainExceptions() {
         perm.capability == Services.perms.DENY_ACTION)
       results.push(perm.principal);
   }
 
   return results;
 }
 
 function getInfoBar() {
-  return gBrowser.getNotificationBox().getNotificationWithValue("translation");
+  let deferred = Promise.defer();
+  let infobar =
+    gBrowser.getNotificationBox().getNotificationWithValue("translation");
+
+  if (!infobar) {
+    deferred.resolve();
+  } else {
+    // Wait for all animations to finish
+    Promise.all(infobar.getAnimations().map(animation => animation.finished))
+      .then(() => deferred.resolve(infobar));
+  }
+
+  return deferred.promise;
 }
 
 function openPopup(aPopup) {
   let deferred = Promise.defer();
 
   aPopup.addEventListener("popupshown", function popupShown() {
     aPopup.removeEventListener("popupshown", popupShown);
     deferred.resolve();
@@ -104,17 +116,17 @@ let gTests = [
 {
   desc: "never for language",
   run: function* checkNeverForLanguage() {
     // Show the infobar for example.com and fr.
     Translation.documentStateReceived(gBrowser.selectedBrowser,
                                       {state: Translation.STATE_OFFER,
                                        originalShown: true,
                                        detectedLanguage: "fr"});
-    let notif = getInfoBar();
+    let notif = yield getInfoBar();
     ok(notif, "the infobar is visible");
     let ui = gBrowser.selectedBrowser.translationUI;
     let uri = gBrowser.selectedBrowser.currentURI;
     ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
     yield openPopup(notif._getAnonElt("options"));
@@ -122,28 +134,29 @@ let gTests = [
        "the options menu is open");
 
     // Check that the item is not disabled.
     ok(!notif._getAnonElt("neverForLanguage").disabled,
        "The 'Never translate <language>' item isn't disabled");
 
     // Click the 'Never for French' item.
     notif._getAnonElt("neverForLanguage").click();
-    ok(!getInfoBar(), "infobar hidden");
+    notif = yield getInfoBar();
+    ok(!notif, "infobar hidden");
 
     // Check this has been saved to the exceptions list.
     let langs = getLanguageExceptions();
     is(langs.length, 1, "one language in the exception list");
     is(langs[0], "fr", "correct language in the exception list");
     ok(!ui.shouldShowInfoBar(uri, "fr"),
        "the infobar wouldn't be shown anymore");
 
     // Reopen the infobar.
     PopupNotifications.getNotification("translate").anchorElement.click();
-    notif = getInfoBar();
+    notif = yield getInfoBar();
     // Open the "options" drop down.
     yield openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("neverForLanguage").disabled,
        "The 'Never translate French' item is disabled");
 
     // Cleanup.
     Services.prefs.setCharPref(kLanguagesPref, "");
     notif.close();
@@ -153,17 +166,17 @@ let gTests = [
 {
   desc: "never for site",
   run: function* checkNeverForSite() {
     // Show the infobar for example.com and fr.
     Translation.documentStateReceived(gBrowser.selectedBrowser,
                                       {state: Translation.STATE_OFFER,
                                        originalShown: true,
                                        detectedLanguage: "fr"});
-    let notif = getInfoBar();
+    let notif = yield getInfoBar();
     ok(notif, "the infobar is visible");
     let ui = gBrowser.selectedBrowser.translationUI;
     let uri = gBrowser.selectedBrowser.currentURI;
     ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
     yield openPopup(notif._getAnonElt("options"));
@@ -171,28 +184,29 @@ let gTests = [
        "the options menu is open");
 
     // Check that the item is not disabled.
     ok(!notif._getAnonElt("neverForSite").disabled,
        "The 'Never translate site' item isn't disabled");
 
     // Click the 'Never for French' item.
     notif._getAnonElt("neverForSite").click();
-    ok(!getInfoBar(), "infobar hidden");
+    notif = yield getInfoBar();
+    ok(!notif, "infobar hidden");
 
     // Check this has been saved to the exceptions list.
     let sites = getDomainExceptions();
     is(sites.length, 1, "one site in the exception list");
     is(sites[0].origin, "http://example.com", "correct site in the exception list");
     ok(!ui.shouldShowInfoBar(uri, "fr"),
        "the infobar wouldn't be shown anymore");
 
     // Reopen the infobar.
     PopupNotifications.getNotification("translate").anchorElement.click();
-    notif = getInfoBar();
+    notif = yield getInfoBar();
     // Open the "options" drop down.
     yield openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("neverForSite").disabled,
        "The 'Never translate French' item is disabled");
 
     // Cleanup.
     Services.perms.remove(makeURI("http://example.com"), "translate");
     notif.close();
--- a/browser/devtools/performance/modules/logic/marker-utils.js
+++ b/browser/devtools/performance/modules/logic/marker-utils.js
@@ -355,16 +355,30 @@ const Formatters = {
     if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) {
       let cause = PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL;
       return {
         [L10N.getStr("marker.field.causeName")]: cause
       };
     }
   },
 
+  GCFields: function (marker) {
+    let fields = Object.create(null);
+    let cause = marker.cause;
+    let label = L10N.getStr(`marker.gcreason.label.${cause}`) || cause;
+
+    fields[L10N.getStr("marker.field.causeName")] = label;
+
+    if ("nonincrementalReason" in marker) {
+      fields[L10N.getStr("marker.field.nonIncrementalCause")] = marker.nonincrementalReason;
+    }
+
+    return fields;
+  },
+
   DOMEventFields: function (marker) {
     let fields = Object.create(null);
     if ("type" in marker) {
       fields[L10N.getStr("marker.field.DOMEventType")] = marker.type;
     }
     if ("eventPhase" in marker) {
       let phase;
       if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) {
--- a/browser/devtools/performance/modules/markers.js
+++ b/browser/devtools/performance/modules/markers.js
@@ -96,20 +96,17 @@ const TIMELINE_BLUEPRINT = {
     group: 1,
     colorName: "graphs-yellow",
     label: L10N.getStr("marker.label.parseXML"),
   },
   "GarbageCollection": {
     group: 1,
     colorName: "graphs-red",
     label: Formatters.GCLabel,
-    fields: [
-      { property: "causeName", label: L10N.getStr("marker.field.causeName") },
-      { property: "nonincrementalReason", label: L10N.getStr("marker.field.nonIncrementalCause") }
-    ],
+    fields: Formatters.GCFields,
   },
   "nsCycleCollector::Collect": {
     group: 1,
     colorName: "graphs-red",
     label: L10N.getStr("marker.label.cycleCollection"),
     fields: Formatters.CycleCollectionFields,
   },
   "nsCycleCollector::ForgetSkippable": {
--- a/browser/devtools/performance/test/unit/test_marker-utils.js
+++ b/browser/devtools/performance/test/unit/test_marker-utils.js
@@ -34,16 +34,22 @@ add_task(function () {
 
   fields = Utils.getMarkerFields({ name: "DOMEvent", eventPhase: Ci.nsIDOMEvent.AT_TARGET, type: "mouseclick" });
   equal(fields.length, 2, "getMarkerFields() returns multiple fields when using a fields function");
   equal(fields[0].label, "Event Type:", "getMarkerFields() correctly returns fields via function (1)");
   equal(fields[0].value, "mouseclick", "getMarkerFields() correctly returns fields via function (2)");
   equal(fields[1].label, "Phase:", "getMarkerFields() correctly returns fields via function (3)");
   equal(fields[1].value, "Target", "getMarkerFields() correctly returns fields via function (4)");
 
+  fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "ALLOC_TRIGGER" });
+  equal(fields[0].value, "Too Many Allocations", "Uses L10N for GC reasons");
+
+  fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "NOT_A_GC_REASON" });
+  equal(fields[0].value, "NOT_A_GC_REASON", "Defaults to enum for GC reasons when not L10N'd");
+
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)",
     "Correctly obfuscates JS markers when platform data is off.");
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "Some Platform Field",
     "Correctly deobfuscates JS markers when platform data is on.");
 
   equal(Utils.getMarkerClassName("Javascript"), "Function Call",
     "getMarkerClassName() returns correct string when defined via function");
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -390,16 +390,21 @@ These should match what Safari and other
 <!ENTITY customizeMenu.addMoreItems.label "Add More Items…">
 <!ENTITY customizeMenu.addMoreItems.accesskey "A">
 
 <!ENTITY openCmd.commandkey           "l">
 <!ENTITY urlbar.placeholder2          "Search or enter address">
 <!ENTITY urlbar.accesskey             "d">
 <!ENTITY urlbar.switchToTab.label     "Switch to tab:">
 
+<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
+<!ENTITY urlbar.searchSuggestionsNotification.learnMore "Learn more…">
+<!ENTITY urlbar.searchSuggestionsNotification.disable "No">
+<!ENTITY urlbar.searchSuggestionsNotification.enable "Yes">
+
 <!-- 
   Comment duplicated from browser-sets.inc:
 
   Search Command Key Logic works like this:
 
   Unix: Ctrl+J (0.8, 0.9 support)
         Ctrl+K (cross platform binding)
   Mac:  Cmd+K (cross platform binding)
@@ -633,16 +638,18 @@ you can use these alternative items. Oth
 <!ENTITY quitApplicationCmdMac2.label   "Quit &brandShorterName;">
 <!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
 <!ENTITY quitApplicationCmdUnix.key     "Q">
 
 <!ENTITY closeCmd.label                 "Close">  
 <!ENTITY closeCmd.key                   "W">  
 <!ENTITY closeCmd.accesskey             "C">
 
+<!ENTITY toggleMuteCmd.key              "M">
+
 <!ENTITY pageStyleMenu.label "Page Style">
 <!ENTITY pageStyleMenu.accesskey "y">
 <!ENTITY pageStyleNoStyle.label "No Style">
 <!ENTITY pageStyleNoStyle.accesskey "n">
 <!ENTITY pageStylePersistentOnly.label "Basic Page Style">
 <!ENTITY pageStylePersistentOnly.accesskey "b">
 
 <!ENTITY pageReportIcon.tooltip            "Change pop-up blocking settings for this website">
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -123,33 +123,45 @@ import_contacts_failure_message=Some con
 ## when user's contacts have been successfully imported.
 ## Semicolon-separated list of plural forms. See:
 ## http://developer.mozilla.org/en/docs/Localization_and_Plurals
 ## In this item, don't translate the part between {{..}}
 import_contacts_success_message={{total}} contact was successfully imported.;{{total}} contacts were successfully imported.
 ## LOCALIZATION NOTE(sync_contacts_button): This button is displayed in place of
 ## importing_contacts_button once contacts have been imported once.
 sync_contacts_button=Sync Contacts
+## LOCALIZATION NOTE(no_contacts_message_heading): Title shown when user has no
+## contacts in his address book
+no_contacts_message_heading=No contacts yet
+## LOCALIZATION NOTE(no_contacts_import_or_add): Subheading inviting the user
+## to add people to his contact list
+no_contacts_import_or_add=Import or add someone
 
 ## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
 ## contacts fails. This is displayed in the error field.
 import_failed_description_simple=Sorry, contact import failed
 import_failed_description_some=Some contacts could not be imported
 import_failed_support_button=Help
 
 ## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in
 ## a pop-up menu next to the contact's name.
 remove_contact_menu_button2=Remove Contact…
+## LOCALIZATION NOTE(remove_contact_title): Displayed in the contact list in
+## a pop-up menu next to the contact's name.
+remove_contact_menu_button3=Remove Contact
 ## LOCALIZATION NOTE(confirm_delete_contact_alert): This is an alert that is displayed
 ## to confirm deletion of a contact.
 confirm_delete_contact_alert=Are you sure you want to delete this contact?
 ## LOCALIZATION NOTE(confirm_delete_contact_remove_button, confirm_delete_contact_cancel_button):
 ## These are displayed on the alert with confirm_delete_contact_alert
 confirm_delete_contact_remove_button=Remove Contact
 confirm_delete_contact_cancel_button=Cancel
+## LOCALIZATION NOTE(contact_list_title): This is in uppercase in English for
+## emphasis, please do what is appropriate for specific locales.
+contact_list_title=MY CONTACTS
 
 ## LOCALIZATION NOTE(block_contact_menu_button): Displayed in the contact list in
 ## a pop-up menu next to the contact's name, used to block a contact from calling
 ## the user.
 block_contact_menu_button=Block Contact
 ## LOCALIZATION NOTE(unblock_contact_menu_button): Displayed in the contact list in
 ## a pop-up menu next to the contact's name, used to unblock a contact and allow them
 ## to call the user.
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -23,16 +23,19 @@
 <!ENTITY  locbar.history.accesskey      "H">
 <!ENTITY  locbar.bookmarks.label        "Bookmarks">
 <!ENTITY  locbar.bookmarks.accesskey    "k">
 <!ENTITY  locbar.openpage.label         "Open tabs">
 <!ENTITY  locbar.openpage.accesskey     "O">
 <!ENTITY  locbar.searches.label         "Related searches from the default search engine">
 <!ENTITY  locbar.searches.accesskey     "d">
 
+<!ENTITY  suggestionSettings.label      "Change preferences for search engine suggestions…">
+<!ENTITY  suggestionSettings.accesskey  "g">
+
 <!ENTITY  acceptCookies.label           "Accept cookies from sites">
 <!ENTITY  acceptCookies.accesskey       "A">
 
 <!ENTITY  acceptThirdParty.pre.label      "Accept third-party cookies:">
 <!ENTITY  acceptThirdParty.pre.accesskey  "c">
 <!ENTITY  acceptThirdParty.always.label   "Always">
 <!ENTITY  acceptThirdParty.never.label    "Never">
 <!ENTITY  acceptThirdParty.visited.label  "From visited">
--- a/browser/locales/en-US/chrome/browser/preferences/search.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/search.dtd
@@ -4,16 +4,19 @@
 
 <!ENTITY defaultSearchEngine.label             "Default Search Engine">
 
 <!ENTITY chooseYourDefaultSearchEngine.label   "Choose your default search engine. &brandShortName; uses it in the location bar, search bar, and start page.">
 
 <!ENTITY provideSearchSuggestions.label        "Provide search suggestions">
 <!ENTITY provideSearchSuggestions.accesskey    "s">
 
+<!ENTITY showURLBarSuggestions.label           "Show search suggestions in location bar results">
+<!ENTITY showURLBarSuggestions.accesskey       "l">
+
 <!ENTITY redirectWindowsSearch.label "Use this search engine for searches from Windows">
 <!ENTITY redirectWindowsSearch.accesskey "W">
 
 <!ENTITY oneClickSearchEngines.label           "One-click search engines">
 
 <!ENTITY chooseWhichOneToDisplay.label         "The search bar lets you search alternate engines directly. Choose which ones to display.">
 
 <!ENTITY engineNameColumn.label                "Search Engine">
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -26,10 +26,14 @@ tabs.closeWarningTitle=Confirm close
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # The singular form is not considered since this string is used only for
 # multiple tabs.
 tabs.closeWarningMultiple=;You are about to close #1 tabs. Are you sure you want to continue?
 tabs.closeButtonMultiple=Close tabs
 tabs.closeWarningPromptMe=Warn me when I attempt to close multiple tabs
 
 tabs.closeTab.tooltip=Close tab
-tabs.playingAudio.tooltip=This tab is playing audio
-tabs.mutedAudio.tooltip=This tab has been muted
+# LOCALIZATION NOTE (tabs.muteAudio.tooltip):
+# %S is the keyboard shortcut for "Mute tab"
+tabs.muteAudio.tooltip=Mute tab (%S)
+# LOCALIZATION NOTE (tabs.unmuteAudio.tooltip):
+# %S is the keyboard shortcut for "Unmute tab"
+tabs.unmuteAudio.tooltip=Unmute tab (%S)
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -322,17 +322,17 @@ menuitem.bookmark-item {
 }
 
 .bookmark-item[cutting] > .toolbarbutton-text,
 .bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
   opacity: 0.7;
 }
 
 /* Stock icons for the menu bar items */
-menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
+menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip):not([endimage]) {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
 }
 
 #placesContext_open\:newwindow,
 #menu_newNavigator,
 #context-openlink,
 #context-openframe {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
@@ -957,19 +957,17 @@ toolbarbutton[constrain-size="true"][cui
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   -moz-margin-start: 0;
   color: GrayText;
 }
 
-#PopupAutoCompleteRichResult > richlistbox {
-  transition: height 100ms;
-}
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
 
 #search-container {
   min-width: calc(54px + 11ch);
 }
 
 /* identity box */
 
 #identity-box:-moz-locale-dir(ltr) {
@@ -1131,17 +1129,19 @@ richlistitem[type~="action"][actiontype=
 
 .ac-result-type-tag,
 .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
   width: 16px;
   height: 16px;
 }
 
-.ac-comment {
+.ac-comment,
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description,
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
   font-size: 1.05em;
 }
 
 .ac-extra > .ac-comment {
   font-size: inherit;
 }
 
 .ac-url-text,
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -91,16 +91,17 @@ browser.jar:
   skin/classic/browser/search-pref.png                      (../shared/search/search-pref.png)
   skin/classic/browser/search-indicator.png                 (../shared/search/search-indicator.png)
   skin/classic/browser/search-engine-placeholder.png        (../shared/search/search-engine-placeholder.png)
   skin/classic/browser/badge-add-engine.png                 (../shared/search/badge-add-engine.png)
   skin/classic/browser/search-indicator-badge-add.png       (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-history-icon.svg              (../shared/search/history-icon.svg)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                  (../shared/search/search-arrow-go.svg)
+  skin/classic/browser/info.svg                             (../shared/info.svg)
   skin/classic/browser/Security-broken.png
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/theme-switcher-icon.png              (../shared/theme-switcher-icon.png)
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar-inverted.png
   skin/classic/browser/Toolbar-small.png
   skin/classic/browser/undoCloseTab.png                        (../shared/undoCloseTab.png)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1787,24 +1787,22 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
-#PopupAutoCompleteRichResult > richlistbox {
-  transition: height 100ms;
-}
-
 #PopupAutoCompleteRichResult {
   margin-top: 2px;
 }
 
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
+
 /* ----- AUTOCOMPLETE ----- */
 
 #treecolAutoCompleteImage {
   max-width: 36px;
 }
 
 .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
@@ -1840,18 +1838,18 @@ richlistitem[selected="true"][current="t
 }
 
 .ac-extra > .ac-comment {
   font-size: inherit;
 }
 
 .ac-url-text,
 .ac-action-text {
+  font: message-box;
   color: -moz-nativehyperlinktext;
-  font: message-box;
 }
 
 richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {
   list-style-image: url("chrome://browser/skin/actionicon-tab.png");
   -moz-image-region: rect(0, 16px, 11px, 0);
   padding: 0 3px;
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -122,16 +122,17 @@ browser.jar:
   skin/classic/browser/search-engine-placeholder@2x.png        (../shared/search/search-engine-placeholder@2x.png)
   skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
   skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
+  skin/classic/browser/info.svg                                (../shared/info.svg)
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
   skin/classic/browser/theme-switcher-icon@2x.png              (../shared/theme-switcher-icon@2x.png)
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar@2x.png
   skin/classic/browser/Toolbar-inverted.png
   skin/classic/browser/Toolbar-inverted@2x.png
   skin/classic/browser/toolbarbutton-dropmarker.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/info.svg
@@ -0,0 +1,9 @@
+<?xml version="1.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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <circle fill="#00a1f2" cx="8" cy="8" r="8" />
+  <circle fill="#fff" cx="8" cy="4" r="1.25" />
+  <rect x="7" y="7" width="2" height="6" rx="1" ry="1" fill="#fff" />
+</svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -89,79 +89,84 @@
 }
 
 .tab-icon-overlay {
   width: 16px;
   height: 16px;
   margin-top: -12px;
   -moz-margin-start: -16px;
   display: none;
+  position: relative;
 }
 
 .tab-icon-overlay[crashed] {
-  display: -moz-box;
   list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
 }
 
+.tab-icon-overlay[crashed],
 .tab-icon-overlay[soundplaying][pinned],
-.tab-icon-overlay[muted][pinned] {
+.tab-icon-overlay[muted][pinned]:not([crashed]) {
   display: -moz-box;
+}
+
+.tab-icon-overlay[soundplaying][pinned],
+.tab-icon-overlay[muted][pinned]:not([crashed]) {
   border-radius: 8px;
 }
 
 .tab-icon-overlay[soundplaying][pinned]:hover,
-.tab-icon-overlay[muted][pinned]:hover {
+.tab-icon-overlay[muted][pinned]:not([crashed]):hover {
   background-color: white;
 }
 
 .tab-icon-overlay[soundplaying][pinned] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
 }
 
 .tab-icon-overlay[soundplaying][pinned]:hover {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
 }
 
 .tab-icon-overlay[soundplaying][pinned]:hover:active {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
 }
 
-.tab-icon-overlay[muted][pinned] {
+.tab-icon-overlay[muted][pinned]:not([crashed]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
 }
 
-.tab-icon-overlay[muted][pinned]:hover {
+.tab-icon-overlay[muted][pinned]:not([crashed]):hover {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
 }
 
-.tab-icon-overlay[muted][pinned]:hover:active {
+.tab-icon-overlay[muted][pinned]:not([crashed]):hover:active {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
 }
 
 #TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-dark");
 }
 
 #TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
 }
 
 #TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover:active {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
 }
 
-#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned] {
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
 }
 
-#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:hover {
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):hover {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
 }
 
-#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:hover:active {
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):hover:active {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
 }
 
 .tab-throbber[busy] {
   list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
 }
 
 .tab-throbber[progress] {
@@ -416,17 +421,17 @@
 /* Tab pointer-events */
 .tabbrowser-tab {
   pointer-events: none;
 }
 
 .tab-background-middle,
 .tabs-newtab-button,
 .tab-icon-overlay[soundplaying],
-.tab-icon-overlay[muted],
+.tab-icon-overlay[muted]:not([crashed]),
 .tab-icon-sound,
 .tab-close-button {
   pointer-events: auto;
 }
 
 /* Pinned tabs */
 
 /* Pinned tab separators need position: absolute when positioned (during overflow). */
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -0,0 +1,55 @@
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
+  border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
+  background-color: hsla(210, 4%, 10%, 0.07);
+  padding: 6px 0;
+  -moz-padding-start: 44px;
+  -moz-padding-end: 6px;
+  background-image: url("chrome://browser/skin/info.svg");
+  background-clip: padding-box;
+  background-position: 20px center;
+  background-repeat: no-repeat;
+  background-size: 16px 16px;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"]:-moz-locale-dir(rtl) {
+  background-position: right 20px center;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description {
+  margin: 0;
+  padding: 0;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description > label.text-link {
+  -moz-margin-start: 0;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
+  -moz-appearance: none;
+  -moz-user-focus: ignore;
+  min-width: 80px;
+  border-radius: 3px;
+  padding: 4px 16px;
+  margin: 0;
+  -moz-margin-start: 10px;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"] {
+  color: hsl(210, 0%, 38%);
+  background-color: hsl(210, 0%, 88%);
+  border: 1px solid hsl(210, 0%, 82%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"]:hover {
+  background-color: hsl(210, 0%, 84%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"] {
+  color: white;
+  background-color: hsl(93, 82%, 44%);
+  border: 1px solid hsl(93, 82%, 44%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"]:hover {
+  background-color: hsl(93, 82%, 40%);
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1424,19 +1424,17 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   -moz-margin-start: 0;
   color: GrayText;
 }
 
-#PopupAutoCompleteRichResult > richlistbox {
-  transition: height 100ms;
-}
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
 
 #search-container {
   min-width: calc(54px + 11ch);
 }
 
 /* identity box */
 
 #identity-box:-moz-locale-dir(ltr) {
@@ -1559,27 +1557,30 @@ richlistitem[type~="action"][actiontype=
 
 .ac-result-type-tag,
 .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
   width: 16px;
   height: 16px;
 }
 
-.ac-comment {
+.ac-comment,
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description,
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
   font-size: 1.06em;
 }
 
-.ac-extra > .ac-comment {
+.ac-extra > .ac-comment,
+.ac-url-text,
+.ac-action-text {
   font-size: 1em;
 }
 
 .ac-url-text,
 .ac-action-text {
-  font-size: 1em;
   color: -moz-nativehyperlinktext;
 }
 
 @media (-moz-os-version: windows-xp) and (-moz-windows-default-theme) {
   .ac-url-text:not([selected="true"]),
   .ac-action-text:not([selected="true"]) {
     color: #008800;
   }
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -119,16 +119,17 @@ browser.jar:
         skin/classic/browser/search-engine-placeholder@2x.png        (../shared/search/search-engine-placeholder@2x.png)
         skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
         skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
         skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
         skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
         skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
         skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
         skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
+        skin/classic/browser/info.svg                                (../shared/info.svg)
         skin/classic/browser/setDesktopBackground.css
         skin/classic/browser/slowStartup-16.png
         skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
         skin/classic/browser/Toolbar.png
         skin/classic/browser/Toolbar@2x.png
         skin/classic/browser/Toolbar-aero.png
         skin/classic/browser/Toolbar-aero@2x.png
         skin/classic/browser/Toolbar-inverted.png
deleted file mode 100644
--- a/build/autoconf/gcc-pr49911.m4
+++ /dev/null
@@ -1,71 +0,0 @@
-dnl This Source Code Form is subject to the terms of the Mozilla Public
-dnl License, v. 2.0. If a copy of the MPL was not distributed with this
-dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-dnl Check if the compiler is gcc and has PR49911. If so
-dnl disable vrp.
-
-AC_DEFUN([MOZ_GCC_PR49911],
-[
-if test "$GNU_CC"; then
-
-AC_MSG_CHECKING(for gcc PR49911)
-ac_have_gcc_pr49911="no"
-AC_LANG_SAVE
-AC_LANG_CPLUSPLUS
-
-_SAVE_CXXFLAGS=$CXXFLAGS
-CXXFLAGS="-O2"
-AC_TRY_RUN([
-extern "C" void abort(void);
-typedef enum {
-eax,         ecx,         edx,         ebx,         esp,         ebp,
-esi,         edi     }
-RegisterID;
-union StateRemat {
-  RegisterID reg_;
-  int offset_;
-};
-static StateRemat FromRegister(RegisterID reg) {
-  StateRemat sr;
-  sr.reg_ = reg;
-  return sr;
-}
-static StateRemat FromAddress3(int address) {
-  StateRemat sr;
-  sr.offset_ = address;
-  if (address < 46 &&    address >= 0) {
-    abort();
-  }
-  return sr;
-}
-struct FrameState {
-  StateRemat dataRematInfo2(bool y, int z) {
-    if (y)         return FromRegister(RegisterID(1));
-    return FromAddress3(z);
-  }
-};
-FrameState frame;
-StateRemat x;
-__attribute__((noinline)) void jsop_setelem(bool y, int z) {
-  x = frame.dataRematInfo2(y, z);
-}
-int main(void) {
-  jsop_setelem(0, 47);
-}
-], true,
-   ac_have_gcc_pr49911="yes",
-   true)
-CXXFLAGS="$_SAVE_CXXFLAGS"
-
-AC_LANG_RESTORE
-
-if test "$ac_have_gcc_pr49911" = "yes"; then
-   AC_MSG_RESULT(yes)
-   CFLAGS="$CFLAGS -fno-tree-vrp"
-   CXXFLAGS="$CXXFLAGS -fno-tree-vrp"
-else
-   AC_MSG_RESULT(no)
-fi
-fi
-])
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -7,17 +7,17 @@
 # Avoid duplication if the file happens to be included twice.
 if test -z "$bucket"; then
 
 read branch platform master <<EOF
 $(python2.7 -c 'import json; p = json.loads(open("'"$topsrcdir"'/../buildprops.json").read())["properties"]; print p["branch"], p["platform"], p["master"]' 2> /dev/null)
 EOF
 
 bucket=
-if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache"; then
+if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET"; then
     case "${branch}" in
     try)
         case "${master}" in
         *scl1.mozilla.com*|*.scl3.mozilla.com*)
             bucket=mozilla-releng-s3-cache-us-west-1-try
             ;;
         *use1.mozilla.com*)
             bucket=mozilla-releng-s3-cache-us-east-1-try
@@ -25,24 +25,20 @@ if test -z "$SCCACHE_DISABLE" -a -z "$no
         *usw2.mozilla.com*)
             bucket=mozilla-releng-s3-cache-us-west-2-try
             ;;
         esac
         ;;
     b2g-inbound|mozilla-inbound|fx-team)
         case "${master}" in
         *use1.mozilla.com*)
-            if test -z "$MOZ_PGO"; then
-                bucket=mozilla-releng-s3-cache-us-east-1-prod
-            fi
+            bucket=mozilla-releng-s3-cache-us-east-1-prod
             ;;
         *usw2.mozilla.com*)
-            if test -z "$MOZ_PGO"; then
-                bucket=mozilla-releng-s3-cache-us-west-2-prod
-            fi
+            bucket=mozilla-releng-s3-cache-us-west-2-prod
             ;;
         esac
         ;;
     esac
 fi
 
 if test -z "$bucket"; then
     case "$platform" in
--- a/build/unix/mozconfig.gtk
+++ b/build/unix/mozconfig.gtk
@@ -3,26 +3,28 @@ TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
 # $TOOLTOOL_DIR/gtk3 comes from tooltool, when the tooltool manifest contains it.
 if [ -d "$TOOLTOOL_DIR/gtk3" ]; then
   if [ -z "$PKG_CONFIG_LIBDIR" ]; then
     echo PKG_CONFIG_LIBDIR must be set >&2
     exit 1
   fi
   export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3"
   export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig"
+  PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config"
   export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}"
   # Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages.
   LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}"
   ac_add_options --enable-default-toolkit=cairo-gtk3
 
   # Set things up to use Gtk+3 from the tooltool package
   mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts"
   mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc"
   mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib"
   mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
   mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders"
   mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib"
 
   # Until a tooltool with bug 1188571 landed is available everywhere
   $TOOLTOOL_DIR/gtk3/setup.sh
 else
+  PKG_CONFIG=pkg-config
   ac_add_options --enable-default-toolkit=cairo-gtk2
 fi
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -838,28 +838,22 @@ nsScriptSecurityManager::CheckLoadURIWit
         // Allow domains that were whitelisted in the prefs. In 99.9% of cases,
         // this array is empty.
         for (size_t i = 0; i < mFileURIWhitelist.Length(); ++i) {
             if (EqualOrSubdomain(sourceURI, mFileURIWhitelist[i])) {
                 return NS_OK;
             }
         }
 
-        // resource: and chrome: are equivalent, securitywise
-        // That's bogus!!  Fix this.  But watch out for
-        // the view-source stylesheet?
-        bool sourceIsChrome;
-        rv = NS_URIChainHasFlags(sourceURI,
-                                 nsIProtocolHandler::URI_IS_UI_RESOURCE,
-                                 &sourceIsChrome);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (sourceIsChrome) {
+        // Allow chrome://
+        if (sourceScheme.EqualsLiteral("chrome")) {
             return NS_OK;
         }
 
+        // Nothing else.
         if (reportErrors) {
             ReportError(nullptr, errorTag, sourceURI, aTargetURI);
         }
         return NS_ERROR_DOM_BAD_URI;
     }
 
     // OK, everyone is allowed to load this, since unflagged handlers are
     // deprecated but treated as URI_LOADABLE_BY_ANYONE.  But check whether we
--- a/config/makefiles/xpidl/Makefile.in
+++ b/config/makefiles/xpidl/Makefile.in
@@ -37,16 +37,24 @@ endif
 # TODO we should use py_action, but that would require extra directories to be
 # in the virtualenv.
 %.xpt:
 	@echo "$(@F)"
 	$(PYTHON_PATH) $(PLY_INCLUDE) -I$(IDL_PARSER_DIR) -I$(IDL_PARSER_CACHE_DIR) \
 		$(process_py) --cache-dir $(IDL_PARSER_CACHE_DIR) $(dist_idl_dir) \
 		$(dist_include_dir) $(@D) $(idl_deps_dir) $(libxul_sdk_includes) \
 		$(basename $(notdir $@)) $($(basename $(notdir $@))_deps)
+# When some IDL is added or removed, if the actual IDL file was already, or
+# still is, in the tree, simple dependencies can't detect that the XPT needs
+# to be rebuilt.
+# Add the current value of $($(xpidl_module)_deps) in the depend file, such that
+# we can later check if the value has changed since last build, which will
+# indicate whether IDLs were added or removed.
+# Note that removing previously built files is not covered.
+	@echo $(basename $(notdir $@))_deps_built = $($(basename $(notdir $@))_deps) >> $(idl_deps_dir)/$(basename $(notdir $@)).pp
 
 # Chrome manifests may be written from several Makefiles at various times during
 # the build. The 'buildlist' action adds to the file if it already exists, but
 # if it does exist, make considers it to be up-to-date (as we have no inputs to
 # depend on). We use FORCE to ensure that we always add the interface manifest,
 # whether or not the chrome manifest already exists.
 %/chrome.manifest: FORCE
 	$(call py_action,buildlist,$@ 'manifest components/interfaces.manifest')
@@ -61,18 +69,21 @@ xpt_files := @xpt_files@
 depends_files := $(foreach root,$(xpidl_modules),$(idl_deps_dir)/$(root).pp)
 
 GARBAGE += $(xpt_files) $(depends_files)
 
 xpidl:: $(xpt_files) $(chrome_manifests)
 
 $(xpt_files): $(process_py) $(call mkdir_deps,$(idl_deps_dir) $(dist_include_dir))
 
+-include $(depends_files)
+
 define xpt_deps
 $(1): $(call mkdir_deps,$(dir $(1)))
 $(1): $(addsuffix .idl,$(addprefix $(dist_idl_dir)/,$($(basename $(notdir $(1)))_deps)))
+ifneq ($($(basename $(notdir $(1)))_deps),$($(basename $(notdir $(1)))_deps_built))
+$(1): FORCE
+endif
 endef
 
 $(foreach xpt,$(xpt_files),$(eval $(call xpt_deps,$(xpt))))
 
-$(call include_deps,$(depends_files))
-
 .PHONY: xpidl
--- a/configure.in
+++ b/configure.in
@@ -2638,39 +2638,18 @@ WINNT|Darwin|Android)
   STL_FLAGS='-I$(DIST)/stl_wrappers'
   WRAP_STL_INCLUDES=1
   ;;
 esac
 
 AC_SUBST(WRAP_SYSTEM_INCLUDES)
 AC_SUBST(VISIBILITY_FLAGS)
 
-MOZ_GCC_PR49911
 MOZ_LLVM_PR8927
 
-dnl Check for __force_align_arg_pointer__ for SSE2 on gcc
-dnl ========================================================
-if test "$GNU_CC"; then
-  CFLAGS_save="${CFLAGS}"
-  CFLAGS="${CFLAGS} -Werror"
-  AC_CACHE_CHECK(for __force_align_arg_pointer__ attribute,
-                 ac_cv_force_align_arg_pointer,
-                 [AC_TRY_COMPILE([__attribute__ ((__force_align_arg_pointer__)) void test() {}],
-                                 [],
-                                 ac_cv_force_align_arg_pointer="yes",
-                                 ac_cv_force_align_arg_pointer="no")])
-  CFLAGS="${CFLAGS_save}"
-  if test "$ac_cv_force_align_arg_pointer" = "yes"; then
-    HAVE_GCC_ALIGN_ARG_POINTER=1
-  else
-    HAVE_GCC_ALIGN_ARG_POINTER=
-  fi
-fi
-AC_SUBST(HAVE_GCC_ALIGN_ARG_POINTER)
-
 dnl Checks for header files.
 dnl ========================================================
 AC_HEADER_DIRENT
 case "$target_os" in
 freebsd*|openbsd*)
 # for stuff like -lXshm
     CPPFLAGS="${CPPFLAGS} ${X_CFLAGS}"
     ;;
@@ -5868,19 +5847,19 @@ if test -n "$MOZ_ANGLE_RENDERER"; then
   fi
 
   ######################################
   # Find _46+ for use by Vista+.
 
   # Find a D3D compiler DLL in a Windows SDK.
   MOZ_D3DCOMPILER_VISTA_DLL=
   case "$MOZ_WINSDK_MAXVER" in
-  0x0603*)
+  0x0603*|0x0A00*)
     MOZ_D3DCOMPILER_VISTA_DLL=d3dcompiler_47.dll
-    AC_MSG_RESULT([Found D3D compiler in Windows SDK 8.1.])
+    AC_MSG_RESULT([Found D3D compiler in Windows SDK.])
   ;;
   esac
 
   if test -n "$MOZ_D3DCOMPILER_VISTA_DLL"; then
     # We have a name, now track down the path.
     if test -n "$WINDOWSSDKDIR"; then
       MOZ_D3DCOMPILER_VISTA_DLL_PATH="$WINDOWSSDKDIR/Redist/D3D/$MOZ_D3D_CPU_SUFFIX/$MOZ_D3DCOMPILER_VISTA_DLL"
       if test -f "$MOZ_D3DCOMPILER_VISTA_DLL_PATH"; then
@@ -5889,16 +5868,18 @@ if test -n "$MOZ_ANGLE_RENDERER"; then
       else
         AC_MSG_RESULT([MOZ_D3DCOMPILER_VISTA_DLL_PATH doesn't exist: $MOZ_D3DCOMPILER_VISTA_DLL_PATH])
         AC_MSG_ERROR([Windows SDK at "$WINDOWSSDKDIR" appears broken. Try updating to MozillaBuild 1.9 final or higher.])
         MOZ_D3DCOMPILER_VISTA_DLL_PATH=
       fi
     else
       AC_MSG_RESULT([Windows SDK not found.])
     fi
+  else
+    AC_MSG_ERROR([Couldn't find Windows SDK 8.1 or higher needed for ANGLE.])
   fi
 
   if test -z "$MOZ_D3DCOMPILER_VISTA_DLL_PATH"; then
     MOZ_D3DCOMPILER_VISTA_DLL=
   fi
 
   # On mingw, check if headers are provided by toolchain.
   if test -n "$GNU_CC"; then
@@ -8194,17 +8175,17 @@ else
     MOZ_PIXMAN_CFLAGS="$PIXMAN_CFLAGS"
     MOZ_PIXMAN_LIBS="$PIXMAN_LIBS"
 fi
 AC_SUBST(MOZ_PIXMAN_CFLAGS)
 AC_SUBST_LIST(MOZ_PIXMAN_LIBS)
 
 # Check for headers defining standard int types.
 if test -n "$COMPILE_ENVIRONMENT"; then
-    MOZ_CHECK_HEADERS(stdint.h inttypes.h sys/int_types.h)
+    MOZ_CHECK_HEADERS(stdint.h inttypes.h)
 
     if test "${ac_cv_header_inttypes_h}" = "yes"; then
         HAVE_INTTYPES_H=1
     fi
 fi
 
 AC_SUBST(HAVE_INTTYPES_H)
 
@@ -8859,17 +8840,17 @@ HOST_CXXFLAGS=`echo \
     $_DEPEND_CFLAGS`
 
 AC_SUBST(MOZ_NATIVE_JPEG)
 AC_SUBST(MOZ_NATIVE_PNG)
 AC_SUBST(MOZ_NATIVE_BZ2)
 
 AC_SUBST(MOZ_JPEG_CFLAGS)
 AC_SUBST_LIST(MOZ_JPEG_LIBS)
-AC_SUBST(MOZ_BZ2_CFLAGS)
+AC_SUBST_LIST(MOZ_BZ2_CFLAGS)
 AC_SUBST_LIST(MOZ_BZ2_LIBS)
 AC_SUBST(MOZ_PNG_CFLAGS)
 AC_SUBST_LIST(MOZ_PNG_LIBS)
 
 if test "$MOZ_WIDGET_TOOLKIT" = gonk -a -n "$MOZ_NUWA_PROCESS"; then
     export MOZ_NUWA_PROCESS
     AC_DEFINE(MOZ_NUWA_PROCESS)
 fi
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -512,53 +512,67 @@ Animation::HasLowerCompositeOrderThan(co
              "Sequence numbers should be unique");
 
   return mSequenceNum < aOther.mSequenceNum;
 }
 
 bool
 Animation::CanThrottle() const
 {
-  if (!mEffect ||
-      mEffect->IsFinishedTransition() ||
-      mEffect->Properties().IsEmpty()) {
+  // This method answers the question, "Can we get away with NOT updating
+  // style on the main thread for this animation on this tick?"
+
+  // Ignore animations that were never going to have any effect anyway.
+  if (!mEffect || mEffect->Properties().IsEmpty()) {
     return true;
   }
 
-  if (!mIsRunningOnCompositor) {
-    return false;
+  // Finished animations can be throttled unless this is the first
+  // sample since finishing. In that case we need an unthrottled sample
+  // so we can apply the correct end-of-animation behavior on the main
+  // thread (either removing the animation style or applying the fill mode).
+  if (PlayState() == AnimationPlayState::Finished) {
+    return mFinishedAtLastComposeStyle;
   }
 
-  if (PlayState() != AnimationPlayState::Finished) {
-    // Unfinished animations can be throttled.
+  // We should also ignore animations which are not "in effect"--i.e. not
+  // producing an output. This includes animations that are idle or in their
+  // delay phase but with no backwards fill.
+  //
+  // Note that unlike newly-finished animations, we don't need to worry about
+  // special handling for newly-idle animations or animations that are newly
+  // yet-to-start since any operation that would cause that change (e.g. a call
+  // to cancel() on the animation, or seeking its current time) will trigger an
+  // unthrottled sample.
+  if (!IsInEffect()) {
     return true;
   }
 
-  // The animation has finished but, if this is the first sample since
-  // finishing, we need an unthrottled sample so we can apply the correct
-  // end-of-animation behavior on the main thread (either removing the
-  // animation style or applying the fill mode).
-  return mFinishedAtLastComposeStyle;
+  return mIsRunningOnCompositor;
 }
 
 void
-Animation::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+Animation::ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                         nsCSSPropertySet& aSetProperties,
                         bool& aNeedsRefreshes)
 {
-  if (!mEffect || mEffect->IsFinishedTransition()) {
+  if (!mEffect) {
     return;
   }
 
   AnimationPlayState playState = PlayState();
   if (playState == AnimationPlayState::Running ||
       playState == AnimationPlayState::Pending) {
     aNeedsRefreshes = true;
   }
 
+  if (!IsInEffect()) {
+    return;
+  }
+
   // In order to prevent flicker, there are a few cases where we want to use
   // a different time for rendering that would otherwise be returned by
   // GetCurrentTime. These are:
   //
   // (a) For animations that are pausing but which are still running on the
   //     compositor. In this case we send a layer transaction that removes the
   //     animation but which also contains the animation values calculated on
   //     the main thread. To prevent flicker when this occurs we want to ensure
@@ -893,19 +907,16 @@ Animation::UpdateFinishedState(SeekFlag 
     }
   }
 
   bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
   if (currentFinishedState && !mFinishedIsResolved) {
     DoFinishNotification(aSyncNotifyFlag);
   } else if (!currentFinishedState && mFinishedIsResolved) {
     ResetFinishedPromise();
-    if (mEffect->AsTransition()) {
-      mEffect->SetIsFinishedTransition(false);
-    }
   }
   // We must recalculate the current time to take account of any mHoldTime
   // changes the code above made.
   mPreviousCurrentTime = GetCurrentTime();
 }
 
 void
 Animation::UpdateEffect()
@@ -1051,17 +1062,17 @@ Animation::GetPresContext() const
     return nullptr;
   }
   return shell->GetPresContext();
 }
 
 AnimationCollection*
 Animation::GetCollection() const
 {
-  css::CommonAnimationManager* manager = GetAnimationManager();
+  CommonAnimationManager* manager = GetAnimationManager();
   if (!manager) {
     return nullptr;
   }
   MOZ_ASSERT(mEffect,
              "An animation with an animation manager must have an effect");
 
   Element* targetElement;
   nsCSSPseudoElements::Type targetPseudoType;
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -31,21 +31,20 @@
 #endif
 
 struct JSContext;
 class nsCSSPropertySet;
 class nsIDocument;
 class nsPresContext;
 
 namespace mozilla {
+
 struct AnimationCollection;
-namespace css {
 class AnimValuesStyleRule;
 class CommonAnimationManager;
-} // namespace css
 
 namespace dom {
 
 class CSSAnimation;
 class CSSTransition;
 
 class Animation
   : public DOMEventTargetHelper
@@ -136,17 +135,17 @@ public:
    * CSSAnimation::PauseFromJS so we leave it for now.
    */
   void PauseFromJS(ErrorResult& aRv) { Pause(aRv); }
 
   // Wrapper functions for Animation DOM methods when called from style.
 
   virtual void CancelFromStyle() { DoCancel(); }
 
-  void Tick();
+  virtual void Tick();
 
   /**
    * Set the time to use for starting or pausing a pending animation.
    *
    * Typically, when an animation is played, it does not start immediately but
    * is added to a table of pending animations on the document of its effect.
    * In the meantime it sets its hold time to the time from which playback
    * should begin.
@@ -290,17 +289,17 @@ public:
    * Updates |aStyleRule| with the animation values of this animation's effect,
    * if any.
    * Any properties already contained in |aSetProperties| are not changed. Any
    * properties that are changed are added to |aSetProperties|.
    * |aNeedsRefreshes| will be set to true if this animation expects to update
    * the style rule on the next refresh driver tick as well (because it
    * is running and has an effect to sample).
    */
-  void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+  void ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties,
                     bool& aNeedsRefreshes);
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   void DoCancel();
   void DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void DoPause(ErrorResult& aRv);
@@ -351,17 +350,17 @@ protected:
    */
   void CancelPendingTasks();
 
   bool IsPossiblyOrphanedPendingAnimation() const;
   StickyTimeDuration EffectEnd() const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
-  virtual css::CommonAnimationManager* GetAnimationManager() const = 0;
+  virtual CommonAnimationManager* GetAnimationManager() const = 0;
   AnimationCollection* GetCollection() const;
 
   nsRefPtr<AnimationTimeline> mTimeline;
   nsRefPtr<KeyframeEffectReadOnly> mEffect;
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
   Nullable<TimeDuration> mHoldTime;  // Animation timescale
   Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -231,45 +231,39 @@ KeyframeEffectReadOnly::ActiveDuration(c
   return StickyTimeDuration(
     aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
 }
 
 // http://w3c.github.io/web-animations/#in-play
 bool
 KeyframeEffectReadOnly::IsInPlay(const Animation& aAnimation) const
 {
-  if (IsFinishedTransition() ||
-      aAnimation.PlayState() == AnimationPlayState::Finished) {
+  if (aAnimation.PlayState() == AnimationPlayState::Finished) {
     return false;
   }
 
   return GetComputedTiming().mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
 // http://w3c.github.io/web-animations/#current
 bool
 KeyframeEffectReadOnly::IsCurrent(const Animation& aAnimation) const
 {
-  if (IsFinishedTransition() ||
-      aAnimation.PlayState() == AnimationPlayState::Finished) {
+  if (aAnimation.PlayState() == AnimationPlayState::Finished) {
     return false;
   }
 
   ComputedTiming computedTiming = GetComputedTiming();
   return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
          computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
 bool
 KeyframeEffectReadOnly::IsInEffect() const
 {
-  if (IsFinishedTransition()) {
-    return false;
-  }
-
   ComputedTiming computedTiming = GetComputedTiming();
   return computedTiming.mProgress != ComputedTiming::kNullProgress;
 }
 
 const AnimationProperty*
 KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
@@ -294,19 +288,18 @@ KeyframeEffectReadOnly::HasAnimationOfPr
     if (HasAnimationOfProperty(aProperties[i])) {
       return true;
     }
   }
   return false;
 }
 
 void
-KeyframeEffectReadOnly::ComposeStyle(
-                          nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
-                          nsCSSPropertySet& aSetProperties)
+KeyframeEffectReadOnly::ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
+                                     nsCSSPropertySet& aSetProperties)
 {
   ComputedTiming computedTiming = GetComputedTiming();
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mProgress == ComputedTiming::kNullProgress) {
     return;
   }
@@ -365,17 +358,17 @@ KeyframeEffectReadOnly::ComposeStyle(
     MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
                size_t(segment - prop.mSegments.Elements()) <
                  prop.mSegments.Length(),
                "out of array bounds");
 
     if (!aStyleRule) {
       // Allocate the style rule now that we know we have animation data.
-      aStyleRule = new css::AnimValuesStyleRule();
+      aStyleRule = new AnimValuesStyleRule();
     }
 
     double positionInSegment =
       (computedTiming.mProgress - segment->mFromKey) /
       (segment->mToKey - segment->mFromKey);
     double valuePosition =
       segment->mTimingFunction.GetValue(positionInSegment);
 
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -21,19 +21,18 @@
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
 class nsCSSPropertySet;
 
 namespace mozilla {
-namespace css {
+
 class AnimValuesStyleRule;
-} // namespace css
 
 /**
  * Input timing parameters.
  *
  * Eventually this will represent all the input timing parameters specified
  * by content but for now it encapsulates just the subset of those
  * parameters passed to GetPositionInIteration.
  */
@@ -192,17 +191,16 @@ class KeyframeEffectReadOnly : public An
 public:
   KeyframeEffectReadOnly(nsIDocument* aDocument,
                          Element* aTarget,
                          nsCSSPseudoElements::Type aPseudoType,
                          const AnimationTiming &aTiming)
     : AnimationEffectReadOnly(aDocument)
     , mTarget(aTarget)
     , mTiming(aTiming)
-    , mIsFinishedTransition(false)
     , mPseudoType(aPseudoType)
   {
     MOZ_ASSERT(aTarget, "null animation target is not yet supported");
   }
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly,
                                                         AnimationEffectReadOnly)
@@ -278,30 +276,16 @@ public:
                                      = nullptr) const {
     return GetComputedTimingAt(GetLocalTime(), aTiming ? *aTiming : mTiming);
   }
 
   // Return the duration of the active interval for the given timing parameters.
   static StickyTimeDuration
   ActiveDuration(const AnimationTiming& aTiming);
 
-  // After transitions finish they need to be retained in order to
-  // address the issue described in
-  // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
-  // However, finished transitions are ignored for many purposes.
-  bool IsFinishedTransition() const {
-    return mIsFinishedTransition;
-  }
-
-  void SetIsFinishedTransition(bool aIsFinished) {
-    MOZ_ASSERT(AsTransition(),
-               "Calling SetIsFinishedTransition but it's not a transition");
-    mIsFinishedTransition = aIsFinished;
-  }
-
   bool IsInPlay(const Animation& aAnimation) const;
   bool IsCurrent(const Animation& aAnimation) const;
   bool IsInEffect() const;
 
   const AnimationProperty*
   GetAnimationOfProperty(nsCSSProperty aProperty) const;
   bool HasAnimationOfProperty(nsCSSProperty aProperty) const {
     return GetAnimationOfProperty(aProperty) != nullptr;
@@ -314,29 +298,26 @@ public:
   InfallibleTArray<AnimationProperty>& Properties() {
     return mProperties;
   }
 
   // Updates |aStyleRule| with the animation values produced by this
   // Animation for the current time except any properties already contained
   // in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
-  void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+  void ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
 
 protected:
   virtual ~KeyframeEffectReadOnly() { }
 
   nsCOMPtr<Element> mTarget;
   Nullable<TimeDuration> mParentTime;
 
   AnimationTiming mTiming;
-  // A flag to mark transitions that have finished and are due to
-  // be removed on the next throttle-able cycle.
-  bool mIsFinishedTransition;
   nsCSSPseudoElements::Type mPseudoType;
 
   InfallibleTArray<AnimationProperty> mProperties;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -776,27 +776,27 @@ EventSource::InitChannelAndRequestEventS
 
   nsCOMPtr<nsIChannel> channel;
   // If we have the document, use it
   if (doc) {
     rv = NS_NewChannel(getter_AddRefs(channel),
                        mSrc,
                        doc,
                        securityFlags,
-                       nsIContentPolicy::TYPE_DATAREQUEST,
+                       nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
                        mLoadGroup,       // loadGroup
                        nullptr,          // aCallbacks
                        loadFlags);       // aLoadFlags
   } else {
     // otherwise use the principal
     rv = NS_NewChannel(getter_AddRefs(channel),
                        mSrc,
                        mPrincipal,
                        securityFlags,
-                       nsIContentPolicy::TYPE_DATAREQUEST,
+                       nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
                        mLoadGroup,       // loadGroup
                        nullptr,          // aCallbacks
                        loadFlags);       // aLoadFlags
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   mHttpChannel = do_QueryInterface(channel);
--- a/dom/base/nsContentPolicyUtils.h
+++ b/dom/base/nsContentPolicyUtils.h
@@ -87,48 +87,50 @@ NS_CP_ResponseName(int16_t response)
  *
  * @param contentType the content type code
  * @return the name of the given content type code
  */
 inline const char *
 NS_CP_ContentTypeName(uint32_t contentType)
 {
   switch (contentType) {
-    CASE_RETURN( TYPE_OTHER                  );
-    CASE_RETURN( TYPE_SCRIPT                 );
-    CASE_RETURN( TYPE_IMAGE                  );
-    CASE_RETURN( TYPE_STYLESHEET             );
-    CASE_RETURN( TYPE_OBJECT                 );
-    CASE_RETURN( TYPE_DOCUMENT               );
-    CASE_RETURN( TYPE_SUBDOCUMENT            );
-    CASE_RETURN( TYPE_REFRESH                );
-    CASE_RETURN( TYPE_XBL                    );
-    CASE_RETURN( TYPE_PING                   );
-    CASE_RETURN( TYPE_XMLHTTPREQUEST         );
-    CASE_RETURN( TYPE_OBJECT_SUBREQUEST      );
-    CASE_RETURN( TYPE_DTD                    );
-    CASE_RETURN( TYPE_FONT                   );
-    CASE_RETURN( TYPE_MEDIA                  );
-    CASE_RETURN( TYPE_WEBSOCKET              );
-    CASE_RETURN( TYPE_CSP_REPORT             );
-    CASE_RETURN( TYPE_XSLT                   );
-    CASE_RETURN( TYPE_BEACON                 );
-    CASE_RETURN( TYPE_FETCH                  );
-    CASE_RETURN( TYPE_IMAGESET               );
-    CASE_RETURN( TYPE_WEB_MANIFEST           );
-    CASE_RETURN( TYPE_INTERNAL_SCRIPT        );
-    CASE_RETURN( TYPE_INTERNAL_WORKER        );
-    CASE_RETURN( TYPE_INTERNAL_SHARED_WORKER );
-    CASE_RETURN( TYPE_INTERNAL_EMBED         );
-    CASE_RETURN( TYPE_INTERNAL_OBJECT        );
-    CASE_RETURN( TYPE_INTERNAL_FRAME         );
-    CASE_RETURN( TYPE_INTERNAL_IFRAME        );
-    CASE_RETURN( TYPE_INTERNAL_AUDIO         );
-    CASE_RETURN( TYPE_INTERNAL_VIDEO         );
-    CASE_RETURN( TYPE_INTERNAL_TRACK         );
+    CASE_RETURN( TYPE_OTHER                   );
+    CASE_RETURN( TYPE_SCRIPT                  );
+    CASE_RETURN( TYPE_IMAGE                   );
+    CASE_RETURN( TYPE_STYLESHEET              );
+    CASE_RETURN( TYPE_OBJECT                  );
+    CASE_RETURN( TYPE_DOCUMENT                );
+    CASE_RETURN( TYPE_SUBDOCUMENT             );
+    CASE_RETURN( TYPE_REFRESH                 );
+    CASE_RETURN( TYPE_XBL                     );
+    CASE_RETURN( TYPE_PING                    );
+    CASE_RETURN( TYPE_XMLHTTPREQUEST          );
+    CASE_RETURN( TYPE_OBJECT_SUBREQUEST       );
+    CASE_RETURN( TYPE_DTD                     );
+    CASE_RETURN( TYPE_FONT                    );
+    CASE_RETURN( TYPE_MEDIA                   );
+    CASE_RETURN( TYPE_WEBSOCKET               );
+    CASE_RETURN( TYPE_CSP_REPORT              );
+    CASE_RETURN( TYPE_XSLT                    );
+    CASE_RETURN( TYPE_BEACON                  );
+    CASE_RETURN( TYPE_FETCH                   );
+    CASE_RETURN( TYPE_IMAGESET                );
+    CASE_RETURN( TYPE_WEB_MANIFEST            );
+    CASE_RETURN( TYPE_INTERNAL_SCRIPT         );
+    CASE_RETURN( TYPE_INTERNAL_WORKER         );
+    CASE_RETURN( TYPE_INTERNAL_SHARED_WORKER  );
+    CASE_RETURN( TYPE_INTERNAL_EMBED          );
+    CASE_RETURN( TYPE_INTERNAL_OBJECT         );
+    CASE_RETURN( TYPE_INTERNAL_FRAME          );
+    CASE_RETURN( TYPE_INTERNAL_IFRAME         );
+    CASE_RETURN( TYPE_INTERNAL_AUDIO          );
+    CASE_RETURN( TYPE_INTERNAL_VIDEO          );
+    CASE_RETURN( TYPE_INTERNAL_TRACK          );
+    CASE_RETURN( TYPE_INTERNAL_XMLHTTPREQUEST );
+    CASE_RETURN( TYPE_INTERNAL_EVENTSOURCE    );
    default:
     return "<Unknown Type>";
   }
 }
 
 #undef CASE_RETURN
 
 /* Passes on parameters from its "caller"'s context. */
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7918,16 +7918,20 @@ nsContentUtils::InternalContentPolicyTyp
   case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
     return nsIContentPolicy::TYPE_SUBDOCUMENT;
 
   case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
   case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
   case nsIContentPolicy::TYPE_INTERNAL_TRACK:
     return nsIContentPolicy::TYPE_MEDIA;
 
+  case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
+  case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
+    return nsIContentPolicy::TYPE_XMLHTTPREQUEST;
+
   default:
     return aType;
   }
 }
 
 
 nsresult
 nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
--- a/dom/base/nsIContentPolicy.idl
+++ b/dom/base/nsIContentPolicy.idl
@@ -15,17 +15,17 @@ interface nsIPrincipal;
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(b545899e-42bd-434c-8fec-a0af3448ea15)]
+[scriptable,uuid(3663021e-5670-496f-887b-b408d6526b5b)]
 interface nsIContentPolicy : nsIContentPolicyBase
 {
   /**
    * Should the resource at this location be loaded?
    * ShouldLoad will be called before loading the resource at aContentLocation
    * to determine whether to start the load at all.
    *
    * @param aContentType      the type of content being tested. This will be one
--- a/dom/base/nsIContentPolicyBase.idl
+++ b/dom/base/nsIContentPolicyBase.idl
@@ -19,17 +19,17 @@ typedef unsigned long nsContentPolicyTyp
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(11b8d725-7c2b-429e-b51f-8b5b542d5009)]
+[scriptable,uuid(20f7b9bf-d7d5-4987-ade8-b7dc0398d44a)]
 interface nsIContentPolicyBase : nsISupports
 {
   /**
    * Indicates a unset or bogus policy type.
    */
   const nsContentPolicyType TYPE_INVALID = 0;
 
   /**
@@ -252,16 +252,30 @@ interface nsIContentPolicyBase : nsISupp
 
   /**
    * Indicates an internal constant for content loaded from track elements.
    *
    * This will be mapped to TYPE_MEDIA.
    */
   const nsContentPolicyType TYPE_INTERNAL_TRACK = 32;
 
+  /**
+   * Indicates an internal constant for an XMLHttpRequest.
+   *
+   * This will be mapped to TYPE_XMLHTTPREQUEST.
+   */
+  const nsContentPolicyType TYPE_INTERNAL_XMLHTTPREQUEST = 33;
+
+  /**
+   * Indicates an internal constant for EventSource.
+   *
+   * This will be mapped to TYPE_DATAREQUEST.
+   */
+  const nsContentPolicyType TYPE_INTERNAL_EVENTSOURCE = 34;
+
   /* When adding new content types, please update nsContentBlocker,
    * NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
    * implementations, the static_assert in dom/cache/DBSchema.cpp,
    * and other things that are not listed here that are related to
    * nsIContentPolicy. */
 
   //////////////////////////////////////////////////////////////////////
 
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -1706,17 +1706,17 @@ nsXMLHttpRequest::Open(const nsACString&
   }
 
   rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, baseURI);
   if (NS_FAILED(rv)) return rv;
 
   rv = CheckInnerWindowCorrectness();
   NS_ENSURE_SUCCESS(rv, rv);
   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                                  uri,
                                  mPrincipal,
                                  doc,
                                  EmptyCString(), //mime guess
                                  nullptr,         //extra
                                  &shouldLoad,
                                  nsContentUtils::GetContentPolicy(),
                                  nsContentUtils::GetSecurityManager());
@@ -1760,27 +1760,27 @@ nsXMLHttpRequest::Open(const nsACString&
   }
 
   // If we have the document, use it
   if (doc) {
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        uri,
                        doc,
                        secFlags,
-                       nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                        loadGroup,
                        nullptr,   // aCallbacks
                        nsIRequest::LOAD_BACKGROUND);
   } else {
     //otherwise use the principal
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        uri,
                        mPrincipal,
                        secFlags,
-                       nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                        loadGroup,
                        nullptr,   // aCallbacks
                        nsIRequest::LOAD_BACKGROUND);
   }
 
   if (NS_FAILED(rv)) return rv;
 
   mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC |
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -136,17 +136,19 @@ static_assert(nsIContentPolicy::TYPE_INV
               nsIContentPolicy::TYPE_INTERNAL_WORKER == 24 &&
               nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER == 25 &&
               nsIContentPolicy::TYPE_INTERNAL_EMBED == 26 &&
               nsIContentPolicy::TYPE_INTERNAL_OBJECT == 27 &&
               nsIContentPolicy::TYPE_INTERNAL_FRAME == 28 &&
               nsIContentPolicy::TYPE_INTERNAL_IFRAME == 29 &&
               nsIContentPolicy::TYPE_INTERNAL_AUDIO == 30 &&
               nsIContentPolicy::TYPE_INTERNAL_VIDEO == 31 &&
-              nsIContentPolicy::TYPE_INTERNAL_TRACK == 32,
+              nsIContentPolicy::TYPE_INTERNAL_TRACK == 32 &&
+              nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST == 33 &&
+              nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34,
               "nsContentPolicytType values are as expected");
 
 namespace {
 
 typedef int32_t EntryId;
 
 struct IdCount
 {
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4263,17 +4263,17 @@ CanvasRenderingContext2D::DrawImage(cons
     mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
   }
 
   MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6);
 
   RefPtr<SourceSurface> srcSurf;
   gfx::IntSize imgSize;
 
-  Element* element;
+  Element* element = nullptr;
 
   EnsureTarget();
   if (image.IsHTMLCanvasElement()) {
     HTMLCanvasElement* canvas = &image.GetAsHTMLCanvasElement();
     element = canvas;
     nsIntSize size = canvas->GetSize();
     if (size.width == 0 || size.height == 0) {
       error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1363,16 +1363,17 @@ WebGLContext::ForceClearFramebufferWithD
 {
     MakeContextCurrent();
 
     bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT);
     bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT);
     bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT);
     bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
     bool shouldOverrideDrawBuffers = false;
+    bool usingDefaultFrameBuffer = !mBoundDrawFramebuffer;
 
     GLenum currentDrawBuffers[WebGLContext::kMaxColorAttachments];
 
     // Fun GL fact: No need to worry about the viewport here, glViewport is just
     // setting up a coordinates transformation, it doesn't affect glClear at all.
     AssertCachedState(); // Can't check cached bindings, as we could
                          // have a different FB bound temporarily.
 
@@ -1380,27 +1381,37 @@ WebGLContext::ForceClearFramebufferWithD
     gl->fDisable(LOCAL_GL_SCISSOR_TEST);
 
     if (initializeColorBuffer) {
 
         if (drawBuffersIsEnabled) {
 
             GLenum drawBuffersCommand[WebGLContext::kMaxColorAttachments] = { LOCAL_GL_NONE };
 
-            for(int32_t i = 0; i < mGLMaxDrawBuffers; i++) {
+            for (int32_t i = 0; i < mGLMaxDrawBuffers; i++) {
                 GLint temp;
                 gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp);
                 currentDrawBuffers[i] = temp;
 
                 if (colorAttachmentsMask[i]) {
                     drawBuffersCommand[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i;
                 }
                 if (currentDrawBuffers[i] != drawBuffersCommand[i])
                     shouldOverrideDrawBuffers = true;
             }
+
+            // When clearing the default framebuffer, we must be clearing only
+            // GL_BACK, and nothing else, or else gl may return an error. We will
+            // only use the first element of currentDrawBuffers in this case.
+            if (usingDefaultFrameBuffer) {
+                gl->Screen()->SetDrawBuffer(LOCAL_GL_BACK);
+                if (currentDrawBuffers[0] == LOCAL_GL_COLOR_ATTACHMENT0)
+                    currentDrawBuffers[0] = LOCAL_GL_BACK;
+                shouldOverrideDrawBuffers = false;
+            }
             // calling draw buffers can cause resolves on adreno drivers so
             // we try to avoid calling it
             if (shouldOverrideDrawBuffers)
                 gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand);
         }
 
         gl->fColorMask(1, 1, 1, 1);
 
@@ -1436,18 +1447,23 @@ WebGLContext::ForceClearFramebufferWithD
         gl->fEnable(LOCAL_GL_SCISSOR_TEST);
 
     if (mRasterizerDiscardEnabled) {
         gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
     // Restore GL state after clearing.
     if (initializeColorBuffer) {
-        if (shouldOverrideDrawBuffers) {
-            gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers);
+
+        if (drawBuffersIsEnabled) {
+            if (usingDefaultFrameBuffer) {
+                gl->Screen()->SetDrawBuffer(currentDrawBuffers[0]);
+            } else if (shouldOverrideDrawBuffers) {
+                gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers);
+            }
         }
 
         gl->fColorMask(mColorWriteMask[0],
                        mColorWriteMask[1],
                        mColorWriteMask[2],
                        mColorWriteMask[3]);
         gl->fClearColor(mColorClearValue[0],
                         mColorClearValue[1],
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLContext.h"
 #include "WebGLTexture.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLFramebuffer.h"
 #include "GLContext.h"
+#include "GLScreenBuffer.h"
 
 namespace mozilla {
 
 void
 WebGLContext::Clear(GLbitfield mask)
 {
     if (IsContextLost())
         return;
@@ -155,26 +156,18 @@ WebGLContext::DrawBuffers(const dom::Seq
          buffered contexts, or into the back buffer for double-buffered
          contexts. If DrawBuffersEXT is supplied with a constant other than
          BACK and NONE, the error INVALID_OPERATION is generated.
          */
         if (buffersLength != 1) {
             return ErrorInvalidValue("drawBuffers: invalid <buffers> (main framebuffer: buffers.length must be 1)");
         }
 
-        MakeContextCurrent();
-
-        if (buffers[0] == LOCAL_GL_NONE) {
-            const GLenum drawBuffersCommand = LOCAL_GL_NONE;
-            gl->fDrawBuffers(1, &drawBuffersCommand);
-            return;
-        }
-        else if (buffers[0] == LOCAL_GL_BACK) {
-            const GLenum drawBuffersCommand = LOCAL_GL_COLOR_ATTACHMENT0;
-            gl->fDrawBuffers(1, &drawBuffersCommand);
+        if (buffers[0] == LOCAL_GL_NONE || buffers[0] == LOCAL_GL_BACK) {
+            gl->Screen()->SetDrawBuffer(buffers[0]);
             return;
         }
         return ErrorInvalidOperation("drawBuffers: invalid operation (main framebuffer: buffers[0] must be GL_NONE or GL_BACK)");
     }
 
     // OK: we are rendering in a framebuffer object
 
     if (buffersLength > size_t(mGLMaxDrawBuffers)) {
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -504,17 +504,17 @@ WebGLFramebuffer::GetAttachPoint(FBAttac
         return mStencilAttachment;
 
     default:
         break;
     }
 
     if (attachPoint >= LOCAL_GL_COLOR_ATTACHMENT1) {
         size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
-        if (colorAttachmentId < WebGLContext::kMaxColorAttachments) {
+        if (colorAttachmentId < (size_t)mContext->mGLMaxColorAttachments) {
             EnsureColorAttachPoints(colorAttachmentId);
             return mMoreColorAttachments[colorAttachmentId - 1];
         }
     }
 
     MOZ_CRASH("bad `attachPoint` validation");
 }
 
@@ -827,27 +827,29 @@ WebGLFramebuffer::CheckAndInitializeAtta
             mMoreColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     }
 
     return true;
 }
 
 void WebGLFramebuffer::EnsureColorAttachPoints(size_t colorAttachmentId)
 {
-    MOZ_ASSERT(colorAttachmentId < WebGLContext::kMaxColorAttachments);
+    size_t maxColorAttachments = mContext->mGLMaxColorAttachments;
+
+    MOZ_ASSERT(colorAttachmentId < maxColorAttachments);
 
     if (colorAttachmentId < ColorAttachmentCount())
         return;
 
-    while (ColorAttachmentCount() < WebGLContext::kMaxColorAttachments) {
+    while (ColorAttachmentCount() < maxColorAttachments) {
         GLenum nextAttachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + ColorAttachmentCount();
         mMoreColorAttachments.AppendElement(WebGLFBAttachPoint(this, nextAttachPoint));
     }
 
-    MOZ_ASSERT(ColorAttachmentCount() == WebGLContext::kMaxColorAttachments);
+    MOZ_ASSERT(ColorAttachmentCount() == maxColorAttachments);
 }
 
 static void
 FinalizeDrawAndReadBuffers(gl::GLContext* gl, bool isColorBufferDefined)
 {
     MOZ_ASSERT(gl, "Expected a valid GLContext ptr.");
     // GLES don't support DrawBuffer()/ReadBuffer.
     // According to http://www.opengl.org/wiki/Framebuffer_Object
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -147,19 +147,22 @@ InternalRequest::MapContentPolicyTypeToR
     context = RequestContext::Internal;
     break;
   case nsIContentPolicy::TYPE_XBL:
     context = RequestContext::Internal;
     break;
   case nsIContentPolicy::TYPE_PING:
     context = RequestContext::Ping;
     break;
-  case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+  case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
     context = RequestContext::Xmlhttprequest;
     break;
+  case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
+    context = RequestContext::Eventsource;
+    break;
   case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
     context = RequestContext::Plugin;
     break;
   case nsIContentPolicy::TYPE_DTD:
     context = RequestContext::Internal;
     break;
   case nsIContentPolicy::TYPE_FONT:
     context = RequestContext::Font;
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -55,22 +55,22 @@ namespace dom {
  * prefetch          |
  * script            | TYPE_INTERNAL_SCRIPT
  * sharedworker      | TYPE_INTERNAL_SHARED_WORKER
  * subresource       | Not supported by Gecko
  * style             | TYPE_STYLESHEET
  * track             | TYPE_INTERNAL_TRACK
  * video             | TYPE_INTERNAL_VIDEO
  * worker            | TYPE_INTERNAL_WORKER
- * xmlhttprequest    | TYPE_XMLHTTPREQUEST
+ * xmlhttprequest    | TYPE_INTERNAL_XMLHTTPREQUEST
+ * eventsource       | TYPE_INTERNAL_EVENTSOURCE
  * xslt              | TYPE_XSLT
  *
  * TODO: Figure out if TYPE_REFRESH maps to anything useful
  * TODO: Figure out if TYPE_DTD maps to anything useful
- * TODO: Split TYPE_XMLHTTPREQUEST and TYPE_DATAREQUEST for EventSource
  * TODO: Figure out if TYPE_WEBSOCKET maps to anything useful
  * TODO: Add a content type for prefetch
  * TODO: Use the content type for manifest when it becomes available
  * TODO: Add a content type for location
  * TODO: Add a content type for hyperlink
  * TODO: Add a content type for form
  * TODO: Add a content type for favicon
  * TODO: Add a content type for download
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -361,17 +361,16 @@ bool MediaDecoder::IsInfinite()
 MediaDecoder::MediaDecoder() :
   mWatchManager(this, AbstractThread::MainThread()),
   mDormantSupported(false),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mLogicalPosition(0.0),
   mDuration(std::numeric_limits<double>::quiet_NaN()),
   mMediaSeekable(true),
-  mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
   mOwner(nullptr),
   mPlaybackStatistics(new MediaChannelStatistics()),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
@@ -407,17 +406,19 @@ MediaDecoder::MediaDecoder() :
                      "MediaDecoder::mEstimatedDuration (Canonical)"),
   mExplicitDuration(AbstractThread::MainThread(), Maybe<double>(),
                     "MediaDecoder::mExplicitDuration (Canonical)"),
   mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
              "MediaDecoder::mPlayState (Canonical)"),
   mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
              "MediaDecoder::mNextState (Canonical)"),
   mLogicallySeeking(AbstractThread::MainThread(), false,
-                    "MediaDecoder::mLogicallySeeking (Canonical)")
+                    "MediaDecoder::mLogicallySeeking (Canonical)"),
+  mSameOriginMedia(AbstractThread::MainThread(), false,
+                   "MediaDecoder::mSameOriginMedia (Canonical)")
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   //
@@ -771,22 +772,16 @@ void MediaDecoder::DecodeError()
 
 void MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mSameOriginMedia = aSameOrigin;
 }
 
-bool MediaDecoder::IsSameOriginMedia()
-{
-  GetReentrantMonitor().AssertCurrentThreadIn();
-  return mSameOriginMedia;
-}
-
 bool MediaDecoder::IsSeeking() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mLogicallySeeking;
 }
 
 bool MediaDecoder::IsEndedOrShutdown() const
 {
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -567,20 +567,16 @@ public:
     nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableFunction([self] () { self->mPlaybackStatistics->Stop(); });
     AbstractThread::MainThread()->Dispatch(r.forget());
   }
 
   // The actual playback rate computation. The monitor must be held.
   virtual double ComputePlaybackRate(bool* aReliable);
 
-  // Return true when the media is same-origin with the element. The monitor
-  // must be held.
-  bool IsSameOriginMedia();
-
   // Returns true if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   bool CanPlayThrough();
 
   void SetAudioChannel(dom::AudioChannel aChannel) { mAudioChannel = aChannel; }
   dom::AudioChannel GetAudioChannel() { return mAudioChannel; }
 
   // Send a new set of metadata to the state machine, to be dispatched to the
@@ -937,20 +933,16 @@ protected:
   virtual int64_t CurrentPosition() { return mCurrentPosition; }
 
   // Official duration of the media resource as observed by script.
   double mDuration;
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable;
 
-  // True if the media is same-origin with the element. Data can only be
-  // passed to MediaStreams when this is true.
-  bool mSameOriginMedia;
-
   /******
    * The following member variables can be accessed from any thread.
    ******/
 
   // Media data resource.
   nsRefPtr<MediaResource> mResource;
 
 private:
@@ -1115,16 +1107,20 @@ protected:
   // OR on the main thread.
   // Any change to the state must call NotifyAll on the monitor.
   // This can only be PLAY_STATE_PAUSED or PLAY_STATE_PLAYING.
   Canonical<PlayState> mNextState;
 
   // True if the decoder is seeking.
   Canonical<bool> mLogicallySeeking;
 
+  // True if the media is same-origin with the element. Data can only be
+  // passed to MediaStreams when this is true.
+  Canonical<bool> mSameOriginMedia;
+
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() {
     return &mVolume;
   }
   AbstractCanonical<double>* CanonicalPlaybackRate() {
     return &mPlaybackRate;
   }
@@ -1141,13 +1137,16 @@ public:
     return &mPlayState;
   }
   AbstractCanonical<PlayState>* CanonicalNextPlayState() {
     return &mNextState;
   }
   AbstractCanonical<bool>* CanonicalLogicallySeeking() {
     return &mLogicallySeeking;
   }
+  AbstractCanonical<bool>* CanonicalSameOriginMedia() {
+    return &mSameOriginMedia;
+  }
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -237,16 +237,18 @@ MediaDecoderStateMachine::MediaDecoderSt
                  "MediaDecoderStateMachine::mNextPlayState (Mirror)"),
   mLogicallySeeking(mTaskQueue, false,
                     "MediaDecoderStateMachine::mLogicallySeeking (Mirror)"),
   mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
   mLogicalPlaybackRate(mTaskQueue, 1.0,
                        "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
   mPreservesPitch(mTaskQueue, true,
                   "MediaDecoderStateMachine::mPreservesPitch (Mirror)"),
+  mSameOriginMedia(mTaskQueue, false,
+                   "MediaDecoderStateMachine::mSameOriginMedia (Mirror)"),
   mDuration(mTaskQueue, NullableTimeUnit(),
             "MediaDecoderStateMachine::mDuration (Canonical"),
   mIsShutdown(mTaskQueue, false,
               "MediaDecoderStateMachine::mIsShutdown (Canonical)"),
   mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mCurrentPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mCurrentPosition (Canonical)")
@@ -312,16 +314,17 @@ MediaDecoderStateMachine::Initialization
   mEstimatedDuration.Connect(mDecoder->CanonicalEstimatedDuration());
   mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
   mPlayState.Connect(mDecoder->CanonicalPlayState());
   mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
   mLogicallySeeking.Connect(mDecoder->CanonicalLogicallySeeking());
   mVolume.Connect(mDecoder->CanonicalVolume());
   mLogicalPlaybackRate.Connect(mDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(mDecoder->CanonicalPreservesPitch());
+  mSameOriginMedia.Connect(mDecoder->CanonicalSameOriginMedia());
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
   mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
@@ -368,17 +371,17 @@ int64_t MediaDecoderStateMachine::GetDec
 }
 
 void MediaDecoderStateMachine::SendStreamData()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
 
-  bool finished = mDecodedStream->SendData(mVolume, mDecoder->IsSameOriginMedia());
+  bool finished = mDecodedStream->SendData(mVolume, mSameOriginMedia);
 
   const auto clockTime = GetClock();
   while (true) {
     const MediaData* a = AudioQueue().PeekFront();
 
     // If we discard audio samples fed to the stream immediately, we will
     // keep decoding audio samples till the end and consume a lot of memory.
     // Therefore we only discard those behind the stream clock to throttle
@@ -2212,16 +2215,17 @@ MediaDecoderStateMachine::FinishShutdown
   mEstimatedDuration.DisconnectIfConnected();
   mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
+  mSameOriginMedia.DisconnectIfConnected();
   mDuration.DisconnectAll();
   mIsShutdown.DisconnectAll();
   mNextFrameStatus.DisconnectAll();
   mCurrentPosition.DisconnectAll();
 
   // Shut down the watch manager before shutting down our task queue.
   mWatchManager.Shutdown();
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1313,16 +1313,20 @@ private:
   // TODO: The separation between mPlaybackRate and mLogicalPlaybackRate is a
   // kludge to preserve existing fragile logic while converting this setup to
   // state-mirroring. Some hero should clean this up.
   Mirror<double> mLogicalPlaybackRate;
 
   // Pitch preservation for the playback rate.
   Mirror<bool> mPreservesPitch;
 
+  // True if the media is same-origin with the element. Data can only be
+  // passed to MediaStreams when this is true.
+  Mirror<bool> mSameOriginMedia;
+
   // Duration of the media. This is guaranteed to be non-null after we finish
   // decoding the first frame.
   Canonical<media::NullableTimeUnit> mDuration;
 
   // Whether we're currently in or transitioning to shutdown state.
   Canonical<bool> mIsShutdown;
 
   // The status of our next frame. Mirrored on the main thread and used to
--- a/dom/media/MediaEventSource.h
+++ b/dom/media/MediaEventSource.h
@@ -10,16 +10,17 @@
 #include "mozilla/AbstractThread.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 
 /**
  * A thread-safe tool to communicate "revocation" across threads. It is used to
  * disconnect a listener from the event source to prevent future notifications
  * from coming. Revoke() can be called on any thread. However, it is recommended
  * to be called on the target thread to avoid race condition.
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -149,20 +149,16 @@ MediaFormatReader::Shutdown()
   mPlatform = nullptr;
 
   return MediaDecoderReader::Shutdown();
 }
 
 void
 MediaFormatReader::InitLayersBackendType()
 {
-  if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) {
-    // Not playing video, we don't care about the layers backend type.
-    return;
-  }
   // Extract the layer manager backend type so that platform decoders
   // can determine whether it's worthwhile using hardware accelerated
   // video decoding.
   MediaDecoderOwner* owner = mDecoder->GetOwner();
   if (!owner) {
     NS_WARNING("MediaFormatReader without a decoder owner, can't get HWAccel");
     return;
   }
@@ -413,18 +409,22 @@ MediaFormatReader::EnsureDecodersSetup()
       // JavaScript player app. Note: we still go through the motions here
       // even if EME is disabled, so that if script tries and fails to create
       // a CDM, we can detect that and notify chrome and show some UI
       // explaining that we failed due to EME being disabled.
       nsRefPtr<CDMProxy> proxy;
       {
         ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
         proxy = mDecoder->GetCDMProxy();
+        MOZ_ASSERT(proxy);
+
+        CDMCaps::AutoLock caps(proxy->Capabilites());
+        mInfo.mVideo.mIsRenderedExternally = caps.CanRenderVideo();
+        mInfo.mAudio.mIsRenderedExternally = caps.CanRenderAudio();
       }
-      MOZ_ASSERT(proxy);
 
       mPlatform = PlatformDecoderModule::CreateCDMWrapper(proxy);
       NS_ENSURE_TRUE(mPlatform, false);
 #else
       // EME not supported.
       return false;
 #endif
     } else {
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -41,16 +41,17 @@ public:
     : mId(aId)
     , mKind(aKind)
     , mLabel(aLabel)
     , mLanguage(aLanguage)
     , mEnabled(aEnabled)
     , mTrackId(aTrackId)
     , mDuration(0)
     , mMediaTime(0)
+    , mIsRenderedExternally(false)
     , mType(aType)
   {
     MOZ_COUNT_CTOR(TrackInfo);
   }
 
   // Only used for backward compatibility. Do not use in new code.
   void Init(TrackType aType,
             const nsAString& aId,
@@ -78,16 +79,20 @@ public:
 
   TrackID mTrackId;
 
   nsAutoCString mMimeType;
   int64_t mDuration;
   int64_t mMediaTime;
   CryptoTrack mCrypto;
 
+  // True if the track is gonna be (decrypted)/decoded and
+  // rendered directly by non-gecko components.
+  bool mIsRenderedExternally;
+
   virtual AudioInfo* GetAsAudioInfo()
   {
     return nullptr;
   }
   virtual VideoInfo* GetAsVideoInfo()
   {
     return nullptr;
   }
@@ -142,16 +147,17 @@ protected:
     mLabel = aOther.mLabel;
     mLanguage = aOther.mLanguage;
     mEnabled = aOther.mEnabled;
     mTrackId = aOther.mTrackId;
     mMimeType = aOther.mMimeType;
     mDuration = aOther.mDuration;
     mMediaTime = aOther.mMediaTime;
     mCrypto = aOther.mCrypto;
+    mIsRenderedExternally = aOther.mIsRenderedExternally;
     mType = aOther.mType;
     MOZ_COUNT_CTOR(TrackInfo);
   }
 
 private:
   TrackType mType;
 };
 
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -254,23 +254,23 @@ function setupEnvironment() {
   // We don't care about waiting for this to complete, we just want to ensure
   // that we don't build up a huge backlog of GC work.
   SpecialPowers.exactGC(window);
 }
 
 // This is called by steeplechase; which provides the test configuration options
 // directly to the test through this function.  If we're not on steeplechase,
 // the test is configured directly and immediately.
-function run_test(is_initiator) {
+function run_test(is_initiator,timeout) {
   var options = { is_local: is_initiator,
                   is_remote: !is_initiator };
 
   setTimeout(() => {
-    unexpectedEventArrived(new Error("PeerConnectionTest timed out after 30s"));
-  }, 30000);
+    unexpectedEventArrived(new Error("PeerConnectionTest timed out after "+timeout+"s"));
+  }, timeout);
 
   // Also load the steeplechase test code.
   var s = document.createElement("script");
   s.src = "/test.js";
   s.onload = () => setTestOptions(options);
   document.head.appendChild(s);
 }
 
--- a/dom/media/tests/mochitest/steeplechase_long/long.js
+++ b/dom/media/tests/mochitest/steeplechase_long/long.js
@@ -168,52 +168,50 @@ function verifyConnectionStatus(test) {
     processPcStats(test.pcRemote, 'REMOTE', OPERATIONS);
   }
 }
 
 
 /**
  * Generates a setInterval wrapper command link for use in pc.js command chains
  *
- * The link will repeatedly call the given callback function every interval ms
+ * This function returns a promise that will resolve once the link repeatedly
+ * calls the given callback function every interval ms
  * until duration ms have passed, then it will continue the test.
  *
  * @param {function} callback
  *        Function to be called on each interval
  * @param {number} [interval=1000]
  *        Frequency in milliseconds with which callback will be called
  * @param {number} [duration=3 hours]
  *        Length of time in milliseconds for which callback will be called
- * @param {string} [name='INTERVAL_COMMAND']
- *        Name of the generated command link
+
+
  */
-function generateIntervalCommand(callback, interval, duration, name) {
+function generateIntervalCommand(callback, interval, duration) {
   interval = interval || 1000;
   duration = duration || 1000 * 3600 * 3;
-  name = name || 'INTERVAL_COMMAND';
+
+  return function INTERVAL_COMMAND(test) {
+      return new Promise (resolve=>{
+        var startTime = Date.now();
+        var intervalId = setInterval(function () {
+          if (callback) {
+            callback(test);
+          }
 
-  return [
-    name,
-    function (test) {
-      var startTime = Date.now();
-      var intervalId = setInterval(function () {
-        if (callback) {
-          callback(test);
-        }
-
-        var failed = false;
-        Object.keys(_errorCount).forEach(function (label) {
-          if (_errorCount[label] > MAX_ERROR_CYCLES) {
-            ok(false, "Encountered more then " + MAX_ERROR_CYCLES + " cycles" +
+          var failed = false;
+          Object.keys(_errorCount).forEach(function (label) {
+            if (_errorCount[label] > MAX_ERROR_CYCLES) {
+              ok(false, "Encountered more then " + MAX_ERROR_CYCLES + " cycles" +
               " with errors on " + label);
-            failed = true;
-          }
-        });
+              failed = true;
+            }
+          });
         var timeElapsed = Date.now() - startTime;
         if ((timeElapsed >= duration) || failed) {
           clearInterval(intervalId);
-          test.next();
+          resolve();
         }
       }, interval);
-    }
-  ]
+    });
+  };
 }
-
--- a/dom/media/tests/mochitest/steeplechase_long/steeplechase_long.ini
+++ b/dom/media/tests/mochitest/steeplechase_long/steeplechase_long.ini
@@ -1,13 +1,12 @@
 [DEFAULT]
 support-files =
   long.js
   ../head.js
   ../mediaStreamPlayback.js
   ../pc.js
   ../templates.js
   ../turnConfig.js
+  ../network.js
 
-[test_peerConnection_basicAudio_long.html]
-[test_peerConnection_basicVideo_long.html]
 [test_peerConnection_basicAudioVideoCombined_long.html]
 
--- a/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicAudioVideoCombined_long.html
+++ b/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicAudioVideoCombined_long.html
@@ -13,23 +13,24 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1014328",
     title: "Basic audio/video (combined) peer connection, long running",
     visible: true
   });
 
+  var STEEPLECHASE_TIMEOUT = 1000 * 3600 * 3;
   var test;
   runNetworkTest(function (options) {
     options = options || {};
-    options.commands = commandsPeerConnection.slice(0);
+    options.commands = makeDefaultCommands();
     options.commands.push(generateIntervalCommand(verifyConnectionStatus,
                                                   1000 * 10,
-                                                  1000 * 3600 * 3));
+                                                  STEEPLECHASE_TIMEOUT));
 
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true, video: true, fake: false}],
                              [{audio: true, video: true, fake: false}]);
     test.run();
   });
 </script>
 </pre>
--- a/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicAudio_long.html
+++ b/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicAudio_long.html
@@ -13,23 +13,24 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796892",
     title: "Basic audio-only peer connection",
     visible: true
   });
 
+  var STEEPLECHASE_TIMEOUT = 1000 * 3600 * 3;
   var test;
   runNetworkTest(function (options) {
     options = options || {};
-    options.commands = commandsPeerConnection.slice(0);
+    options.commands = makeDefaultCommands();
     options.commands.push(generateIntervalCommand(verifyConnectionStatus,
                                                   1000 * 10,
-                                                  1000 * 3600 * 3));
+                                                  STEEPLECHASE_TIMEOUT));
 
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true, fake: false}],
                              [{audio: true, fake: false}]);
     test.run();
   });
 </script>
 </pre>
--- a/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicVideo_long.html
+++ b/dom/media/tests/mochitest/steeplechase_long/test_peerConnection_basicVideo_long.html
@@ -13,23 +13,24 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796888",
     title: "Basic video-only peer connection",
     visible: true
   });
 
+  var STEEPLECHASE_TIMEOUT = 1000 * 3600 * 3;
   var test;
   runNetworkTest(function (options) {
     options = options || {};
-    options.commands = commandsPeerConnection.slice(0);
+    options.commands = makeDefaultCommands();
     options.commands.push(generateIntervalCommand(verifyConnectionStatus,
                                                   1000 * 10,
-                                                  1000 * 3600 * 3));
+                                                  STEEPLECHASE_TIMEOUT));
 
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{video: true, fake: false}],
                              [{video: true, fake: false}]);
     test.run();
   });
 </script>
 </pre>
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -747,17 +747,17 @@ MediaEngineGonkVideoSource::RotateImage(
 
   uint32_t half_width = dstWidth / 2;
 
   layers::GrallocImage* videoImage = static_cast<layers::GrallocImage*>(image.get());
   MOZ_ASSERT(mTextureClientAllocator);
   RefPtr<layers::TextureClient> textureClient
     = mTextureClientAllocator->CreateOrRecycleForDrawing(gfx::SurfaceFormat::YUV,
                                                          gfx::IntSize(dstWidth, dstHeight),
-                                                         gfx::BackendType::NONE,
+                                                         layers::BackendSelector::Content,
                                                          layers::TextureFlags::DEFAULT,
                                                          layers::ALLOC_DISALLOW_BUFFERTEXTURECLIENT);
   if (textureClient) {
     RefPtr<layers::GrallocTextureClientOGL> grallocTextureClient =
       static_cast<layers::GrallocTextureClientOGL*>(textureClient.get());
 
     android::sp<android::GraphicBuffer> destBuffer = grallocTextureClient->GetGraphicBuffer();
 
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -124,19 +124,19 @@ SpeechRecognition::SpeechRecognition(nsP
   mTestConfig.Init();
   if (mTestConfig.mEnableTests) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     obs->AddObserver(this, SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC, false);
     obs->AddObserver(this, SPEECH_RECOGNITION_TEST_END_TOPIC, false);
   }
 
   mEndpointer.set_speech_input_complete_silence_length(
-      Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 500000));
+      Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 1250000));
   mEndpointer.set_long_speech_input_complete_silence_length(
-      Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
+      Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 2500000));
   mEndpointer.set_long_speech_length(
       Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000));
   Reset();
 }
 
 bool
 SpeechRecognition::StateBetween(FSMState begin, FSMState end)
 {
--- a/dom/network/NetworkStatsService.jsm
+++ b/dom/network/NetworkStatsService.jsm
@@ -122,17 +122,17 @@ this.NetworkStatsService = {
     this.messages.forEach(function(aMsgName) {
       ppmm.addMessageListener(aMsgName, this);
     }, this);
 
     this._db = new NetworkStatsDB();
 
     // Stats for all interfaces are updated periodically
     this.timer.initWithCallback(this, this._db.sampleRate,
-                                Ci.nsITimer.TYPE_REPEATING_PRECISE);
+                                Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
 
     // Stats not from netd are firstly stored in the cached.
     this.cachedStats = Object.create(null);
     this.cachedStatsDate = new Date();
 
     this.updateQueue = [];
     this.isQueueRunning = false;
 
--- a/dom/plugins/ipc/PluginInterposeOSX.mm
+++ b/dom/plugins/ipc/PluginInterposeOSX.mm
@@ -579,17 +579,17 @@ void OnPluginShowWindow(uint32_t window_
 
   CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID());
 
   if (CGRectEqualToRect(window_bounds, main_display_bounds) &&
       (plugin_fullscreen_windows_set_.find(window_id) ==
        plugin_fullscreen_windows_set_.end())) {
     plugin_fullscreen_windows_set_.insert(window_id);
 
-    nsCocoaUtils::HideOSChromeOnScreen(TRUE, [[NSScreen screens] objectAtIndex:0]);
+    nsCocoaUtils::HideOSChromeOnScreen(true);
   }
 }
 
 static void ActivateProcess(pid_t pid) {
   ProcessSerialNumber process;
   OSStatus status = ::GetProcessForPID(pid, &process);
 
   if (status == noErr) {
@@ -602,17 +602,17 @@ static void ActivateProcess(pid_t pid) {
 // Must be called on the UI thread.
 // If plugin_pid is -1, the browser will be the active process on return,
 // otherwise that process will be given focus back before this function returns.
 static void ReleasePluginFullScreen(pid_t plugin_pid) {
   // Releasing full screen only works if we are the frontmost process; grab
   // focus, but give it back to the plugin process if requested.
   ActivateProcess(base::GetCurrentProcId());
 
-  nsCocoaUtils::HideOSChromeOnScreen(FALSE, [[NSScreen screens] objectAtIndex:0]);
+  nsCocoaUtils::HideOSChromeOnScreen(false);
 
   if (plugin_pid != -1) {
     ActivateProcess(plugin_pid);
   }
 }
 
 void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) {
   bool had_windows = !plugin_visible_windows_set_.empty();
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -445,19 +445,23 @@ public:
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
     nsRefPtr<Promise> promise = proxy->GetWorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
-      nsRefPtr<WorkerPushSubscription> sub =
-        new WorkerPushSubscription(mEndpoint, mScope);
-      promise->MaybeResolve(sub);
+      if (mEndpoint.IsEmpty()) {
+        promise->MaybeResolve(JS::NullHandleValue);
+      } else {
+        nsRefPtr<WorkerPushSubscription> sub =
+          new WorkerPushSubscription(mEndpoint, mScope);
+        promise->MaybeResolve(sub);
+      }
     } else {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     }
 
     proxy->CleanUp(aCx);
     return true;
   }
 private:
--- a/dom/security/test/cors/mochitest.ini
+++ b/dom/security/test/cors/mochitest.ini
@@ -4,9 +4,8 @@ support-files =
   file_CrossSiteXHR_inner.html
   file_CrossSiteXHR_inner.jar
   file_CrossSiteXHR_inner_data.sjs
   file_CrossSiteXHR_server.sjs
 
 [test_CrossSiteXHR.html]
 [test_CrossSiteXHR_cache.html]
 [test_CrossSiteXHR_origin.html]
-skip-if = buildapp == 'b2g' || e10s # last test fails to trigger onload on e10s/b2g
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -927,19 +927,19 @@ var interfaceNamesInGlobalScope =
     "ProcessingInstruction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ProgressEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PropertyNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "PushManager", b2g: false, android: false, release: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "PushSubscription", b2g: false, android: false, release: false},
+    {name: "PushManager", b2g: false, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "PushSubscription", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RadioNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Range",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RecordErrorEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Rect",
@@ -965,21 +965,21 @@ var interfaceNamesInGlobalScope =
     "Screen",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ScriptProcessorNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ScrollAreaEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Selection",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorker", release: false, b2g: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorkerContainer", release: false, b2g: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorkerRegistration", release: false, b2g: false},
+    {name: "ServiceWorker", b2g: false, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ServiceWorkerContainer", b2g: false, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ServiceWorkerRegistration", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SettingsLock",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SettingsManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ShadowRoot", // Bogus, but the test harness forces it on.  See bug 1159768.
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SharedWorker",
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -91,16 +91,26 @@ static_assert(nsIHttpChannelInternal::CO
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight),
               "RequestMode enumeration value should match Necko CORS mode value.");
 
 static StaticRefPtr<ServiceWorkerManager> gInstance;
 
+// Tracks the "dom.disable_open_click_delay" preference.  Modified on main
+// thread, read on worker threads. This is set once in the ServiceWorkerManager
+// constructor before any service workers are spawned.
+// It is updated every time a "notificationclick" event is dispatched. While
+// this is done without synchronization, at the worst, the thread will just get
+// an older value within which a popup is allowed to be displayed, which will
+// still be a valid value since it was set in the constructor. I (:nsm) don't
+// think this needs to be synchronized.
+Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
+
 struct ServiceWorkerManager::RegistrationDataPerPrincipal
 {
   // Ordered list of scopes for glob matching.
   // Each entry is an absolute URL representing the scope.
   // Each value of the hash table is an array of an absolute URLs representing
   // the scopes.
   //
   // An array is used for now since the number of controlled scopes per
@@ -381,16 +391,18 @@ NS_INTERFACE_MAP_BEGIN(ServiceWorkerMana
 NS_INTERFACE_MAP_END
 
 ServiceWorkerManager::ServiceWorkerManager()
   : mActor(nullptr)
   , mShuttingDown(false)
 {
   // Register this component to PBackground.
   MOZ_ALWAYS_TRUE(BackgroundChild::GetOrCreateForCurrentThread(this));
+
+  gDOMDisableOpenClickDelay = Preferences::GetInt("dom.disable_open_click_delay");
 }
 
 ServiceWorkerManager::~ServiceWorkerManager()
 {
   // The map will assert if it is not empty when destroyed.
   mRegistrationInfos.Clear();
   MOZ_ASSERT(!mActor);
 }
@@ -1609,25 +1621,26 @@ ServiceWorkerManager::AppendPendingOpera
   if (!mShuttingDown) {
     PendingOperation* opt = mPendingOperations.AppendElement();
     opt->mRunnable = aRunnable;
   }
 }
 
 namespace {
 // Just holds a ref to a ServiceWorker until the Promise is fulfilled.
-class KeepAliveHandler final : public PromiseNativeHandler
+class KeepAliveHandler : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
 
+protected:
   virtual ~KeepAliveHandler()
   {}
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
 
   explicit KeepAliveHandler(const nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
     : mServiceWorker(aServiceWorker)
   {}
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
@@ -1646,16 +1659,160 @@ public:
     MOZ_ASSERT(workerPrivate);
     workerPrivate->AssertIsOnWorkerThread();
 #endif
   }
 };
 
 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
 
+void
+DummyCallback(nsITimer* aTimer, void* aClosure)
+{
+  // Nothing.
+}
+
+class AllowWindowInteractionKeepAliveHandler;
+
+class ClearWindowAllowedRunnable final : public WorkerRunnable
+{
+public:
+  ClearWindowAllowedRunnable(WorkerPrivate* aWorkerPrivate,
+                             AllowWindowInteractionKeepAliveHandler* aHandler)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+  , mHandler(aHandler)
+  { }
+
+private:
+  bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    // WorkerRunnable asserts that the dispatch is from parent thread if
+    // the busy count modification is WorkerThreadUnchangedBusyCount.
+    // Since this runnable will be dispatched from the timer thread, we override
+    // PreDispatch and PostDispatch to skip the check.
+    return true;
+  }
+
+  void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) override
+  {
+    // Silence bad assertions.
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+  nsRefPtr<AllowWindowInteractionKeepAliveHandler> mHandler;
+};
+
+class AllowWindowInteractionKeepAliveHandler final : public KeepAliveHandler
+{
+  friend class ClearWindowAllowedRunnable;
+  nsCOMPtr<nsITimer> mTimer;
+
+  ~AllowWindowInteractionKeepAliveHandler()
+  {
+    MOZ_ASSERT(!mTimer);
+  }
+
+  void
+  ClearWindowAllowed(WorkerPrivate* aWorkerPrivate)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+
+    if (mTimer) {
+      aWorkerPrivate->GlobalScope()->ConsumeWindowInteraction();
+      mTimer->Cancel();
+      mTimer = nullptr;
+      MOZ_ALWAYS_TRUE(aWorkerPrivate->ModifyBusyCountFromWorker(aWorkerPrivate->GetJSContext(), false));
+    }
+  }
+
+  void
+  StartClearWindowTimer(WorkerPrivate* aWorkerPrivate)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(!mTimer);
+
+    nsresult rv;
+    nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+
+    nsRefPtr<ClearWindowAllowedRunnable> r =
+      new ClearWindowAllowedRunnable(aWorkerPrivate, this);
+
+    nsRefPtr<TimerThreadEventTarget> target =
+      new TimerThreadEventTarget(aWorkerPrivate, r);
+
+    rv = timer->SetTarget(target);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+
+    // The important stuff that *has* to be reversed.
+    if (NS_WARN_IF(!aWorkerPrivate->ModifyBusyCountFromWorker(aWorkerPrivate->GetJSContext(), true))) {
+      return;
+    }
+    aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
+    timer.swap(mTimer);
+
+    // We swap first and then initialize the timer so that even if initializing
+    // fails, we still clean the busy count and interaction count correctly.
+    // The timer can't be initialized before modifying the busy count since the
+    // timer thread could run and call the timeout but the worker may
+    // already be terminating and modifying the busy count could fail.
+    rv = mTimer->InitWithFuncCallback(DummyCallback, nullptr, gDOMDisableOpenClickDelay, nsITimer::TYPE_ONE_SHOT);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ClearWindowAllowed(aWorkerPrivate);
+      return;
+    }
+  }
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  AllowWindowInteractionKeepAliveHandler(const nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
+                                         WorkerPrivate* aWorkerPrivate)
+    : KeepAliveHandler(aServiceWorker)
+  {
+    StartClearWindowTimer(aWorkerPrivate);
+  }
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+    ClearWindowAllowed(worker);
+    KeepAliveHandler::ResolvedCallback(aCx, aValue);
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+    ClearWindowAllowed(worker);
+    KeepAliveHandler::RejectedCallback(aCx, aValue);
+  }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(AllowWindowInteractionKeepAliveHandler, KeepAliveHandler)
+
+bool
+ClearWindowAllowedRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+  mHandler->ClearWindowAllowed(aWorkerPrivate);
+  return true;
+}
+
 // Returns a Promise if the event was successfully dispatched and no exceptions
 // were raised, otherwise returns null.
 already_AddRefed<Promise>
 DispatchExtendableEventOnWorkerScope(JSContext* aCx,
                                      WorkerGlobalScope* aWorkerScope,
                                      ExtendableEvent* aEvent)
 {
   MOZ_ASSERT(aWorkerScope);
@@ -2376,22 +2533,27 @@ public:
     nei.mCancelable = true;
 
     nsRefPtr<NotificationEvent> event =
       NotificationEvent::Constructor(target, NS_LITERAL_STRING("notificationclick"), nei, result);
     if (NS_WARN_IF(result.Failed())) {
       return false;
     }
 
+    aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
     event->SetTrusted(true);
     nsRefPtr<Promise> waitUntilPromise =
       DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event);
+    // If the handler calls WaitUntil(), that will manage its own interaction
+    // 'stack'.
+    aWorkerPrivate->GlobalScope()->ConsumeWindowInteraction();
 
     if (waitUntilPromise) {
-      nsRefPtr<KeepAliveHandler> handler = new KeepAliveHandler(mServiceWorker);
+      nsRefPtr<AllowWindowInteractionKeepAliveHandler> handler =
+        new AllowWindowInteractionKeepAliveHandler(mServiceWorker, aWorkerPrivate);
       waitUntilPromise->AppendNativeHandler(handler);
     }
 
     return true;
   }
 };
 
 NS_IMETHODIMP
@@ -2407,16 +2569,18 @@ ServiceWorkerManager::SendNotificationCl
                                                  const nsAString& aData,
                                                  const nsAString& aBehavior)
 {
   OriginAttributes attrs;
   if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  gDOMDisableOpenClickDelay = Preferences::GetInt("dom.disable_open_click_delay");
+
   nsRefPtr<ServiceWorker> serviceWorker =
     CreateServiceWorkerForScope(attrs, aScope, nullptr);
   if (!serviceWorker) {
     return NS_ERROR_FAILURE;
   }
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(serviceWorker));
 
--- a/dom/workers/ServiceWorkerWindowClient.cpp
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -85,19 +85,17 @@ public:
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
     nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
     UniquePtr<ServiceWorkerClientInfo> clientInfo;
 
     if (window) {
-      mozilla::ErrorResult result;
-      //FIXME(catalinb): Bug 1144660 - check if we are allowed to focus here.
-      window->Focus(result);
+      nsContentUtils::DispatchChromeEvent(window->GetExtantDoc(), window->GetOuterWindow(), NS_LITERAL_STRING("DOMServiceWorkerFocusClient"), true, true);
       clientInfo.reset(new ServiceWorkerClientInfo(window->GetDocument(),
                                                    window->GetOuterWindow()));
     }
 
     DispatchResult(Move(clientInfo));
     return NS_OK;
   }
 
@@ -137,24 +135,28 @@ ServiceWorkerWindowClient::Focus(ErrorRe
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
   MOZ_ASSERT(global);
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  nsRefPtr<PromiseWorkerProxy> promiseProxy =
-    PromiseWorkerProxy::Create(workerPrivate, promise);
-  if (!promiseProxy->GetWorkerPromise()) {
-    // Don't dispatch if adding the worker feature failed.
-    return promise.forget();
-  }
+  if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+    nsRefPtr<PromiseWorkerProxy> promiseProxy =
+      PromiseWorkerProxy::Create(workerPrivate, promise);
+    if (!promiseProxy->GetWorkerPromise()) {
+      // Don't dispatch if adding the worker feature failed.
+      return promise.forget();
+    }
 
-  nsRefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
-                                                            promiseProxy);
-  aRv = NS_DispatchToMainThread(r);
-  if (NS_WARN_IF(aRv.Failed())) {
-    promise->MaybeReject(aRv);
+    nsRefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
+                                                              promiseProxy);
+    aRv = NS_DispatchToMainThread(r);
+    if (NS_WARN_IF(aRv.Failed())) {
+      promise->MaybeReject(aRv);
+    }
+  } else {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
   return promise.forget();
 }
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1861,78 +1861,16 @@ private:
 };
 
 void
 DummyCallback(nsITimer* aTimer, void* aClosure)
 {
   // Nothing!
 }
 
-class TimerThreadEventTarget final : public nsIEventTarget
-{
-  ~TimerThreadEventTarget() {}
-
-  WorkerPrivate* mWorkerPrivate;
-  nsRefPtr<WorkerRunnable> mWorkerRunnable;
-
-public:
-  TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
-                         WorkerRunnable* aWorkerRunnable)
-  : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable)
-  {
-    MOZ_ASSERT(aWorkerPrivate);
-    MOZ_ASSERT(aWorkerRunnable);
-  }
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-protected:
-  NS_IMETHOD
-  DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override
-  {
-    nsCOMPtr<nsIRunnable> runnable(aRunnable);
-    return Dispatch(runnable.forget(), aFlags);
-  }
-
-  NS_IMETHOD
-  Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags) override
-  {
-    // This should only happen on the timer thread.
-    MOZ_ASSERT(!NS_IsMainThread());
-    MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
-
-    nsRefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
-
-    // Run the runnable we're given now (should just call DummyCallback()),
-    // otherwise the timer thread will leak it...  If we run this after
-    // dispatch running the event can race against resetting the timer.
-    nsCOMPtr<nsIRunnable> runnable(aRunnable);
-    runnable->Run();
-
-    // This can fail if we're racing to terminate or cancel, should be handled
-    // by the terminate or cancel code.
-    mWorkerRunnable->Dispatch(nullptr);
-
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  IsOnCurrentThread(bool* aIsOnCurrentThread) override
-  {
-    MOZ_ASSERT(aIsOnCurrentThread);
-
-    nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    return NS_OK;
-  }
-};
-
 class KillCloseEventRunnable final : public WorkerRunnable
 {
   nsCOMPtr<nsITimer> mTimer;
 
   class KillScriptRunnable final : public WorkerControlRunnable
   {
   public:
     explicit KillScriptRunnable(WorkerPrivate* aWorkerPrivate)
@@ -2376,16 +2314,70 @@ PRThreadFromThread(nsIThread* aThread)
 }
 
 } /* anonymous namespace */
 
 NS_IMPL_ISUPPORTS_INHERITED0(MainThreadReleaseRunnable, nsRunnable)
 
 NS_IMPL_ISUPPORTS_INHERITED0(TopLevelWorkerFinishedRunnable, nsRunnable)
 
+TimerThreadEventTarget::TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+                                               WorkerRunnable* aWorkerRunnable)
+  : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+  MOZ_ASSERT(aWorkerRunnable);
+}
+
+TimerThreadEventTarget::~TimerThreadEventTarget()
+{
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+  nsCOMPtr<nsIRunnable> runnable(aRunnable);
+  return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags)
+{
+  // This should only happen on the timer thread.
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
+
+  nsRefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
+
+  // Run the runnable we're given now (should just call DummyCallback()),
+  // otherwise the timer thread will leak it...  If we run this after
+  // dispatch running the event can race against resetting the timer.
+  nsCOMPtr<nsIRunnable> runnable(aRunnable);
+  runnable->Run();
+
+  // This can fail if we're racing to terminate or cancel, should be handled
+  // by the terminate or cancel code.
+  mWorkerRunnable->Dispatch(nullptr);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+  MOZ_ASSERT(aIsOnCurrentThread);
+
+  nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS(TimerThreadEventTarget, nsIEventTarget)
 
 WorkerLoadInfo::WorkerLoadInfo()
   : mWindowID(UINT64_MAX)
   , mServiceWorkerID(0)
   , mFromWindow(false)
   , mEvalAllowed(false)
   , mReportCSPViolations(false)
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1570,11 +1570,35 @@ public:
 
   nsIEventTarget*
   EventTarget() const
   {
     return mTarget;
   }
 };
 
+class TimerThreadEventTarget final : public nsIEventTarget
+{
+  ~TimerThreadEventTarget();
+
+  WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<WorkerRunnable> mWorkerRunnable;
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+                         WorkerRunnable* aWorkerRunnable);
+
+protected:
+  NS_IMETHOD
+  DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
+
+
+  NS_IMETHOD
+  Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags) override;
+
+  NS_IMETHOD
+  IsOnCurrentThread(bool* aIsOnCurrentThread) override;
+};
+
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_workerprivate_h__ */
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -54,17 +54,18 @@ USING_WORKERS_NAMESPACE
 
 using mozilla::dom::cache::CacheStorage;
 using mozilla::dom::indexedDB::IDBFactory;
 using mozilla::ipc::PrincipalInfo;
 
 BEGIN_WORKERS_NAMESPACE
 
 WorkerGlobalScope::WorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
-: mWorkerPrivate(aWorkerPrivate)
+: mWindowInteractionsAllowed(0)
+, mWorkerPrivate(aWorkerPrivate)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 }
 
 WorkerGlobalScope::~WorkerGlobalScope()
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 }
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -54,16 +54,18 @@ class WorkerGlobalScope : public DOMEven
 
   nsRefPtr<Console> mConsole;
   nsRefPtr<WorkerLocation> mLocation;
   nsRefPtr<WorkerNavigator> mNavigator;
   nsRefPtr<Performance> mPerformance;
   nsRefPtr<IDBFactory> mIndexedDB;
   nsRefPtr<cache::CacheStorage> mCacheStorage;
 
+  uint32_t mWindowInteractionsAllowed;
+
 protected:
   WorkerPrivate* mWorkerPrivate;
 
   explicit WorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
   virtual ~WorkerGlobalScope();
 
 public:
   virtual JSObject*
@@ -157,16 +159,35 @@ public:
 
   already_AddRefed<Promise>
   CreateImageBitmap(const ImageBitmapSource& aImage, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   CreateImageBitmap(const ImageBitmapSource& aImage,
                     int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
                     ErrorResult& aRv);
+
+  bool
+  WindowInteractionAllowed() const
+  {
+    return mWindowInteractionsAllowed > 0;
+  }
+
+  void
+  AllowWindowInteraction()
+  {
+    mWindowInteractionsAllowed++;
+  }
+
+  void
+  ConsumeWindowInteraction()
+  {
+    MOZ_ASSERT(mWindowInteractionsAllowed > 0);
+    mWindowInteractionsAllowed--;
+  }
 };
 
 class DedicatedWorkerGlobalScope final : public WorkerGlobalScope
 {
   ~DedicatedWorkerGlobalScope() { }
 
 public:
   explicit DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
--- a/dom/workers/test/serviceworkers/browser_force_refresh.js
+++ b/dom/workers/test/serviceworkers/browser_force_refresh.js
@@ -12,16 +12,17 @@ function forceRefresh() {
   EventUtils.synthesizeKey('R', { accelKey: true, shiftKey: true });
 }
 
 function test() {
   waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true],
                                      ['dom.serviceWorkers.exemptFromPerDomainMax', true],
                                      ['dom.serviceWorkers.testing.enabled', true],
+                                     ['dom.serviceWorkers.interception.enabled', true],
                                      ['dom.caches.enabled', true]]},
                             function() {
     var url = gTestRoot + 'browser_base_force_refresh.html';
     var tab = gBrowser.addTab(url);
     gBrowser.selectedTab = tab;
 
     var cachedLoad = false;
 
deleted file mode 100644
--- a/dom/workers/test/serviceworkers/client_focus_worker.js
+++ /dev/null
@@ -1,15 +0,0 @@
-onmessage = function(e) {
-  if (!e.source) {
-    dump("ERROR: message doesn't have a source.");
-  }
-
-  // The client should be a window client
-  if (e.source instanceof WindowClient) {
-    // this will dispatch a focus event on the client
-    e.source.focus().then(function(client) {
-      client.postMessage(client.focused);
-    });
-  } else {
-    dump("ERROR: client should be a WindowClient");
-  }
-};
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -91,16 +91,21 @@ fetchXHR('nonresponse.txt', null, functi
   finish();
 });
 
 fetchXHR('nonresponse2.txt', null, function(xhr) {
   my_ok(xhr.status == 0, "load should not complete");
   finish();
 });
 
+fetchXHR('nonpromise.txt', null, function(xhr) {
+  my_ok(xhr.status == 0, "load should not complete");
+  finish();
+});
+
 fetchXHR('headers.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "1", "request header checks should have passed");
   finish();
 }, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]);
 
 var expectedUncompressedResponse = "";
 for (var i = 0; i < 10; ++i) {
--- a/dom/workers/test/serviceworkers/fetch_event_worker.js
+++ b/dom/workers/test/serviceworkers/fetch_event_worker.js
@@ -71,16 +71,27 @@ onfetch = function(ev) {
   else if (ev.request.url.includes("nonresponse.txt")) {
     ev.respondWith(Promise.resolve(5));
   }
 
   else if (ev.request.url.includes("nonresponse2.txt")) {
     ev.respondWith(Promise.resolve({}));
   }
 
+  else if (ev.request.url.includes("nonpromise.txt")) {
+    try {
+      // This should coerce to Promise(5) instead of throwing
+      ev.respondWith(5);
+    } catch (e) {
+      // test is expecting failure, so return a success if we get a thrown
+      // exception
+      ev.respondWith(new Response('respondWith(5) threw ' + e));
+    }
+  }
+
   else if (ev.request.url.includes("headers.txt")) {
     var ok = true;
     ok &= ev.request.headers.get("X-Test1") == "header1";
     ok &= ev.request.headers.get("X-Test2") == "header2";
     ev.respondWith(Promise.resolve(
       new Response(ok.toString(), {})
     ));
   }
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -12,17 +12,16 @@ support-files =
   simpleregister/index.html
   simpleregister/ready.html
   controller/index.html
   unregister/index.html
   unregister/unregister.html
   workerUpdate/update.html
   sw_clients/simple.html
   sw_clients/service_worker_controlled.html
-  sw_clients/focus_stealing_client.html
   match_all_worker.js
   match_all_advanced_worker.js
   worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
@@ -85,21 +84,22 @@ support-files =
   serviceworker_not_sharedworker.js
   match_all_client/match_all_client_id.html
   match_all_client_id_worker.js
   source_message_posting_worker.js
   scope/scope_worker.js
   redirect_serviceworker.sjs
   importscript.sjs
   importscript_worker.js
-  client_focus_worker.js
   bug1151916_worker.js
   bug1151916_driver.html
   notificationclick.html
   notificationclick.js
+  notificationclick_focus.html
+  notificationclick_focus.js
   worker_updatefoundevent.js
   worker_updatefoundevent2.js
   updatefoundevent.html
   empty.js
   periodic_update_test.js
   periodic.sjs
   periodic/frame.html
   periodic/register.html
@@ -158,17 +158,16 @@ support-files =
   sw_clients/dummy.html
 
 [test_app_protocol.html]
 skip-if = release_build
 [test_bug1151916.html]
 [test_claim.html]
 [test_claim_fetch.html]
 [test_claim_oninstall.html]
-[test_client_focus.html]
 [test_close.html]
 [test_controller.html]
 [test_cross_origin_url_after_redirect.html]
 [test_empty_serviceworker.html]
 [test_eval_allowed.html]
 [test_eval_not_allowed.html]
 [test_fetch_event.html]
 [test_force_refresh.html]
@@ -222,16 +221,18 @@ skip-if = release_build
 [test_request_context_track.html]
 [test_request_context_video.html]
 [test_request_context_worker.html]
 [test_request_context_xhr.html]
 [test_request_context_xslt.html]
 [test_scopes.html]
 [test_sandbox_intercept.html]
 [test_notificationclick.html]
+[test_notificationclick_focus.html]
+skip-if = toolkit == "android" || toolkit == "gonk"
 [test_notification_constructor_error.html]
 [test_notification_get.html]
 [test_sanitize.html]
 [test_sanitize_domain.html]
 [test_service_worker_allowed.html]
 [test_serviceworker_interfaces.html]
 [test_serviceworker_not_sharedworker.html]
 [test_skip_waiting.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick_focus.html
@@ -0,0 +1,28 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1144660 - controlled page</title>
+<script class="testbody" type="text/javascript">
+  var testWindow = parent;
+  if (opener) {
+    testWindow = opener;
+  }
+
+  navigator.serviceWorker.ready.then(function(swr) {
+    swr.showNotification("Hi there. The ServiceWorker should receive a click event for this.");
+  });
+
+  navigator.serviceWorker.onmessage = function(msg) {
+    dump("GOT Message " + JSON.stringify(msg.data) + "\n");
+    testWindow.callback(msg.data.ok);
+  };
+</script>
+
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick_focus.js
@@ -0,0 +1,40 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+
+function promisifyTimerFocus(client, delay) {
+  return new Promise(function(resolve, reject) {
+    setTimeout(function() {
+      client.focus().then(resolve, reject);
+    }, delay);
+  });
+}
+
+onnotificationclick = function(e) {
+  e.waitUntil(self.clients.matchAll().then(function(clients) {
+    if (clients.length === 0) {
+      dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n");
+      return Promise.resolve();
+    }
+
+    var immediatePromise = clients[0].focus();
+    var withinTimeout = promisifyTimerFocus(clients[0], 100);
+
+    var afterTimeout = promisifyTimerFocus(clients[0], 2000).then(function() {
+      throw "Should have failed!";
+    }, function() {
+      return Promise.resolve();
+    });
+
+    return Promise.all([immediatePromise, withinTimeout, afterTimeout]).then(function() {
+      clients.forEach(function(client) {
+        client.postMessage({ok: true});
+      });
+    }).catch(function(e) {
+      dump("Error " + e + "\n");
+      clients.forEach(function(client) {
+        client.postMessage({ok: false});
+      });
+    });
+  }));
+}
--- a/dom/workers/test/serviceworkers/periodic_update_test.js
+++ b/dom/workers/test/serviceworkers/periodic_update_test.js
@@ -63,12 +63,13 @@ function unregisterSW() {
 function runTheTest() {
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
   ]}, function() {
     start();
   });
 }
deleted file mode 100644
--- a/dom/workers/test/serviceworkers/sw_clients/focus_stealing_client.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-  Any copyright is dedicated to the Public Domain.
-  http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Bug 1130686 - Test service worker client.focus: client </title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <!--
-    We load this as an iframe to blur the main test window.
-  -->
-</head>
-<body>/
-<p id="display"></p>
-<div id="content" style="display: none"></div>
-<pre id="test"></pre>
-<script class="testbody" type="text/javascript">
-  if (!parent) {
-    info("error: sw_clients/focus_stealing_client.html shouldn't be launched directly!");
-  }
-
-  window.onload = function() {
-    navigator.serviceWorker.ready.then(function() {
-      parent.postMessage("READY", "*");
-    });
-  }
-</script>
-</pre>
-</body>
-</html>
-
--- a/dom/workers/test/serviceworkers/test_app_installation.html
+++ b/dom/workers/test/serviceworkers/test_app_installation.html
@@ -42,17 +42,18 @@ addLoadEvent(go);
 function setup() {
   info('Setting up');
   return new Promise((resolve, reject) => {
     SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.pushPrefEnv({'set': [
       ['dom.mozBrowserFramesEnabled', true],
       ['dom.serviceWorkers.exemptFromPerDomainMax', true],
       ['dom.serviceWorkers.enabled', true],
-      ['dom.serviceWorkers.testing.enabled', true]
+      ['dom.serviceWorkers.testing.enabled', true],
+      ['dom.serviceWorkers.interception.enabled', true],
     ]}, () => {
       SpecialPowers.pushPermissions([
         { 'type': 'webapps-manage', 'allow': 1, 'context': document },
         { 'type': 'browser', 'allow': 1, 'context': document },
         { 'type': 'embed-apps', 'allow': 1, 'context': document }
       ], () => {
         SpecialPowers.autoConfirmAppInstall(() => {
           SpecialPowers.autoConfirmAppUninstall(resolve);
--- a/dom/workers/test/serviceworkers/test_client_focus.html
+++ b/dom/workers/test/serviceworkers/test_client_focus.html
@@ -4,18 +4,18 @@
 -->
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Bug 1130686 - Test service worker client.focus </title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 <!--
-  This test checks that client.focus is able to restore focus to the main window
-  when an iframe is holding the focus.
+  This test checks that client.focus is available.
+  Actual focusing is tested by test_notificationclick_focus.html since only notification events have permission to change focus.
 -->
 </head>
 <body>
 <p id="display"></p>
 <div id="content"></div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
   var registration;
--- a/dom/workers/test/serviceworkers/test_eval_allowed.html
+++ b/dom/workers/test/serviceworkers/test_eval_allowed.html
@@ -29,14 +29,15 @@
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
+    ["dom.serviceWorkers.testing.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_eval_not_allowed.html
+++ b/dom/workers/test/serviceworkers/test_eval_not_allowed.html
@@ -32,14 +32,15 @@
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
+    ["dom.serviceWorkers.testing.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_fetch_event_client_postmessage.html
+++ b/dom/workers/test/serviceworkers/test_fetch_event_client_postmessage.html
@@ -60,12 +60,14 @@
         }).then(SimpleTest.finish);
     }
 
     SimpleTest.waitForExplicitFinish();
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
     ]}, runTest);
   </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_gzip_redirect.html
+++ b/dom/workers/test/serviceworkers/test_gzip_redirect.html
@@ -71,14 +71,15 @@
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
+    ["dom.serviceWorkers.testing.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_notificationclick.html
+++ b/dom/workers/test/serviceworkers/test_notificationclick.html
@@ -44,13 +44,14 @@ https://bugzilla.mozilla.org/show_bug.cg
   };
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.webnotifications.workers.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
     ["notification.prompt.testing", true],
   ]}, runTest);
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclick_focus.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+  <title>Bug 1144660 - Test client.focus() permissions on notification click</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1114554">Bug 1114554</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+  SimpleTest.requestFlakyTimeout("Mock alert service dispatches show and click events.");
+
+  function testFrame(src) {
+    var iframe = document.createElement("iframe");
+    iframe.src = src;
+    window.callback = function(result) {
+      window.callback = null;
+      document.body.removeChild(iframe);
+      iframe = null;
+      ok(result, "All tests passed.");
+      MockServices.unregister();
+      SimpleTest.finish();
+    };
+    document.body.appendChild(iframe);
+  }
+
+  function runTest() {
+    MockServices.register();
+    testFrame('notificationclick_focus.html');
+    navigator.serviceWorker.register("notificationclick_focus.js", { scope: "notificationclick_focus.html" }).then(function(reg) {
+    }, function(e) {
+      ok(false, "registration should have passed!");
+    });
+  };
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true],
+    ["dom.webnotifications.workers.enabled", true],
+    ["notification.prompt.testing", true],
+    ["dom.disable_open_click_delay", 1000],
+  ]}, runTest);
+</script>
+</body>
+</html>
--- a/dom/workers/test/serviceworkers/test_opaque_intercept.html
+++ b/dom/workers/test/serviceworkers/test_opaque_intercept.html
@@ -72,15 +72,16 @@
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
+    ['dom.serviceWorkers.interception.enabled', true],
     ["dom.serviceWorkers.interception.opaque.enabled", true],
     ["dom.caches.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_origin_after_redirect.html
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html
@@ -43,15 +43,16 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
+      ['dom.serviceWorkers.interception.enabled', true],
       ["dom.caches.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -169,23 +169,23 @@ var interfaceNamesInGlobalScope =
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushEvent", b2g: false, android: false, release: false },
+    { name: "PushEvent", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushManager", b2g: false, android: false, release: false },
+    { name: "PushManager", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushMessageData", b2g: false, android: false, release: false },
+    { name: "PushMessageData", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushSubscription", b2g: false, android: false, release: false },
+    { name: "PushSubscription", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorkerGlobalScope",
--- a/dom/workers/test/serviceworkers/test_third_party_iframes.html
+++ b/dom/workers/test/serviceworkers/test_third_party_iframes.html
@@ -138,16 +138,17 @@ const COOKIE_BEHAVIOR_REJECTFOREIGN = 1;
 const COOKIE_BEHAVIOR_REJECT        = 2;
 const COOKIE_BEHAVIOR_LIMITFOREIGN  = 3;
 
 let steps = [() => {
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["browser.dom.window.dump.enabled", true],
     ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT]
   ]}, next);
 }, () => {
   testShouldIntercept(next);
 }, () => {
   SpecialPowers.pushPrefEnv({"set": [
     ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN]
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -161,25 +161,25 @@ var interfaceNamesInGlobalScope =
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushManager", b2g: false, android: false, release: false },
+    { name: "PushManager", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "PushSubscription", b2g: false, android: false, release: false },
+    { name: "PushSubscription", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "ServiceWorkerRegistration", release: false, b2g: false },
+    { name: "ServiceWorkerRegistration", b2g: false, android: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TextDecoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TextEncoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "XMLHttpRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "XMLHttpRequestEventTarget",
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -343,17 +343,17 @@ XMLDocument::Load(const nsAString& aUrl,
   if (!nsContentUtils::IsSystemPrincipal(principal)) {
     rv = principal->CheckMayLoad(uri, false, false);
     if (NS_FAILED(rv)) {
       aRv.Throw(rv);
       return false;
     }
 
     int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                                    uri,
                                    principal,
                                    callingDoc ? callingDoc.get() :
                                      static_cast<nsIDocument*>(this),
                                    NS_LITERAL_CSTRING("application/xml"),
                                    nullptr,
                                    &shouldLoad,
                                    nsContentUtils::GetContentPolicy(),
@@ -440,17 +440,17 @@ XMLDocument::Load(const nsAString& aUrl,
   nsCOMPtr<nsIChannel> channel;
   // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active,
   // which in turn keeps STOP button from becoming active  
   rv = NS_NewChannel(getter_AddRefs(channel),
                      uri,
                      callingDoc ? callingDoc.get() :
                                   static_cast<nsIDocument*>(this),
                      nsILoadInfo::SEC_NORMAL,
-                     nsIContentPolicy::TYPE_XMLHTTPREQUEST,
+                     nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                      loadGroup,
                      req,
                      nsIRequest::LOAD_BACKGROUND);
 
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
--- a/extensions/permissions/nsContentBlocker.cpp
+++ b/extensions/permissions/nsContentBlocker.cpp
@@ -50,17 +50,19 @@ static const char *kTypeString[] = {
                                     "", // TYPE_INTERNAL_WORKER
                                     "", // TYPE_INTERNAL_SHARED_WORKER
                                     "", // TYPE_INTERNAL_EMBED
                                     "", // TYPE_INTERNAL_OBJECT
                                     "", // TYPE_INTERNAL_FRAME
                                     "", // TYPE_INTERNAL_IFRAME
                                     "", // TYPE_INTERNAL_AUDIO
                                     "", // TYPE_INTERNAL_VIDEO
-                                    ""  // TYPE_INTERNAL_TRACK
+                                    "", // TYPE_INTERNAL_TRACK
+                                    "", // TYPE_INTERNAL_XMLHTTPREQUEST
+                                    ""  // TYPE_INTERNAL_EVENTSOURCE
 };
 
 #define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString)
 uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES];
 
 NS_IMPL_ISUPPORTS(nsContentBlocker, 
                   nsIContentPolicy,
                   nsIObserver,
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-54837
+54838
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -49506,16 +49506,17 @@ substation/MS
 substituent/MS
 substitute/XMGNDS
 substitution/M
 substrata
 substrate/MS
 substratum/M
 substructure/SM
 subsume/DSG
+subsumption/S
 subsurface/M
 subsystem/SM
 subteen/SM
 subtenancy/M
 subtenant/SM
 subtend/SDG
 subterfuge/SM
 subterranean
--- a/gfx/cairo/libpixman/src/moz.build
+++ b/gfx/cairo/libpixman/src/moz.build
@@ -74,28 +74,22 @@ DEFINES['PACKAGE'] = 'mozpixman'
 DEFINES['_USE_MATH_DEFINES'] = True
 
 use_mmx = False
 use_sse2 = False
 use_vmx = False
 use_arm_simd_gcc = False
 use_arm_neon_gcc = False
 if '86' in CONFIG['OS_TEST']:
-    if '64' in CONFIG['OS_TEST']:
-        if CONFIG['GNU_CC']:
-            use_sse2 = True
-    else:
+    use_sse2 = True
+    if '64' not in CONFIG['OS_TEST']:
         if CONFIG['_MSC_VER']:
             use_mmx = True
-        if CONFIG['HAVE_GCC_ALIGN_ARG_POINTER']:
-            use_sse2 = True
     if CONFIG['GNU_CC']:
         use_mmx = True
-    if CONFIG['_MSC_VER']:
-        use_sse2 = True
 elif 'ppc' in CONFIG['OS_TEST']:
     if CONFIG['GNU_CC']:
         use_vmx = True
 # Apple's arm assembler doesn't support the same syntax as
 # the standard GNU assembler, so use the C fallback paths for now.
 # This may be fixable if clang's ARM/iOS assembler improves into a
 # viable solution in the future.
 elif 'arm' in CONFIG['OS_TEST']:
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -54,16 +54,17 @@ GLScreenBuffer::Create(GLContext* gl,
 GLScreenBuffer::GLScreenBuffer(GLContext* gl,
                                const SurfaceCaps& caps,
                                UniquePtr<SurfaceFactory> factory)
     : mGL(gl)
     , mCaps(caps)
     , mFactory(Move(factory))
     , mNeedsBlit(true)
     , mUserReadBufferMode(LOCAL_GL_BACK)
+    , mUserDrawBufferMode(LOCAL_GL_BACK)
     , mUserDrawFB(0)
     , mUserReadFB(0)
     , mInternalDrawFB(0)
     , mInternalReadFB(0)
 #ifdef DEBUG
     , mInInternalMode_DrawFB(true)
     , mInInternalMode_ReadFB(true)
 #endif
@@ -440,16 +441,22 @@ GLScreenBuffer::Attach(SharedSurface* su
     MOZ_ASSERT(SharedSurf() == surf);
 
     // Update the ReadBuffer mode.
     if (mGL->IsSupported(gl::GLFeature::read_buffer)) {
         BindFB(0);
         mRead->SetReadBuffer(mUserReadBufferMode);
     }
 
+    // Update the DrawBuffer mode.
+    if (mGL->IsSupported(gl::GLFeature::draw_buffers)) {
+        BindFB(0);
+        SetDrawBuffer(mUserDrawBufferMode);
+    }
+
     RequireBlit();
 
     return true;
 }
 
 bool
 GLScreenBuffer::Swap(const gfx::IntSize& size)
 {
@@ -544,16 +551,48 @@ GLScreenBuffer::CreateRead(SharedSurface
     GLContext* gl = mFactory->mGL;
     const GLFormats& formats = mFactory->mFormats;
     const SurfaceCaps& caps = mFactory->ReadCaps();
 
     return ReadBuffer::Create(gl, caps, formats, surf);
 }
 
 void
+GLScreenBuffer::SetDrawBuffer(GLenum mode)
+{
+    MOZ_ASSERT(mGL->IsSupported(gl::GLFeature::draw_buffers));
+    MOZ_ASSERT(GetDrawFB() == 0);
+
+    if (!mGL->IsSupported(GLFeature::draw_buffers))
+        return;
+
+    mUserDrawBufferMode = mode;
+
+    GLuint fb = mDraw ? mDraw->mFB : mRead->mFB;
+    GLenum internalMode;
+
+    switch (mode) {
+    case LOCAL_GL_BACK:
+        internalMode = (fb == 0) ? LOCAL_GL_BACK
+                                 : LOCAL_GL_COLOR_ATTACHMENT0;
+        break;
+
+    case LOCAL_GL_NONE:
+        internalMode = LOCAL_GL_NONE;
+        break;
+
+    default:
+        MOZ_CRASH("Bad value.");
+    }
+
+    mGL->MakeCurrent();
+    mGL->fDrawBuffers(1, &internalMode);
+}
+
+void
 GLScreenBuffer::SetReadBuffer(GLenum mode)
 {
     MOZ_ASSERT(mGL->IsSupported(gl::GLFeature::read_buffer));
     MOZ_ASSERT(GetReadFB() == 0);
 
     mUserReadBufferMode = mode;
     mRead->SetReadBuffer(mUserReadBufferMode);
 }
--- a/gfx/gl/GLScreenBuffer.h
+++ b/gfx/gl/GLScreenBuffer.h
@@ -144,16 +144,17 @@ protected:
     RefPtr<layers::SharedSurfaceTextureClient> mFront;
 
     UniquePtr<DrawBuffer> mDraw;
     UniquePtr<ReadBuffer> mRead;
 
     bool mNeedsBlit;
 
     GLenum mUserReadBufferMode;
+    GLenum mUserDrawBufferMode;
 
     // Below are the parts that help us pretend to be framebuffer 0:
     GLuint mUserDrawFB;
     GLuint mUserReadFB;
     GLuint mInternalDrawFB;
     GLuint mInternalReadFB;
 
 #ifdef DEBUG
@@ -217,16 +218,17 @@ public:
     void AssureBlitted();
     void AfterDrawCall();
     void BeforeReadCall();
 
     bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x,
                         GLint y, GLsizei width, GLsizei height, GLint border);
 
     void SetReadBuffer(GLenum userMode);
+    void SetDrawBuffer(GLenum userMode);
 
     /**
      * Attempts to read pixels from the current bound framebuffer, if
      * it is backed by a SharedSurface.
      *
      * Returns true if the pixel data has been read back, false
      * otherwise.
      */
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -618,27 +618,27 @@ CairoImage::GetTextureClient(Compositabl
 // TextureHost is not used by CompositableHost.
 #ifdef MOZ_WIDGET_GONK
   RefPtr<TextureClientRecycleAllocator> recycler =
     aClient->GetTextureClientRecycler();
   if (recycler) {
     textureClient =
       recycler->CreateOrRecycleForDrawing(surface->GetFormat(),
                                           surface->GetSize(),
-                                          gfx::BackendType::NONE,
+                                          BackendSelector::Content,
                                           aClient->GetTextureFlags());
   }
 #endif
 
 #endif
   if (!textureClient) {
     // gfx::BackendType::NONE means default to content backend
     textureClient = aClient->CreateTextureClientForDrawing(surface->GetFormat(),
                                                            surface->GetSize(),
-                                                           gfx::BackendType::NONE,
+                                                           BackendSelector::Content,
                                                            TextureFlags::DEFAULT);
   }
   if (!textureClient) {
     return nullptr;
   }
 
   if (!textureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) {
     return nullptr;
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -121,24 +121,23 @@ CanvasClient2D::CreateTextureClientForCa
     // We want a cairo backend here as we don't want to be copying into
     // an accelerated backend and we like LockBits to work. This is currently
     // the most effective way to make this work.
     return TextureClient::CreateForRawBufferAccess(GetForwarder(),
                                                    aFormat, aSize, BackendType::CAIRO,
                                                    mTextureFlags | aFlags);
   }
 
-  gfx::BackendType backend = gfxPlatform::GetPlatform()->GetPreferredCanvasBackend();
 #ifdef XP_WIN
-  return CreateTextureClientForDrawing(aFormat, aSize, backend, aFlags);
+  return CreateTextureClientForDrawing(aFormat, aSize, BackendSelector::Canvas, aFlags);
 #else
   // XXX - We should use CreateTextureClientForDrawing, but we first need
   // to use double buffering.
   return TextureClient::CreateForRawBufferAccess(GetForwarder(),
-                                                 aFormat, aSize, backend,
+                                                 aFormat, aSize, gfx::BackendType::NONE,
                                                  mTextureFlags | aFlags);
 #endif
 }
 
 ////////////////////////////////////////////////////////////////////////
 
 CanvasClientSharedSurface::CanvasClientSharedSurface(CompositableForwarder* aLayerForwarder,
                                                      TextureFlags aFlags)
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -202,22 +202,22 @@ CompositableClient::CreateBufferTextureC
   return TextureClient::CreateForRawBufferAccess(GetForwarder(),
                                                  aFormat, aSize, aMoz2DBackend,
                                                  aTextureFlags | mTextureFlags);
 }
 
 already_AddRefed<TextureClient>
 CompositableClient::CreateTextureClientForDrawing(gfx::SurfaceFormat aFormat,
                                                   gfx::IntSize aSize,
-                                                  gfx::BackendType aMoz2DBackend,
+                                                  BackendSelector aSelector,
                                                   TextureFlags aTextureFlags,
                                                   TextureAllocationFlags aAllocFlags)
 {
   return TextureClient::CreateForDrawing(GetForwarder(),
-                                         aFormat, aSize, aMoz2DBackend,
+                                         aFormat, aSize, aSelector,
                                          aTextureFlags | mTextureFlags,
                                          aAllocFlags);
 }
 
 bool
 CompositableClient::AddTextureClient(TextureClient* aClient)
 {
   if(!aClient || !aClient->IsAllocated()) {
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -135,17 +135,17 @@ public:
   CreateBufferTextureClient(gfx::SurfaceFormat aFormat,
                             gfx::IntSize aSize,
                             gfx::BackendType aMoz2dBackend = gfx::BackendType::NONE,
                             TextureFlags aFlags = TextureFlags::DEFAULT);
 
   already_AddRefed<TextureClient>
   CreateTextureClientForDrawing(gfx::SurfaceFormat aFormat,
                                 gfx::IntSize aSize,
-                                gfx::BackendType aMoz2DBackend,
+                                BackendSelector aSelector,
                                 TextureFlags aTextureFlags,
                                 TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT);
 
   /**
    * Establishes the connection with compositor side through IPDL
    */
   virtual bool Connect(ImageContainer* aImageContainer = nullptr);
 
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -288,17 +288,17 @@ ContentClientRemoteBuffer::BuildTextureC
   CreateBackBuffer(mBufferRect);
 }
 
 void
 ContentClientRemoteBuffer::CreateBackBuffer(const IntRect& aBufferRect)
 {
   // gfx::BackendType::NONE means fallback to the content backend
   mTextureClient = CreateTextureClientForDrawing(
-    mSurfaceFormat, mSize, gfx::BackendType::NONE,
+    mSurfaceFormat, mSize, BackendSelector::Content,
     mTextureFlags | ExtraTextureFlags(),
     TextureAllocationFlags::ALLOC_CLEAR_BUFFER
   );
   if (!mTextureClient || !AddTextureClient(mTextureClient)) {
     AbortTextureClientCreation();
     return;
   }
 
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -211,17 +211,17 @@ ImageClientSingle::UpdateImage(ImageCont
 #endif
         } else {
           MOZ_ASSERT(false, "Bad ImageFormat.");
         }
       } else {
         RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
         MOZ_ASSERT(surface);
         texture = CreateTextureClientForDrawing(surface->GetFormat(), image->GetSize(),
-                                                gfx::BackendType::NONE, mTextureFlags);
+                                                BackendSelector::Content, mTextureFlags);
         if (!texture) {
           return false;
         }
 
         MOZ_ASSERT(texture->CanExposeDrawTarget());
 
         if (!texture->Lock(OpenMode::OPEN_WRITE_ONLY)) {
           return false;
--- a/gfx/layers/client/SingleTiledContentClient.cpp
+++ b/gfx/layers/client/SingleTiledContentClient.cpp
@@ -91,17 +91,17 @@ ClientSingleTiledLayerBuffer::GetSurface
                                 mFrameResolution.xScale,
                                 mFrameResolution.yScale);
 }
 
 already_AddRefed<TextureClient>
 ClientSingleTiledLayerBuffer::GetTextureClient()
 {
   return mCompositableClient->CreateTextureClientForDrawing(
-    gfx::ImageFormatToSurfaceFormat(mFormat), mSize, gfx::BackendType::NONE,
+    gfx::ImageFormatToSurfaceFormat(mFormat), mSize, BackendSelector::Content,
     TextureFlags::IMMEDIATE_UPLOAD);
 }
 
 void
 ClientSingleTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion,
                                           const nsIntRegion& aPaintRegion,
                                           const nsIntRegion& aDirtyRegion,
                                           LayerManager::DrawPaintedLayerCallback aCallback,
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -315,75 +315,87 @@ CreateBufferTextureClient(ISurfaceAlloca
     return result.forget();
   }
   RefPtr<BufferTextureClient> result = new ShmemTextureClient(aAllocator, aFormat,
                                                               aMoz2DBackend,
                                                               aTextureFlags);
   return result.forget();
 }
 
+static inline gfx::BackendType
+BackendTypeForBackendSelector(BackendSelector aSelector)
+{
+  switch (aSelector) {
+    case BackendSelector::Canvas:
+      return gfxPlatform::GetPlatform()->GetPreferredCanvasBackend();
+    case BackendSelector::Content:
+      return gfxPlatform::GetPlatform()->GetContentBackend();
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown backend selector");
+      return gfx::BackendType::NONE;
+  }
+};
+
 // static
 already_AddRefed<TextureClient>
 TextureClient::CreateForDrawing(ISurfaceAllocator* aAllocator,
                                 gfx::SurfaceFormat aFormat,
                                 gfx::IntSize aSize,
-                                gfx::BackendType aMoz2DBackend,
+                                BackendSelector aSelector,
                                 TextureFlags aTextureFlags,
                                 TextureAllocationFlags aAllocFlags)
 {
-  if (aMoz2DBackend == gfx::BackendType::NONE) {
-    aMoz2DBackend = gfxPlatform::GetPlatform()->GetContentBackend();
-  }
+  gfx::BackendType moz2DBackend = BackendTypeForBackendSelector(aSelector);
 
   RefPtr<TextureClient> texture;
 
 #if defined(MOZ_WIDGET_GONK) || defined(XP_WIN)
   int32_t maxTextureSize = aAllocator->GetMaxTextureSize();
 #endif
 
 #ifdef XP_WIN
   LayersBackend parentBackend = aAllocator->GetCompositorBackendType();
   if (parentBackend == LayersBackend::LAYERS_D3D11 &&
-      ((aMoz2DBackend == gfx::BackendType::DIRECT2D && Factory::GetDirect3D10Device()) ||
-       (aMoz2DBackend == gfx::BackendType::DIRECT2D1_1 && Factory::GetDirect3D11Device())) &&
+      (moz2DBackend == gfx::BackendType::DIRECT2D ||
+       moz2DBackend == gfx::BackendType::DIRECT2D1_1) &&
       aSize.width <= maxTextureSize &&
       aSize.height <= maxTextureSize)
   {
     texture = new TextureClientD3D11(aAllocator, aFormat, aTextureFlags);
   }
   if (parentBackend == LayersBackend::LAYERS_D3D9 &&
-      aMoz2DBackend == gfx::BackendType::CAIRO &&
+      moz2DBackend == gfx::BackendType::CAIRO &&
       aAllocator->IsSameProcess() &&
       aSize.width <= maxTextureSize &&
       aSize.height <= maxTextureSize &&
       NS_IsMainThread()) {
     if (gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) {
       texture = new TextureClientD3D9(aAllocator, aFormat, aTextureFlags);
     }
   }
 
   if (!texture && aFormat == SurfaceFormat::B8G8R8X8 &&
       aAllocator->IsSameProcess() &&
-      aMoz2DBackend == gfx::BackendType::CAIRO &&
+      moz2DBackend == gfx::BackendType::CAIRO &&
       NS_IsMainThread()) {
     if (aAllocator->IsSameProcess()) {
       texture = new TextureClientMemoryDIB(aAllocator, aFormat, aTextureFlags);
     } else {
       texture = new TextureClientShmemDIB(aAllocator, aFormat, aTextureFlags);
     }
   }
 #endif
 
 #ifdef MOZ_X11
   LayersBackend parentBackend = aAllocator->GetCompositorBackendType();
   gfxSurfaceType type =
     gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType();
 
   if (parentBackend == LayersBackend::LAYERS_BASIC &&
-      aMoz2DBackend == gfx::BackendType::CAIRO &&
+      moz2DBackend == gfx::BackendType::CAIRO &&
       type == gfxSurfaceType::Xlib)
   {
     texture = new TextureClientX11(aAllocator, aFormat, aTextureFlags);
   }
 #ifdef GL_PROVIDER_GLX
   if (parentBackend == LayersBackend::LAYERS_OPENGL &&
       type == gfxSurfaceType::Xlib &&
       aFormat != SurfaceFormat::A8 &&
@@ -394,17 +406,17 @@ TextureClient::CreateForDrawing(ISurface
 #endif
 #endif
 
 #ifdef MOZ_WIDGET_GONK
   if (!DisableGralloc(aFormat, aSize)) {
     // Don't allow Gralloc texture clients to exceed the maximum texture size.
     // BufferTextureClients have code to handle tiling the surface client-side.
     if (aSize.width <= maxTextureSize && aSize.height <= maxTextureSize) {
-      texture = new GrallocTextureClientOGL(aAllocator, aFormat, aMoz2DBackend,
+      texture = new GrallocTextureClientOGL(aAllocator, aFormat, moz2DBackend,
                                            aTextureFlags);
     }
   }
 #endif
 
   MOZ_ASSERT(!texture || texture->CanExposeDrawTarget(), "texture cannot expose a DrawTarget?");
 
   if (texture && texture->AllocateForSurface(aSize, aAllocFlags)) {
@@ -415,17 +427,17 @@ TextureClient::CreateForDrawing(ISurface
     return nullptr;
   }
 
   if (texture) {
     NS_WARNING("Failed to allocate a TextureClient, falling back to BufferTextureClient.");
   }
 
   // Can't do any better than a buffer texture client.
-  texture = CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, aMoz2DBackend);
+  texture = CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, moz2DBackend);
 
   if (!texture->AllocateForSurface(aSize, aAllocFlags)) {
     return nullptr;
   }
 
   return texture.forget();
 }
 
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -133,16 +133,22 @@ public:
    * DataSourceSurface beyond the execution of this function.
    */
   virtual void ProcessReadback(gfx::DataSourceSurface *aSourceSurface) = 0;
 
 protected:
   virtual ~TextureReadbackSink() {}
 };
 
+enum class BackendSelector
+{
+  Content,
+  Canvas
+};
+
 /**
  * TextureClient is a thin abstraction over texture data that need to be shared
  * between the content process and the compositor process. It is the
  * content-side half of a TextureClient/TextureHost pair. A corresponding
  * TextureHost lives on the compositor-side.
  *
  * TextureClient's primary purpose is to present texture data in a way that is
  * understood by the IPC system. There are two ways to use it:
@@ -169,17 +175,17 @@ public:
                          TextureFlags aFlags = TextureFlags::DEFAULT);
   virtual ~TextureClient();
 
   // Creates and allocates a TextureClient usable with Moz2D.
   static already_AddRefed<TextureClient>
   CreateForDrawing(ISurfaceAllocator* aAllocator,
                    gfx::SurfaceFormat aFormat,
                    gfx::IntSize aSize,
-                   gfx::BackendType aMoz2dBackend,
+                   BackendSelector aSelector,
                    TextureFlags aTextureFlags,
                    TextureAllocationFlags flags = ALLOC_DEFAULT);
 
   // Creates and allocates a BufferTextureClient supporting the YCbCr format.
   static already_AddRefed<BufferTextureClient>
   CreateForYCbCr(ISurfaceAllocator* aAllocator,
                  gfx::IntSize aYSize,
                  gfx::IntSize aCbCrSize,
--- a/gfx/layers/client/TextureClientPool.cpp
+++ b/gfx/layers/client/TextureClientPool.cpp
@@ -106,17 +106,17 @@ TextureClientPool::GetTextureClient()
   // No unused clients in the pool, create one
   if (gfxPrefs::ForceShmemTiles()) {
     // gfx::BackendType::NONE means use the content backend
     textureClient = TextureClient::CreateForRawBufferAccess(mSurfaceAllocator,
       mFormat, mSize, gfx::BackendType::NONE,
       TextureFlags::IMMEDIATE_UPLOAD, ALLOC_DEFAULT);
   } else {
     textureClient = TextureClient::CreateForDrawing(mSurfaceAllocator,
-      mFormat, mSize, gfx::BackendType::NONE, TextureFlags::IMMEDIATE_UPLOAD);
+      mFormat, mSize, BackendSelector::Content, TextureFlags::IMMEDIATE_UPLOAD);
   }
 
   mOutstandingClients++;
 #ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
   if (textureClient) {
     textureClient->mPoolTracker = this;
   }
 #endif
--- a/gfx/layers/client/TextureClientRecycleAllocator.cpp
+++ b/gfx/layers/client/TextureClientRecycleAllocator.cpp
@@ -29,17 +29,17 @@ public:
       mMaxPooledSize = aMax;
     }
   }
 
   // Creates and allocates a TextureClient.
   already_AddRefed<TextureClient>
   CreateOrRecycleForDrawing(gfx::SurfaceFormat aFormat,
                             gfx::IntSize aSize,
-                            gfx::BackendType aMoz2dBackend,
+                            BackendSelector aSelector,
                             TextureFlags aTextureFlags,
                             TextureAllocationFlags flags);
 
   void Destroy();
 
   void RecycleCallbackImp(TextureClient* aClient);
 
   static void RecycleCallback(TextureClient* aClient, void* aClosure);
@@ -136,33 +136,29 @@ TextureClientRecycleAllocatorImp::~Textu
   MOZ_ASSERT(mPooledClients.empty());
   MOZ_ASSERT(mInUseClients.empty());
 }
 
 already_AddRefed<TextureClient>
 TextureClientRecycleAllocatorImp::CreateOrRecycleForDrawing(
                                              gfx::SurfaceFormat aFormat,
                                              gfx::IntSize aSize,
-                                             gfx::BackendType aMoz2DBackend,
+                                             BackendSelector aSelector,
                                              TextureFlags aTextureFlags,
                                              TextureAllocationFlags aAllocFlags)
 {
   // TextureAllocationFlags is actually used only by ContentClient.
   // This class does not handle ConteClient's TextureClient allocation.
   MOZ_ASSERT(aAllocFlags == TextureAllocationFlags::ALLOC_DEFAULT ||
              aAllocFlags == TextureAllocationFlags::ALLOC_DISALLOW_BUFFERTEXTURECLIENT);
   MOZ_ASSERT(!(aTextureFlags & TextureFlags::RECYCLE));
   aTextureFlags = aTextureFlags | TextureFlags::RECYCLE; // Set recycle flag
 
   RefPtr<TextureClientHolder> textureHolder;
 
-  if (aMoz2DBackend == gfx::BackendType::NONE) {
-    aMoz2DBackend = gfxPlatform::GetPlatform()->GetContentBackend();
-  }
-
   {
     MutexAutoLock lock(mLock);
     if (mDestroyed) {
       return nullptr;
     } else if (!mPooledClients.empty()) {
       textureHolder = mPooledClients.top();
       mPooledClients.pop();
       // If a pooled TextureClient is not compatible, release it.
@@ -178,17 +174,17 @@ TextureClientRecycleAllocatorImp::Create
         textureHolder->GetTextureClient()->RecycleTexture(aTextureFlags);
       }
     }
   }
 
   if (!textureHolder) {
     // Allocate new TextureClient
     RefPtr<TextureClient> texture;
-    texture = TextureClient::CreateForDrawing(this, aFormat, aSize, aMoz2DBackend,
+    texture = TextureClient::CreateForDrawing(this, aFormat, aSize, aSelector,
                                               aTextureFlags, aAllocFlags);
     if (!texture) {
       return nullptr;
     }
     textureHolder = new TextureClientHolder(texture);
   }
 
   {
@@ -256,21 +252,21 @@ TextureClientRecycleAllocator::SetMaxPoo
 {
   mAllocator->SetMaxPoolSize(aMax);
 }
 
 already_AddRefed<TextureClient>
 TextureClientRecycleAllocator::CreateOrRecycleForDrawing(
                                             gfx::SurfaceFormat aFormat,
                                             gfx::IntSize aSize,
-                                            gfx::BackendType aMoz2DBackend,
+                                            BackendSelector aSelector,
                                             TextureFlags aTextureFlags,
                                             TextureAllocationFlags aAllocFlags)
 {
   return mAllocator->CreateOrRecycleForDrawing(aFormat,
                                                aSize,
-                                               aMoz2DBackend,
+                                               aSelector,
                                                aTextureFlags,
                                                aAllocFlags);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/TextureClientRecycleAllocator.h
+++ b/gfx/layers/client/TextureClientRecycleAllocator.h
@@ -33,17 +33,17 @@ public:
   explicit TextureClientRecycleAllocator(ISurfaceAllocator* aAllocator);
 
   void SetMaxPoolSize(uint32_t aMax);
 
   // Creates and allocates a TextureClient.
   already_AddRefed<TextureClient>
   CreateOrRecycleForDrawing(gfx::SurfaceFormat aFormat,
                             gfx::IntSize aSize,
-                            gfx::BackendType aMoz2dBackend,
+                            BackendSelector aSelector,
                             TextureFlags aTextureFlags,
                             TextureAllocationFlags flags = ALLOC_DEFAULT);
 
 private:
   RefPtr<TextureClientRecycleAllocatorImp> mAllocator;
 };
 
 } // namespace layers
--- a/gfx/skia/generate_mozbuild.py
+++ b/gfx/skia/generate_mozbuild.py
@@ -135,17 +135,19 @@ elif CONFIG['CLANG_CL']:
 DEFINES['SKIA_IMPLEMENTATION'] = 1
 DEFINES['GR_IMPLEMENTATION'] = 1
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-overloaded-virtual',
         '-Wno-unused-function',
     ]
-    if not CONFIG['CLANG_CXX']:
+    if CONFIG['CLANG_CXX']:
+        CXXFLAGS += ['-Wno-inconsistent-missing-override']
+    else:
         CXXFLAGS += ['-Wno-logical-op']
     if CONFIG['CPU_ARCH'] == 'arm':
         SOURCES['skia/src/opts/SkBlitRow_opts_arm.cpp'].flags += ['-fomit-frame-pointer']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'android', 'gonk', 'qt'):
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
     CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
 
--- a/gfx/skia/moz.build
+++ b/gfx/skia/moz.build
@@ -663,17 +663,19 @@ elif CONFIG['CLANG_CL']:
 DEFINES['SKIA_IMPLEMENTATION'] = 1
 DEFINES['GR_IMPLEMENTATION'] = 1
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-overloaded-virtual',
         '-Wno-unused-function',
     ]
-    if not CONFIG['CLANG_CXX']:
+    if CONFIG['CLANG_CXX']:
+        CXXFLAGS += ['-Wno-inconsistent-missing-override']
+    else:
         CXXFLAGS += ['-Wno-logical-op']
     if CONFIG['CPU_ARCH'] == 'arm':
         SOURCES['skia/src/opts/SkBlitRow_opts_arm.cpp'].flags += ['-fomit-frame-pointer']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'android', 'gonk', 'qt'):
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
     CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
 
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -94,16 +94,17 @@ using JS::CallArgs;
 using JS::CallNonGenericMethod;
 using JS::CallReceiver;
 using JS::CompileOptions;
 using JS::IsAcceptableThis;
 using JS::NativeImpl;
 using JS::OwningCompileOptions;
 using JS::ReadOnlyCompileOptions;
 using JS::SourceBufferHolder;
+using JS::TransitiveCompileOptions;
 
 using JS::Rooted;
 using JS::RootedFunction;
 using JS::RootedId;
 using JS::RootedObject;
 using JS::RootedScript;
 using JS::RootedString;
 using JS::RootedSymbol;
--- a/js/src/aclocal.m4
+++ b/js/src/aclocal.m4
@@ -13,17 +13,16 @@ builtin(include, ../../build/autoconf/pk
 builtin(include, ../../build/autoconf/nspr.m4)dnl
 builtin(include, ../../build/autoconf/nspr-build.m4)dnl
 builtin(include, ../../build/autoconf/codeset.m4)dnl
 builtin(include, ../../build/autoconf/altoptions.m4)dnl
 builtin(include, ../../build/autoconf/mozprog.m4)dnl
 builtin(include, ../../build/autoconf/mozheader.m4)dnl
 builtin(include, ../../build/autoconf/mozcommonheader.m4)dnl
 builtin(include, ../../build/autoconf/lto.m4)dnl
-builtin(include, ../../build/autoconf/gcc-pr49911.m4)dnl
 builtin(include, ../../build/autoconf/llvm-pr8927.m4)dnl
 builtin(include, ../../build/autoconf/frameptr.m4)dnl
 builtin(include, ../../build/autoconf/compiler-opts.m4)dnl
 builtin(include, ../../build/autoconf/expandlibs.m4)dnl
 builtin(include, ../../build/autoconf/arch.m4)dnl
 builtin(include, ../../build/autoconf/android.m4)dnl
 builtin(include, ../../build/autoconf/zlib.m4)dnl
 builtin(include, ../../build/autoconf/linux.m4)dnl
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -2153,17 +2153,16 @@ WINNT|Darwin|Android)
   STL_FLAGS='-I$(DIST)/stl_wrappers'
   WRAP_STL_INCLUDES=1
   ;;
 esac
 
 AC_SUBST(WRAP_SYSTEM_INCLUDES)
 AC_SUBST(VISIBILITY_FLAGS)
 
-MOZ_GCC_PR49911
 MOZ_LLVM_PR8927
 
 dnl Checks for header files.
 dnl ========================================================
 AC_HEADER_DIRENT
 case "$target_os" in
 freebsd*)
 # for stuff like -lXshm
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -5762,23 +5762,23 @@ BytecodeEmitter::emitFunction(ParseNode*
                 fun->lazyScript()->setTreatAsRunOnce();
         } else {
             SharedContext* outersc = sc;
 
             if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals())
                 funbox->setMightAliasLocals();      // inherit mightAliasLocals from parent
             MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
 
-            // Inherit most things (principals, version, etc) from the parent.
+            // Inherit most things (principals, version, etc) from the
+            // parent.  Use default values for the rest.
             Rooted<JSScript*> parent(cx, script);
-            CompileOptions options(cx, parser->options());
-            options.setMutedErrors(parent->mutedErrors())
-                   .setNoScriptRval(false)
-                   .setForEval(false)
-                   .setVersion(parent->getVersion());
+            MOZ_ASSERT(parent->getVersion() == parser->options().version);
+            MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors());
+            const TransitiveCompileOptions& transitiveOptions = parser->options();
+            CompileOptions options(cx, transitiveOptions);
 
             Rooted<JSObject*> enclosingScope(cx, enclosingStaticScope());
             Rooted<JSObject*> sourceObject(cx, script->sourceObject());
             Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
                                                           parent->staticLevel() + 1,
                                                           sourceObject,
                                                           funbox->bufStart, funbox->bufEnd));
             if (!script)
--- a/js/src/jit-test/lib/class.js
+++ b/js/src/jit-test/lib/class.js
@@ -1,1 +1,1 @@
-load(libdir + "../../tests/ecma_6/Class/shell.js");
+load(libdir + "../../tests/ecma_6/shell.js");
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -36,62 +36,16 @@ using JS::GenericNaN;
 using JS::ToInt32;
 
 // shared
 CodeGeneratorARM::CodeGeneratorARM(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorShared(gen, graph, masm)
 {
 }
 
-bool
-CodeGeneratorARM::generatePrologue()
-{
-    MOZ_ASSERT(masm.framePushed() == 0);
-    MOZ_ASSERT(!gen->compilingAsmJS());
-#ifdef JS_USE_LINK_REGISTER
-    masm.pushReturnAddress();
-#endif
-
-    // If profiling, save the current frame pointer to a per-thread global field.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerEnterFrame(StackPointer, CallTempReg0);
-
-    // Ensure that the Ion frames is properly aligned.
-    masm.assertStackAlignment(JitStackAlignment, 0);
-
-    // Note that this automatically sets MacroAssembler::framePushed().
-    masm.reserveStack(frameSize());
-    masm.checkStackAlignment();
-
-    emitTracelogIonStart();
-
-    return true;
-}
-
-bool
-CodeGeneratorARM::generateEpilogue()
-{
-    MOZ_ASSERT(!gen->compilingAsmJS());
-    masm.bind(&returnLabel_);
-
-    emitTracelogIonStop();
-
-    masm.freeStack(frameSize());
-    MOZ_ASSERT(masm.framePushed() == 0);
-
-    // If profiling, reset the per-thread global lastJitFrame to point to
-    // the previous frame.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerExitFrame();
-
-    masm.pop(pc);
-    masm.flushBuffer();
-    return true;
-}
-
 void
 CodeGeneratorARM::emitBranch(Assembler::Condition cond, MBasicBlock* mirTrue, MBasicBlock* mirFalse)
 {
     if (isNextBlock(mirFalse->lir())) {
         jumpToBlock(mirTrue, cond);
     } else {
         jumpToBlock(mirFalse, Assembler::InvertCondition(cond));
         jumpToBlock(mirTrue);
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -18,18 +18,16 @@ class OutOfLineTableSwitch;
 
 class CodeGeneratorARM : public CodeGeneratorShared
 {
     friend class MoveResolverARM;
 
     CodeGeneratorARM* thisFromCtor() {return this;}
 
   protected:
-    // Label for the common return path.
-    NonAssertingLabel returnLabel_;
     NonAssertingLabel deoptLabel_;
 
     MoveOperand toMoveOperand(LAllocation a) const;
 
     void bailoutIf(Assembler::Condition condition, LSnapshot* snapshot);
     void bailoutFrom(Label* label, LSnapshot* snapshot);
     void bailout(LSnapshot* snapshot);
 
@@ -57,18 +55,16 @@ class CodeGeneratorARM : public CodeGene
         bailoutIf(Assembler::Zero, snapshot);
     }
 
     template<class T>
     void generateUDivModZeroCheck(Register rhs, Register output, Label* done, LSnapshot* snapshot,
                                   T* mir);
 
   protected:
-    bool generatePrologue();
-    bool generateEpilogue();
     bool generateOutOfLineCode();
 
     void emitRoundDouble(FloatRegister src, Register dest, Label* fail);
 
     // Emits a branch that directs control flow to the true block if |cond| is
     // true, and the false block if |cond| is false.
     void emitBranch(Assembler::Condition cond, MBasicBlock* ifTrue, MBasicBlock* ifFalse);
 
--- a/js/src/jit/arm/LIR-arm.h
+++ b/js/src/jit/arm/LIR-arm.h
@@ -5,37 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_arm_LIR_arm_h
 #define jit_arm_LIR_arm_h
 
 namespace js {
 namespace jit {
 
-class LBox : public LInstructionHelper<2, 1, 0>
-{
-    MIRType type_;
-
-  public:
-    LIR_HEADER(Box);
-
-    LBox(const LAllocation& in_payload, MIRType type)
-      : type_(type)
-    {
-        setOperand(0, in_payload);
-    }
-
-    MIRType type() const {
-        return type_;
-    }
-    const char* extraName() const {
-        return StringFromMIRType(type_);
-    }
-};
-
 class LBoxFloatingPoint : public LInstructionHelper<2, 1, 1>
 {
     MIRType type_;
 
   public:
     LIR_HEADER(BoxFloatingPoint);
 
     LBoxFloatingPoint(const LAllocation& in, const LDefinition& temp, MIRType type)
@@ -283,32 +262,16 @@ class LModMaskI : public LInstructionHel
         return shift_;
     }
 
     MMod* mir() const {
         return mir_->toMod();
     }
 };
 
-class LPowHalfD : public LInstructionHelper<1, 1, 0>
-{
-  public:
-    LIR_HEADER(PowHalfD);
-    LPowHalfD(const LAllocation& input) {
-        setOperand(0, input);
-    }
-
-    const LAllocation* input() {
-        return getOperand(0);
-    }
-    const LDefinition* output() {
-        return getDef(0);
-    }
-};
-
 // Takes a tableswitch with an integer to decide.
 class LTableSwitch : public LInstructionHelper<0, 1, 1>
 {
   public:
     LIR_HEADER(TableSwitch);
 
     LTableSwitch(const LAllocation& in, const LDefinition& inputCopy, MTableSwitch* ins) {
         setOperand(0, in);
--- a/js/src/jit/arm64/AtomicOperations-arm64.h
+++ b/js/src/jit/arm64/AtomicOperations-arm64.h
@@ -10,95 +10,117 @@
 #define jit_arm64_AtomicOperations_arm64_h
 
 #include "jit/arm64/Architecture-arm64.h"
 #include "jit/AtomicOperations.h"
 
 inline bool
 js::jit::AtomicOperations::isLockfree8()
 {
-    MOZ_CRASH("isLockfree8()");
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int8_t), 0));
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int16_t), 0));
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int32_t), 0));
+    return true;
 }
 
 inline void
 js::jit::AtomicOperations::fenceSeqCst()
 {
-    MOZ_CRASH("fenceSeqCst()");
+    __atomic_thread_fence(__ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::loadSeqCst(T* addr)
 {
-    MOZ_CRASH("loadSeqCst()");
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+    T v;
+    __atomic_load(addr, &v, __ATOMIC_SEQ_CST);
+    return v;
 }
 
 template<typename T>
 inline void
 js::jit::AtomicOperations::storeSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("storeSeqCst()");
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+    __atomic_store(addr, &val, __ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::exchangeSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("exchangeSeqCst()");
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+    T v;
+    __atomic_exchange(addr, &val, &v, __ATOMIC_SEQ_CST);
+    return v;
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::compareExchangeSeqCst(T* addr, T oldval, T newval)
 {
-    MOZ_CRASH("compareExchangeSeqCst()");
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+    __atomic_compare_exchange(addr, &oldval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
+    return oldval;
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchAddSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("fetchAddSeqCst()");
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+    return __atomic_fetch_add(addr, val, __ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchSubSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("fetchSubSeqCst()");
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+    return __atomic_fetch_sub(addr, val, __ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchAndSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("fetchAndSeqCst()");
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+    return __atomic_fetch_and(addr, val, __ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchOrSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("fetchOrSeqCst()");
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+    return __atomic_fetch_or(addr, val, __ATOMIC_SEQ_CST);
 }
 
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchXorSeqCst(T* addr, T val)
 {
-    MOZ_CRASH("fetchXorSeqCst()");
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+    return __atomic_fetch_xor(addr, val, __ATOMIC_SEQ_CST);
 }
 
 template<size_t nbytes>
 inline void
 js::jit::RegionLock::acquire(void* addr)
 {
-    MOZ_CRASH("acquire()");
+    uint32_t zero = 0;
+    uint32_t one = 1;
+    while (!__atomic_compare_exchange(&spinlock, &zero, &one, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
+        continue;
 }
 
 template<size_t nbytes>
 inline void
 js::jit::RegionLock::release(void* addr)
 {
-    MOZ_CRASH("release()");
+    MOZ_ASSERT(AtomicOperations::loadSeqCst(&spinlock) == 1, "releasing unlocked region lock");
+    uint32_t zero = 0;
+    __atomic_store(&spinlock, &zero, __ATOMIC_SEQ_CST);
 }
 
 #endif // jit_arm64_AtomicOperations_arm64_h
--- a/js/src/jit/arm64/CodeGenerator-arm64.cpp
+++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp
@@ -33,28 +33,16 @@ using JS::GenericNaN;
 
 // shared
 CodeGeneratorARM64::CodeGeneratorARM64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorShared(gen, graph, masm)
 {
 }
 
 bool
-CodeGeneratorARM64::generatePrologue()
-{
-    MOZ_CRASH("generatePrologue");
-}
-
-bool
-CodeGeneratorARM64::generateEpilogue()
-{
-    MOZ_CRASH("generateEpilogue");
-}
-
-bool
 CodeGeneratorARM64::generateOutOfLineCode()
 {
     MOZ_CRASH("generateOutOfLineCode");
 }
 
 void
 CodeGeneratorARM64::emitBranch(Assembler::Condition cond, MBasicBlock* mirTrue, MBasicBlock* mirFalse)
 {
--- a/js/src/jit/arm64/CodeGenerator-arm64.h
+++ b/js/src/jit/arm64/CodeGenerator-arm64.h
@@ -21,18 +21,16 @@ class CodeGeneratorARM64 : public CodeGe
     friend class MoveResolverARM64;
 
     CodeGeneratorARM64* thisFromCtor() { return this; }
 
   public:
     CodeGeneratorARM64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
 
   protected:
-    // Label for the common return path.
-    NonAssertingLabel returnLabel_;
     NonAssertingLabel deoptLabel_;
 
     // FIXME: VIXL Operand does not match the platform-agnostic Operand,
     // which is just a union of possible arguments.
     inline Operand ToOperand(const LAllocation& a) {
         MOZ_CRASH("ToOperand");
     }
     inline Operand ToOperand(const LAllocation* a) {
@@ -68,18 +66,16 @@ class CodeGeneratorARM64 : public CodeGe
         return bailoutIf(c, snapshot);
     }
     void bailoutIfFalseBool(Register reg, LSnapshot* snapshot) {
         masm.test32(reg, Imm32(0xFF));
         return bailoutIf(Assembler::Zero, snapshot);
     }
 
   protected:
-    bool generatePrologue();
-    bool generateEpilogue();
     bool generateOutOfLineCode();
 
     void emitRoundDouble(FloatRegister src, Register dest, Label* fail);
 
     // Emits a branch that directs control flow to the true block if |cond| is
     // true, and the false block if |cond| is false.
     void emitBranch(Assembler::Condition cond, MBasicBlock* ifTrue, MBasicBlock* ifFalse);
 
--- a/js/src/jit/arm64/LIR-arm64.h
+++ b/js/src/jit/arm64/LIR-arm64.h
@@ -5,37 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_arm64_LIR_arm64_h
 #define jit_arm64_LIR_arm64_h
 
 namespace js {
 namespace jit {
 
-class LBox : public LInstructionHelper<1, 1, 0>
-{
-    MIRType type_;
-
-  public:
-    LIR_HEADER(Box);
-
-    LBox(const LAllocation& in_payload, MIRType type)
-      : type_(type)
-    {
-        setOperand(0, in_payload);
-    }
-
-    MIRType type() const {
-        return type_;
-    }
-    const char* extraName() const {
-        return StringFromMIRType(type_);
-    }
-};
-
 class LUnboxBase : public LInstructionHelper<1, 1, 0>
 {
   public:
     LUnboxBase(const LAllocation& input) {
         setOperand(0, input);
     }
 
     static const size_t Input = 0;
@@ -264,32 +243,16 @@ class LModMaskI : public LInstructionHel
         return shift_;
     }
 
     MMod* mir() const {
         return mir_->toMod();
     }
 };
 
-class LPowHalfD : public LInstructionHelper<1, 1, 0>
-{
-  public:
-    LIR_HEADER(PowHalfD);
-    LPowHalfD(const LAllocation& input) {
-        setOperand(0, input);
-    }
-
-    const LAllocation* input() {
-        return getOperand(0);
-    }
-    const LDefinition* output() {
-        return getDef(0);
-    }
-};
-
 // Takes a tableswitch with an integer to decide
 class LTableSwitch : public LInstructionHelper<0, 1, 1>
 {
   public:
     LIR_HEADER(TableSwitch);
 
     LTableSwitch(const LAllocation& in, const LDefinition& inputCopy, MTableSwitch* ins) {
         setOperand(0, in);
--- a/js/src/jit/arm64/Lowering-arm64.cpp
+++ b/js/src/jit/arm64/Lowering-arm64.cpp
@@ -13,23 +13,16 @@
 #include "jit/shared/Lowering-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::FloorLog2;
 
 void
-LIRGeneratorARM64::useBox(LInstruction* lir, size_t n, MDefinition* mir,
-                          LUse::Policy policy, bool useAtStart)
-{
-    MOZ_CRASH("useBox");
-}
-
-void
 LIRGeneratorARM64::useBoxFixed(LInstruction* lir, size_t n, MDefinition* mir, Register reg1, Register)
 {
     MOZ_CRASH("useBoxFixed");
 }
 
 LAllocation
 LIRGeneratorARM64::useByteOpRegister(MDefinition* mir)
 {
--- a/js/src/jit/arm64/Lowering-arm64.h
+++ b/js/src/jit/arm64/Lowering-arm64.h
@@ -17,18 +17,16 @@ class LIRGeneratorARM64 : public LIRGene
   public:
     LIRGeneratorARM64(MIRGenerator* gen, MIRGraph& graph, LIRGraph& lirGraph)
       : LIRGeneratorShared(gen, graph, lirGraph)
     { }
 
   protected:
     // Adds a box input to an instruction, setting operand |n| to the type and
     // |n+1| to the payload.
-    void useBox(LInstruction* lir, size_t n, MDefinition* mir,
-                LUse::Policy policy = LUse::REGISTER, bool useAtStart = false);
     void useBoxFixed(LInstruction* lir, size_t n, MDefinition* mir, Register reg1, Register reg2);
 
     LAllocation useByteOpRegister(MDefinition* mir);
     LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition* mir);
 
     inline LDefinition tempToUnbox() {
         return temp();
     }
--- a/js/src/jit/mips/CodeGenerator-mips.cpp
+++ b/js/src/jit/mips/CodeGenerator-mips.cpp
@@ -54,58 +54,16 @@ CodeGeneratorMIPS::ToAddress(const LAllo
 
 
 // shared
 CodeGeneratorMIPS::CodeGeneratorMIPS(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorShared(gen, graph, masm)
 {
 }
 
-bool
-CodeGeneratorMIPS::generatePrologue()
-{
-    MOZ_ASSERT(masm.framePushed() == 0);
-    MOZ_ASSERT(!gen->compilingAsmJS());
-
-    // If profiling, save the current frame pointer to a per-thread global field.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerEnterFrame(StackPointer, CallTempReg0);
-
-    // Ensure that the Ion frames is properly aligned.
-    masm.assertStackAlignment(JitStackAlignment, 0);
-
-    // Note that this automatically sets MacroAssembler::framePushed().
-    masm.reserveStack(frameSize());
-    masm.checkStackAlignment();
-
-    emitTracelogIonStart();
-
-    return true;
-}
-
-bool
-CodeGeneratorMIPS::generateEpilogue()
-{
-    MOZ_ASSERT(!gen->compilingAsmJS());
-    masm.bind(&returnLabel_);
-
-    emitTracelogIonStop();
-
-    masm.freeStack(frameSize());
-    MOZ_ASSERT(masm.framePushed() == 0);
-
-    // If profiling, reset the per-thread global lastJitFrame to point to
-    // the previous frame.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerExitFrame();
-
-    masm.ret();
-    return true;
-}
-
 void
 CodeGeneratorMIPS::branchToBlock(Assembler::FloatFormat fmt, FloatRegister lhs, FloatRegister rhs,
                                  MBasicBlock* mir, Assembler::DoubleCondition cond)
 {
     // Skip past trivial blocks.
     mir = skipTrivialBlocks(mir);
 
     Label* label = mir->lir()->label();
--- a/js/src/jit/mips/CodeGenerator-mips.h
+++ b/js/src/jit/mips/CodeGenerator-mips.h
@@ -20,18 +20,16 @@ class CodeGeneratorMIPS : public CodeGen
 {
     friend class MoveResolverMIPS;
 
     CodeGeneratorMIPS* thisFromCtor() {
         return this;
     }
 
   protected:
-    // Label for the common return path.
-    NonAssertingLabel returnLabel_;
     NonAssertingLabel deoptLabel_;
 
     inline Address ToAddress(const LAllocation& a);
     inline Address ToAddress(const LAllocation* a);
 
     MoveOperand toMoveOperand(LAllocation a) const;
 
     template <typename T1, typename T2>
@@ -70,18 +68,16 @@ class CodeGeneratorMIPS : public CodeGen
         masm.branchTest32(Assembler::Zero, reg, Imm32(0xFF), &bail);
         bailoutFrom(&bail, snapshot);
     }
 
     void bailoutFrom(Label* label, LSnapshot* snapshot);
     void bailout(LSnapshot* snapshot);
 
   protected:
-    bool generatePrologue();
-    bool generateEpilogue();
     bool generateOutOfLineCode();
 
     template <typename T>
     void branchToBlock(Register lhs, T rhs, MBasicBlock* mir, Assembler::Condition cond)
     {
         mir = skipTrivialBlocks(mir);
 
         Label* label = mir->lir()->label();
--- a/js/src/jit/mips/LIR-mips.h
+++ b/js/src/jit/mips/LIR-mips.h
@@ -5,37 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_mips_LIR_mips_h
 #define jit_mips_LIR_mips_h
 
 namespace js {
 namespace jit {
 
-class LBox : public LInstructionHelper<2, 1, 0>
-{
-    MIRType type_;
-
-  public:
-    LIR_HEADER(Box);
-
-    LBox(const LAllocation& in_payload, MIRType type)
-      : type_(type)
-    {
-        setOperand(0, in_payload);
-    }
-
-    MIRType type() const {
-        return type_;
-    }
-    const char* extraName() const {
-        return StringFromMIRType(type_);
-    }
-};
-
 class LBoxFloatingPoint : public LInstructionHelper<2, 1, 1>
 {
     MIRType type_;
 
   public:
     LIR_HEADER(BoxFloatingPoint);
 
     LBoxFloatingPoint(const LAllocation& in, const LDefinition& temp, MIRType type)
@@ -228,32 +207,16 @@ class LModMaskI : public LInstructionHel
         return shift_;
     }
 
     MMod* mir() const {
         return mir_->toMod();
     }
 };
 
-class LPowHalfD : public LInstructionHelper<1, 1, 0>
-{
-  public:
-    LIR_HEADER(PowHalfD);
-    LPowHalfD(const LAllocation& input) {
-        setOperand(0, input);
-    }
-
-    const LAllocation* input() {
-        return getOperand(0);
-    }
-    const LDefinition* output() {
-        return getDef(0);
-    }
-};
-
 // Takes a tableswitch with an integer to decide
 class LTableSwitch : public LInstructionHelper<0, 1, 2>
 {
   public:
     LIR_HEADER(TableSwitch);
 
     LTableSwitch(const LAllocation& in, const LDefinition& inputCopy,
                  const LDefinition& jumpTablePointer, MTableSwitch* ins) {
--- a/js/src/jit/none/CodeGenerator-none.h
+++ b/js/src/jit/none/CodeGenerator-none.h
@@ -10,18 +10,16 @@
 #include "jit/shared/CodeGenerator-shared.h"
 
 namespace js {
 namespace jit {
 
 class CodeGeneratorNone : public CodeGeneratorShared
 {
   public:
-    NonAssertingLabel returnLabel_;
-
     CodeGeneratorNone(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
       : CodeGeneratorShared(gen, graph, masm)
     {
         MOZ_CRASH();
     }
 
     MoveOperand toMoveOperand(LAllocation) const { MOZ_CRASH(); }
     template <typename T1, typename T2>
@@ -30,18 +28,16 @@ class CodeGeneratorNone : public CodeGen
     void bailoutTest32(Assembler::Condition, Register, T, LSnapshot*) { MOZ_CRASH(); }
     template <typename T1, typename T2>
     void bailoutCmpPtr(Assembler::Condition, T1, T2, LSnapshot*) { MOZ_CRASH(); }
     void bailoutTestPtr(Assembler::Condition, Register, Register, LSnapshot*) { MOZ_CRASH(); }
     void bailoutIfFalseBool(Register, LSnapshot*) { MOZ_CRASH(); }
     void bailoutFrom(Label*, LSnapshot*) { MOZ_CRASH(); }
     void bailout(LSnapshot*) { MOZ_CRASH(); }
     void bailoutIf(Assembler::Condition, LSnapshot*) { MOZ_CRASH(); }
-    bool generatePrologue() { MOZ_CRASH(); }
-    bool generateEpilogue() { MOZ_CRASH(); }
     bool generateOutOfLineCode() { MOZ_CRASH(); }
     void testNullEmitBranch(Assembler::Condition, ValueOperand, MBasicBlock*, MBasicBlock*) {
         MOZ_CRASH();
     }
     void testUndefinedEmitBranch(Assembler::Condition, ValueOperand, MBasicBlock*, MBasicBlock*) {
         MOZ_CRASH();
     }
     void testObjectEmitBranch(Assembler::Condition, ValueOperand, MBasicBlock*, MBasicBlock*) {
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -49,16 +49,17 @@ CodeGeneratorShared::CodeGeneratorShared
     snapshots_(),
     recovers_(),
     deoptTable_(nullptr),
 #ifdef DEBUG
     pushedArgs_(0),
 #endif
     lastOsiPointOffset_(0),
     safepoints_(graph->totalSlotCount(), (gen->info().nargs() + 1) * sizeof(Value)),
+    returnLabel_(),
     nativeToBytecodeMap_(nullptr),
     nativeToBytecodeMapSize_(0),
     nativeToBytecodeTableOffset_(0),
     nativeToBytecodeNumRegions_(0),
     nativeToBytecodeScriptList_(nullptr),
     nativeToBytecodeScriptListLength_(0),
     trackedOptimizationsMap_(nullptr),
     trackedOptimizationsMapSize_(0),
@@ -105,16 +106,64 @@ CodeGeneratorShared::CodeGeneratorShared
         // asm.js code.
         frameClass_ = FrameSizeClass::None();
     } else {
         frameClass_ = FrameSizeClass::FromDepth(frameDepth_);
     }
 }
 
 bool
+CodeGeneratorShared::generatePrologue()
+{
+    MOZ_ASSERT(masm.framePushed() == 0);
+    MOZ_ASSERT(!gen->compilingAsmJS());
+
+#ifdef JS_USE_LINK_REGISTER
+    masm.pushReturnAddress();
+#endif
+
+    // If profiling, save the current frame pointer to a per-thread global field.
+    if (isProfilerInstrumentationEnabled())
+        masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0);
+
+    // Ensure that the Ion frame is properly aligned.
+    masm.assertStackAlignment(JitStackAlignment, 0);
+
+    // Note that this automatically sets MacroAssembler::framePushed().
+    masm.reserveStack(frameSize());
+    masm.checkStackAlignment();
+
+    emitTracelogIonStart();
+    return true;
+}
+
+bool
+CodeGeneratorShared::generateEpilogue()
+{
+    MOZ_ASSERT(!gen->compilingAsmJS());
+    masm.bind(&returnLabel_);
+
+    emitTracelogIonStop();
+
+    masm.freeStack(frameSize());
+    MOZ_ASSERT(masm.framePushed() == 0);
+
+    // If profiling, reset the per-thread global lastJitFrame to point to
+    // the previous frame.
+    if (isProfilerInstrumentationEnabled())
+        masm.profilerExitFrame();
+
+    masm.ret();
+
+    // On systems that use a constant pool, this is a good time to emit.
+    masm.flushBuffer();
+    return true;
+}
+
+bool
 CodeGeneratorShared::generateOutOfLineCode()
 {
     for (size_t i = 0; i < outOfLineCode_.length(); i++) {
         // Add native => bytecode mapping entries for OOL sites.
         // Not enabled on asm.js yet since asm doesn't contain bytecode mappings.
         if (!gen->compilingAsmJS()) {
             if (!addNativeToBytecodeEntry(outOfLineCode_[i]->bytecodeSite()))
                 return false;
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -100,16 +100,19 @@ class CodeGeneratorShared : public LElem
     // Patchable backedges generated for loops.
     Vector<PatchableBackedgeInfo, 0, SystemAllocPolicy> patchableBackedges_;
 
 #ifdef JS_TRACE_LOGGING
     js::Vector<CodeOffsetLabel, 0, SystemAllocPolicy> patchableTraceLoggers_;
     js::Vector<CodeOffsetLabel, 0, SystemAllocPolicy> patchableTLScripts_;
 #endif
 
+    // Label for the common return path.
+    NonAssertingLabel returnLabel_;
+
   public:
     struct NativeToBytecode {
         CodeOffsetLabel nativeOffset;
         InlineScriptTree* tree;
         jsbytecode* pc;
     };
 
   protected:
@@ -445,16 +448,19 @@ class CodeGeneratorShared : public LElem
     inline OutOfLineCode* oolCallVM(const VMFunction& fun, LInstruction* ins, const ArgSeq& args,
                                     const StoreOutputTo& out);
 
     void addCache(LInstruction* lir, size_t cacheIndex);
     size_t addCacheLocations(const CacheLocationList& locs, size_t* numLocs);
     ReciprocalMulConstants computeDivisionConstants(uint32_t d, int maxLog);
 
   protected:
+    bool generatePrologue();
+    bool generateEpilogue();
+
     void addOutOfLineCode(OutOfLineCode* code, const MInstruction* mir);
     void addOutOfLineCode(OutOfLineCode* code, const BytecodeSite* site);
     bool generateOutOfLineCode();
 
     Label* labelForBackedgeWithImplicitCheck(MBasicBlock* mir);
 
     // Generate a jump to the start of the specified block, adding information
     // if this is a loop backedge. Use this in place of jumping directly to
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -12,17 +12,36 @@
 #include "jit/AtomicOp.h"
 #include "jit/shared/Assembler-shared.h"
 
 // This file declares LIR instructions that are common to every platform.
 
 namespace js {
 namespace jit {
 
-class Range;
+class LBox : public LInstructionHelper<BOX_PIECES, 1, 0>
+{
+    MIRType type_;
+
+  public:
+    LIR_HEADER(Box);
+
+    LBox(const LAllocation& payload, MIRType type)
+      : type_(type)
+    {
+        setOperand(0, payload);
+    }
+
+    MIRType type() const {
+        return type_;
+    }
+    const char* extraName() const {
+        return StringFromMIRType(type_);
+    }
+};
 
 template <size_t Temps, size_t ExtraUses = 0>
 class LBinaryMath : public LInstructionHelper<1, 2 + ExtraUses, Temps>
 {
   public:
     const LAllocation* lhs() {
         return this->getOperand(0);
     }
@@ -3719,16 +3738,36 @@ class LFloat32x4ToInt32x4 : public LInst
     const LDefinition* temp() {
         return getTemp(0);
     }
     const MSimdConvert* mir() const {
         return mir_->toSimdConvert();
     }
 };
 
+// Double raised to a half power.
+class LPowHalfD : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(PowHalfD);
+    explicit LPowHalfD(const LAllocation& input) {
+        setOperand(0, input);
+    }
+
+    const LAllocation* input() {
+        return getOperand(0);
+    }
+    const LDefinition* output() {
+        return getDef(0);
+    }
+    MPowHalf* mir() const {
+        return mir_->toPowHalf();
+    }
+};
+
 // No-op instruction that is used to hold the entry snapshot. This simplifies
 // register allocation as it doesn't need to sniff the snapshot out of the
 // LIRGraph.
 class LStart : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(Start)
 };
--- a/js/src/jit/x64/LIR-x64.h
+++ b/js/src/jit/x64/LIR-x64.h
@@ -5,38 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_x64_LIR_x64_h
 #define jit_x64_LIR_x64_h
 
 namespace js {
 namespace jit {
 
-// Given a typed input, returns an untyped box.
-class LBox : public LInstructionHelper<1, 1, 0>
-{
-    MIRType type_;
-
-  public:
-    LIR_HEADER(Box)
-
-    LBox(MIRType type, const LAllocation& payload)
-      : type_(type)
-    {
-        setOperand(0, payload);
-    }
-
-    MIRType type() const {
-        return type_;
-    }
-    const char* extraName() const {
-        return StringFromMIRType(type_);
-    }
-};
-
 // Given an untyped input, guards on whether it's a specific type and returns
 // the unboxed payload.
 class LUnboxBase : public LInstructionHelper<1, 1, 0>
 {
   public:
     explicit LUnboxBase(const LAllocation& input) {
         setOperand(0, input);
     }
--- a/js/src/jit/x64/Lowering-x64.cpp
+++ b/js/src/jit/x64/Lowering-x64.cpp
@@ -56,17 +56,17 @@ LIRGeneratorX64::visitBox(MBox* box)
     if (opd->isConstant() && box->canEmitAtUses()) {
         emitAtUses(box);
         return;
     }
 
     if (opd->isConstant()) {
         define(new(alloc()) LValue(opd->toConstant()->value()), box, LDefinition(LDefinition::BOX));
     } else {
-        LBox* ins = new(alloc()) LBox(opd->type(), useRegister(opd));
+        LBox* ins = new(alloc()) LBox(useRegister(opd), opd->type());
         define(ins, box, LDefinition(LDefinition::BOX));
     }
 }
 
 void
 LIRGeneratorX64::visitUnbox(MUnbox* unbox)
 {
     MDefinition* box = unbox->getOperand(0);
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -34,59 +34,16 @@ using JS::GenericNaN;
 namespace js {
 namespace jit {
 
 CodeGeneratorX86Shared::CodeGeneratorX86Shared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorShared(gen, graph, masm)
 {
 }
 
-bool
-CodeGeneratorX86Shared::generatePrologue()
-{
-    MOZ_ASSERT(masm.framePushed() == 0);
-    MOZ_ASSERT(!gen->compilingAsmJS());
-
-    // If profiling, save the current frame pointer to a per-thread global field.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerEnterFrame(StackPointer, CallTempReg0);
-
-    // Ensure that the Ion frames is properly aligned.
-    masm.assertStackAlignment(JitStackAlignment, 0);
-
-    // Note that this automatically sets MacroAssembler::framePushed().
-    masm.reserveStack(frameSize());
-
-    emitTracelogIonStart();
-
-    return true;
-}
-
-bool
-CodeGeneratorX86Shared::generateEpilogue()
-{
-    MOZ_ASSERT(!gen->compilingAsmJS());
-
-    masm.bind(&returnLabel_);
-
-    emitTracelogIonStop();
-
-    // Pop the stack we allocated at the start of the function.
-    masm.freeStack(frameSize());
-    MOZ_ASSERT(masm.framePushed() == 0);
-
-    // If profiling, reset the per-thread global lastJitFrame to point to
-    // the previous frame.
-    if (isProfilerInstrumentationEnabled())
-        masm.profilerExitFrame();
-
-    masm.ret();
-    return true;
-}
-
 void
 OutOfLineBailout::accept(CodeGeneratorX86Shared* codegen)
 {
     codegen->visitOutOfLineBailout(this);
 }
 
 void
 CodeGeneratorX86Shared::emitBranch(Assembler::Condition cond, MBasicBlock* mirTrue,
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -92,18 +92,16 @@ class CodeGeneratorX86Shared : public Co
     };
 
     // Functions for emitting bounds-checking code with branches.
     MOZ_WARN_UNUSED_RESULT
     uint32_t emitAsmJSBoundsCheckBranch(const MAsmJSHeapAccess* mir, const MInstruction* ins,
                                         Register ptr, Label* fail);
     void cleanupAfterAsmJSBoundsCheckBranch(const MAsmJSHeapAccess* mir, Register ptr);
 
-    // Label for the common return path.
-    NonAssertingLabel returnLabel_;
     NonAssertingLabel deoptLabel_;
 
     MoveOperand toMoveOperand(LAllocation a) const;
 
     void bailoutIf(Assembler::Condition condition, LSnapshot* snapshot);
     void bailoutIf(Assembler::DoubleCondition condition, LSnapshot* snapshot);
     void bailoutFrom(Label* label, LSnapshot* snapshot);
     void bailout(LSnapshot* snapshot);
@@ -143,18 +141,16 @@ class CodeGeneratorX86Shared : public Co
     void bailoutCvttss2si(FloatRegister src, Register dest, LSnapshot* snapshot) {
         // Same trick as explained in the above comment.
         masm.vcvttss2si(src, dest);
         masm.cmp32(dest, Imm32(1));
         bailoutIf(Assembler::Overflow, snapshot);
     }
 
   protected:
-    bool generatePrologue();
-    bool generateEpilogue();
     bool generateOutOfLineCode();
 
     void emitCompare(MCompare::CompareType type, const LAllocation* left, const LAllocation* right);
 
     // Emits a branch that directs control flow to the true block if |cond| is
     // true, and the false block if |cond| is false.
     void emitBranch(Assembler::Condition cond, MBasicBlock* ifTrue, MBasicBlock* ifFalse,
                     Assembler::NaNCond ifNaN = Assembler::NaN_HandledByCond);
--- a/js/src/jit/x86-shared/LIR-x86-shared.h
+++ b/js/src/jit/x86-shared/LIR-x86-shared.h
@@ -213,36 +213,16 @@ class LModPowTwoI : public LInstructionH
     const LDefinition* remainder() {
         return getDef(0);
     }
     MMod* mir() const {
         return mir_->toMod();
     }
 };
 
-// Double raised to a half power.
-class LPowHalfD : public LInstructionHelper<1, 1, 0>
-{
-  public:
-    LIR_HEADER(PowHalfD)
-    explicit LPowHalfD(const LAllocation& input) {
-        setOperand(0, input);
-    }
-
-    const LAllocation* input() {
-        return getOperand(0);
-    }
-    const LDefinition* output() {
-        return getDef(0);
-    }
-    MPowHalf* mir() const {
-        return mir_->toPowHalf();
-    }
-};
-
 // Takes a tableswitch with an integer to decide
 class LTableSwitch : public LInstructionHelper<0, 1, 2>
 {
   public:
     LIR_HEADER(TableSwitch)
 
     LTableSwitch(const LAllocation& in, const LDefinition& inputCopy,
                  const LDefinition& jumpTablePointer, MTableSwitch* ins)
--- a/js/src/jit/x86/LIR-x86.h
+++ b/js/src/jit/x86/LIR-x86.h
@@ -5,37 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_x86_LIR_x86_h
 #define jit_x86_LIR_x86_h
 
 namespace js {
 namespace jit {
 
-class LBox : public LInstructionHelper<2, 1, 0>
-{
-    MIRType type_;
-
-  public:
-    LIR_HEADER(Box);
-
-    LBox(const LAllocation& in_payload, MIRType type)
-      : type_(type)
-    {
-        setOperand(0, in_payload);
-    }
-
-    MIRType type() const {
-        return type_;
-    }
-    const char* extraName() const {
-        return StringFromMIRType(type_);
-    }
-};
-
 class LBoxFloatingPoint : public LInstructionHelper<2, 1, 1>
 {
     MIRType type_;
 
   public:
     LIR_HEADER(BoxFloatingPoint);
 
     LBoxFloatingPoint(const LAllocation& in, const LDefinition& temp, MIRType type)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3808,41 +3808,47 @@ AutoFile::open(JSContext* cx, const char
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_OPEN,
                                  filename, "No such file or directory");
             return false;
         }
     }
     return true;
 }
 
-JSObject * const JS::ReadOnlyCompileOptions::nullObjectPtr = nullptr;
-
 void
-JS::ReadOnlyCompileOptions::copyPODOptions(const ReadOnlyCompileOptions& rhs)
-{
+JS::TransitiveCompileOptions::copyPODTransitiveOptions(const TransitiveCompileOptions& rhs)
+{
+    mutedErrors_ = rhs.mutedErrors_;
     version = rhs.version;
     versionSet = rhs.versionSet;
     utf8 = rhs.utf8;
-    lineno = rhs.lineno;
-    column = rhs.column;
-    forEval = rhs.forEval;
-    noScriptRval = rhs.noScriptRval;
     selfHostingMode = rhs.selfHostingMode;
     canLazilyParse = rhs.canLazilyParse;
     strictOption = rhs.strictOption;
     extraWarningsOption = rhs.extraWarningsOption;
     werrorOption = rhs.werrorOption;
     asmJSOption = rhs.asmJSOption;
     forceAsync = rhs.forceAsync;
     installedFile = rhs.installedFile;
     sourceIsLazy = rhs.sourceIsLazy;
     introductionType = rhs.introductionType;
     introductionLineno = rhs.introductionLineno;
     introductionOffset = rhs.introductionOffset;
     hasIntroductionInfo = rhs.hasIntroductionInfo;
+};
+
+void
+JS::ReadOnlyCompileOptions::copyPODOptions(const ReadOnlyCompileOptions& rhs)
+{
+    copyPODTransitiveOptions(rhs);
+    lineno = rhs.lineno;
+    column = rhs.column;
+    isRunOnce = rhs.isRunOnce;
+    forEval = rhs.forEval;
+    noScriptRval = rhs.noScriptRval;
 }
 
 JS::OwningCompileOptions::OwningCompileOptions(JSContext* cx)
     : ReadOnlyCompileOptions(),
       runtime(GetRuntime(cx)),
       elementRoot(cx),
       elementAttributeNameRoot(cx),
       introductionScriptRoot(cx)
@@ -3857,17 +3863,16 @@ JS::OwningCompileOptions::~OwningCompile
     js_free(const_cast<char*>(introducerFilename_));
 }
 
 bool
 JS::OwningCompileOptions::copy(JSContext* cx, const ReadOnlyCompileOptions& rhs)
 {
     copyPODOptions(rhs);
 
-    setMutedErrors(rhs.mutedErrors());
     setElement(rhs.element());
     setElementAttributeName(rhs.elementAttributeName());
     setIntroductionScript(rhs.introductionScript());
 
     return setFileAndLine(cx, rhs.filename(), rhs.lineno) &&
            setSourceMapURL(cx, rhs.sourceMapURL()) &&
            setIntroducerFilename(cx, rhs.introducerFilename());
 }
@@ -4265,18 +4270,17 @@ CompileFunction(JSContext* cx, const Rea
     if (!fun)
         return false;
 
     // Make sure the static scope chain matches up when we have a
     // non-syntactic scope.
     MOZ_ASSERT_IF(!enclosingDynamicScope->is<GlobalObject>(),
                   HasNonSyntacticStaticScopeChain(enclosingStaticScope));
 
-    CompileOptions options(cx, optionsArg);
-    if (!frontend::CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingStaticScope))
+    if (!frontend::CompileFunctionBody(cx, fun, optionsArg, formals, srcBuf, enclosingStaticScope))
         return false;
 
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileFunction(JSContext* cx, AutoObjectVector& scopeChain,
                     const ReadOnlyCompileOptions& options,
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3379,42 +3379,48 @@ namespace JS {
  * compilation options where a worker thread can find them, and then return
  * immediately. The worker thread will come along at some later point, and use
  * the options.
  *
  * The compiler itself just needs to be able to access a collection of options;
  * it doesn't care who owns them, or what's keeping them alive. It does its own
  * addrefs/copies/tracing/etc.
  *
- * So, we have a class hierarchy that reflects these three use cases:
+ * Furthermore, in some cases compile options are propagated from one entity to
+ * another (e.g. from a scriipt to a function defined in that script).  This
+ * involves copying over some, but not all, of the options.
+ *
+ * So, we have a class hierarchy that reflects these four use cases:
  *
- * - ReadOnlyCompileOptions is the common base class. It can be used by code
- *   that simply needs to access options set elsewhere, like the compiler.
+ * - TransitiveCompileOptions is the common base class, representing options
+ *   that should get propagated from a script to functions defined in that
+ *   script.  This is never instantiated directly.
+ *
+ * - ReadOnlyCompileOptions is the only subclass of TransitiveCompileOptions,
+ *   representing a full set of compile options.  It can be used by code that
+ *   simply needs to access options set elsewhere, like the compiler.  This,
+ *   again, is never instantiated directly.
  *
  * - The usual CompileOptions class must be stack-allocated, and holds
  *   non-owning references to the filename, element, and so on. It's derived
  *   from ReadOnlyCompileOptions, so the compiler can use it.
  *
  * - OwningCompileOptions roots / copies / reference counts of all its values,
  *   and unroots / frees / releases them when it is destructed. It too is
  *   derived from ReadOnlyCompileOptions, so the compiler accepts it.
  */
 
 /*
  * The common base class for the CompileOptions hierarchy.
  *
- * Use this in code that only needs to access compilation options created
- * elsewhere, like the compiler. Don't instantiate this class (the constructor
- * is protected anyway); instead, create instances only of the derived classes:
- * CompileOptions and OwningCompileOptions.
- */
-class JS_FRIEND_API(ReadOnlyCompileOptions)
+ * Use this in code that needs to propagate compile options from one compilation
+ * unit to another.
+ */
+class JS_FRIEND_API(TransitiveCompileOptions)
 {
-    friend class CompileOptions;
-
   protected:
     // The Web Platform allows scripts to be loaded from arbitrary cross-origin
     // sources. This allows an attack by which a malicious website loads a
     // sensitive file (say, a bank statement) cross-origin (using the user's
     // cookies), and sniffs the generated syntax errors (via a window.onerror
     // handler) for juicy morsels of its contents.
     //
     // To counter this attack, HTML5 specifies that script errors should be
@@ -3426,29 +3432,24 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
     const char* filename_;
     const char* introducerFilename_;
     const char16_t* sourceMapURL_;
 
     // This constructor leaves 'version' set to JSVERSION_UNKNOWN. The structure
     // is unusable until that's set to something more specific; the derived
     // classes' constructors take care of that, in ways appropriate to their
     // purpose.
-    ReadOnlyCompileOptions()
+    TransitiveCompileOptions()
       : mutedErrors_(false),
         filename_(nullptr),
         introducerFilename_(nullptr),
         sourceMapURL_(nullptr),
         version(JSVERSION_UNKNOWN),
         versionSet(false),
         utf8(false),
-        lineno(1),
-        column(0),
-        isRunOnce(false),
-        forEval(false),
-        noScriptRval(false),
         selfHostingMode(false),
         canLazilyParse(true),
         strictOption(false),
         extraWarningsOption(false),
         werrorOption(false),
         asmJSOption(false),
         forceAsync(false),
         installedFile(false),
@@ -3456,58 +3457,100 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
         introductionType(nullptr),
         introductionLineno(0),
         introductionOffset(0),
         hasIntroductionInfo(false)
     { }
 
     // Set all POD options (those not requiring reference counts, copies,
     // rooting, or other hand-holding) to their values in |rhs|.
+    void copyPODTransitiveOptions(const TransitiveCompileOptions& rhs);
+
+  public:
+    // Read-only accessors for non-POD options. The proper way to set these
+    // depends on the derived type.
+    bool mutedErrors() const { return mutedErrors_; }
+    const char* filename() const { return filename_; }
+    const char* introducerFilename() const { return introducerFilename_; }
+    const char16_t* sourceMapURL() const { return sourceMapURL_; }
+    virtual JSObject* element() const = 0;
+    virtual JSString* elementAttributeName() const = 0;
+    virtual JSScript* introductionScript() const = 0;
+
+    // POD options.
+    JSVersion version;
+    bool versionSet;
+    bool utf8;
+    bool selfHostingMode;
+    bool canLazilyParse;
+    bool strictOption;
+    bool extraWarningsOption;
+    bool werrorOption;
+    bool asmJSOption;
+    bool forceAsync;
+    bool installedFile;  // 'true' iff pre-compiling js file in packaged app
+    bool sourceIsLazy;
+
+    // |introductionType| is a statically allocated C string:
+    // one of "eval", "Function", or "GeneratorFunction".
+    const char* introductionType;
+    unsigned introductionLineno;
+    uint32_t introductionOffset;
+    bool hasIntroductionInfo;
+
+  private:
+    void operator=(const TransitiveCompileOptions&) = delete;
+};
+
+/*
+ * The class representing a full set of compile options.
+ *
+ * Use this in code that only needs to access compilation options created
+ * elsewhere, like the compiler. Don't instantiate this class (the constructor
+ * is protected anyway); instead, create instances only of the derived classes:
+ * CompileOptions and OwningCompileOptions.
+ */
+class JS_FRIEND_API(ReadOnlyCompileOptions) : public TransitiveCompileOptions
+{
+    friend class CompileOptions;
+
+  protected:
+    ReadOnlyCompileOptions()
+      : TransitiveCompileOptions(),
+        lineno(1),
+        column(0),
+        isRunOnce(false),
+        forEval(false),
+        noScriptRval(false)
+    { }
+
+    // Set all POD options (those not requiring reference counts, copies,
+    // rooting, or other hand-holding) to their values in |rhs|.
     void copyPODOptions(const ReadOnlyCompileOptions& rhs);
 
   public:
     // Read-only accessors for non-POD options. The proper way to set these
     // depends on the derived type.
     bool mutedErrors() const { return mutedErrors_; }
     const char* filename() const { return filename_; }
     const char* introducerFilename() const { return introducerFilename_; }
     const char16_t* sourceMapURL() const { return sourceMapURL_; }
     virtual JSObject* element() const = 0;
     virtual JSString* elementAttributeName() const = 0;
     virtual JSScript* introductionScript() const = 0;
 
     // POD options.
-    JSVersion version;
-    bool versionSet;
-    bool utf8;
     unsigned lineno;
     unsigned column;
     // isRunOnce only applies to non-function scripts.
     bool isRunOnce;
     bool forEval;
     bool noScriptRval;
-    bool selfHostingMode;
-    bool canLazilyParse;
-    bool strictOption;
-    bool extraWarningsOption;
-    bool werrorOption;
-    bool asmJSOption;
-    bool forceAsync;
-    bool installedFile;  // 'true' iff pre-compiling js file in packaged app
-    bool sourceIsLazy;
-
-    // |introductionType| is a statically allocated C string:
-    // one of "eval", "Function", or "GeneratorFunction".
-    const char* introductionType;
-    unsigned introductionLineno;
-    uint32_t introductionOffset;
-    bool hasIntroductionInfo;
 
   private:
-    static JSObject * const nullObjectPtr;
     void operator=(const ReadOnlyCompileOptions&) = delete;
 };
 
 /*
  * Compilation options, with dynamic lifetime. An instance of this type
  * makes a copy of / holds / roots all dynamically allocated resources
  * (principals; elements; strings) that it refers to. Its destructor frees
  * / drops / unroots them. This is heavier than CompileOptions, below, but
@@ -3612,18 +3655,32 @@ class MOZ_STACK_CLASS JS_FRIEND_API(Comp
   public:
     explicit CompileOptions(JSContext* cx, JSVersion version = JSVERSION_UNKNOWN);
     CompileOptions(js::ContextFriendFields* cx, const ReadOnlyCompileOptions& rhs)
       : ReadOnlyCompileOptions(), elementRoot(cx), elementAttributeNameRoot(cx),
         introductionScriptRoot(cx)
     {
         copyPODOptions(rhs);
 
-        mutedErrors_ = rhs.mutedErrors_;
         filename_ = rhs.filename();
+        introducerFilename_ = rhs.introducerFilename();
+        sourceMapURL_ = rhs.sourceMapURL();
+        elementRoot = rhs.element();
+        elementAttributeNameRoot = rhs.elementAttributeName();
+        introductionScriptRoot = rhs.introductionScript();
+    }
+
+    CompileOptions(js::ContextFriendFields* cx, const TransitiveCompileOptions& rhs)
+      : ReadOnlyCompileOptions(), elementRoot(cx), elementAttributeNameRoot(cx),
+        introductionScriptRoot(cx)
+    {
+        copyPODTransitiveOptions(rhs);
+
+        filename_ = rhs.filename();
+        introducerFilename_ = rhs.introducerFilename();
         sourceMapURL_ = rhs.sourceMapURL();
         elementRoot = rhs.element();
         elementAttributeNameRoot = rhs.elementAttributeName();
         introductionScriptRoot = rhs.introductionScript();
     }
 
     JSObject* element() const override { return elementRoot; }
     JSString* elementAttributeName() const override { return elementAttributeNameRoot; }
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -32,16 +32,17 @@ typedef AutoVectorRooter<jsid> AutoIdVec
 class CallArgs;
 
 template <typename T>
 class Rooted;
 
 class JS_FRIEND_API(CompileOptions);
 class JS_FRIEND_API(ReadOnlyCompileOptions);
 class JS_FRIEND_API(OwningCompileOptions);
+class JS_FRIEND_API(TransitiveCompileOptions);
 class JS_PUBLIC_API(CompartmentOptions);
 
 class Value;
 struct Zone;
 
 } /* namespace JS */
 
 namespace js {
--- a/js/src/tests/ecma_6/Class/shell.js
+++ b/js/src/tests/ecma_6/Class/shell.js
@@ -1,23 +1,14 @@
 // NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
 //       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
 //       the browser supports).
 if (typeof version != 'undefined')
   version(185);
 
-function classesEnabled() {
-    try {
-        new Function("class Foo { constructor() { } }");
-        return true;
-    } catch (e if e instanceof SyntaxError) {
-        return false;
-    }
-}
-
 function assertThrownErrorContains(thunk, substr) {
     try {
         thunk();
     } catch (e) {
         if (e.message.indexOf(substr) !== -1)
             return;
         throw new Error("Expected error containing " + substr + ", got " + e);
     }
--- a/js/src/tests/ecma_6/Reflect/apply.js
+++ b/js/src/tests/ecma_6/Reflect/apply.js
@@ -1,17 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 // Reflect.apply calls functions.
 assertEq(Reflect.apply(Math.floor, undefined, [1.75]), 1);
 
 // Reflect.apply requires a target object that's callable.
-class clsX { constructor() {} }  // classes are not callable
-var nonCallable = [{}, [], clsX];
+var nonCallable = [{}, []];
+if (classesEnabled()) {
+    // classes are not callable
+    nonCallable.push(eval("(class clsX { constructor() {} })"));
+}
 for (var value of nonCallable) {
     assertThrowsInstanceOf(() => Reflect.apply(nonCallable), TypeError);
 }
 
 // When target is not callable, Reflect.apply does not try to get argumentList.length before throwing.
 var hits = 0;
 var bogusArgumentList = {get length() { hit++; throw "FAIL";}};
 assertThrowsInstanceOf(() => Reflect.apply({callable: false}, null, bogusArgumentList),
--- a/js/src/tests/ecma_6/Reflect/construct.js
+++ b/js/src/tests/ecma_6/Reflect/construct.js
@@ -25,26 +25,16 @@ assertDeepEq(Reflect.construct(f, [3]), 
 f.prototype = Array.prototype;
 assertDeepEq(Reflect.construct(f, [3]), new f(3));
 
 // Bound functions:
 var bound = f.bind(null, "carrot");
 assertDeepEq(Reflect.construct(b