merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 26 Apr 2017 08:41:31 +0200
changeset 354969 0f5ba06c4c5959030a05cb852656d854065e2226
parent 354928 08a5a97f615fb65b3455b435c3e56d96c4c12208 (current diff)
parent 354968 3e34e56f5e7adb50f3827125c25f121170ed071b (diff)
child 354970 46ed496bf8cb095c037b4e96903d535acfed9cf5
child 354999 0dfc3bd141e6cc6c3157fc0086b719fbf80a48e5
child 355054 45c2aad0e684e5608481cccd408ae3eb1afab256
push id31717
push usercbook@mozilla.com
push dateWed, 26 Apr 2017 06:41:51 +0000
treeherdermozilla-central@0f5ba06c4c59 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
0f5ba06c4c59 / 55.0a1 / 20170426100611 / files
nightly linux64
0f5ba06c4c59 / 55.0a1 / 20170426100344 / files
nightly mac
0f5ba06c4c59 / 55.0a1 / 20170426030329 / files
nightly win32
0f5ba06c4c59 / 55.0a1 / 20170426030329 / files
nightly win64
0f5ba06c4c59 / 55.0a1 / 20170426030329 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/components/customizableui/content/panelUI.inc.xul
mfbt/ThreadLocal.h
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/histogram-whitelists.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -67,17 +67,18 @@ browser/components/translation/cld2/**
 # Screenshots is imported as a system add-on and has its own lint rules currently.
 browser/extensions/screenshots/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
-# vendor library files in activity-stream
+# generated or library files in activity-stream
+browser/extensions/activity-stream/data/content/activity-stream.bundle.js
 browser/extensions/activity-stream/vendor/**
 # imported from chromium
 browser/extensions/mortar/**
 
 # devtools/ exclusions
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -671,17 +671,17 @@ NotificationController::WillRefresh(mozi
         if (logging::IsEnabled(logging::eTree | logging::eText)) {
           logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument);
           logging::Node("container", containerElm);
           logging::Node("content", textNode);
           logging::MsgEnd();
         }
   #endif
 
-        mDocument->ContentRemoved(containerElm, textNode);
+        mDocument->ContentRemoved(textAcc);
         continue;
       }
 
       // Update text of the accessible and fire text change events.
   #ifdef A11Y_LOG
       if (logging::IsEnabled(logging::eText)) {
         logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
         logging::Node("container", containerElm);
--- a/accessible/base/TreeWalker.cpp
+++ b/accessible/base/TreeWalker.cpp
@@ -47,16 +47,28 @@ TreeWalker::
   MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
 
   mChildFilter |= mContext->NoXBLKids() ?
     nsIContent::eAllButXBL : nsIContent::eAllChildren;
 
   MOZ_COUNT_CTOR(TreeWalker);
 }
 
+TreeWalker::
+  TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode) :
+  mDoc(aDocument), mContext(nullptr), mAnchorNode(aAnchorNode),
+  mARIAOwnsIdx(0),
+  mChildFilter(nsIContent::eSkipPlaceholderContent | nsIContent::eAllChildren),
+  mFlags(eWalkCache),
+  mPhase(eAtStart)
+{
+  MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
+  MOZ_COUNT_CTOR(TreeWalker);
+}
+
 TreeWalker::~TreeWalker()
 {
   MOZ_COUNT_DTOR(TreeWalker);
 }
 
 Accessible*
 TreeWalker::Scope(nsIContent* aAnchorNode)
 {
--- a/accessible/base/TreeWalker.h
+++ b/accessible/base/TreeWalker.h
@@ -42,16 +42,21 @@ public:
    *
    * @param aContext [in] container accessible for the given node, used to
    *                   define accessible context
    * @param aAnchorNode [in] the node the search will be prepared relative to
    * @param aFlags   [in] flags (see enum above)
    */
   TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = eWalkCache);
 
+  /**
+   * Navigates the accessible children within the anchor node subtree.
+   */
+  TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode);
+
   ~TreeWalker();
 
   /**
    * Resets the walker state, and sets the given node as an anchor. Returns a
    * first accessible element within the node including the node itself.
    */
   Accessible* Scope(nsIContent* aAnchorNode);
 
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -519,17 +519,17 @@ nsAccessibilityService::DeckPanelSwitche
     if (logging::IsEnabled(logging::eTree)) {
       logging::MsgBegin("TREE", "deck panel unselected");
       logging::Node("container", panelNode);
       logging::Node("content", aDeckNode);
       logging::MsgEnd();
     }
 #endif
 
-    document->ContentRemoved(aDeckNode, panelNode);
+    document->ContentRemoved(panelNode);
   }
 
   if (aCurrentBoxFrame) {
     nsIContent* panelNode = aCurrentBoxFrame->GetContent();
 #ifdef A11Y_LOG
     if (logging::IsEnabled(logging::eTree)) {
       logging::MsgBegin("TREE", "deck panel selected");
       logging::Node("container", panelNode);
@@ -577,36 +577,17 @@ nsAccessibilityService::ContentRemoved(n
     logging::MsgBegin("TREE", "content removed; doc: %p", document);
     logging::Node("container node", aChildNode->GetFlattenedTreeParent());
     logging::Node("content node", aChildNode);
     logging::MsgEnd();
   }
 #endif
 
   if (document) {
-    // Flatten hierarchy may be broken at this point so we cannot get a true
-    // container by traversing up the DOM tree. Find a parent of first accessible
-    // from the subtree of the given DOM node, that'll be a container. If no
-    // accessibles in subtree then we don't care about the change.
-    Accessible* child = document->GetAccessible(aChildNode);
-    if (!child) {
-      Accessible* container = document->GetContainerAccessible(aChildNode);
-      a11y::TreeWalker walker(container ? container : document, aChildNode,
-                              a11y::TreeWalker::eWalkCache);
-      child = walker.Next();
-    }
-
-    if (child) {
-      MOZ_DIAGNOSTIC_ASSERT(child->Parent(), "Unattached accessible from tree");
-      document->ContentRemoved(child->Parent(), aChildNode);
-#ifdef A11Y_LOG
-      if (logging::IsEnabled(logging::eTree))
-        logging::AccessibleNNode("real container", child->Parent());
-#endif
-    }
+    document->ContentRemoved(aChildNode);
   }
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eTree)) {
     logging::MsgEnd();
     logging::Stack();
   }
 #endif
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1165,20 +1165,17 @@ DocAccessible::ContentRemoved(nsIDocumen
     logging::Node("container node", aContainerNode);
     logging::Node("content node", aChildNode);
     logging::MsgEnd();
   }
 #endif
   // This one and content removal notification from layout may result in
   // double processing of same subtrees. If it pops up in profiling, then
   // consider reusing a document node cache to reject these notifications early.
-  Accessible* container = GetAccessibleOrContainer(aContainerNode);
-  if (container) {
-    UpdateTreeOnRemoval(container, aChildNode);
-  }
+  ContentRemoved(aChildNode);
 }
 
 void
 DocAccessible::ParentChainChanged(nsIContent* aContent)
 {
 }
 
 
@@ -1377,17 +1374,17 @@ DocAccessible::RecreateAccessible(nsICon
 #endif
 
   // XXX: we shouldn't recreate whole accessible subtree, instead we should
   // subclass hide and show events to handle them separately and implement their
   // coalescence with normal hide and show events. Note, in this case they
   // should be coalesced with normal show/hide events.
 
   nsIContent* parent = aContent->GetFlattenedTreeParent();
-  ContentRemoved(parent, aContent);
+  ContentRemoved(aContent);
   ContentInserted(parent, aContent, aContent->GetNextSibling());
 }
 
 void
 DocAccessible::ProcessInvalidationList()
 {
   // Invalidate children of container accessible for each element in
   // invalidation list. Allow invalidation list insertions while container
@@ -1967,43 +1964,46 @@ DocAccessible::FireEventsOnInsertion(Acc
         break;
       }
     }
     while ((ancestor = ancestor->Parent()));
   }
 }
 
 void
-DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
+DocAccessible::ContentRemoved(Accessible* aContent)
 {
-  // If child node is not accessible then look for its accessible children.
-  Accessible* child = GetAccessible(aChildNode);
+  MOZ_DIAGNOSTIC_ASSERT(aContent->Parent(), "Unattached accessible from tree");
+
 #ifdef A11Y_LOG
   logging::TreeInfo("process content removal", 0,
-                    "container", aContainer, "child", aChildNode);
+                    "container", aContent->Parent(), "child", aContent, nullptr);
 #endif
 
-  TreeMutation mt(aContainer);
-  if (child) {
-    mt.BeforeRemoval(child);
-    MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
-    aContainer->RemoveChild(child);
-    UncacheChildrenInSubtree(child);
-    mt.Done();
-    return;
+  TreeMutation mt(aContent->Parent());
+  mt.BeforeRemoval(aContent);
+  aContent->Parent()->RemoveChild(aContent);
+  UncacheChildrenInSubtree(aContent);
+  mt.Done();
+}
+
+void
+DocAccessible::ContentRemoved(nsIContent* aContentNode)
+{
+  // If child node is not accessible then look for its accessible children.
+  Accessible* acc = GetAccessible(aContentNode);
+  if (acc) {
+    ContentRemoved(acc);
   }
-
-  TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
-  while (Accessible* child = walker.Next()) {
-    mt.BeforeRemoval(child);
-    MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
-    aContainer->RemoveChild(child);
-    UncacheChildrenInSubtree(child);
+  else {
+    TreeWalker walker(this, aContentNode);
+    while (Accessible* acc = walker.Next()) {
+      ContentRemoved(acc);
+    }
   }
-  mt.Done();
 }
 
 bool
 DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
 {
   if (!aElement->HasID())
     return false;
 
@@ -2046,17 +2046,17 @@ DocAccessible::ValidateARIAOwned()
         idx--;
         continue;
       }
 
       NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
 
       // If DOM node doesn't have a frame anymore then shutdown its accessible.
       if (child->Parent() && !child->GetFrame()) {
-        UpdateTreeOnRemoval(child->Parent(), child->GetContent());
+        ContentRemoved(child);
         children->RemoveElementAt(idx);
         idx--;
         continue;
       }
 
       NS_ASSERTION(child->Parent() == owner,
                    "Illigally stolen ARIA owned child!");
     }
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -337,28 +337,20 @@ public:
   /**
    * Notify the document accessible that content was inserted.
    */
   void ContentInserted(nsIContent* aContainerNode,
                        nsIContent* aStartChildNode,
                        nsIContent* aEndChildNode);
 
   /**
-   * Notify the document accessible that content was removed.
+   * Update the tree on content removal.
    */
-  void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
-  {
-    // Update the whole tree of this document accessible when the container is
-    // null (document element is removed).
-    UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
-  }
-  void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
-  {
-    ContentRemoved(AccessibleOrTrueContainer(aContainerNode), aChildNode);
-  }
+  void ContentRemoved(Accessible* aContent);
+  void ContentRemoved(nsIContent* aContentNode);
 
   /**
    * Updates accessible tree when rendered text is changed.
    */
   void UpdateText(nsIContent* aTextNode);
 
   /**
    * Recreate an accessible, results in hide/show events pair.
@@ -508,21 +500,16 @@ protected:
    *
    * While children are cached we may encounter the case there's no accessible
    * for referred content by related accessible. Store these related nodes to
    * invalidate their containers later.
    */
   void ProcessInvalidationList();
 
   /**
-   * Update the accessible tree for content removal.
-   */
-  void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
-
-  /**
    * Validates all aria-owns connections and updates the tree accordingly.
    */
   void ValidateARIAOwned();
 
   /**
    * Steals or puts back accessible subtrees.
    */
   void DoARIAOwnsRelocation(Accessible* aOwner);
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -66,17 +66,17 @@
               <hbox id="downloadAndInstall" align="center">
                 <button id="downloadAndInstallButton" align="start"
                         oncommand="gAppUpdater.startDownload();"/>
                         <!-- label and accesskey will be filled by JS -->
                 <spacer flex="1"/>
               </hbox>
               <hbox id="apply" align="center">
                 <button id="updateButton" align="start"
-                        label="&update.updateButton.label2;"
+                        label="&update.updateButton.label3;"
                         accesskey="&update.updateButton.accesskey;"
                         oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
                 <spacer flex="1"/>
               </hbox>
               <hbox id="checkingForUpdates" align="center">
                 <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
               </hbox>
               <hbox id="downloading" align="center">
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -470,17 +470,17 @@
       <description id="update-manual-description">&updateManual.message;
         <label id="update-manual-whats-new" class="text-link" value="&updateManual.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
   <popupnotification id="PanelUI-update-restart-notification"
                      popupid="update-restart"
-                     label="&updateRestart.header.message;"
+                     label="&updateRestart.header.message2;"
                      buttonlabel="&updateRestart.acceptButton.label;"
                      buttonaccesskey="&updateRestart.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateRestart.cancelButton.label;"
                      secondarybuttonaccesskey="&updateRestart.cancelButton.accesskey;"
                      dropmarkerhidden="true"
                      checkboxhidden="true"
                      hidden="true">
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -237,17 +237,17 @@ var SessionSaverInternal = {
     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
       // Don't save (or even collect) anything in permanent private
       // browsing mode
 
       this.updateLastSaveTime();
       return Promise.resolve();
     }
 
-    stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
+    stopWatchStart("COLLECT_DATA_MS");
     let state = SessionStore.getCurrentState(forceUpdateAllWindows);
     PrivacyFilter.filterPrivateWindowsAndTabs(state);
 
     // Make sure we only write worth saving tabs to disk.
     SessionStore.keepOnlyWorthSavingTabs(state);
 
     // Make sure that we keep the previous session if we started with a single
     // private window and no non-private windows have been opened, yet.
@@ -272,17 +272,17 @@ var SessionSaverInternal = {
         delete state._closedWindows[i]._shouldRestore;
         state.windows.unshift(state._closedWindows.pop());
       }
     }
 
     // Clear cookies and storage on clean shutdown.
     this._maybeClearCookiesAndStorage(state);
 
-    stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
+    stopWatchFinish("COLLECT_DATA_MS");
     return this._writeState(state);
   },
 
   /**
    * Purges cookies and DOMSessionStorage data from the session on clean
    * shutdown, only if requested by the user's preferences.
    */
   _maybeClearCookiesAndStorage(state) {
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3133,19 +3133,17 @@ var SessionStoreInternal = {
       windows: total,
       selectedWindow: ix + 1,
       _closedWindows: lastClosedWindowsCopy,
       session,
       global: this._globalState.getState()
     };
 
     // Collect and store session cookies.
-    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
     state.cookies = SessionCookies.collect();
-    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
 
     if (Cu.isModuleLoaded("resource://devtools/client/scratchpad/scratchpad-manager.jsm")) {
       // get open Scratchpad window states too
       let scratchpads = ScratchpadManager.getSessionState();
       if (scratchpads && scratchpads.length) {
         state.scratchpads = scratchpads;
       }
     }
--- a/browser/extensions/activity-stream/bootstrap.js
+++ b/browser/extensions/activity-stream/bootstrap.js
@@ -1,29 +1,32 @@
 /* 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/. */
-/* globals Components, XPCOMUtils, Preferences, ActivityStream */
+/* globals Components, XPCOMUtils, Preferences, Services, ActivityStream */
 "use strict";
 
 const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ActivityStream",
   "resource://activity-stream/lib/ActivityStream.jsm");
 
+const BROWSER_READY_NOTIFICATION = "browser-ui-startup-complete";
 const ACTIVITY_STREAM_ENABLED_PREF = "browser.newtabpage.activity-stream.enabled";
 const REASON_STARTUP_ON_PREF_CHANGE = "PREF_ON";
 const REASON_SHUTDOWN_ON_PREF_CHANGE = "PREF_OFF";
 
 const ACTIVITY_STREAM_OPTIONS = {newTabURL: "about:newtab"};
 
 let activityStream;
 let startupData;
+let startupReason;
 
 /**
  * init - Initializes an instance of ActivityStream. This could be called by
  *        the startup() function exposed by bootstrap.js, or it could be called
  *        when ACTIVITY_STREAM_ENABLED_PREF is changed from false to true.
  *
  * @param  {string} reason - Reason for initialization. Could be install, upgrade, or PREF_ON
  */
@@ -59,36 +62,47 @@ function uninit(reason) {
 function onPrefChanged(isEnabled) {
   if (isEnabled) {
     init(REASON_STARTUP_ON_PREF_CHANGE);
   } else {
     uninit(REASON_SHUTDOWN_ON_PREF_CHANGE);
   }
 }
 
+function observe(subject, topic, data) {
+  switch (topic) {
+    case BROWSER_READY_NOTIFICATION:
+      // Listen for changes to the pref that enables Activity Stream
+      Preferences.observe(ACTIVITY_STREAM_ENABLED_PREF, onPrefChanged);
+      // Only initialize if the pref is true
+      if (Preferences.get(ACTIVITY_STREAM_ENABLED_PREF)) {
+        init(startupReason);
+        Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
+      }
+      break;
+  }
+}
+
 // The functions below are required by bootstrap.js
 
 this.install = function install(data, reason) {};
 
 this.startup = function startup(data, reason) {
+  // Only start Activity Stream up when the browser UI is ready
+  Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
+
   // Cache startup data which contains stuff like the version number, etc.
   // so we can use it when we init
   startupData = data;
-
-  // Listen for changes to the pref that enables Activity Stream
-  Preferences.observe(ACTIVITY_STREAM_ENABLED_PREF, onPrefChanged);
-
-  // Only initialize if the pref is true
-  if (Preferences.get(ACTIVITY_STREAM_ENABLED_PREF)) {
-    init(reason);
-  }
+  startupReason = reason;
 };
 
 this.shutdown = function shutdown(data, reason) {
   // Uninitialize Activity Stream
   startupData = null;
+  startupReason = null;
   uninit(reason);
 
   // Stop listening to the pref that enables Activity Stream
   Preferences.ignore(ACTIVITY_STREAM_ENABLED_PREF, onPrefChanged);
 };
 
 this.uninstall = function uninstall(data, reason) {};
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -1,22 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-this.MAIN_MESSAGE_TYPE = "ActivityStream:Main";
-this.CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
+const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
+const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
 
-this.actionTypes = [
+const actionTypes = [
   "INIT",
   "UNINIT",
   "NEW_TAB_INITIAL_STATE",
   "NEW_TAB_LOAD",
-  "NEW_TAB_UNLOAD"
+  "NEW_TAB_UNLOAD",
+  "PERFORM_SEARCH",
+  "SCREENSHOT_UPDATED",
+  "SEARCH_STATE_UPDATED",
+  "TOP_SITES_UPDATED"
 // The line below creates an object like this:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 // It prevents accidentally adding a different key/value name.
 ].reduce((obj, type) => { obj[type] = type; return obj; }, {});
 
@@ -81,16 +85,18 @@ function SendToContent(action, target) {
   }
   return _RouteMessage(action, {
     from: MAIN_MESSAGE_TYPE,
     to: CONTENT_MESSAGE_TYPE,
     toTarget: target
   });
 }
 
+this.actionTypes = actionTypes;
+
 this.actionCreators = {
   SendToMain,
   SendToContent,
   BroadcastToContent
 };
 
 // These are helpers to test for certain kinds of actions
 this.actionUtils = {
--- a/browser/extensions/activity-stream/common/Reducers.jsm
+++ b/browser/extensions/activity-stream/common/Reducers.jsm
@@ -1,44 +1,65 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-this.INITIAL_STATE = {
+const {actionTypes: at} = Components.utils.import("resource://activity-stream/common/Actions.jsm", {});
+
+const INITIAL_STATE = {
   TopSites: {
-    rows: [
-      {
-        "title": "Facebook",
-        "url": "https://www.facebook.com/"
-      },
-      {
-        "title": "YouTube",
-        "url": "https://www.youtube.com/"
-      },
-      {
-        "title": "Amazon",
-        "url": "http://www.amazon.com/"
-      },
-      {
-        "title": "Yahoo",
-        "url": "https://www.yahoo.com/"
-      },
-      {
-        "title": "eBay",
-        "url": "http://www.ebay.com"
-      },
-      {
-        "title": "Twitter",
-        "url": "https://twitter.com/"
-      }
-    ]
+    init: false,
+    rows: []
+  },
+  Search: {
+    currentEngine: {
+      name: "",
+      icon: ""
+    },
+    engines: []
   }
 };
 
 // TODO: Handle some real actions here, once we have a TopSites feed working
 function TopSites(prevState = INITIAL_STATE.TopSites, action) {
-  return prevState;
+  let hasMatch;
+  let newRows;
+  switch (action.type) {
+    case at.TOP_SITES_UPDATED:
+      if (!action.data) {
+        return prevState;
+      }
+      return Object.assign({}, prevState, {init: true, rows: action.data});
+    case at.SCREENSHOT_UPDATED:
+      newRows = prevState.rows.map(row => {
+        if (row.url === action.data.url) {
+          hasMatch = true;
+          return Object.assign({}, row, {screenshot: action.data.screenshot});
+        }
+        return row;
+      });
+      return hasMatch ? Object.assign({}, prevState, {rows: newRows}) : prevState;
+    default:
+      return prevState;
+  }
 }
 
-this.reducers = {TopSites};
+function Search(prevState = INITIAL_STATE.Search, action) {
+  switch (action.type) {
+    case at.SEARCH_STATE_UPDATED: {
+      if (!action.data) {
+        return prevState;
+      }
+      let {currentEngine, engines} = action.data;
+      return Object.assign({}, prevState, {
+        currentEngine,
+        engines
+      });
+    }
+    default:
+      return prevState;
+  }
+}
+this.INITIAL_STATE = INITIAL_STATE;
+this.reducers = {TopSites, Search};
 
 this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -0,0 +1,570 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+/******/
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 10);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports) {
+
+module.exports = React;
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports) {
+
+module.exports = ReactRedux;
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
+const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
+
+const actionTypes = ["INIT", "UNINIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "PERFORM_SEARCH", "SCREENSHOT_UPDATED", "SEARCH_STATE_UPDATED", "TOP_SITES_UPDATED"
+// The line below creates an object like this:
+// {
+//   INIT: "INIT",
+//   UNINIT: "UNINIT"
+// }
+// It prevents accidentally adding a different key/value name.
+].reduce((obj, type) => {
+  obj[type] = type;return obj;
+}, {});
+
+// Helper function for creating routed actions between content and main
+// Not intended to be used by consumers
+function _RouteMessage(action, options) {
+  const meta = action.meta ? Object.assign({}, action.meta) : {};
+  if (!options || !options.from || !options.to) {
+    throw new Error("Routed Messages must have options as the second parameter, and must at least include a .from and .to property.");
+  }
+  // For each of these fields, if they are passed as an option,
+  // add them to the action. If they are not defined, remove them.
+  ["from", "to", "toTarget", "fromTarget", "skipOrigin"].forEach(o => {
+    if (typeof options[o] !== "undefined") {
+      meta[o] = options[o];
+    } else if (meta[o]) {
+      delete meta[o];
+    }
+  });
+  return Object.assign({}, action, { meta });
+}
+
+/**
+ * SendToMain - Creates a message that will be sent to the Main process.
+ *
+ * @param  {object} action Any redux action (required)
+ * @param  {object} options
+ * @param  {string} options.fromTarget The id of the content port from which the action originated. (optional)
+ * @return {object} An action with added .meta properties
+ */
+function SendToMain(action) {
+  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+  return _RouteMessage(action, {
+    from: CONTENT_MESSAGE_TYPE,
+    to: MAIN_MESSAGE_TYPE,
+    fromTarget: options.fromTarget
+  });
+}
+
+/**
+ * BroadcastToContent - Creates a message that will be sent to ALL content processes.
+ *
+ * @param  {object} action Any redux action (required)
+ * @return {object} An action with added .meta properties
+ */
+function BroadcastToContent(action) {
+  return _RouteMessage(action, {
+    from: MAIN_MESSAGE_TYPE,
+    to: CONTENT_MESSAGE_TYPE
+  });
+}
+
+/**
+ * SendToContent - Creates a message that will be sent to a particular Content process.
+ *
+ * @param  {object} action Any redux action (required)
+ * @param  {string} target The id of a content port
+ * @return {object} An action with added .meta properties
+ */
+function SendToContent(action, target) {
+  if (!target) {
+    throw new Error("You must provide a target ID as the second parameter of SendToContent. If you want to send to all content processes, use BroadcastToContent");
+  }
+  return _RouteMessage(action, {
+    from: MAIN_MESSAGE_TYPE,
+    to: CONTENT_MESSAGE_TYPE,
+    toTarget: target
+  });
+}
+
+var actionCreators = {
+  SendToMain,
+  SendToContent,
+  BroadcastToContent
+};
+
+// These are helpers to test for certain kinds of actions
+
+var actionUtils = {
+  isSendToMain(action) {
+    if (!action.meta) {
+      return false;
+    }
+    return action.meta.to === MAIN_MESSAGE_TYPE && action.meta.from === CONTENT_MESSAGE_TYPE;
+  },
+  isBroadcastToContent(action) {
+    if (!action.meta) {
+      return false;
+    }
+    if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
+      return true;
+    }
+    return false;
+  },
+  isSendToContent(action) {
+    if (!action.meta) {
+      return false;
+    }
+    if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
+      return true;
+    }
+    return false;
+  },
+  _RouteMessage
+};
+module.exports = {
+  actionTypes,
+  actionCreators,
+  actionUtils,
+  MAIN_MESSAGE_TYPE,
+  CONTENT_MESSAGE_TYPE
+};
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const React = __webpack_require__(0);
+const TopSites = __webpack_require__(8);
+const Search = __webpack_require__(7);
+
+const Base = () => React.createElement(
+  "div",
+  { className: "outer-wrapper" },
+  React.createElement(
+    "main",
+    null,
+    React.createElement(Search, null),
+    React.createElement(TopSites, null)
+  )
+);
+
+module.exports = Base;
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* globals sendAsyncMessage, addMessageListener */
+
+var _require = __webpack_require__(9);
+
+const createStore = _require.createStore,
+      combineReducers = _require.combineReducers,
+      applyMiddleware = _require.applyMiddleware;
+
+var _require2 = __webpack_require__(2);
+
+const au = _require2.actionUtils;
+
+
+const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
+const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
+const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
+
+/**
+ * A higher-order function which returns a reducer that, on MERGE_STORE action,
+ * will return the action.data object merged into the previous state.
+ *
+ * For all other actions, it merely calls mainReducer.
+ *
+ * Because we want this to merge the entire state object, it's written as a
+ * higher order function which takes the main reducer (itself often a call to
+ * combineReducers) as a parameter.
+ *
+ * @param  {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
+ * @return {function}             a reducer that, on MERGE_STORE_ACTION action,
+ *                                will return the action.data object merged
+ *                                into the previous state, and the result
+ *                                of calling mainReducer otherwise.
+ */
+function mergeStateReducer(mainReducer) {
+  return (prevState, action) => {
+    if (action.type === MERGE_STORE_ACTION) {
+      return Object.assign({}, prevState, action.data);
+    }
+
+    return mainReducer(prevState, action);
+  };
+}
+
+/**
+ * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
+ */
+const messageMiddleware = store => next => action => {
+  if (au.isSendToMain(action)) {
+    sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
+  }
+  next(action);
+};
+
+/**
+ * initStore - Create a store and listen for incoming actions
+ *
+ * @param  {object} reducers An object containing Redux reducers
+ * @return {object}          A redux store
+ */
+module.exports = function initStore(reducers) {
+  const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware));
+
+  addMessageListener(INCOMING_MESSAGE_NAME, msg => {
+    store.dispatch(msg.data);
+  });
+
+  return store;
+};
+
+module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION;
+module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
+module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
+
+/***/ }),
+/* 5 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+var _require = __webpack_require__(2);
+
+const at = _require.actionTypes;
+
+
+const INITIAL_STATE = {
+  TopSites: {
+    init: false,
+    rows: []
+  },
+  Search: {
+    currentEngine: {
+      name: "",
+      icon: ""
+    },
+    engines: []
+  }
+};
+
+// TODO: Handle some real actions here, once we have a TopSites feed working
+function TopSites() {
+  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites;
+  let action = arguments[1];
+
+  let hasMatch;
+  let newRows;
+  switch (action.type) {
+    case at.TOP_SITES_UPDATED:
+      if (!action.data) {
+        return prevState;
+      }
+      return Object.assign({}, prevState, { init: true, rows: action.data });
+    case at.SCREENSHOT_UPDATED:
+      newRows = prevState.rows.map(row => {
+        if (row.url === action.data.url) {
+          hasMatch = true;
+          return Object.assign({}, row, { screenshot: action.data.screenshot });
+        }
+        return row;
+      });
+      return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState;
+    default:
+      return prevState;
+  }
+}
+
+function Search() {
+  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Search;
+  let action = arguments[1];
+
+  switch (action.type) {
+    case at.SEARCH_STATE_UPDATED:
+      {
+        if (!action.data) {
+          return prevState;
+        }
+        var _action$data = action.data;
+        let currentEngine = _action$data.currentEngine,
+            engines = _action$data.engines;
+
+        return Object.assign({}, prevState, {
+          currentEngine,
+          engines
+        });
+      }
+    default:
+      return prevState;
+  }
+}
+var reducers = { TopSites, Search };
+module.exports = {
+  reducers,
+  INITIAL_STATE
+};
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports) {
+
+module.exports = ReactDOM;
+
+/***/ }),
+/* 7 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const React = __webpack_require__(0);
+
+var _require = __webpack_require__(1);
+
+const connect = _require.connect;
+
+var _require2 = __webpack_require__(2);
+
+const actionTypes = _require2.actionTypes,
+      actionCreators = _require2.actionCreators;
+
+
+const Search = React.createClass({
+  displayName: "Search",
+
+  getInitialState() {
+    return { searchString: "" };
+  },
+  performSearch(options) {
+    let searchData = {
+      engineName: options.engineName,
+      searchString: options.searchString,
+      searchPurpose: "newtab",
+      healthReportKey: "newtab"
+    };
+    this.props.dispatch(actionCreators.SendToMain({ type: actionTypes.PERFORM_SEARCH, data: searchData }));
+  },
+  onClick(event) {
+    const currentEngine = this.props.Search.currentEngine;
+
+    event.preventDefault();
+    this.performSearch({ engineName: currentEngine.name, searchString: this.state.searchString });
+  },
+  onChange(event) {
+    this.setState({ searchString: event.target.value });
+  },
+  render() {
+    return React.createElement(
+      "form",
+      { className: "search-wrapper" },
+      React.createElement("span", { className: "search-label" }),
+      React.createElement("input", { value: this.state.searchString, type: "search",
+        onChange: this.onChange,
+        maxLength: "256", title: "Submit search",
+        placeholder: "Search the Web" }),
+      React.createElement("button", { onClick: this.onClick })
+    );
+  }
+});
+
+module.exports = connect(state => ({ Search: state.Search }))(Search);
+
+/***/ }),
+/* 8 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const React = __webpack_require__(0);
+
+var _require = __webpack_require__(1);
+
+const connect = _require.connect;
+
+
+function displayURL(url) {
+  return new URL(url).hostname.replace(/^www./, "");
+}
+
+const TopSites = props => React.createElement(
+  "section",
+  null,
+  React.createElement(
+    "h3",
+    { className: "section-title" },
+    "Top Sites"
+  ),
+  React.createElement(
+    "ul",
+    { className: "top-sites-list" },
+    props.TopSites.rows.map(link => {
+      const title = displayURL(link.url);
+      const className = `screenshot${link.screenshot ? " active" : ""}`;
+      const style = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
+      return React.createElement(
+        "li",
+        { key: link.url },
+        React.createElement(
+          "a",
+          { href: link.url },
+          React.createElement(
+            "div",
+            { className: "tile" },
+            React.createElement(
+              "span",
+              { className: "letter-fallback", ariaHidden: true },
+              title[0]
+            ),
+            React.createElement("div", { className: className, style: style })
+          ),
+          React.createElement(
+            "div",
+            { className: "title" },
+            title
+          )
+        )
+      );
+    })
+  )
+);
+
+module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSites);
+
+/***/ }),
+/* 9 */
+/***/ (function(module, exports) {
+
+module.exports = Redux;
+
+/***/ }),
+/* 10 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* globals addMessageListener, removeMessageListener */
+const React = __webpack_require__(0);
+const ReactDOM = __webpack_require__(6);
+const Base = __webpack_require__(3);
+
+var _require = __webpack_require__(1);
+
+const Provider = _require.Provider;
+
+const initStore = __webpack_require__(4);
+
+var _require2 = __webpack_require__(5);
+
+const reducers = _require2.reducers;
+
+
+const store = initStore(reducers);
+
+ReactDOM.render(React.createElement(
+  Provider,
+  { store: store },
+  React.createElement(Base, null)
+), document.getElementById("root"));
+
+/***/ })
+/******/ ]);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -0,0 +1,334 @@
+html {
+  box-sizing: border-box; }
+
+*,
+*::before,
+*::after {
+  box-sizing: inherit; }
+
+body {
+  margin: 0; }
+
+button,
+input {
+  font-family: inherit;
+  font-size: inherit; }
+
+[hidden] {
+  display: none !important; }
+
+html,
+body,
+#root {
+  height: 100%; }
+
+body {
+  background: #F6F6F8;
+  color: #383E49;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif;
+  font-size: 16px; }
+
+h1,
+h2 {
+  font-weight: normal; }
+
+a {
+  color: #00AFF7;
+  text-decoration: none; }
+  a:hover {
+    color: #2bc1ff; }
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0; }
+
+.inner-border {
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  border-radius: 3px;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  z-index: 100; }
+
+@keyframes fadeIn {
+  from {
+    opacity: 0; }
+  to {
+    opacity: 1; } }
+
+.show-on-init {
+  opacity: 0;
+  transition: opacity 0.2s ease-in; }
+  .show-on-init.on {
+    opacity: 1;
+    animation: fadeIn 0.2s; }
+
+.actions {
+  border-top: solid 1px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: row;
+  margin: 0;
+  padding: 15px 25px;
+  justify-content: flex-start; }
+  .actions button {
+    background: #FBFBFB;
+    border: solid 1px #BFBFBF;
+    border-radius: 5px;
+    color: #858585;
+    cursor: pointer;
+    padding: 10px 30px; }
+    .actions button:hover {
+      box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
+      transition: box-shadow 150ms; }
+    .actions button.done {
+      background: #0695F9;
+      border: solid 1px #1677CF;
+      color: #FFF;
+      margin-inline-start: auto; }
+
+.outer-wrapper {
+  display: flex;
+  flex-grow: 1;
+  padding: 62px 32px 32px;
+  height: 100%; }
+
+main {
+  margin: auto; }
+  @media (min-width: 672px) {
+    main {
+      width: 608px; } }
+  @media (min-width: 800px) {
+    main {
+      width: 736px; } }
+  main section {
+    margin-bottom: 41px; }
+
+.section-title {
+  color: #6E707E;
+  font-size: 13px;
+  font-weight: bold;
+  text-transform: uppercase;
+  margin: 0 0 18px; }
+
+.top-sites-list {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  margin-inline-end: -32px; }
+  @media (min-width: 672px) {
+    .top-sites-list {
+      width: 640px; } }
+  @media (min-width: 800px) {
+    .top-sites-list {
+      width: 768px; } }
+  .top-sites-list li {
+    display: inline-block;
+    margin: 0 0 18px;
+    margin-inline-end: 32px; }
+  .top-sites-list a {
+    display: block;
+    color: inherit; }
+  .top-sites-list .tile {
+    position: relative;
+    height: 96px;
+    width: 96px;
+    border-radius: 6px;
+    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+    color: #A0A0A0;
+    font-weight: 200;
+    font-size: 32px;
+    text-transform: uppercase;
+    display: flex;
+    align-items: center;
+    justify-content: center; }
+    .top-sites-list .tile:hover {
+      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
+      transition: box-shadow 150ms; }
+  .top-sites-list .screenshot {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    background-color: #FFF;
+    border-radius: 6px;
+    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+    background-size: 250%;
+    background-position: top left;
+    transition: opacity 1s;
+    opacity: 0; }
+    .top-sites-list .screenshot.active {
+      opacity: 1; }
+  .top-sites-list .title {
+    height: 30px;
+    line-height: 30px;
+    text-align: center;
+    white-space: nowrap;
+    font-size: 11px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 96px; }
+
+.search-wrapper {
+  cursor: default;
+  display: flex;
+  position: relative;
+  margin: 0 0 48px;
+  width: 100%;
+  height: 36px; }
+  .search-wrapper .search-container {
+    z-index: 1001;
+    background: #FFF;
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 100%;
+    margin-top: -2px;
+    border: 1px solid #BFBFBF;
+    font-size: 12px;
+    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
+    overflow: hidden; }
+    .search-wrapper .search-container .search-title {
+      color: #666;
+      padding: 5px 10px;
+      background-color: #F7F7F7;
+      display: flex;
+      align-items: center;
+      word-break: break-all; }
+      .search-wrapper .search-container .search-title p {
+        margin: 0; }
+      .search-wrapper .search-container .search-title #current-engine-icon {
+        margin-inline-end: 8px; }
+    .search-wrapper .search-container section {
+      border-bottom: 1px solid #EAEAEA;
+      margin-bottom: 0; }
+    .search-wrapper .search-container .search-suggestions ul {
+      padding: 0;
+      margin: 0;
+      list-style: none; }
+      .search-wrapper .search-container .search-suggestions ul li a {
+        cursor: default;
+        color: #000;
+        display: block;
+        padding: 4px 36px; }
+        .search-wrapper .search-container .search-suggestions ul li a:hover, .search-wrapper .search-container .search-suggestions ul li a.active {
+          background: #0996F8;
+          color: #FFF; }
+    .search-wrapper .search-container .history-search-suggestions {
+      border-bottom: 0; }
+      .search-wrapper .search-container .history-search-suggestions ul {
+        padding: 0;
+        margin: 0;
+        list-style: none; }
+        .search-wrapper .search-container .history-search-suggestions ul li a {
+          cursor: default;
+          color: #000;
+          display: block;
+          padding: 4px 10px; }
+          .search-wrapper .search-container .history-search-suggestions ul li a:hover, .search-wrapper .search-container .history-search-suggestions ul li a.active {
+            background: #0996F8;
+            color: #FFF; }
+          .search-wrapper .search-container .history-search-suggestions ul li a:hover > #historyIcon,
+          .search-wrapper .search-container .history-search-suggestions ul li a.active > #historyIcon {
+            background-image: url("assets/glyph-search-history.svg#search-history-active"); }
+    .search-wrapper .search-container .history-search-suggestions #historyIcon {
+      width: 16px;
+      height: 16px;
+      display: inline-block;
+      margin-inline-end: 10px;
+      margin-bottom: -3px;
+      background-image: url("assets/glyph-search-history.svg#search-history"); }
+    .search-wrapper .search-container .search-partners ul {
+      padding: 0;
+      margin: 0;
+      list-style: none; }
+      .search-wrapper .search-container .search-partners ul li {
+        display: inline-block;
+        padding: 5px 0; }
+        .search-wrapper .search-container .search-partners ul li a {
+          display: block;
+          padding: 3px 16px;
+          border-right: 1px solid #BFBFBF; }
+        .search-wrapper .search-container .search-partners ul li:hover, .search-wrapper .search-container .search-partners ul li.active {
+          background: #0996F8;
+          color: #FFF; }
+          .search-wrapper .search-container .search-partners ul li:hover a, .search-wrapper .search-container .search-partners ul li.active a {
+            border-color: transparent; }
+    .search-wrapper .search-container .search-settings button {
+      color: #666;
+      margin: 0;
+      padding: 0;
+      height: 32px;
+      text-align: center;
+      width: 100%;
+      border-style: solid none none;
+      border-radius: 0;
+      background: #F7F7F7;
+      border-top: 0; }
+      .search-wrapper .search-container .search-settings button:hover, .search-wrapper .search-container .search-settings button.active {
+        background: #EBEBEB;
+        box-shadow: none; }
+  .search-wrapper input {
+    border: 0;
+    box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
+    flex-grow: 1;
+    margin: 0;
+    outline: none;
+    padding: 0 12px 0 35px;
+    height: 100%;
+    border-top-left-radius: 4px;
+    border-bottom-left-radius: 4px;
+    padding-inline-start: 35px; }
+    .search-wrapper input:focus {
+      border-color: #0996F8;
+      box-shadow: 0 0 0 2px #0996F8;
+      transition: box-shadow 150ms;
+      z-index: 1; }
+    .search-wrapper input:focus + button {
+      z-index: 1;
+      transition: box-shadow 150ms;
+      box-shadow: 0 0 0 2px #0996F8;
+      background-color: #0996F8;
+      background-image: url("assets/glyph-forward-16-white.svg");
+      color: #FFF; }
+  .search-wrapper input:dir(rtl) {
+    border-radius: 0 4px 4px 0; }
+  .search-wrapper .search-label {
+    background: url("assets/glyph-search-16.svg") no-repeat center center/20px;
+    position: absolute;
+    top: 0;
+    offset-inline-start: 0;
+    height: 100%;
+    width: 35px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 2; }
+  .search-wrapper button {
+    border-radius: 0 3px 3px 0;
+    margin-inline-start: -1px;
+    border: 0;
+    width: 36px;
+    padding: 0;
+    transition: box-shadow 150ms;
+    box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
+    background: #FFF url("assets/glyph-forward-16.svg") no-repeat center center;
+    background-size: 16px 16px; }
+    .search-wrapper button:hover {
+      z-index: 1;
+      transition: box-shadow 150ms;
+      box-shadow: 0 1px 0 0 rgba(0, 0, 1, 0.5);
+      background-color: #0996F8;
+      background-image: url("assets/glyph-forward-16-white.svg");
+      color: #FFF; }
+  .search-wrapper button:dir(rtl) {
+    transform: scaleX(-1); }
--- a/browser/extensions/activity-stream/data/content/activity-stream.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream.html
@@ -1,31 +1,16 @@
 <!doctype html>
 <html lang="en-us" dir="ltr">
   <head>
     <meta charset="utf-8">
     <title>New Tab</title>
+    <link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
   </head>
-  <body>
-    <div id="root">
-      <h1>New Tab</h1>
-      <ul id="top-sites"></ul>
-    </div>
-    <script>
-      const topSitesEl = document.getElementById("top-sites");
-      window.addMessageListener("ActivityStream:MainToContent", msg => {
-        if (msg.data.type === "NEW_TAB_INITIAL_STATE") {
-          const fragment = document.createDocumentFragment()
-          for (const row of msg.data.data.TopSites.rows) {
-            const li = document.createElement("li");
-            const a = document.createElement("a");
-            a.href = row.url;
-            a.textContent = row.title;
-            li.appendChild(a);
-            fragment.appendChild(li);
-          }
-          topSitesEl.appendChild(fragment);
-        }
-      });
-
-    </script>
+  <body class="activity-stream">
+    <div id="root"></div>
+    <script src="resource://activity-stream/vendor/react.js"></script>
+    <script src="resource://activity-stream/vendor/react-dom.js"></script>
+    <script src="resource://activity-stream/vendor/redux.js"></script>
+    <script src="resource://activity-stream/vendor/react-redux.js"></script>
+    <script src="resource://activity-stream/data/content/activity-stream.bundle.js"></script>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/assets/glyph-forward-16-white.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <title>Forward - 16</title>
+  <g>
+    <polyline points="9 2 15 8 9 14" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+    <line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/assets/glyph-forward-16.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <title>Forward - 16</title>
+  <g>
+    <polyline points="9 2 15 8 9 14" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+    <line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/assets/glyph-search-16.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: #a0a0a0;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <g id="glyph-search-16">
+    <path id="Icon_-_Search_-_16" data-name="Icon - Search - 16" class="cls-1" d="M226.989,348.571l-2.2,2.2-9.533-9.534a11.436,11.436,0,1,1,2.2-2.2ZM208.37,323.745a8.407,8.407,0,1,0,8.406,8.406A8.406,8.406,0,0,0,208.37,323.745Z" transform="translate(-196 -320)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/assets/glyph-search-history.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+  <style>
+    use:not(:target) {
+      display: none;
+    }
+    use {
+      fill: graytext;
+    }
+    use[id$="-active"] {
+      fill: HighlightText;
+    }
+  </style>
+  <defs>
+    <path id="search-history-glyph" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7 C15,4.1,11.9,1,8,1z M8,13.3c-2.9,0-5.3-2.4-5.3-5.3S5.1,2.7,8,2.7c2.9,0,5.3,2.4,5.3,5.3S10.9,13.3,8,13.3z M10.5,7H9V5 c0-0.6-0.4-1-1-1S7,4.4,7,5v3c0,0.6,0.4,1,1,1h2.5c0.6,0,1-0.4,1-1C11.5,7.4,11.1,7,10.5,7z"/>
+  </defs>
+  <use id="search-history" xlink:href="#search-history-glyph"/>
+  <use id="search-history-active" xlink:href="#search-history-glyph"/>
+</svg>
--- a/browser/extensions/activity-stream/jar.mn
+++ b/browser/extensions/activity-stream/jar.mn
@@ -2,9 +2,13 @@
 # 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/.
 
 [features/activity-stream@mozilla.org] chrome.jar:
 % resource activity-stream %content/
   content/lib/ (./lib/*)
   content/common/ (./common/*)
   content/vendor/Redux.jsm (./vendor/Redux.jsm)
+  content/vendor/react.js (./vendor/react.js)
+  content/vendor/react-dom.js (./vendor/react-dom.js)
+  content/vendor/redux.js (./vendor/redux.js)
+  content/vendor/react-redux.js (./vendor/react-redux.js)
   content/data/ (./data/*)
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -1,39 +1,62 @@
 /* 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/. */
+/* globals XPCOMUtils, NewTabInit, TopSitesFeed, SearchFeed */
+
 "use strict";
 
 const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 const {Store} = Cu.import("resource://activity-stream/lib/Store.jsm", {});
+const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 
 // Feeds
-const {NewTabInit} = Cu.import("resource://activity-stream/lib/NewTabInit.jsm", {});
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabInit",
+  "resource://activity-stream/lib/NewTabInit.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TopSitesFeed",
+  "resource://activity-stream/lib/TopSitesFeed.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SearchFeed",
+  "resource://activity-stream/lib/SearchFeed.jsm");
+
+const feeds = {
+  // When you add a feed here:
+  // 1. The key in this object should directly refer to a pref, not including the
+  //    prefix (so "feeds.newtabinit" refers to the
+  //    "browser.newtabpage.activity-stream.feeds.newtabinit" pref)
+  // 2. The value should be a function that returns a feed.
+  // 3. You should use XPCOMUtils.defineLazyModuleGetter to import the Feed,
+  //    so it isn't loaded until the feed is enabled.
+  "feeds.newtabinit": () => new NewTabInit(),
+  "feeds.topsites": () => new TopSitesFeed(),
+  "feeds.search": () => new SearchFeed()
+};
 
 this.ActivityStream = class ActivityStream {
 
   /**
    * constructor - Initializes an instance of ActivityStream
    *
    * @param  {object} options Options for the ActivityStream instance
    * @param  {string} options.id Add-on ID. e.g. "activity-stream@mozilla.org".
    * @param  {string} options.version Version of the add-on. e.g. "0.1.0"
    * @param  {string} options.newTabURL URL of New Tab page on which A.S. is displayed. e.g. "about:newtab"
    */
   constructor(options) {
     this.initialized = false;
     this.options = options;
     this.store = new Store();
+    this.feeds = feeds;
   }
   init() {
     this.initialized = true;
-    this.store.init([
-      new NewTabInit()
-    ]);
+    this.store.init(this.feeds);
+    this.store.dispatch({type: at.INIT});
   }
   uninit() {
+    this.store.dispatch({type: at.UNINIT});
     this.store.uninit();
     this.initialized = false;
   }
 };
 
 this.EXPORTED_SYMBOLS = ["ActivityStream"];
--- a/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
@@ -28,16 +28,17 @@ const DEFAULT_OPTIONS = {
     throw new Error(`\nMessageChannel: Received action ${action.type}, but no dispatcher was defined.\n`);
   },
   pageURL: ABOUT_NEW_TAB_URL,
   outgoingMessageName: "ActivityStream:MainToContent",
   incomingMessageName: "ActivityStream:ContentToMain"
 };
 
 this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
+
   /**
    * ActivityStreamMessageChannel - This module connects a Redux store to a RemotePageManager in Firefox.
    *                  Call .createChannel to start the connection, and .destroyChannel to destroy it.
    *                  You should use the BroadcastToContent, SendToContent, and SendToMain action creators
    *                  in common/Actions.jsm to help you create actions that will be automatically routed
    *                  to the correct location.
    *
    * @param  {object} options
@@ -178,20 +179,24 @@ this.ActivityStreamMessageChannel = clas
    * onMessage - Handles custom messages from content. It expects all messages to
    *             be formatted as Redux actions, and dispatches them to this.store
    *
    * @param  {obj} msg A custom message from content
    * @param  {obj} msg.action A Redux action (e.g. {type: "HELLO_WORLD"})
    * @param  {obj} msg.target A message target
    */
   onMessage(msg) {
-    const action = msg.data;
     const {portID} = msg.target;
-    if (!action || !action.type) {
+    if (!msg.data || !msg.data.type) {
       Cu.reportError(new Error(`Received an improperly formatted message from ${portID}`));
       return;
     }
-    this.onActionFromContent(action, msg.target.portID);
+    let action = {};
+    Object.assign(action, msg.data);
+    // target is used to access a browser reference that came from the content
+    // and should only be used in feeds (not reducers)
+    action._target = msg.target;
+    this.onActionFromContent(action, portID);
   }
 }
 
 this.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
 this.EXPORTED_SYMBOLS = ["ActivityStreamMessageChannel", "DEFAULT_OPTIONS"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/lib/SearchFeed.jsm
@@ -0,0 +1,65 @@
+/* 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/. */
+ /* globals ContentSearch, XPCOMUtils, Services */
+"use strict";
+
+const {utils: Cu} = Components;
+const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
+const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
+  "resource:///modules/ContentSearch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+
+this.SearchFeed = class SearchFeed {
+  addObservers() {
+    Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC);
+  }
+  removeObservers() {
+    Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
+  }
+  observe(subject, topic, data) {
+    switch (topic) {
+      case SEARCH_ENGINE_TOPIC:
+        if (data !== "engine-default") {
+          this.getState();
+        }
+        break;
+    }
+  }
+  async getState() {
+    const state = await ContentSearch.currentStateObj(true);
+    const engines = state.engines.map(engine => ({
+      name: engine.name,
+      icon: engine.iconBuffer
+    }));
+    const currentEngine = {
+      name: state.currentEngine.name,
+      icon: state.currentEngine.iconBuffer
+    };
+    const action = {type: at.SEARCH_STATE_UPDATED, data: {engines, currentEngine}};
+    this.store.dispatch(ac.BroadcastToContent(action));
+  }
+  performSearch(browser, data) {
+    ContentSearch.performSearch({target: browser}, data);
+  }
+  onAction(action) {
+    switch (action.type) {
+      case at.INIT:
+        this.addObservers();
+        this.getState();
+        break;
+      case at.PERFORM_SEARCH:
+        this.performSearch(action._target.browser, action.data);
+        break;
+      case at.UNINIT:
+        this.removeObservers();
+        break;
+    }
+  }
+};
+this.EXPORTED_SYMBOLS = ["SearchFeed"];
--- a/browser/extensions/activity-stream/lib/Store.jsm
+++ b/browser/extensions/activity-stream/lib/Store.jsm
@@ -1,20 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* global Preferences */
 "use strict";
 
 const {utils: Cu} = Components;
 
 const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {});
-const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
 const {ActivityStreamMessageChannel} = Cu.import("resource://activity-stream/lib/ActivityStreamMessageChannel.jsm", {});
 
+const PREF_PREFIX = "browser.newtabpage.activity-stream.";
+Cu.import("resource://gre/modules/Preferences.jsm");
+
 /**
  * Store - This has a similar structure to a redux store, but includes some extra
  *         functionality to allow for routing of actions between the Main processes
  *         and child processes via a ActivityStreamMessageChannel.
  *         It also accepts an array of "Feeds" on inititalization, which
  *         can listen for any action that is dispatched through the store.
  */
 this.Store = class Store {
@@ -27,17 +30,19 @@ this.Store = class Store {
     this._middleware = this._middleware.bind(this);
     // Bind each redux method so we can call it directly from the Store. E.g.,
     // store.dispatch() will call store._store.dispatch();
     ["dispatch", "getState", "subscribe"].forEach(method => {
       this[method] = function(...args) {
         return this._store[method](...args);
       }.bind(this);
     });
-    this.feeds = new Set();
+    this.feeds = new Map();
+    this._feedFactories = null;
+    this._prefHandlers = new Map();
     this._messageChannel = new ActivityStreamMessageChannel({dispatch: this.dispatch});
     this._store = redux.createStore(
       redux.combineReducers(reducers),
       redux.applyMiddleware(this._middleware, this._messageChannel.middleware)
     );
   }
 
   /**
@@ -48,38 +53,98 @@ this.Store = class Store {
   _middleware(store) {
     return next => action => {
       next(action);
       this.feeds.forEach(s => s.onAction && s.onAction(action));
     };
   }
 
   /**
+   * initFeed - Initializes a feed by calling its constructor function
+   *
+   * @param  {string} feedName The name of a feed, as defined in the object
+   *                           passed to Store.init
+   */
+  initFeed(feedName) {
+    const feed = this._feedFactories[feedName]();
+    feed.store = this;
+    this.feeds.set(feedName, feed);
+  }
+
+  /**
+   * uninitFeed - Removes a feed and calls its uninit function if defined
+   *
+   * @param  {string} feedName The name of a feed, as defined in the object
+   *                           passed to Store.init
+   */
+  uninitFeed(feedName) {
+    const feed = this.feeds.get(feedName);
+    if (!feed) {
+      return;
+    }
+    if (feed.uninit) {
+      feed.uninit();
+    }
+    this.feeds.delete(feedName);
+  }
+
+  /**
+   * maybeStartFeedAndListenForPrefChanges - Listen for pref changes that turn a
+   *     feed off/on, and as long as that pref was not explicitly set to
+   *     false, initialize the feed immediately.
+   *
+   * @param  {string} name The name of a feed, as defined in the object passed
+   *                       to Store.init
+   */
+  maybeStartFeedAndListenForPrefChanges(name) {
+    const prefName = PREF_PREFIX + name;
+
+    // If the pref was never set, set it to true by default.
+    if (!Preferences.has(prefName)) {
+      Preferences.set(prefName, true);
+    }
+
+    // Create a listener that turns the feed off/on based on changes
+    // to the pref, and cache it so we can unlisten on shut-down.
+    const onPrefChanged = isEnabled => (isEnabled ? this.initFeed(name) : this.uninitFeed(name));
+    this._prefHandlers.set(prefName, onPrefChanged);
+    Preferences.observe(prefName, onPrefChanged);
+
+    // TODO: This should propbably be done in a generic pref manager for Activity Stream.
+    // If the pref is true, start the feed immediately.
+    if (Preferences.get(prefName)) {
+      this.initFeed(name);
+    }
+  }
+
+  /**
    * init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
-   *        After initialization has finished, an INIT action is dispatched.
    *
    * @param  {array} feeds An array of objects with an optional .onAction method
    */
-  init(feeds) {
-    if (feeds) {
-      feeds.forEach(subscriber => {
-        subscriber.store = this;
-        this.feeds.add(subscriber);
-      });
+  init(feedConstructors) {
+    if (feedConstructors) {
+      this._feedFactories = feedConstructors;
+      for (const name of Object.keys(feedConstructors)) {
+        this.maybeStartFeedAndListenForPrefChanges(name);
+      }
     }
     this._messageChannel.createChannel();
-    this.dispatch({type: at.INIT});
   }
 
   /**
-   * uninit - Clears all feeds, dispatches an UNINIT action, and
-   *          destroys the message manager channel.
+   * uninit -  Uninitalizes each feed, clears them, and destroys the message
+   *           manager channel.
    *
    * @return {type}  description
    */
   uninit() {
+    this.feeds.forEach(feed => this.uninitFeed(feed));
+    this._prefHandlers.forEach((handler, pref) => Preferences.ignore(pref, handler));
+    this._prefHandlers.clear();
+    this._feedFactories = null;
     this.feeds.clear();
-    this.dispatch({type: at.UNINIT});
     this._messageChannel.destroyChannel();
   }
 };
 
-this.EXPORTED_SYMBOLS = ["Store"];
+this.PREF_PREFIX = PREF_PREFIX;
+this.EXPORTED_SYMBOLS = ["Store", "PREF_PREFIX"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -0,0 +1,83 @@
+/* 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/. */
+ /* globals PlacesProvider, PreviewProvider */
+"use strict";
+
+const {utils: Cu} = Components;
+const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
+
+Cu.import("resource:///modules/PlacesProvider.jsm");
+Cu.import("resource:///modules/PreviewProvider.jsm");
+
+const TOP_SITES_SHOWMORE_LENGTH = 12;
+const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
+const DEFAULT_TOP_SITES = [
+  {"url": "https://www.facebook.com/"},
+  {"url": "https://www.youtube.com/"},
+  {"url": "http://www.amazon.com/"},
+  {"url": "https://www.yahoo.com/"},
+  {"url": "http://www.ebay.com"},
+  {"url": "https://twitter.com/"}
+].map(row => Object.assign(row, {isDefault: true}));
+
+this.TopSitesFeed = class TopSitesFeed {
+  constructor() {
+    this.lastUpdated = 0;
+  }
+  async getScreenshot(url) {
+    let screenshot = await PreviewProvider.getThumbnail(url);
+    const action = {type: at.SCREENSHOT_UPDATED, data: {url, screenshot}};
+    this.store.dispatch(ac.BroadcastToContent(action));
+  }
+  async getLinksWithDefaults(action) {
+    let links = await PlacesProvider.links.getLinks();
+
+    if (!links) {
+      links = [];
+    } else {
+      links = links.filter(link => link && link.type !== "affiliate").slice(0, 12);
+    }
+
+    if (links.length < TOP_SITES_SHOWMORE_LENGTH) {
+      links = [...links, ...DEFAULT_TOP_SITES].slice(0, TOP_SITES_SHOWMORE_LENGTH);
+    }
+
+    return links;
+  }
+  async refresh(action) {
+    const links = await this.getLinksWithDefaults();
+    const newAction = {type: at.TOP_SITES_UPDATED, data: links};
+
+    // Send an update to content so the preloaded tab can get the updated content
+    this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
+    this.lastUpdated = Date.now();
+
+    // Now, get a screenshot for every item
+    for (let link of links) {
+      this.getScreenshot(link.url);
+    }
+  }
+  onAction(action) {
+    let realRows;
+    switch (action.type) {
+      case at.NEW_TAB_LOAD:
+        // Only check against real rows returned from history, not default ones.
+        realRows = this.store.getState().TopSites.rows.filter(row => !row.isDefault);
+        // When a new tab is opened, if we don't have enough top sites yet, refresh the data.
+        if (realRows.length < TOP_SITES_SHOWMORE_LENGTH) {
+          this.refresh(action);
+        } else if (Date.now() - this.lastUpdated >= UPDATE_TIME) {
+          // When a new tab is opened, if the last time we refreshed the data
+          // is greater than 15 minutes, refresh the data.
+          this.refresh(action);
+        }
+        break;
+    }
+  }
+};
+
+this.UPDATE_TIME = UPDATE_TIME;
+this.TOP_SITES_SHOWMORE_LENGTH = TOP_SITES_SHOWMORE_LENGTH;
+this.DEFAULT_TOP_SITES = DEFAULT_TOP_SITES;
+this.EXPORTED_SYMBOLS = ["TopSitesFeed", "UPDATE_TIME", "DEFAULT_TOP_SITES", "TOP_SITES_SHOWMORE_LENGTH"];
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/.eslintrc.js
@@ -0,0 +1,11 @@
+module.exports = {
+  "env": {
+    "node": true,
+    "es6": true,
+    "mocha": true
+  },
+  "globals": {
+    "assert": true,
+    "sinon": true
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/.eslintrc.js
@@ -0,0 +1,42 @@
+module.exports = {
+  "globals": {
+    "add_task": false,
+    "Assert": false,
+    "BrowserOpenTab": false,
+    "BrowserTestUtils": false,
+    "content": false,
+    "ContentTask": false,
+    "ContentTaskUtils": false,
+    "Components": false,
+    "EventUtils": false,
+    "executeSoon": false,
+    "expectUncaughtException": false,
+    "export_assertions": false,
+    "extractJarToTmp": false,
+    "finish": false,
+    "getJar": false,
+    "getRootDirectory": false,
+    "getTestFilePath": false,
+    "gBrowser": false,
+    "gTestPath": false,
+    "info": false,
+    "is": false,
+    "isnot": false,
+    "ok": false,
+    "OpenBrowserWindow": false,
+    "Preferences": false,
+    "registerCleanupFunction": false,
+    "requestLongerTimeout": false,
+    "Services": false,
+    "SimpleTest": false,
+    "SpecialPowers": false,
+    "TestUtils": false,
+    "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
+    "todo": false,
+    "todo_is": false,
+    "todo_isnot": false,
+    "waitForClipboard": false,
+    "waitForExplicitFinish": false,
+    "waitForFocus": false
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+# XXX This defaults to forcing activity-stream tests to be skipped in m-c,
+# since, as of this writing, mozilla-central itself is still turned off.
+# The tests can be run locally using 'npm run mochitest' which does various
+# overrides.
+skip-if=!activity_stream
+
+[browser_dummy_test.js]
+skip-if=true
+# XXX The above test is required because having only one test causes
+# The default skip-if to silently fail.  As soon as we add another test here, 
+# we should get rid of it, and the following line.
+[browser_as_load_location.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_as_load_location.js
@@ -0,0 +1,34 @@
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Tests that opening a new tab opens a page with the expected activity stream
+ * content.
+ *
+ * XXX /browser/components/newtab/tests/browser/browser_newtab_overrides in
+ * mozilla-central is where this test was adapted from.  Once we get decide on
+ * and implement how we're going to set the URL in mozilla-central, we may well
+ * want to (separately from this test), clone/adapt that entire file for our
+ * new setup.
+ */
+add_task(async function checkActivityStreamLoads() {
+  const asURL = "resource://activity-stream/data/content/activity-stream.html";
+
+  // simulate a newtab open as a user would
+  BrowserOpenTab();
+
+  // wait until the browser loads
+  let browser = gBrowser.selectedBrowser;
+  await BrowserTestUtils.browserLoaded(browser);
+
+  // check what the content task thinks has been loaded.
+  await ContentTask.spawn(browser, {url: asURL}, args => {
+    Assert.ok(content.document.querySelector("body.activity-stream"),
+      'Got <body class="activity-stream" Element');
+  });
+
+  // avoid leakage
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_dummy_test.js
@@ -0,0 +1,34 @@
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Tests that opening a new tab opens a page with the expected activity stream
+ * content.
+ *
+ * XXX /browser/components/newtab/tests/browser/browser_newtab_overrides in
+ * mozilla-central is where this test was adapted from.  Once we get decide on
+ * and implement how we're going to set the URL in mozilla-central, we may well
+ * want to (separately from this test), clone/adapt that entire file for our
+ * new setup.
+ */
+add_task(async function checkActivityStreamLoads() {
+  const asURL = "resource://activity-stream/data/content/activity-stream.html";
+
+  // simulate a newtab open as a user would
+  BrowserOpenTab();
+
+  // wait until the browser loads
+  let browser = gBrowser.selectedBrowser;
+  await BrowserTestUtils.browserLoaded(browser);
+
+  // check what the content task thinks has been loaded.
+  await ContentTask.spawn(browser, {url: asURL}, args => {
+    Assert.ok(content.document.querySelector("body.activity-stream"),
+      'Got <body class="activity-stream" Element');
+  });
+
+  // avoid leakage
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/mozinfo.json
@@ -0,0 +1,3 @@
+{
+  "activity_stream": true
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/common/Actions.test.js
@@ -0,0 +1,93 @@
+const {
+  actionCreators: ac,
+  actionUtils: au,
+  MAIN_MESSAGE_TYPE,
+  CONTENT_MESSAGE_TYPE
+} = require("common/Actions.jsm");
+
+describe("ActionCreators", () => {
+  describe("_RouteMessage", () => {
+    it("should throw if options are not passed as the second param", () => {
+      assert.throws(() => {
+        au._RouteMessage({type: "FOO"});
+      });
+    });
+    it("should set all defined options on the .meta property of the new action", () => {
+      assert.deepEqual(
+        au._RouteMessage({type: "FOO", meta: {hello: "world"}}, {from: "foo", to: "bar"}),
+        {type: "FOO", meta: {hello: "world", from: "foo", to: "bar"}}
+      );
+    });
+    it("should remove any undefined options related to message routing", () => {
+      const action = au._RouteMessage({type: "FOO", meta: {fromTarget: "bar"}}, {from: "foo", to: "bar"});
+      assert.isUndefined(action.meta.fromTarget);
+    });
+  });
+  describe("SendToMain", () => {
+    it("should create the right action", () => {
+      const action = {type: "FOO", data: "BAR"};
+      const newAction = ac.SendToMain(action);
+      assert.deepEqual(newAction, {
+        type: "FOO",
+        data: "BAR",
+        meta: {from: CONTENT_MESSAGE_TYPE, to: MAIN_MESSAGE_TYPE}
+      });
+    });
+    describe("isSendToMain", () => {
+      it("should return true if action is SendToMain", () => {
+        const newAction = ac.SendToMain({type: "FOO"});
+        assert.isTrue(au.isSendToMain(newAction));
+      });
+      it("should return false if action is not SendToMain", () => {
+        assert.isFalse(au.isSendToMain({type: "FOO"}));
+      });
+    });
+  });
+  describe("SendToContent", () => {
+    it("should create the right action", () => {
+      const action = {type: "FOO", data: "BAR"};
+      const targetId = "abc123";
+      const newAction = ac.SendToContent(action, targetId);
+      assert.deepEqual(newAction, {
+        type: "FOO",
+        data: "BAR",
+        meta: {from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE, toTarget: targetId}
+      });
+    });
+    it("should throw if no targetId is provided", () => {
+      assert.throws(() => {
+        ac.SendToContent({type: "FOO"});
+      });
+    });
+    describe("isSendToContent", () => {
+      it("should return true if action is SendToContent", () => {
+        const newAction = ac.SendToContent({type: "FOO"}, "foo123");
+        assert.isTrue(au.isSendToContent(newAction));
+      });
+      it("should return false if action is not SendToMain", () => {
+        assert.isFalse(au.isSendToContent({type: "FOO"}));
+        assert.isFalse(au.isSendToContent(ac.BroadcastToContent({type: "FOO"})));
+      });
+    });
+  });
+  describe("BroadcastToContent", () => {
+    it("should create the right action", () => {
+      const action = {type: "FOO", data: "BAR"};
+      const newAction = ac.BroadcastToContent(action);
+      assert.deepEqual(newAction, {
+        type: "FOO",
+        data: "BAR",
+        meta: {from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE}
+      });
+    });
+    describe("isBroadcastToContent", () => {
+      it("should return true if action is BroadcastToContent", () => {
+        assert.isTrue(au.isBroadcastToContent(ac.BroadcastToContent({type: "FOO"})));
+      });
+      it("should return false if action is not BroadcastToContent", () => {
+        assert.isFalse(au.isBroadcastToContent({type: "FOO"}));
+        assert.isFalse(au.isBroadcastToContent(ac.SendToContent({type: "FOO"}, "foo123")));
+      });
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
@@ -0,0 +1,51 @@
+const {reducers, INITIAL_STATE} = require("common/Reducers.jsm");
+const {TopSites, Search} = reducers;
+const {actionTypes: at} = require("common/Actions.jsm");
+
+describe("Reducers", () => {
+  describe("TopSites", () => {
+    it("should return the initial state", () => {
+      const nextState = TopSites(undefined, {type: "FOO"});
+      assert.equal(nextState, INITIAL_STATE.TopSites);
+    });
+    it("should add top sites on TOP_SITES_UPDATED", () => {
+      const newRows = [{url: "foo.com"}, {url: "bar.com"}];
+      const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED, data: newRows});
+      assert.equal(nextState.rows, newRows);
+    });
+    it("should not update state for empty action.data on TOP_SITES_UPDATED", () => {
+      const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED});
+      assert.equal(nextState, INITIAL_STATE.TopSites);
+    });
+    it("should add screenshots for SCREENSHOT_UPDATED", () => {
+      const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
+      const action = {type: at.SCREENSHOT_UPDATED, data: {url: "bar.com", screenshot: "data:123"}};
+      const nextState = TopSites(oldState, action);
+      assert.deepEqual(nextState.rows, [{url: "foo.com"}, {url: "bar.com", screenshot: "data:123"}]);
+    });
+    it("should not modify rows if nothing matches the url for SCREENSHOT_UPDATED", () => {
+      const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
+      const action = {type: at.SCREENSHOT_UPDATED, data: {url: "baz.com", screenshot: "data:123"}};
+      const nextState = TopSites(oldState, action);
+      assert.deepEqual(nextState, oldState);
+    });
+  });
+  describe("Search", () => {
+    it("should return the initial state", () => {
+      const nextState = Search(undefined, {type: "FOO"});
+      assert.equal(nextState, INITIAL_STATE.Search);
+    });
+    it("should not update state for empty action.data on Search", () => {
+      const nextState = Search(undefined, {type: at.SEARCH_STATE_UPDATED});
+      assert.equal(nextState, INITIAL_STATE.Search);
+    });
+    it("should update the current engine and the engines on SEARCH_STATE_UPDATED", () => {
+      const newEngine = {name: "Google", iconBuffer: "icon.ico"};
+      const nextState = Search(undefined, {type: at.SEARCH_STATE_UPDATED, data: {currentEngine: newEngine, engines: [newEngine]}});
+      assert.equal(nextState.currentEngine.name, newEngine.name);
+      assert.equal(nextState.currentEngine.icon, newEngine.icon);
+      assert.equal(nextState.engines[0].name, newEngine.name);
+      assert.equal(nextState.engines[0].icon, newEngine.icon);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
@@ -0,0 +1,70 @@
+const injector = require("inject!lib/ActivityStream.jsm");
+
+describe("ActivityStream", () => {
+  let sandbox;
+  let as;
+  let ActivityStream;
+  function NewTabInit() {}
+  function TopSitesFeed() {}
+  function SearchFeed() {}
+  before(() => {
+    sandbox = sinon.sandbox.create();
+    ({ActivityStream} = injector({
+      "lib/NewTabInit.jsm": {NewTabInit},
+      "lib/TopSitesFeed.jsm": {TopSitesFeed},
+      "lib/SearchFeed.jsm": {SearchFeed}
+    }));
+  });
+
+  afterEach(() => sandbox.restore());
+
+  beforeEach(() => {
+    as = new ActivityStream();
+    sandbox.stub(as.store, "init");
+    sandbox.stub(as.store, "uninit");
+  });
+
+  it("should exist", () => {
+    assert.ok(ActivityStream);
+  });
+  it("should initialize with .initialized=false", () => {
+    assert.isFalse(as.initialized, ".initialized");
+  });
+  describe("#init", () => {
+    beforeEach(() => {
+      as.init();
+    });
+    it("should set .initialized to true", () => {
+      assert.isTrue(as.initialized, ".initialized");
+    });
+    it("should call .store.init", () => {
+      assert.calledOnce(as.store.init);
+    });
+  });
+  describe("#uninit", () => {
+    beforeEach(() => {
+      as.init();
+      as.uninit();
+    });
+    it("should set .initialized to false", () => {
+      assert.isFalse(as.initialized, ".initialized");
+    });
+    it("should call .store.uninit", () => {
+      assert.calledOnce(as.store.uninit);
+    });
+  });
+  describe("feeds", () => {
+    it("should create a NewTabInit feed", () => {
+      const feed = as.feeds["feeds.newtabinit"]();
+      assert.instanceOf(feed, NewTabInit);
+    });
+    it("should create a TopSites feed", () => {
+      const feed = as.feeds["feeds.topsites"]();
+      assert.instanceOf(feed, TopSitesFeed);
+    });
+    it("should create a Search feed", () => {
+      const feed = as.feeds["feeds.search"]();
+      assert.instanceOf(feed, SearchFeed);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js
@@ -0,0 +1,235 @@
+const {ActivityStreamMessageChannel, DEFAULT_OPTIONS} = require("lib/ActivityStreamMessageChannel.jsm");
+const {addNumberReducer, GlobalOverrider} = require("test/unit/utils");
+const {createStore, applyMiddleware} = require("redux");
+const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
+
+const OPTIONS = ["pageURL, outgoingMessageName", "incomingMessageName", "dispatch"];
+
+describe("ActivityStreamMessageChannel", () => {
+  let globals;
+  let dispatch;
+  let mm;
+  before(() => {
+    function RP(url) {
+      this.url = url;
+      this.messagePorts = [];
+      this.addMessageListener = globals.sandbox.spy();
+      this.sendAsyncMessage = globals.sandbox.spy();
+      this.destroy = globals.sandbox.spy();
+    }
+    globals = new GlobalOverrider();
+    globals.set("AboutNewTab", {
+      override: globals.sandbox.spy(),
+      reset: globals.sandbox.spy()
+    });
+    globals.set("RemotePages", RP);
+    dispatch = globals.sandbox.spy();
+  });
+  beforeEach(() => {
+    mm = new ActivityStreamMessageChannel({dispatch});
+  });
+
+  afterEach(() => globals.reset());
+  after(() => globals.restore());
+
+  it("should exist", () => {
+    assert.ok(ActivityStreamMessageChannel);
+  });
+  it("should apply default options", () => {
+    mm = new ActivityStreamMessageChannel();
+    OPTIONS.forEach(o => assert.equal(mm[o], DEFAULT_OPTIONS[o], o));
+  });
+  it("should add options", () => {
+    const options = {dispatch: () => {}, pageURL: "FOO.html", outgoingMessageName: "OUT", incomingMessageName: "IN"};
+    mm = new ActivityStreamMessageChannel(options);
+    OPTIONS.forEach(o => assert.equal(mm[o], options[o], o));
+  });
+  it("should throw an error if no dispatcher was provided", () => {
+    mm = new ActivityStreamMessageChannel();
+    assert.throws(() => mm.dispatch({type: "FOO"}));
+  });
+  describe("Creating/destroying the channel", () => {
+    describe("#createChannel", () => {
+      it("should create .channel with the correct URL", () => {
+        mm.createChannel();
+        assert.ok(mm.channel);
+        assert.equal(mm.channel.url, mm.pageURL);
+      });
+      it("should add 3 message listeners", () => {
+        mm.createChannel();
+        assert.callCount(mm.channel.addMessageListener, 3);
+      });
+      it("should add the custom message listener to the channel", () => {
+        mm.createChannel();
+        assert.calledWith(mm.channel.addMessageListener, mm.incomingMessageName, mm.onMessage);
+      });
+      it("should override AboutNewTab", () => {
+        mm.createChannel();
+        assert.calledOnce(global.AboutNewTab.override);
+      });
+      it("should not override AboutNewTab if the pageURL is not about:newtab", () => {
+        mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
+        mm.createChannel();
+        assert.notCalled(global.AboutNewTab.override);
+      });
+    });
+    describe("#destroyChannel", () => {
+      let channel;
+      beforeEach(() => {
+        mm.createChannel();
+        channel = mm.channel;
+      });
+      it("should call channel.destroy()", () => {
+        mm.destroyChannel();
+        assert.calledOnce(channel.destroy);
+      });
+      it("should set .channel to null", () => {
+        mm.destroyChannel();
+        assert.isNull(mm.channel);
+      });
+      it("should reset AboutNewTab", () => {
+        mm.destroyChannel();
+        assert.calledOnce(global.AboutNewTab.reset);
+      });
+      it("should not reset AboutNewTab if the pageURL is not about:newtab", () => {
+        mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
+        mm.createChannel();
+        mm.destroyChannel();
+        assert.notCalled(global.AboutNewTab.reset);
+      });
+    });
+  });
+  describe("Message handling", () => {
+    describe("#getTargetById", () => {
+      it("should get an id if it exists", () => {
+        const t = {portID: "foo"};
+        mm.createChannel();
+        mm.channel.messagePorts.push(t);
+        assert.equal(mm.getTargetById("foo"), t);
+      });
+      it("should return null if the target doesn't exist", () => {
+        const t = {portID: "foo"};
+        mm.createChannel();
+        mm.channel.messagePorts.push(t);
+        assert.equal(mm.getTargetById("bar"), null);
+      });
+    });
+    describe("#onNewTabLoad", () => {
+      it("should dispatch a NEW_TAB_LOAD action", () => {
+        const t = {portID: "foo"};
+        sinon.stub(mm, "onActionFromContent");
+        mm.onNewTabLoad({target: t});
+        assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_LOAD}, "foo");
+      });
+    });
+    describe("#onNewTabUnload", () => {
+      it("should dispatch a NEW_TAB_UNLOAD action", () => {
+        const t = {portID: "foo"};
+        sinon.stub(mm, "onActionFromContent");
+        mm.onNewTabUnload({target: t});
+        assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_UNLOAD}, "foo");
+      });
+    });
+    describe("#onMessage", () => {
+      it("should report an error if the msg.data is missing", () => {
+        mm.onMessage({target: {portID: "foo"}});
+        assert.calledOnce(global.Components.utils.reportError);
+      });
+      it("should report an error if the msg.data.type is missing", () => {
+        mm.onMessage({target: {portID: "foo"}, data: "foo"});
+        assert.calledOnce(global.Components.utils.reportError);
+      });
+      it("should call onActionFromContent", () => {
+        sinon.stub(mm, "onActionFromContent");
+        const action = {data: {data: {}, type: "FOO"}, target: {portID: "foo"}};
+        const expectedAction = {
+          type: action.data.type,
+          data: action.data.data,
+          _target: {portID: "foo"}
+        };
+        mm.onMessage(action);
+        assert.calledWith(mm.onActionFromContent, expectedAction, "foo");
+      });
+    });
+  });
+  describe("Sending and broadcasting", () => {
+    describe("#send", () => {
+      it("should send a message on the right port", () => {
+        const t = {portID: "foo", sendAsyncMessage: sinon.spy()};
+        mm.createChannel();
+        mm.channel.messagePorts = [t];
+        const action = ac.SendToContent({type: "HELLO"}, "foo");
+        mm.send(action, "foo");
+        assert.calledWith(t.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
+      });
+      it("should not throw if the target isn't around", () => {
+        mm.createChannel();
+        // port is not added to the channel
+        const action = ac.SendToContent({type: "HELLO"}, "foo");
+
+        assert.doesNotThrow(() => mm.send(action, "foo"));
+      });
+    });
+    describe("#broadcast", () => {
+      it("should send a message on the channel", () => {
+        mm.createChannel();
+        const action = ac.BroadcastToContent({type: "HELLO"});
+        mm.broadcast(action);
+        assert.calledWith(mm.channel.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
+      });
+    });
+  });
+  describe("Handling actions", () => {
+    describe("#onActionFromContent", () => {
+      beforeEach(() => mm.onActionFromContent({type: "FOO"}, "foo"));
+      it("should dispatch a SendToMain action", () => {
+        assert.calledOnce(dispatch);
+        const action = dispatch.firstCall.args[0];
+        assert.equal(action.type, "FOO", "action.type");
+      });
+      it("should have the right fromTarget", () => {
+        const action = dispatch.firstCall.args[0];
+        assert.equal(action.meta.fromTarget, "foo", "meta.fromTarget");
+      });
+    });
+    describe("#middleware", () => {
+      let store;
+      beforeEach(() => {
+        store = createStore(addNumberReducer, applyMiddleware(mm.middleware));
+      });
+      it("should just call next if no channel is found", () => {
+        store.dispatch({type: "ADD", data: 10});
+        assert.equal(store.getState(), 10);
+      });
+      it("should call .send if the action is SendToContent", () => {
+        sinon.stub(mm, "send");
+        const action = ac.SendToContent({type: "FOO"}, "foo");
+
+        mm.createChannel();
+        store.dispatch(action);
+
+        assert.calledWith(mm.send, action);
+      });
+      it("should call .broadcast if the action is BroadcastToContent", () => {
+        sinon.stub(mm, "broadcast");
+        const action = ac.BroadcastToContent({type: "FOO"});
+
+        mm.createChannel();
+        store.dispatch(action);
+
+        assert.calledWith(mm.broadcast, action);
+      });
+      it("should dispatch other actions normally", () => {
+        sinon.stub(mm, "send");
+        sinon.stub(mm, "broadcast");
+
+        mm.createChannel();
+        store.dispatch({type: "ADD", data: 1});
+
+        assert.equal(store.getState(), 1);
+        assert.notCalled(mm.send);
+        assert.notCalled(mm.broadcast);
+      });
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/SearchFeed.test.js
@@ -0,0 +1,77 @@
+"use strict";
+const {SearchFeed} = require("lib/SearchFeed.jsm");
+const {GlobalOverrider} = require("test/unit/utils");
+const {actionTypes: at} = require("common/Actions.jsm");
+const fakeEngines = [{name: "Google", iconBuffer: "icon.ico"}];
+describe("Search Feed", () => {
+  let feed;
+  let globals;
+  before(() => {
+    globals = new GlobalOverrider();
+    globals.set("ContentSearch", {
+      currentStateObj: globals.sandbox.spy(() => Promise.resolve({engines: fakeEngines, currentEngine: {}})),
+      performSearch: globals.sandbox.spy((browser, searchData) => Promise.resolve({browser, searchData}))
+    });
+  });
+  beforeEach(() => {
+    feed = new SearchFeed();
+    feed.store = {dispatch: sinon.spy()};
+  });
+  afterEach(() => globals.reset());
+  after(() => globals.restore());
+
+  it("should call get state (with true) from the content search provider on INIT", () => {
+    feed.onAction({type: at.INIT});
+    // calling currentStateObj with 'true' allows us to return a data uri for the
+    // icon, instead of an array buffer
+    assert.calledWith(global.ContentSearch.currentStateObj, true);
+  });
+  it("should get the the state on INIT", () => {
+    sinon.stub(feed, "getState");
+    feed.onAction({type: at.INIT});
+    assert.calledOnce(feed.getState);
+  });
+  it("should add observers on INIT", () => {
+    sinon.stub(feed, "addObservers");
+    feed.onAction({type: at.INIT});
+    assert.calledOnce(feed.addObservers);
+  });
+  it("should remove observers on UNINIT", () => {
+    sinon.stub(feed, "removeObservers");
+    feed.onAction({type: at.UNINIT});
+    assert.calledOnce(feed.removeObservers);
+  });
+  it("should call services.obs.addObserver on INIT", () => {
+    feed.onAction({type: at.INIT});
+    assert.calledOnce(global.Services.obs.addObserver);
+  });
+  it("should call services.obs.removeObserver on UNINIT", () => {
+    feed.onAction({type: at.UNINIT});
+    assert.calledOnce(global.Services.obs.removeObserver);
+  });
+  it("should dispatch one event with the state", () => (
+    feed.getState().then(() => {
+      assert.calledOnce(feed.store.dispatch);
+    })
+  ));
+  it("should perform a search on PERFORM_SEARCH", () => {
+    sinon.stub(feed, "performSearch");
+    feed.onAction({_target: {browser: {}}, type: at.PERFORM_SEARCH});
+    assert.calledOnce(feed.performSearch);
+  });
+  it("should call performSearch with an action", () => {
+    const action = {_target: {browser: "browser"}, data: {searchString: "hello"}};
+    feed.performSearch(action._target.browser, action.data);
+    assert.calledWith(global.ContentSearch.performSearch, {target: action._target.browser}, action.data);
+  });
+  it("should get the state if we change the search engines", () => {
+    sinon.stub(feed, "getState");
+    feed.observe(null, "browser-search-engine-modified", "engine-current");
+    assert.calledOnce(feed.getState);
+  });
+  it("shouldn't get the state if it's not the right notification", () => {
+    sinon.stub(feed, "getState");
+    feed.observe(null, "some-other-notification", "engine-current");
+    assert.notCalled(feed.getState);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/Store.test.js
@@ -0,0 +1,210 @@
+const injector = require("inject!lib/Store.jsm");
+const {createStore} = require("redux");
+const {addNumberReducer} = require("test/unit/utils");
+const {GlobalOverrider} = require("test/unit/utils");
+describe("Store", () => {
+  let Store;
+  let Preferences;
+  let sandbox;
+  let store;
+  let globals;
+  let PREF_PREFIX;
+  beforeEach(() => {
+    globals = new GlobalOverrider();
+    sandbox = globals.sandbox;
+    Preferences = new Map();
+    Preferences.observe = sandbox.spy();
+    Preferences.ignore = sandbox.spy();
+    globals.set("Preferences", Preferences);
+    function ActivityStreamMessageChannel(options) {
+      this.dispatch = options.dispatch;
+      this.createChannel = sandbox.spy();
+      this.destroyChannel = sandbox.spy();
+      this.middleware = sandbox.spy(s => next => action => next(action));
+    }
+    ({Store, PREF_PREFIX} = injector({"lib/ActivityStreamMessageChannel.jsm": {ActivityStreamMessageChannel}}));
+    store = new Store();
+  });
+  afterEach(() => {
+    Preferences.clear();
+    globals.restore();
+  });
+  it("should have an .feeds property that is a Map", () => {
+    assert.instanceOf(store.feeds, Map);
+    assert.equal(store.feeds.size, 0, ".feeds.size");
+  });
+  it("should have a redux store at ._store", () => {
+    assert.ok(store._store);
+    assert.property(store, "dispatch");
+    assert.property(store, "getState");
+  });
+  it("should create a ActivityStreamMessageChannel with the right dispatcher", () => {
+    assert.ok(store._messageChannel);
+    assert.equal(store._messageChannel.dispatch, store.dispatch);
+  });
+  it("should connect the ActivityStreamMessageChannel's middleware", () => {
+    store.dispatch({type: "FOO"});
+    assert.calledOnce(store._messageChannel.middleware);
+  });
+  describe("#initFeed", () => {
+    it("should add an instance of the feed to .feeds", () => {
+      class Foo {}
+      Preferences.set(`${PREF_PREFIX}foo`, false);
+      store.init({foo: () => new Foo()});
+      store.initFeed("foo");
+
+      assert.isTrue(store.feeds.has("foo"), "foo is set");
+      assert.instanceOf(store.feeds.get("foo"), Foo);
+    });
+    it("should add a .store property to the feed", () => {
+      class Foo {}
+      store._feedFactories = {foo: () => new Foo()};
+      store.initFeed("foo");
+
+      assert.propertyVal(store.feeds.get("foo"), "store", store);
+    });
+  });
+  describe("#uninitFeed", () => {
+    it("should not throw if no feed with that name exists", () => {
+      assert.doesNotThrow(() => {
+        store.uninitFeed("bar");
+      });
+    });
+    it("should call the feed's uninit function if it is defined", () => {
+      let feed;
+      function createFeed() {
+        feed = {uninit: sinon.spy()};
+        return feed;
+      }
+      store._feedFactories = {foo: createFeed};
+
+      store.initFeed("foo");
+      store.uninitFeed("foo");
+
+      assert.calledOnce(feed.uninit);
+    });
+    it("should remove the feed from .feeds", () => {
+      class Foo {}
+      store._feedFactories = {foo: () => new Foo()};
+
+      store.initFeed("foo");
+      store.uninitFeed("foo");
+
+      assert.isFalse(store.feeds.has("foo"), "foo is not in .feeds");
+    });
+  });
+  describe("maybeStartFeedAndListenForPrefChanges", () => {
+    beforeEach(() => {
+      sinon.stub(store, "initFeed");
+      sinon.stub(store, "uninitFeed");
+    });
+    it("should set the new pref in Preferences to true, if it was never defined", () => {
+      store.maybeStartFeedAndListenForPrefChanges("foo");
+      assert.isTrue(Preferences.get(`${PREF_PREFIX}foo`));
+    });
+    it("should not override the pref if it was already set", () => {
+      Preferences.set(`${PREF_PREFIX}foo`, false);
+      store.maybeStartFeedAndListenForPrefChanges("foo");
+      assert.isFalse(Preferences.get(`${PREF_PREFIX}foo`));
+    });
+    it("should initialize the feed if the Pref is set to true", () => {
+      Preferences.set(`${PREF_PREFIX}foo`, true);
+      store.maybeStartFeedAndListenForPrefChanges("foo");
+      assert.calledWith(store.initFeed, "foo");
+    });
+    it("should not initialize the feed if the Pref is set to false", () => {
+      Preferences.set(`${PREF_PREFIX}foo`, false);
+      store.maybeStartFeedAndListenForPrefChanges("foo");
+      assert.notCalled(store.initFeed);
+    });
+    it("should observe the pref", () => {
+      store.maybeStartFeedAndListenForPrefChanges("foo");
+      assert.calledWith(Preferences.observe, `${PREF_PREFIX}foo`, store._prefHandlers.get(`${PREF_PREFIX}foo`));
+    });
+    describe("handler", () => {
+      let handler;
+      beforeEach(() => {
+        store.maybeStartFeedAndListenForPrefChanges("foo");
+        handler = store._prefHandlers.get(`${PREF_PREFIX}foo`);
+      });
+      it("should initialize the feed if called with true", () => {
+        handler(true);
+        assert.calledWith(store.initFeed, "foo");
+      });
+      it("should uninitialize the feed if called with false", () => {
+        handler(false);
+        assert.calledWith(store.uninitFeed, "foo");
+      });
+    });
+  });
+  describe("#init", () => {
+    it("should call .maybeStartFeedAndListenForPrefChanges with each key", () => {
+      sinon.stub(store, "maybeStartFeedAndListenForPrefChanges");
+      store.init({foo: () => {}, bar: () => {}});
+      assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "foo");
+      assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "bar");
+    });
+    it("should initialize the ActivityStreamMessageChannel channel", () => {
+      store.init();
+      assert.calledOnce(store._messageChannel.createChannel);
+    });
+  });
+  describe("#uninit", () => {
+    it("should clear .feeds, ._prefHandlers, and ._feedFactories", () => {
+      store.init({
+        a: () => ({}),
+        b: () => ({}),
+        c: () => ({})
+      });
+
+      store.uninit();
+
+      assert.equal(store.feeds.size, 0);
+      assert.equal(store._prefHandlers.size, 0);
+      assert.isNull(store._feedFactories);
+    });
+    it("should destroy the ActivityStreamMessageChannel channel", () => {
+      store.uninit();
+      assert.calledOnce(store._messageChannel.destroyChannel);
+    });
+  });
+  describe("#getState", () => {
+    it("should return the redux state", () => {
+      store._store = createStore((prevState = 123) => prevState);
+      const {getState} = store;
+      assert.equal(getState(), 123);
+    });
+  });
+  describe("#dispatch", () => {
+    it("should call .onAction of each feed", () => {
+      const {dispatch} = store;
+      const sub = {onAction: sinon.spy()};
+      const action = {type: "FOO"};
+
+      store.init({sub: () => sub});
+
+      dispatch(action);
+
+      assert.calledWith(sub.onAction, action);
+    });
+    it("should call the reducers", () => {
+      const {dispatch} = store;
+      store._store = createStore(addNumberReducer);
+
+      dispatch({type: "ADD", data: 14});
+
+      assert.equal(store.getState(), 14);
+    });
+  });
+  describe("#subscribe", () => {
+    it("should subscribe to changes to the store", () => {
+      const sub = sinon.spy();
+      const action = {type: "FOO"};
+
+      store.subscribe(sub);
+      store.dispatch(action);
+
+      assert.calledOnce(sub);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/TelemetrySender.test.js
@@ -0,0 +1,271 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+const {GlobalOverrider, FakePrefs} = require("test/unit/utils");
+const {TelemetrySender} = require("lib/TelemetrySender.jsm");
+
+/**
+ * A reference to the fake preferences object created by the TelemetrySender
+ * constructor so that we can use the API.
+ */
+let fakePrefs;
+const prefInitHook = function() {
+  fakePrefs = this; // eslint-disable-line consistent-this
+};
+const tsArgs = {prefInitHook};
+
+describe("TelemetrySender", () => {
+  let globals;
+  let tSender;
+  let fetchStub;
+  const observerTopics = ["user-action-event", "performance-event",
+    "tab-session-complete", "undesired-event"];
+  const fakeEndpointUrl = "http://127.0.0.1/stuff";
+  const fakePingJSON = JSON.stringify({action: "fake_action", monkey: 1});
+  const fakeFetchHttpErrorResponse = {ok: false, status: 400};
+  const fakeFetchSuccessResponse = {ok: true, status: 200};
+
+  function assertNotificationObserversAdded() {
+    observerTopics.forEach(topic => {
+      assert.calledWithExactly(
+        global.Services.obs.addObserver, tSender, topic, true);
+    });
+  }
+
+  function assertNotificationObserversRemoved() {
+    observerTopics.forEach(topic => {
+      assert.calledWithExactly(
+        global.Services.obs.removeObserver, tSender, topic);
+    });
+  }
+
+  before(() => {
+    globals = new GlobalOverrider();
+
+    fetchStub = globals.sandbox.stub();
+
+    globals.set("Preferences", FakePrefs);
+    globals.set("fetch", fetchStub);
+  });
+
+  beforeEach(() => {
+  });
+
+  afterEach(() => {
+    globals.reset();
+    FakePrefs.prototype.prefs = {};
+  });
+
+  after(() => globals.restore());
+
+  it("should construct the Prefs object with the right branch", () => {
+    globals.sandbox.spy(global, "Preferences");
+
+    tSender = new TelemetrySender(tsArgs);
+
+    assert.calledOnce(global.Preferences);
+    assert.calledWith(global.Preferences,
+      sinon.match.has("branch", "browser.newtabpage.activity-stream"));
+  });
+
+  it("should set the enabled prop to false if the pref is false", () => {
+    FakePrefs.prototype.prefs = {telemetry: false};
+
+    tSender = new TelemetrySender(tsArgs);
+
+    assert.isFalse(tSender.enabled);
+  });
+
+  it("should not add notification observers if the enabled pref is false", () => {
+    FakePrefs.prototype.prefs = {telemetry: false};
+
+    tSender = new TelemetrySender(tsArgs);
+
+    assert.notCalled(global.Services.obs.addObserver);
+  });
+
+  it("should set the enabled prop to true if the pref is true", () => {
+    FakePrefs.prototype.prefs = {telemetry: true};
+
+    tSender = new TelemetrySender(tsArgs);
+
+    assert.isTrue(tSender.enabled);
+  });
+
+  it("should add all notification observers if the enabled pref is true", () => {
+    FakePrefs.prototype.prefs = {telemetry: true};
+
+    tSender = new TelemetrySender(tsArgs);
+
+    assertNotificationObserversAdded();
+  });
+
+  describe("#_sendPing()", () => {
+    beforeEach(() => {
+      FakePrefs.prototype.prefs = {
+        "telemetry": true,
+        "telemetry.ping.endpoint": fakeEndpointUrl
+      };
+      tSender = new TelemetrySender(tsArgs);
+    });
+
+    it("should POST given ping data to telemetry.ping.endpoint pref w/fetch",
+    async () => {
+      fetchStub.resolves(fakeFetchSuccessResponse);
+      await tSender._sendPing(fakePingJSON);
+
+      assert.calledOnce(fetchStub);
+      assert.calledWithExactly(fetchStub, fakeEndpointUrl,
+        {method: "POST", body: fakePingJSON});
+    });
+
+    it("should log HTTP failures using Cu.reportError", async () => {
+      fetchStub.resolves(fakeFetchHttpErrorResponse);
+
+      await tSender._sendPing(fakePingJSON);
+
+      assert.called(Components.utils.reportError);
+    });
+
+    it("should log an error using Cu.reportError if fetch rejects", async () => {
+      fetchStub.rejects("Oh noes!");
+
+      await tSender._sendPing(fakePingJSON);
+
+      assert.called(Components.utils.reportError);
+    });
+
+    it("should log if logging is on && if action is not activity_stream_performance", async () => {
+      FakePrefs.prototype.prefs = {
+        "telemetry": true,
+        "performance.log": true
+      };
+      fetchStub.resolves(fakeFetchSuccessResponse);
+      tSender = new TelemetrySender(tsArgs);
+
+      await tSender._sendPing(fakePingJSON);
+
+      assert.called(console.log); // eslint-disable-line no-console
+    });
+  });
+
+  describe("#observe()", () => {
+    before(() => {
+      globals.sandbox.stub(TelemetrySender.prototype, "_sendPing");
+    });
+
+    observerTopics.forEach(topic => {
+      it(`should call this._sendPing with data for ${topic}`, () => {
+        const fakeSubject = "fakeSubject";
+        tSender = new TelemetrySender(tsArgs);
+
+        tSender.observe(fakeSubject, topic, fakePingJSON);
+
+        assert.calledOnce(TelemetrySender.prototype._sendPing);
+        assert.calledWithExactly(TelemetrySender.prototype._sendPing,
+          fakePingJSON);
+      });
+    });
+
+    it("should not call this._sendPing for 'nonexistent-topic'", () => {
+      const fakeSubject = "fakeSubject";
+      tSender = new TelemetrySender(tsArgs);
+
+      tSender.observe(fakeSubject, "nonexistent-topic", fakePingJSON);
+
+      assert.notCalled(TelemetrySender.prototype._sendPing);
+    });
+  });
+
+  describe("#uninit()", () => {
+    it("should remove the telemetry pref listener", () => {
+      tSender = new TelemetrySender(tsArgs);
+      assert.property(fakePrefs.observers, "telemetry");
+
+      tSender.uninit();
+
+      assert.notProperty(fakePrefs.observers, "telemetry");
+    });
+
+    it("should remove all notification observers if telemetry pref is true", () => {
+      FakePrefs.prototype.prefs = {telemetry: true};
+      tSender = new TelemetrySender(tsArgs);
+
+      tSender.uninit();
+
+      assertNotificationObserversRemoved();
+    });
+
+    it("should not remove notification observers if telemetry pref is false", () => {
+      FakePrefs.prototype.prefs = {telemetry: false};
+      tSender = new TelemetrySender(tsArgs);
+
+      tSender.uninit();
+
+      assert.notCalled(global.Services.obs.removeObserver);
+    });
+
+    it("should call Cu.reportError if this._prefs.ignore throws", () => {
+      globals.sandbox.stub(FakePrefs.prototype, "ignore").throws("Some Error");
+      tSender = new TelemetrySender(tsArgs);
+
+      tSender.uninit();
+
+      assert.called(global.Components.utils.reportError);
+    });
+  });
+
+  describe("Misc pref changes", () => {
+    describe("telemetry changes from true to false", () => {
+      beforeEach(() => {
+        FakePrefs.prototype.prefs = {"telemetry": true};
+        tSender = new TelemetrySender(tsArgs);
+        assert.propertyVal(tSender, "enabled", true);
+      });
+
+      it("should set the enabled property to false", () => {
+        fakePrefs.set("telemetry", false);
+
+        assert.propertyVal(tSender, "enabled", false);
+      });
+
+      it("should remove all notification observers", () => {
+        fakePrefs.set("telemetry", false);
+
+        assertNotificationObserversRemoved();
+      });
+    });
+
+    describe("telemetry changes from false to true", () => {
+      beforeEach(() => {
+        FakePrefs.prototype.prefs = {"telemetry": false};
+        tSender = new TelemetrySender(tsArgs);
+        assert.propertyVal(tSender, "enabled", false);
+      });
+
+      it("should set the enabled property to true", () => {
+        fakePrefs.set("telemetry", true);
+
+        assert.propertyVal(tSender, "enabled", true);
+      });
+
+      it("should add all topic observers", () => {
+        fakePrefs.set("telemetry", true);
+
+        assertNotificationObserversAdded();
+      });
+    });
+
+    describe("performance.log changes from false to true", () => {
+      it("should change this.logging from false to true", () => {
+        FakePrefs.prototype.prefs = {"performance.log": false};
+        tSender = new TelemetrySender(tsArgs);
+        assert.propertyVal(tSender, "logging", false);
+
+        fakePrefs.set("performance.log", true);
+
+        assert.propertyVal(tSender, "logging", true);
+      });
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -0,0 +1,116 @@
+"use strict";
+const {TopSitesFeed, UPDATE_TIME, TOP_SITES_SHOWMORE_LENGTH, DEFAULT_TOP_SITES} = require("lib/TopSitesFeed.jsm");
+const {GlobalOverrider} = require("test/unit/utils");
+const action = {meta: {fromTarget: {}}};
+const {actionTypes: at} = require("common/Actions.jsm");
+const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({url: `site${i}.com`}));
+const FAKE_SCREENSHOT = "data123";
+
+describe("Top Sites Feed", () => {
+  let feed;
+  let globals;
+  let sandbox;
+  let links;
+  let clock;
+  before(() => {
+    globals = new GlobalOverrider();
+    sandbox = globals.sandbox;
+  });
+  beforeEach(() => {
+    globals.set("PlacesProvider", {links: {getLinks: sandbox.spy(() => Promise.resolve(links))}});
+    globals.set("PreviewProvider", {getThumbnail: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT))});
+    feed = new TopSitesFeed();
+    feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }};
+    links = FAKE_LINKS;
+    clock = sinon.useFakeTimers();
+  });
+  afterEach(() => {
+    globals.restore();
+    clock.restore();
+  });
+
+  it("should have default sites with .isDefault = true", () => {
+    DEFAULT_TOP_SITES.forEach(link => assert.propertyVal(link, "isDefault", true));
+  });
+
+  describe("#getLinksWithDefaults", () => {
+    it("should get the links from Places Provider", async () => {
+      const result = await feed.getLinksWithDefaults();
+      assert.deepEqual(result, links);
+      assert.calledOnce(global.PlacesProvider.links.getLinks);
+    });
+    it("should add defaults if there are are not enough links", async () => {
+      links = [{url: "foo.com"}];
+      const result = await feed.getLinksWithDefaults();
+      assert.deepEqual(result, [{url: "foo.com"}, ...DEFAULT_TOP_SITES]);
+    });
+    it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
+      links = new Array(TOP_SITES_SHOWMORE_LENGTH - 1).fill({url: "foo.com"});
+      const result = await feed.getLinksWithDefaults();
+      assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
+      assert.deepEqual(result, [...links, DEFAULT_TOP_SITES[0]]);
+    });
+    it("should not throw if PlacesProvider returns null", () => {
+      links = null;
+      assert.doesNotThrow(() => {
+        feed.getLinksWithDefaults(action);
+      });
+    });
+  });
+  describe("#refresh", () => {
+    it("should dispatch an action with the links returned", async () => {
+      sandbox.stub(feed, "getScreenshot");
+      await feed.refresh(action);
+      assert.calledOnce(feed.store.dispatch);
+      assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
+      assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, links);
+    });
+    it("should call .getScreenshot for each link", async () => {
+      sandbox.stub(feed, "getScreenshot");
+      await feed.refresh(action);
+
+      links.forEach(link => assert.calledWith(feed.getScreenshot, link.url));
+    });
+  });
+  describe("getScreenshot", () => {
+    it("should call PreviewProvider.getThumbnail with the right url", async () => {
+      const url = "foo.com";
+      await feed.getScreenshot(url);
+      assert.calledWith(global.PreviewProvider.getThumbnail, url);
+    });
+  });
+  describe("#onAction", () => {
+    it("should call refresh if there are not enough sites on NEW_TAB_LOAD", () => {
+      feed.store.getState = function() { return {TopSites: {rows: []}}; };
+      sinon.stub(feed, "refresh");
+      feed.onAction({type: at.NEW_TAB_LOAD});
+      assert.calledOnce(feed.refresh);
+    });
+    it("should call refresh if there are not sites on NEW_TAB_LOAD, not counting defaults", () => {
+      feed.store.getState = function() { return {TopSites: {rows: [{url: "foo.com"}, ...DEFAULT_TOP_SITES]}}; };
+      sinon.stub(feed, "refresh");
+      feed.onAction({type: at.NEW_TAB_LOAD});
+      assert.calledOnce(feed.refresh);
+    });
+    it("should not call refresh if there are enough sites on NEW_TAB_LOAD", () => {
+      feed.lastUpdated = Date.now();
+      sinon.stub(feed, "refresh");
+      feed.onAction({type: at.NEW_TAB_LOAD});
+      assert.notCalled(feed.refresh);
+    });
+    it("should call refresh if .lastUpdated is too old on NEW_TAB_LOAD", () => {
+      feed.lastUpdated = 0;
+      clock.tick(UPDATE_TIME);
+      sinon.stub(feed, "refresh");
+      feed.onAction({type: at.NEW_TAB_LOAD});
+      assert.calledOnce(feed.refresh);
+    });
+    it("should not call refresh if .lastUpdated is less than update time on NEW_TAB_LOAD", () => {
+      feed.lastUpdated = 0;
+      clock.tick(UPDATE_TIME - 1);
+      sinon.stub(feed, "refresh");
+      feed.onAction({type: at.NEW_TAB_LOAD});
+      assert.notCalled(feed.refresh);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
@@ -0,0 +1,43 @@
+const initStore = require("content-src/lib/init-store");
+const {GlobalOverrider, addNumberReducer} = require("test/unit/utils");
+const {actionCreators: ac} = require("common/Actions.jsm");
+
+describe("initStore", () => {
+  let globals;
+  let store;
+  before(() => {
+    globals = new GlobalOverrider();
+    globals.set("sendAsyncMessage", globals.sandbox.spy());
+    globals.set("addMessageListener", globals.sandbox.spy());
+  });
+  beforeEach(() => {
+    store = initStore({number: addNumberReducer});
+  });
+  afterEach(() => globals.reset());
+  after(() => globals.restore());
+  it("should create a store with the provided reducers", () => {
+    assert.ok(store);
+    assert.property(store.getState(), "number");
+  });
+  it("should add a listener for incoming actions", () => {
+    assert.calledWith(global.addMessageListener, initStore.INCOMING_MESSAGE_NAME);
+    const callback = global.addMessageListener.firstCall.args[1];
+    globals.sandbox.spy(store, "dispatch");
+    const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
+    callback(message);
+    assert.calledWith(store.dispatch, message.data);
+  });
+  it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
+    store.dispatch({type: initStore.MERGE_STORE_ACTION, data: {number: 42}});
+    assert.deepEqual(store.getState(), {number: 42});
+  });
+  it("should send out SendToMain ations", () => {
+    const action = ac.SendToMain({type: "FOO"});
+    store.dispatch(action);
+    assert.calledWith(global.sendAsyncMessage, initStore.OUTGOING_MESSAGE_NAME, action);
+  });
+  it("should not send out other types of ations", () => {
+    store.dispatch({type: "FOO"});
+    assert.notCalled(global.sendAsyncMessage);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/unit-entry.js
@@ -0,0 +1,38 @@
+const {GlobalOverrider} = require("test/unit/utils");
+
+const req = require.context(".", true, /\.test\.js$/);
+const files = req.keys();
+
+// This exposes sinon assertions to chai.assert
+sinon.assert.expose(assert, {prefix: ""});
+
+let overrider = new GlobalOverrider();
+overrider.set({
+  Components: {
+    interfaces: {},
+    utils: {
+      import: overrider.sandbox.spy(),
+      importGlobalProperties: overrider.sandbox.spy(),
+      reportError: overrider.sandbox.spy()
+    }
+  },
+  XPCOMUtils: {
+    defineLazyModuleGetter: overrider.sandbox.spy(),
+    defineLazyServiceGetter: overrider.sandbox.spy(),
+    generateQI: overrider.sandbox.stub().returns(() => {})
+  },
+  console: {log: overrider.sandbox.spy()},
+  dump: overrider.sandbox.spy(),
+  Services: {
+    obs: {
+      addObserver: overrider.sandbox.spy(),
+      removeObserver: overrider.sandbox.spy()
+    }
+  }
+});
+
+describe("activity-stream", () => {
+  afterEach(() => overrider.reset());
+  after(() => overrider.restore());
+  files.forEach(file => req(file));
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/utils.js
@@ -0,0 +1,122 @@
+/**
+ * GlobalOverrider - Utility that allows you to override properties on the global object.
+ *                   See unit-entry.js for example usage.
+ */
+class GlobalOverrider {
+  constructor() {
+    this.originalGlobals = new Map();
+    this.sandbox = sinon.sandbox.create();
+  }
+
+  /**
+   * _override - Internal method to override properties on the global object.
+   *             The first time a given key is overridden, we cache the original
+   *             value in this.originalGlobals so that later it can be restored.
+   *
+   * @param  {string} key The identifier of the property
+   * @param  {any} value The value to which the property should be reassigned
+   */
+  _override(key, value) {
+    if (key === "Components") {
+      // Components can be reassigned, but it will subsequently throw a deprecation
+      // error in Firefox which will stop execution. Adding the assignment statement
+      // to a try/catch block will prevent this from happening.
+      try {
+        global[key] = value;
+      } catch (e) {} // eslint-disable-line no-empty
+      return;
+    }
+    if (!this.originalGlobals.has(key)) {
+      this.originalGlobals.set(key, global[key]);
+    }
+    global[key] = value;
+  }
+
+  /**
+   * set - Override a given property, or all properties on an object
+   *
+   * @param  {string|object} key If a string, the identifier of the property
+   *                             If an object, a number of properties and values to which they should be reassigned.
+   * @param  {any} value The value to which the property should be reassigned
+   * @return {type}       description
+   */
+  set(key, value) {
+    if (!value && typeof key === "object") {
+      const overrides = key;
+      Object.keys(overrides).forEach(k => this._override(k, overrides[k]));
+    } else {
+      this._override(key, value);
+    }
+  }
+
+  /**
+   * reset - Reset the global sandbox, so all state on spies, stubs etc. is cleared.
+   *         You probably want to call this after each test.
+   */
+  reset() {
+    this.sandbox.reset();
+  }
+
+  /**
+   * restore - Restore the global sandbox and reset all overriden properties to
+   *           their original values. You should call this after all tests have completed.
+   */
+  restore() {
+    this.sandbox.restore();
+    this.originalGlobals.forEach((value, key) => {
+      global[key] = value;
+    });
+  }
+}
+
+/**
+ * Very simple fake for the most basic semantics of Preferences.jsm. Lots of
+ * things aren't yet supported.  Feel free to add them in.
+ *
+ * @param {Object} args - optional arguments
+ * @param {Function} args.initHook - if present, will be called back
+ *                   inside the constructor. Typically used from tests
+ *                   to save off a pointer to the created instance so that
+ *                   stubs and spies can be inspected by the test code.
+ */
+function FakePrefs(args) {
+  if (args) {
+    if ("initHook" in args) {
+      args.initHook.call(this);
+    }
+  }
+}
+FakePrefs.prototype = {
+  observers: {},
+  observe(prefName, callback) {
+    this.observers[prefName] = callback;
+  },
+  ignore(prefName, callback) {
+    if (prefName in this.observers) {
+      delete this.observers[prefName];
+    }
+  },
+
+  prefs: {},
+  get(prefName) { return this.prefs[prefName]; },
+  set(prefName, value) {
+    this.prefs[prefName] = value;
+
+    if (prefName in this.observers) {
+      this.observers[prefName](value);
+    }
+  }
+};
+
+/**
+ * addNumberReducer - a simple dummy reducer for testing that adds a number
+ */
+function addNumberReducer(prevState = 0, action) {
+  return action.type === "ADD" ? prevState + action.data : prevState;
+}
+
+module.exports = {
+  FakePrefs,
+  GlobalOverrider,
+  addNumberReducer
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/vendor/redux.js
@@ -0,0 +1,948 @@
+/**
+ * Redux v.3.6.0
+ */
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["Redux"] = factory();
+	else
+		root["Redux"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined;
+
+	var _createStore = __webpack_require__(2);
+
+	var _createStore2 = _interopRequireDefault(_createStore);
+
+	var _combineReducers = __webpack_require__(7);
+
+	var _combineReducers2 = _interopRequireDefault(_combineReducers);
+
+	var _bindActionCreators = __webpack_require__(6);
+
+	var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
+
+	var _applyMiddleware = __webpack_require__(5);
+
+	var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
+
+	var _compose = __webpack_require__(1);
+
+	var _compose2 = _interopRequireDefault(_compose);
+
+	var _warning = __webpack_require__(3);
+
+	var _warning2 = _interopRequireDefault(_warning);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	/*
+	* This is a dummy function to check if the function name has been altered by minification.
+	* If the function has been minified and NODE_ENV !== 'production', warn the user.
+	*/
+	function isCrushed() {}
+
+	if (("development") !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
+	  (0, _warning2['default'])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
+	}
+
+	exports.createStore = _createStore2['default'];
+	exports.combineReducers = _combineReducers2['default'];
+	exports.bindActionCreators = _bindActionCreators2['default'];
+	exports.applyMiddleware = _applyMiddleware2['default'];
+	exports.compose = _compose2['default'];
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	exports.__esModule = true;
+	exports["default"] = compose;
+	/**
+	 * Composes single-argument functions from right to left. The rightmost
+	 * function can take multiple arguments as it provides the signature for
+	 * the resulting composite function.
+	 *
+	 * @param {...Function} funcs The functions to compose.
+	 * @returns {Function} A function obtained by composing the argument functions
+	 * from right to left. For example, compose(f, g, h) is identical to doing
+	 * (...args) => f(g(h(...args))).
+	 */
+
+	function compose() {
+	  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
+	    funcs[_key] = arguments[_key];
+	  }
+
+	  if (funcs.length === 0) {
+	    return function (arg) {
+	      return arg;
+	    };
+	  }
+
+	  if (funcs.length === 1) {
+	    return funcs[0];
+	  }
+
+	  var last = funcs[funcs.length - 1];
+	  var rest = funcs.slice(0, -1);
+	  return function () {
+	    return rest.reduceRight(function (composed, f) {
+	      return f(composed);
+	    }, last.apply(undefined, arguments));
+	  };
+	}
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.ActionTypes = undefined;
+	exports['default'] = createStore;
+
+	var _isPlainObject = __webpack_require__(4);
+
+	var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+	var _symbolObservable = __webpack_require__(12);
+
+	var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	/**
+	 * These are private action types reserved by Redux.
+	 * For any unknown actions, you must return the current state.
+	 * If the current state is undefined, you must return the initial state.
+	 * Do not reference these action types directly in your code.
+	 */
+	var ActionTypes = exports.ActionTypes = {
+	  INIT: '@@redux/INIT'
+	};
+
+	/**
+	 * Creates a Redux store that holds the state tree.
+	 * The only way to change the data in the store is to call `dispatch()` on it.
+	 *
+	 * There should only be a single store in your app. To specify how different
+	 * parts of the state tree respond to actions, you may combine several reducers
+	 * into a single reducer function by using `combineReducers`.
+	 *
+	 * @param {Function} reducer A function that returns the next state tree, given
+	 * the current state tree and the action to handle.
+	 *
+	 * @param {any} [preloadedState] The initial state. You may optionally specify it
+	 * to hydrate the state from the server in universal apps, or to restore a
+	 * previously serialized user session.
+	 * If you use `combineReducers` to produce the root reducer function, this must be
+	 * an object with the same shape as `combineReducers` keys.
+	 *
+	 * @param {Function} enhancer The store enhancer. You may optionally specify it
+	 * to enhance the store with third-party capabilities such as middleware,
+	 * time travel, persistence, etc. The only store enhancer that ships with Redux
+	 * is `applyMiddleware()`.
+	 *
+	 * @returns {Store} A Redux store that lets you read the state, dispatch actions
+	 * and subscribe to changes.
+	 */
+	function createStore(reducer, preloadedState, enhancer) {
+	  var _ref2;
+
+	  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
+	    enhancer = preloadedState;
+	    preloadedState = undefined;
+	  }
+
+	  if (typeof enhancer !== 'undefined') {
+	    if (typeof enhancer !== 'function') {
+	      throw new Error('Expected the enhancer to be a function.');
+	    }
+
+	    return enhancer(createStore)(reducer, preloadedState);
+	  }
+
+	  if (typeof reducer !== 'function') {
+	    throw new Error('Expected the reducer to be a function.');
+	  }
+
+	  var currentReducer = reducer;
+	  var currentState = preloadedState;
+	  var currentListeners = [];
+	  var nextListeners = currentListeners;
+	  var isDispatching = false;
+
+	  function ensureCanMutateNextListeners() {
+	    if (nextListeners === currentListeners) {
+	      nextListeners = currentListeners.slice();
+	    }
+	  }
+
+	  /**
+	   * Reads the state tree managed by the store.
+	   *
+	   * @returns {any} The current state tree of your application.
+	   */
+	  function getState() {
+	    return currentState;
+	  }
+
+	  /**
+	   * Adds a change listener. It will be called any time an action is dispatched,
+	   * and some part of the state tree may potentially have changed. You may then
+	   * call `getState()` to read the current state tree inside the callback.
+	   *
+	   * You may call `dispatch()` from a change listener, with the following
+	   * caveats:
+	   *
+	   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
+	   * If you subscribe or unsubscribe while the listeners are being invoked, this
+	   * will not have any effect on the `dispatch()` that is currently in progress.
+	   * However, the next `dispatch()` call, whether nested or not, will use a more
+	   * recent snapshot of the subscription list.
+	   *
+	   * 2. The listener should not expect to see all state changes, as the state
+	   * might have been updated multiple times during a nested `dispatch()` before
+	   * the listener is called. It is, however, guaranteed that all subscribers
+	   * registered before the `dispatch()` started will be called with the latest
+	   * state by the time it exits.
+	   *
+	   * @param {Function} listener A callback to be invoked on every dispatch.
+	   * @returns {Function} A function to remove this change listener.
+	   */
+	  function subscribe(listener) {
+	    if (typeof listener !== 'function') {
+	      throw new Error('Expected listener to be a function.');
+	    }
+
+	    var isSubscribed = true;
+
+	    ensureCanMutateNextListeners();
+	    nextListeners.push(listener);
+
+	    return function unsubscribe() {
+	      if (!isSubscribed) {
+	        return;
+	      }
+
+	      isSubscribed = false;
+
+	      ensureCanMutateNextListeners();
+	      var index = nextListeners.indexOf(listener);
+	      nextListeners.splice(index, 1);
+	    };
+	  }
+
+	  /**
+	   * Dispatches an action. It is the only way to trigger a state change.
+	   *
+	   * The `reducer` function, used to create the store, will be called with the
+	   * current state tree and the given `action`. Its return value will
+	   * be considered the **next** state of the tree, and the change listeners
+	   * will be notified.
+	   *
+	   * The base implementation only supports plain object actions. If you want to
+	   * dispatch a Promise, an Observable, a thunk, or something else, you need to
+	   * wrap your store creating function into the corresponding middleware. For
+	   * example, see the documentation for the `redux-thunk` package. Even the
+	   * middleware will eventually dispatch plain object actions using this method.
+	   *
+	   * @param {Object} action A plain object representing “what changed”. It is
+	   * a good idea to keep actions serializable so you can record and replay user
+	   * sessions, or use the time travelling `redux-devtools`. An action must have
+	   * a `type` property which may not be `undefined`. It is a good idea to use
+	   * string constants for action types.
+	   *
+	   * @returns {Object} For convenience, the same action object you dispatched.
+	   *
+	   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
+	   * return something else (for example, a Promise you can await).
+	   */
+	  function dispatch(action) {
+	    if (!(0, _isPlainObject2['default'])(action)) {
+	      throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
+	    }
+
+	    if (typeof action.type === 'undefined') {
+	      throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
+	    }
+
+	    if (isDispatching) {
+	      throw new Error('Reducers may not dispatch actions.');
+	    }
+
+	    try {
+	      isDispatching = true;
+	      currentState = currentReducer(currentState, action);
+	    } finally {
+	      isDispatching = false;
+	    }
+
+	    var listeners = currentListeners = nextListeners;
+	    for (var i = 0; i < listeners.length; i++) {
+	      listeners[i]();
+	    }
+
+	    return action;
+	  }
+
+	  /**
+	   * Replaces the reducer currently used by the store to calculate the state.
+	   *
+	   * You might need this if your app implements code splitting and you want to
+	   * load some of the reducers dynamically. You might also need this if you
+	   * implement a hot reloading mechanism for Redux.
+	   *
+	   * @param {Function} nextReducer The reducer for the store to use instead.
+	   * @returns {void}
+	   */
+	  function replaceReducer(nextReducer) {
+	    if (typeof nextReducer !== 'function') {
+	      throw new Error('Expected the nextReducer to be a function.');
+	    }
+
+	    currentReducer = nextReducer;
+	    dispatch({ type: ActionTypes.INIT });
+	  }
+
+	  /**
+	   * Interoperability point for observable/reactive libraries.
+	   * @returns {observable} A minimal observable of state changes.
+	   * For more information, see the observable proposal:
+	   * https://github.com/zenparsing/es-observable
+	   */
+	  function observable() {
+	    var _ref;
+
+	    var outerSubscribe = subscribe;
+	    return _ref = {
+	      /**
+	       * The minimal observable subscription method.
+	       * @param {Object} observer Any object that can be used as an observer.
+	       * The observer object should have a `next` method.
+	       * @returns {subscription} An object with an `unsubscribe` method that can
+	       * be used to unsubscribe the observable from the store, and prevent further
+	       * emission of values from the observable.
+	       */
+	      subscribe: function subscribe(observer) {
+	        if (typeof observer !== 'object') {
+	          throw new TypeError('Expected the observer to be an object.');
+	        }
+
+	        function observeState() {
+	          if (observer.next) {
+	            observer.next(getState());
+	          }
+	        }
+
+	        observeState();
+	        var unsubscribe = outerSubscribe(observeState);
+	        return { unsubscribe: unsubscribe };
+	      }
+	    }, _ref[_symbolObservable2['default']] = function () {
+	      return this;
+	    }, _ref;
+	  }
+
+	  // When a store is created, an "INIT" action is dispatched so that every
+	  // reducer returns their initial state. This effectively populates
+	  // the initial state tree.
+	  dispatch({ type: ActionTypes.INIT });
+
+	  return _ref2 = {
+	    dispatch: dispatch,
+	    subscribe: subscribe,
+	    getState: getState,
+	    replaceReducer: replaceReducer
+	  }, _ref2[_symbolObservable2['default']] = observable, _ref2;
+	}
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = warning;
+	/**
+	 * Prints a warning in the console if it exists.
+	 *
+	 * @param {String} message The warning message.
+	 * @returns {void}
+	 */
+	function warning(message) {
+	  /* eslint-disable no-console */
+	  if (typeof console !== 'undefined' && typeof console.error === 'function') {
+	    console.error(message);
+	  }
+	  /* eslint-enable no-console */
+	  try {
+	    // This error was thrown as a convenience so that if you enable
+	    // "break on all exceptions" in your console,
+	    // it would pause the execution at this line.
+	    throw new Error(message);
+	    /* eslint-disable no-empty */
+	  } catch (e) {}
+	  /* eslint-enable no-empty */
+	}
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var getPrototype = __webpack_require__(8),
+	    isHostObject = __webpack_require__(9),
+	    isObjectLike = __webpack_require__(11);
+
+	/** `Object#toString` result references. */
+	var objectTag = '[object Object]';
+
+	/** Used for built-in method references. */
+	var funcProto = Function.prototype,
+	    objectProto = Object.prototype;
+
+	/** Used to resolve the decompiled source of functions. */
+	var funcToString = funcProto.toString;
+
+	/** Used to check objects for own properties. */
+	var hasOwnProperty = objectProto.hasOwnProperty;
+
+	/** Used to infer the `Object` constructor. */
+	var objectCtorString = funcToString.call(Object);
+
+	/**
+	 * Used to resolve the
+	 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+	 * of values.
+	 */
+	var objectToString = objectProto.toString;
+
+	/**
+	 * Checks if `value` is a plain object, that is, an object created by the
+	 * `Object` constructor or one with a `[[Prototype]]` of `null`.
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 0.8.0
+	 * @category Lang
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+	 * @example
+	 *
+	 * function Foo() {
+	 *   this.a = 1;
+	 * }
+	 *
+	 * _.isPlainObject(new Foo);
+	 * // => false
+	 *
+	 * _.isPlainObject([1, 2, 3]);
+	 * // => false
+	 *
+	 * _.isPlainObject({ 'x': 0, 'y': 0 });
+	 * // => true
+	 *
+	 * _.isPlainObject(Object.create(null));
+	 * // => true
+	 */
+	function isPlainObject(value) {
+	  if (!isObjectLike(value) ||
+	      objectToString.call(value) != objectTag || isHostObject(value)) {
+	    return false;
+	  }
+	  var proto = getPrototype(value);
+	  if (proto === null) {
+	    return true;
+	  }
+	  var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+	  return (typeof Ctor == 'function' &&
+	    Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+	}
+
+	module.exports = isPlainObject;
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+	exports['default'] = applyMiddleware;
+
+	var _compose = __webpack_require__(1);
+
+	var _compose2 = _interopRequireDefault(_compose);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	/**
+	 * Creates a store enhancer that applies middleware to the dispatch method
+	 * of the Redux store. This is handy for a variety of tasks, such as expressing
+	 * asynchronous actions in a concise manner, or logging every action payload.
+	 *
+	 * See `redux-thunk` package as an example of the Redux middleware.
+	 *
+	 * Because middleware is potentially asynchronous, this should be the first
+	 * store enhancer in the composition chain.
+	 *
+	 * Note that each middleware will be given the `dispatch` and `getState` functions
+	 * as named arguments.
+	 *
+	 * @param {...Function} middlewares The middleware chain to be applied.
+	 * @returns {Function} A store enhancer applying the middleware.
+	 */
+	function applyMiddleware() {
+	  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
+	    middlewares[_key] = arguments[_key];
+	  }
+
+	  return function (createStore) {
+	    return function (reducer, preloadedState, enhancer) {
+	      var store = createStore(reducer, preloadedState, enhancer);
+	      var _dispatch = store.dispatch;
+	      var chain = [];
+
+	      var middlewareAPI = {
+	        getState: store.getState,
+	        dispatch: function dispatch(action) {
+	          return _dispatch(action);
+	        }
+	      };
+	      chain = middlewares.map(function (middleware) {
+	        return middleware(middlewareAPI);
+	      });
+	      _dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
+
+	      return _extends({}, store, {
+	        dispatch: _dispatch
+	      });
+	    };
+	  };
+	}
+
+/***/ },
+/* 6 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = bindActionCreators;
+	function bindActionCreator(actionCreator, dispatch) {
+	  return function () {
+	    return dispatch(actionCreator.apply(undefined, arguments));
+	  };
+	}
+
+	/**
+	 * Turns an object whose values are action creators, into an object with the
+	 * same keys, but with every function wrapped into a `dispatch` call so they
+	 * may be invoked directly. This is just a convenience method, as you can call
+	 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
+	 *
+	 * For convenience, you can also pass a single function as the first argument,
+	 * and get a function in return.
+	 *
+	 * @param {Function|Object} actionCreators An object whose values are action
+	 * creator functions. One handy way to obtain it is to use ES6 `import * as`
+	 * syntax. You may also pass a single function.
+	 *
+	 * @param {Function} dispatch The `dispatch` function available on your Redux
+	 * store.
+	 *
+	 * @returns {Function|Object} The object mimicking the original object, but with
+	 * every action creator wrapped into the `dispatch` call. If you passed a
+	 * function as `actionCreators`, the return value will also be a single
+	 * function.
+	 */
+	function bindActionCreators(actionCreators, dispatch) {
+	  if (typeof actionCreators === 'function') {
+	    return bindActionCreator(actionCreators, dispatch);
+	  }
+
+	  if (typeof actionCreators !== 'object' || actionCreators === null) {
+	    throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
+	  }
+
+	  var keys = Object.keys(actionCreators);
+	  var boundActionCreators = {};
+	  for (var i = 0; i < keys.length; i++) {
+	    var key = keys[i];
+	    var actionCreator = actionCreators[key];
+	    if (typeof actionCreator === 'function') {
+	      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
+	    }
+	  }
+	  return boundActionCreators;
+	}
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports['default'] = combineReducers;
+
+	var _createStore = __webpack_require__(2);
+
+	var _isPlainObject = __webpack_require__(4);
+
+	var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+	var _warning = __webpack_require__(3);
+
+	var _warning2 = _interopRequireDefault(_warning);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	function getUndefinedStateErrorMessage(key, action) {
+	  var actionType = action && action.type;
+	  var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
+
+	  return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.';
+	}
+
+	function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
+	  var reducerKeys = Object.keys(reducers);
+	  var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
+
+	  if (reducerKeys.length === 0) {
+	    return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
+	  }
+
+	  if (!(0, _isPlainObject2['default'])(inputState)) {
+	    return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
+	  }
+
+	  var unexpectedKeys = Object.keys(inputState).filter(function (key) {
+	    return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
+	  });
+
+	  unexpectedKeys.forEach(function (key) {
+	    unexpectedKeyCache[key] = true;
+	  });
+
+	  if (unexpectedKeys.length > 0) {
+	    return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
+	  }
+	}
+
+	function assertReducerSanity(reducers) {
+	  Object.keys(reducers).forEach(function (key) {
+	    var reducer = reducers[key];
+	    var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT });
+
+	    if (typeof initialState === 'undefined') {
+	      throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.');
+	    }
+
+	    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
+	    if (typeof reducer(undefined, { type: type }) === 'undefined') {
+	      throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.');
+	    }
+	  });
+	}
+
+	/**
+	 * Turns an object whose values are different reducer functions, into a single
+	 * reducer function. It will call every child reducer, and gather their results
+	 * into a single state object, whose keys correspond to the keys of the passed
+	 * reducer functions.
+	 *
+	 * @param {Object} reducers An object whose values correspond to different
+	 * reducer functions that need to be combined into one. One handy way to obtain
+	 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
+	 * undefined for any action. Instead, they should return their initial state
+	 * if the state passed to them was undefined, and the current state for any
+	 * unrecognized action.
+	 *
+	 * @returns {Function} A reducer function that invokes every reducer inside the
+	 * passed object, and builds a state object with the same shape.
+	 */
+	function combineReducers(reducers) {
+	  var reducerKeys = Object.keys(reducers);
+	  var finalReducers = {};
+	  for (var i = 0; i < reducerKeys.length; i++) {
+	    var key = reducerKeys[i];
+
+	    if (true) {
+	      if (typeof reducers[key] === 'undefined') {
+	        (0, _warning2['default'])('No reducer provided for key "' + key + '"');
+	      }
+	    }
+
+	    if (typeof reducers[key] === 'function') {
+	      finalReducers[key] = reducers[key];
+	    }
+	  }
+	  var finalReducerKeys = Object.keys(finalReducers);
+
+	  if (true) {
+	    var unexpectedKeyCache = {};
+	  }
+
+	  var sanityError;
+	  try {
+	    assertReducerSanity(finalReducers);
+	  } catch (e) {
+	    sanityError = e;
+	  }
+
+	  return function combination() {
+	    var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+	    var action = arguments[1];
+
+	    if (sanityError) {
+	      throw sanityError;
+	    }
+
+	    if (true) {
+	      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
+	      if (warningMessage) {
+	        (0, _warning2['default'])(warningMessage);
+	      }
+	    }
+
+	    var hasChanged = false;
+	    var nextState = {};
+	    for (var i = 0; i < finalReducerKeys.length; i++) {
+	      var key = finalReducerKeys[i];
+	      var reducer = finalReducers[key];
+	      var previousStateForKey = state[key];
+	      var nextStateForKey = reducer(previousStateForKey, action);
+	      if (typeof nextStateForKey === 'undefined') {
+	        var errorMessage = getUndefinedStateErrorMessage(key, action);
+	        throw new Error(errorMessage);
+	      }
+	      nextState[key] = nextStateForKey;
+	      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
+	    }
+	    return hasChanged ? nextState : state;
+	  };
+	}
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var overArg = __webpack_require__(10);
+
+	/** Built-in value references. */
+	var getPrototype = overArg(Object.getPrototypeOf, Object);
+
+	module.exports = getPrototype;
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+	/**
+	 * Checks if `value` is a host object in IE < 9.
+	 *
+	 * @private
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+	 */
+	function isHostObject(value) {
+	  // Many host objects are `Object` objects that can coerce to strings
+	  // despite having improperly defined `toString` methods.
+	  var result = false;
+	  if (value != null && typeof value.toString != 'function') {
+	    try {
+	      result = !!(value + '');
+	    } catch (e) {}
+	  }
+	  return result;
+	}
+
+	module.exports = isHostObject;
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+	/**
+	 * Creates a unary function that invokes `func` with its argument transformed.
+	 *
+	 * @private
+	 * @param {Function} func The function to wrap.
+	 * @param {Function} transform The argument transform.
+	 * @returns {Function} Returns the new function.
+	 */
+	function overArg(func, transform) {
+	  return function(arg) {
+	    return func(transform(arg));
+	  };
+	}
+
+	module.exports = overArg;
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports) {
+
+	/**
+	 * Checks if `value` is object-like. A value is object-like if it's not `null`
+	 * and has a `typeof` result of "object".
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 4.0.0
+	 * @category Lang
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+	 * @example
+	 *
+	 * _.isObjectLike({});
+	 * // => true
+	 *
+	 * _.isObjectLike([1, 2, 3]);
+	 * // => true
+	 *
+	 * _.isObjectLike(_.noop);
+	 * // => false
+	 *
+	 * _.isObjectLike(null);
+	 * // => false
+	 */
+	function isObjectLike(value) {
+	  return !!value && typeof value == 'object';
+	}
+
+	module.exports = isObjectLike;
+
+
+/***/ },
+/* 12 */
+/***/ function(module, exports, __webpack_require__) {
+
+	module.exports = __webpack_require__(13);
+
+
+/***/ },
+/* 13 */
+/***/ function(module, exports, __webpack_require__) {
+
+	/* WEBPACK VAR INJECTION */(function(global) {'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+		value: true
+	});
+
+	var _ponyfill = __webpack_require__(14);
+
+	var _ponyfill2 = _interopRequireDefault(_ponyfill);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var root = undefined; /* global window */
+
+	if (typeof global !== 'undefined') {
+		root = global;
+	} else if (typeof window !== 'undefined') {
+		root = window;
+	}
+
+	var result = (0, _ponyfill2['default'])(root);
+	exports['default'] = result;
+	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 14 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+		value: true
+	});
+	exports['default'] = symbolObservablePonyfill;
+	function symbolObservablePonyfill(root) {
+		var result;
+		var _Symbol = root.Symbol;
+
+		if (typeof _Symbol === 'function') {
+			if (_Symbol.observable) {
+				result = _Symbol.observable;
+			} else {
+				result = _Symbol('observable');
+				_Symbol.observable = result;
+			}
+		} else {
+			result = '@@observable';
+		}
+
+		return result;
+	};
+
+/***/ }
+/******/ ])
+});
+;
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -5,17 +5,17 @@
 
 <!-- LOCALIZATION NOTE (update.checkForUpdatesButton.*, update.updateButton.*):
 # Only one button is present at a time.
 # The button when displayed is located directly under the Firefox version in
 # the about dialog (see bug 596813 for screenshots).
 -->
 <!ENTITY update.checkForUpdatesButton.label       "Check for updates">
 <!ENTITY update.checkForUpdatesButton.accesskey   "C">
-<!ENTITY update.updateButton.label2               "Restart &brandShortName; to Update">
+<!ENTITY update.updateButton.label3               "Restart to update &brandShorterName;">
 <!ENTITY update.updateButton.accesskey            "R">
 
 
 <!-- LOCALIZATION NOTE (warningDesc.version): This is a warning about the experimental nature of Nightly and Aurora builds. It is only shown in those versions. -->
 <!ENTITY warningDesc.version        "&brandShortName; is experimental and may be unstable.">
 <!-- LOCALIZATION NOTE (warningDesc.telemetryDesc): This is a notification that Nightly/Aurora builds automatically send Telemetry data back to Mozilla. It is only shown in those versions. "It" refers to brandShortName. -->
 <!ENTITY warningDesc.telemetryDesc  "It automatically sends information about performance, hardware, usage and customizations back to &vendorShortName; to help make &brandShortName; better.">
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -915,14 +915,14 @@ you can use these alternative items. Oth
 <!ENTITY updateManual.header.message "&brandShorterName; can’t update to the latest version.">
 <!ENTITY updateManual.acceptButton.label "Download &brandShorterName;">
 <!ENTITY updateManual.acceptButton.accesskey "D">
 <!ENTITY updateManual.cancelButton.label "Not Now">
 <!ENTITY updateManual.cancelButton.accesskey "N">
 <!ENTITY updateManual.panelUI.label "Download a fresh copy of &brandShorterName;">
 
 <!ENTITY updateRestart.message "After a quick restart, &brandShorterName; will restore all your open tabs and windows.">
-<!ENTITY updateRestart.header.message "Restart &brandShorterName; to apply the update.">
+<!ENTITY updateRestart.header.message2 "Restart to update &brandShorterName;.">
 <!ENTITY updateRestart.acceptButton.label "Restart and Restore">
 <!ENTITY updateRestart.acceptButton.accesskey "R">
 <!ENTITY updateRestart.cancelButton.label "Not Now">
 <!ENTITY updateRestart.cancelButton.accesskey "N">
 <!ENTITY updateRestart.panelUI.label "Restart &brandShorterName; to apply the update">
--- a/devtools/client/responsive.html/images/select-arrow.svg
+++ b/devtools/client/responsive.html/images/select-arrow.svg
@@ -1,37 +1,8 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
-  <defs>
-    <style>
-      use:not(:target) {
-        display: none;
-      }
-      #light {
-        fill: #999797;
-      }
-      #light-hovered {
-        fill: #393f4c; /* --theme-body-color */
-      }
-      #light-selected {
-        fill: #3b3b3b;
-      }
-      #dark {
-        fill: #c6ccd0;
-      }
-      #dark-hovered {
-        fill: #dde1e4;
-      }
-      #dark-selected {
-        fill: #fcfcfc;
-      }
-    </style>
-    <path id="base-path" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
-  </defs>
-  <use xlink:href="#base-path" id="light"/>
-  <use xlink:href="#base-path" id="light-hovered"/>
-  <use xlink:href="#base-path" id="light-selected"/>
-  <use xlink:href="#base-path" id="dark"/>
-  <use xlink:href="#base-path" id="dark-hovered"/>
-  <use xlink:href="#base-path" id="dark-selected"/>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
 </svg>
+
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -7,35 +7,27 @@
 
 .theme-light {
   --rdm-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
   --submit-button-active-background-color: rgba(0,0,0,0.12);
   --submit-button-active-color: var(--theme-body-color);
   --viewport-color: #999797;
   --viewport-hover-color: var(--theme-body-color);
   --viewport-active-color: #3b3b3b;
-  --viewport-selection-arrow: url("./images/select-arrow.svg#light");
-  --viewport-selection-arrow-hovered:
-    url("./images/select-arrow.svg#light-hovered");
-  --viewport-selection-arrow-selected:
-    url("./images/select-arrow.svg#light-selected");
+  --viewport-selection-arrow: url("./images/select-arrow.svg");
 }
 
 .theme-dark {
   --rdm-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
   --submit-button-active-background-color: var(--theme-toolbar-hover-active);
   --submit-button-active-color: var(--theme-selection-color);
   --viewport-color: #c6ccd0;
   --viewport-hover-color: #dde1e4;
   --viewport-active-color: #fcfcfc;
-  --viewport-selection-arrow: url("./images/select-arrow.svg#dark");
-  --viewport-selection-arrow-hovered:
-    url("./images/select-arrow.svg#dark-hovered");
-  --viewport-selection-arrow-selected:
-    url("./images/select-arrow.svg#dark-selected");
+  --viewport-selection-arrow: url("./images/select-arrow.svg");
 }
 
 * {
   box-sizing: border-box;
 }
 
 :root,
 input,
@@ -86,43 +78,42 @@ body,
 .toolbar-button:active::before {
   filter: var(--checked-icon-filter);
 }
 
 select {
   -moz-appearance: none; appearance: none;
   background-color: var(--theme-toolbar-background);
   background-image: var(--viewport-selection-arrow);
+  /* uncomment after bug 1350010 lands: context-properties: fill; */
+  fill: currentColor;
+  color: var(--viewport-color);
   background-position: 100% 50%;
   background-repeat: no-repeat;
   background-size: 7px;
   border: none;
-  color: var(--viewport-color);
   height: 100%;
   padding: 0 8px;
   text-align: center;
   text-overflow: ellipsis;
 }
 
 select.selected {
-  background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
 select:not(:disabled):hover {
-  background-image: var(--viewport-selection-arrow-hovered);
   color: var(--viewport-hover-color);
 }
 
 /* This is (believed to be?) separate from the identical select.selected rule
    set so that it overrides select:hover because of file ordering once the
    select is focused.  It's unclear whether the visual effect that results here
    is intentional and desired. */
 select:focus {
-  background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
 select > option {
   text-align: left;
   padding: 5px 10px;
 }
 
@@ -198,22 +189,20 @@ select > option.divider {
 }
 
 #global-dpr-selector.focused,
 #global-dpr-selector:not(.disabled):hover {
   color: var(--viewport-hover-color);
 }
 
 #global-dpr-selector:not(.disabled):hover > select {
-  background-image: var(--viewport-selection-arrow-hovered);
   color: var(--viewport-hover-color);
 }
 
 #global-dpr-selector:focus > select {
-  background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
 #global-dpr-selector.selected,
 #global-dpr-selector.selected > select {
   color: var(--viewport-active-color);
 }
 
--- a/dom/file/Blob.cpp
+++ b/dom/file/Blob.cpp
@@ -251,17 +251,17 @@ Blob::Constructor(const GlobalObject& aG
                   const BlobPropertyBag& aBag,
                   ErrorResult& aRv)
 {
   RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl();
 
   if (aData.WasPassed()) {
     nsAutoString type(aBag.mType);
     MakeValidBlobType(type);
-    impl->InitializeBlob(aGlobal.Context(), aData.Value(), type,
+    impl->InitializeBlob(aData.Value(), type,
                          aBag.mEndings == EndingTypes::Native, aRv);
   } else {
     impl->InitializeBlob(aRv);
   }
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
--- a/dom/file/BlobSet.cpp
+++ b/dom/file/BlobSet.cpp
@@ -30,32 +30,33 @@ BlobSet::AppendVoidPtr(const void* aData
 
   RefPtr<BlobImpl> blobImpl = new MemoryBlobImpl(data, aLength, EmptyString());
   mBlobImpls.AppendElement(blobImpl);
 
   return NS_OK;
 }
 
 nsresult
-BlobSet::AppendString(const nsAString& aString, bool nativeEOL, JSContext* aCx)
+BlobSet::AppendString(const nsAString& aString, bool nativeEOL)
 {
   nsCString utf8Str = NS_ConvertUTF16toUTF8(aString);
 
   if (nativeEOL) {
     if (utf8Str.Contains('\r')) {
       utf8Str.ReplaceSubstring("\r\n", "\n");
       utf8Str.ReplaceSubstring("\r", "\n");
     }
 #ifdef XP_WIN
     utf8Str.ReplaceSubstring("\n", "\r\n");
 #endif
   }
 
-  return AppendVoidPtr((void*)utf8Str.Data(),
-                       utf8Str.Length());
+  RefPtr<StringBlobImpl> blobImpl =
+    StringBlobImpl::Create(utf8Str, EmptyString());
+  return AppendBlobImpl(blobImpl);
 }
 
 nsresult
 BlobSet::AppendBlobImpl(BlobImpl* aBlobImpl)
 {
   NS_ENSURE_ARG_POINTER(aBlobImpl);
   mBlobImpls.AppendElement(aBlobImpl);
   return NS_OK;
--- a/dom/file/BlobSet.h
+++ b/dom/file/BlobSet.h
@@ -17,18 +17,17 @@ namespace dom {
 
 class BlobImpl;
 
 class BlobSet final
 {
 public:
   nsresult AppendVoidPtr(const void* aData, uint32_t aLength);
 
-  nsresult AppendString(const nsAString& aString, bool nativeEOL,
-                        JSContext* aCx);
+  nsresult AppendString(const nsAString& aString, bool nativeEOL);
 
   nsresult AppendBlobImpl(BlobImpl* aBlobImpl);
 
   nsTArray<RefPtr<BlobImpl>>& GetBlobImpls() { return mBlobImpls; }
 
 private:
   nsTArray<RefPtr<BlobImpl>> mBlobImpls;
 };
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -143,17 +143,17 @@ File::Constructor(const GlobalObject& aG
   // Normalizing the filename
   nsString name(aName);
   name.ReplaceChar('/', ':');
 
   RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(name);
 
   nsAutoString type(aBag.mType);
   MakeValidBlobType(type);
-  impl->InitializeBlob(aGlobal.Context(), aData, type, false, aRv);
+  impl->InitializeBlob(aData, type, false, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   MOZ_ASSERT(impl->IsFile());
 
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -166,35 +166,34 @@ MultipartBlobImpl::CreateSlice(uint64_t 
 void
 MultipartBlobImpl::InitializeBlob(ErrorResult& aRv)
 {
   SetLengthAndModifiedDate(aRv);
   NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
 }
 
 void
-MultipartBlobImpl::InitializeBlob(JSContext* aCx,
-                                  const Sequence<Blob::BlobPart>& aData,
+MultipartBlobImpl::InitializeBlob(const Sequence<Blob::BlobPart>& aData,
                                   const nsAString& aContentType,
                                   bool aNativeEOL,
                                   ErrorResult& aRv)
 {
   mContentType = aContentType;
   BlobSet blobSet;
 
   for (uint32_t i = 0, len = aData.Length(); i < len; ++i) {
     const Blob::BlobPart& data = aData[i];
 
     if (data.IsBlob()) {
       RefPtr<Blob> blob = data.GetAsBlob().get();
       blobSet.AppendBlobImpl(blob->Impl());
     }
 
     else if (data.IsUSVString()) {
-      aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL, aCx);
+      aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL);
       if (aRv.Failed()) {
         return;
       }
     }
 
     else if (data.IsArrayBuffer()) {
       const ArrayBuffer& buffer = data.GetAsArrayBuffer();
       buffer.ComputeLengthAndData();
--- a/dom/file/MultipartBlobImpl.h
+++ b/dom/file/MultipartBlobImpl.h
@@ -44,18 +44,17 @@ public:
   MultipartBlobImpl()
     : BaseBlobImpl(EmptyString(), UINT64_MAX),
       mIsFromNsIFile(false)
   {
   }
 
   void InitializeBlob(ErrorResult& aRv);
 
-  void InitializeBlob(JSContext* aCx,
-                      const Sequence<Blob::BlobPart>& aData,
+  void InitializeBlob(const Sequence<Blob::BlobPart>& aData,
                       const nsAString& aContentType,
                       bool aNativeEOL,
                       ErrorResult& aRv);
 
   nsresult InitializeChromeFile(nsIFile* aData,
                                 const nsAString& aType,
                                 const nsAString& aName,
                                 bool aLastModifiedPassed,
--- a/dom/file/ipc/IPCBlobInputStream.cpp
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -203,17 +203,17 @@ IPCBlobInputStream::AsyncWait(nsIInputSt
   // First call, we need to retrieve the stream from the parent actor.
   case eInit:
     MOZ_ASSERT(mActor);
 
     mCallback = aCallback;
     mCallbackEventTarget = aEventTarget;
     mState = ePending;
 
-    mActor->StreamNeeded(this);
+    mActor->StreamNeeded(this, aEventTarget);
     return NS_OK;
 
   // We are still waiting for the remote inputStream
   case ePending:
     if (mCallback && aCallback) {
       return NS_ERROR_FAILURE;
     }
 
--- a/dom/file/ipc/IPCBlobInputStreamChild.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamChild.cpp
@@ -140,24 +140,25 @@ IPCBlobInputStreamChild::ForgetStream(IP
     return;
   }
 
   RefPtr<DeleteRunnable> runnable = new DeleteRunnable(this);
   mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 void
-IPCBlobInputStreamChild::StreamNeeded(IPCBlobInputStream* aStream)
+IPCBlobInputStreamChild::StreamNeeded(IPCBlobInputStream* aStream,
+                                      nsIEventTarget* aEventTarget)
 {
   MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mStreams.Contains(aStream));
 
   PendingOperation* opt = mPendingOperations.AppendElement();
   opt->mStream = aStream;
-  opt->mThread = NS_GetCurrentThread();
+  opt->mEventTarget = aEventTarget ? aEventTarget : NS_GetCurrentThread();
 
   if (mOwningThread == NS_GetCurrentThread()) {
     SendStreamNeeded();
     return;
   }
 
   RefPtr<StreamNeededRunnable> runnable = new StreamNeededRunnable(this);
   mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
@@ -167,17 +168,17 @@ mozilla::ipc::IPCResult
 IPCBlobInputStreamChild::RecvStreamReady(const OptionalIPCStream& aStream)
 {
   MOZ_ASSERT(!mPendingOperations.IsEmpty());
 
   nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
 
   RefPtr<StreamReadyRunnable> runnable =
     new StreamReadyRunnable(mPendingOperations[0].mStream, stream);
-  mPendingOperations[0].mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  mPendingOperations[0].mEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
 
   mPendingOperations.RemoveElementAt(0);
 
   return IPC_OK();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/file/ipc/IPCBlobInputStreamChild.h
+++ b/dom/file/ipc/IPCBlobInputStreamChild.h
@@ -45,17 +45,18 @@ public:
 
   uint64_t
   Size() const
   {
     return mSize;
   }
 
   void
-  StreamNeeded(IPCBlobInputStream* aStream);
+  StreamNeeded(IPCBlobInputStream* aStream,
+               nsIEventTarget* aEventTarget);
 
   mozilla::ipc::IPCResult
   RecvStreamReady(const OptionalIPCStream& aStream) override;
 
 private:
   ~IPCBlobInputStreamChild();
 
   // Raw pointers because these streams keep this actor alive. When the last
@@ -71,17 +72,17 @@ private:
 
   // false when ActorDestroy() is called.
   bool mActorAlive;
 
   // This struct and the array are used for creating streams when needed.
   struct PendingOperation
   {
     RefPtr<IPCBlobInputStream> mStream;
-    nsCOMPtr<nsIThread> mThread;
+    nsCOMPtr<nsIEventTarget> mEventTarget;
   };
   nsTArray<PendingOperation> mPendingOperations;
 
   nsCOMPtr<nsIThread> mOwningThread;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/file/ipc/moz.build
+++ b/dom/file/ipc/moz.build
@@ -50,8 +50,9 @@ include('/ipc/chromium/chromium-config.m
 FINAL_LIBRARY = 'xul'
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-error=shadow']
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/tests/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  script_file.js
+
+[test_ipcBlob_fileReaderSync.html]
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/tests/script_file.js
@@ -0,0 +1,14 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("file.open", function (e) {
+  var testFile = Cc["@mozilla.org/file/directory_service;1"]
+                   .getService(Ci.nsIDirectoryService)
+                   .QueryInterface(Ci.nsIProperties)
+                   .get("ProfD", Ci.nsIFile);
+  testFile.append("prefs.js");
+
+  File.createFromNsIFile(testFile).then(function(file) {
+    sendAsyncMessage("file.opened", { file });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/tests/test_ipcBlob_fileReaderSync.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test IPCBlob and FileReaderSync</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript">
+
+function workerScript() {
+  onmessage = function(event) {
+    let readerMemoryBlob = new FileReaderSync();
+    let status = readerMemoryBlob.readAsText(new Blob(['hello world'])) == 'hello world';
+
+    let readerIPCBlob = new FileReaderSync();
+    postMessage({ blob: event.data, data: readerIPCBlob.readAsText(event.data), status });
+  }
+}
+
+let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
+let worker = new Worker(workerUrl);
+worker.onmessage = event => {
+  let fr = new FileReader();
+  fr.readAsText(event.data.blob);
+  fr.onload = () => {
+    is(event.data.data, fr.result, "The file has been read");
+    ok(event.data.status, "FileReaderSync with memory blob still works");
+    SimpleTest.finish();
+  }
+};
+
+let url = SimpleTest.getTestFileURL("script_file.js");
+let script = SpecialPowers.loadChromeScript(url);
+script.addMessageListener("file.opened", message => {
+  worker.postMessage(message.file);
+});
+
+script.sendAsyncMessage("file.open");
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -26,16 +26,17 @@
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIXULRuntime.h"
 #include "nsNPAPIPlugin.h"
 #include "nsPrintfCString.h"
 #include "prsystem.h"
 #include "PluginQuirks.h"
+#include "gfxPlatform.h"
 #ifdef MOZ_GECKO_PROFILER
 #include "CrossProcessProfilerController.h"
 #endif
 #include "GeckoProfiler.h"
 #include "nsPluginTags.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 
@@ -2773,17 +2774,18 @@ PluginModuleParent::NPP_NewInternal(NPMI
 #elif defined(MOZ_WIDGET_GTK)
         // We no longer support windowed mode on Linux.
         ForceWindowless(names, values);
 #endif
 #ifdef XP_WIN
         // For all builds that use async rendering force use of the accelerated
         // direct path for flash objects that have wmode=window or no wmode
         // specified.
-        if (supportsAsyncRender && supportsForceDirect) {
+        if (supportsAsyncRender && supportsForceDirect &&
+            gfxWindowsPlatform::GetPlatform()->SupportsPluginDirectDXGIDrawing()) {
             ForceDirect(names, values);
         }
 #endif
     }
 
     // Release the surrogate reference that was in pdata
     RefPtr<PluginAsyncSurrogate> surrogate(
         dont_AddRef(PluginAsyncSurrogate::Cast(instance)));
--- a/dom/webidl/Blob.webidl
+++ b/dom/webidl/Blob.webidl
@@ -17,26 +17,22 @@ typedef (BufferSource or Blob or USVStri
  Exposed=(Window,Worker)]
 interface Blob {
 
   [GetterThrows]
   readonly attribute unsigned long long size;
 
   readonly attribute DOMString type;
 
-  // readonly attribute boolean isClosed; TODO bug 1048321
-
   //slice Blob into byte-ranged chunks
 
   [Throws]
   Blob slice([Clamp] optional long long start,
              [Clamp] optional long long end,
              optional DOMString contentType = "");
-
-  // void close(); TODO bug 1048325
 };
 
 enum EndingTypes { "transparent", "native" };
 
 dictionary BlobPropertyBag {
   DOMString type = "";
   EndingTypes endings = "transparent";
 };
--- a/dom/workers/FileReaderSync.cpp
+++ b/dom/workers/FileReaderSync.cpp
@@ -20,16 +20,19 @@
 #include "nsError.h"
 #include "nsIConverterInputStream.h"
 #include "nsIInputStream.h"
 #include "nsIMultiplexInputStream.h"
 #include "nsStringStream.h"
 #include "nsISupportsImpl.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
+#include "nsIAsyncInputStream.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 #include "RuntimeService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::Optional;
 using mozilla::dom::GlobalObject;
 
@@ -70,17 +73,17 @@ FileReaderSync::ReadAsArrayBuffer(JSCont
 
   nsCOMPtr<nsIInputStream> stream;
   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   uint32_t numRead;
-  aRv = stream->Read(bufferData.get(), blobSize, &numRead);
+  aRv = Read(stream, bufferData.get(), blobSize, &numRead);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
   NS_ASSERTION(numRead == blobSize, "failed to read data");
 
   JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
   if (!arrayBuffer) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
@@ -102,17 +105,17 @@ FileReaderSync::ReadAsBinaryString(Blob&
   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   uint32_t numRead;
   do {
     char readBuf[4096];
-    aRv = stream->Read(readBuf, sizeof(readBuf), &numRead);
+    aRv = Read(stream, readBuf, sizeof(readBuf), &numRead);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
     uint32_t oldLength = aResult.Length();
     AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
     if (aResult.Length() - oldLength != numRead) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
@@ -137,17 +140,17 @@ FileReaderSync::ReadAsText(Blob& aBlob,
 
   nsCString sniffBuf;
   if (!sniffBuf.SetLength(3, fallible)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   uint32_t numRead = 0;
-  aRv = stream->Read(sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
+  aRv = Read(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   // The BOM sniffing is baked into the "decode" part of the Encoding
   // Standard, which the File API references.
   if (!nsContentUtils::CheckForBOM((const unsigned char*)sniffBuf.BeginReading(),
                                    numRead, encoding)) {
@@ -283,8 +286,127 @@ FileReaderSync::ConvertStream(nsIInputSt
     if (aResult.Length() - oldLength != result.Length()) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   return rv;
 }
 
+namespace {
+
+// This runnable is used to terminate the sync event loop.
+class ReadReadyRunnable final : public WorkerSyncRunnable
+{
+public:
+  ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
+                    nsIEventTarget* aSyncLoopTarget)
+    : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
+  {}
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(mSyncLoopTarget);
+
+    nsCOMPtr<nsIEventTarget> syncLoopTarget;
+    mSyncLoopTarget.swap(syncLoopTarget);
+
+    aWorkerPrivate->StopSyncLoop(syncLoopTarget, true);
+    return true;
+  }
+
+private:
+  ~ReadReadyRunnable()
+  {}
+};
+
+// This class implements nsIInputStreamCallback and it will be called when the
+// stream is ready to be read.
+class ReadCallback final : public nsIInputStreamCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mEventTarget(aEventTarget)
+  {}
+
+  NS_IMETHOD
+  OnInputStreamReady(nsIAsyncInputStream* aStream) override
+  {
+    // I/O Thread. Now we need to block the sync event loop.
+    RefPtr<ReadReadyRunnable> runnable =
+      new ReadReadyRunnable(mWorkerPrivate, mEventTarget);
+    return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+  }
+
+private:
+  ~ReadCallback()
+  {}
+
+  // The worker is kept alive because of the sync event loop.
+  WorkerPrivate* mWorkerPrivate;
+  nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ADDREF(ReadCallback);
+NS_IMPL_RELEASE(ReadCallback);
+
+NS_INTERFACE_MAP_BEGIN(ReadCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
+NS_INTERFACE_MAP_END
+
+} // anonymous
+
+nsresult
+FileReaderSync::Read(nsIInputStream* aStream, char* aBuffer, uint32_t aBufferSize,
+                     uint32_t* aRead)
+{
+  MOZ_ASSERT(aStream);
+  MOZ_ASSERT(aBuffer);
+  MOZ_ASSERT(aRead);
+
+  // Let's try to read, directly.
+  nsresult rv = aStream->Read(aBuffer, aBufferSize, aRead);
+  if (NS_SUCCEEDED(rv) || rv != NS_BASE_STREAM_WOULD_BLOCK) {
+    return rv;
+  }
+
+  // We need to proceed async.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
+  if (!asyncStream) {
+    return rv;
+  }
+
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+
+  AutoSyncLoopHolder syncLoop(workerPrivate, Closing);
+
+  nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
+  if (!syncLoopTarget) {
+    // SyncLoop creation can fail if the worker is shutting down.
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  RefPtr<ReadCallback> callback =
+    new ReadCallback(workerPrivate, syncLoopTarget);
+
+  nsCOMPtr<nsIEventTarget> target =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+
+  rv = asyncStream->AsyncWait(callback, 0, aBufferSize, target);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!syncLoop.Run()) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  // Now, we can try to read again.
+  return Read(aStream, aBuffer, aBufferSize, aRead);
+}
--- a/dom/workers/FileReaderSync.h
+++ b/dom/workers/FileReaderSync.h
@@ -27,16 +27,19 @@ private:
   // Private destructor, to discourage deletion outside of Release():
   ~FileReaderSync()
   {
   }
 
   nsresult ConvertStream(nsIInputStream *aStream, const char *aCharset,
                          nsAString &aResult);
 
+  nsresult Read(nsIInputStream* aStream, char* aBuffer, uint32_t aBufferSize,
+                uint32_t* aRead);
+
 public:
   static already_AddRefed<FileReaderSync>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
 
   bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
 
   void ReadAsArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aScopeObj,
                          Blob& aBlob, JS::MutableHandle<JSObject*> aRetval,
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -273,16 +273,20 @@ AndroidDynamicToolbarAnimator::ToolbarAn
     NotifyControllerPendingAnimation(MOVE_TOOLBAR_DOWN, eAnimate);
     break;
   case REQUEST_HIDE_TOOLBAR_IMMEDIATELY:
     NotifyControllerPendingAnimation(MOVE_TOOLBAR_UP, eImmediate);
     break;
   case REQUEST_HIDE_TOOLBAR_ANIMATED:
     NotifyControllerPendingAnimation(MOVE_TOOLBAR_UP, eAnimate);
     break;
+  case TOOLBAR_SNAPSHOT_FAILED:
+    mToolbarState = eToolbarVisible;
+    NotifyControllerSnapshotFailed();
+    break;
   default:
     break;
   }
 }
 
 bool
 AndroidDynamicToolbarAnimator::UpdateAnimation(const TimeStamp& aCurrentFrame)
 {
@@ -888,10 +892,23 @@ AndroidDynamicToolbarAnimator::Translate
 
 ScreenIntCoord
 AndroidDynamicToolbarAnimator::GetFixedLayerMarginsBottom()
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   return mCompositorToolbarHeight - (mCompositorSurfaceHeight - mCompositorCompositionSize.height);
 }
 
+void
+AndroidDynamicToolbarAnimator::NotifyControllerSnapshotFailed()
+{
+  if (!APZThreadUtils::IsControllerThread()) {
+    APZThreadUtils::RunOnControllerThread(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::NotifyControllerSnapshotFailed));
+    return;
+  }
+
+  mControllerToolbarHeight = 0;
+  mControllerState = eNothingPending;
+  UpdateCompositorToolbarHeight(mControllerToolbarHeight);
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
@@ -144,16 +144,17 @@ protected:
   void PostToolbarReady();
   void UpdateFrameMetrics(ScreenPoint aScrollOffset,
                           CSSToScreenScale aScale,
                           CSSRect aCssPageRect);
   bool IsEnoughPageToHideToolbar(ScreenIntCoord delta);
   void ShowToolbarIfNotVisible(StaticToolbarState aCurrentToolbarState);
   void TranslateTouchEvent(MultiTouchInput& aTouchEvent);
   ScreenIntCoord GetFixedLayerMarginsBottom();
+  void NotifyControllerSnapshotFailed();
 
   // Read only Compositor and Controller threads after Initialize()
   uint64_t mRootLayerTreeId;
 
   // Read/Write Compositor Thread, Read only Controller thread
   Atomic<StaticToolbarState> mToolbarState; // Current toolbar state.
   Atomic<uint32_t> mPinnedFlags;            // The toolbar should not be moved or animated unless no flags are set
 
--- a/gfx/layers/ipc/UiCompositorControllerChild.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerChild.cpp
@@ -125,16 +125,21 @@ UiCompositorControllerChild::SetPinned(c
 
 bool
 UiCompositorControllerChild::ToolbarAnimatorMessageFromUI(const int32_t& aMessage)
 {
   if (!mIsOpen) {
     return false;
   }
 
+  if (aMessage == IS_COMPOSITOR_CONTROLLER_OPEN) {
+    RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN);
+    return true;
+  }
+
   return SendToolbarAnimatorMessageFromUI(aMessage);
 }
 
 bool
 UiCompositorControllerChild::SetDefaultClearColor(const uint32_t& aColor)
 {
   if (!mIsOpen) {
     mDefaultClearColor = Some(aColor);
--- a/gfx/layers/ipc/UiCompositorControllerMessageTypes.h
+++ b/gfx/layers/ipc/UiCompositorControllerMessageTypes.h
@@ -22,15 +22,17 @@ enum UiCompositorControllerMessageTypes 
   TOOLBAR_VISIBLE                  = 3,  // Sent to compositor when the real toolbar has been made  visible
   TOOLBAR_SHOW                     = 4,  // Sent from compositor when the real toolbar should be shown
   FIRST_PAINT                      = 5,  // Sent from compositor after first paint
   REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6,  // Sent to the compositor when the snapshot should be shown immediately
   REQUEST_SHOW_TOOLBAR_ANIMATED    = 7,  // Sent to the compositor when the snapshot should be shown with an animation
   REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8,  // Sent to the compositor when the snapshot should be hidden immediately
   REQUEST_HIDE_TOOLBAR_ANIMATED    = 9,  // Sent to the compositor when the snapshot should be hidden with an animation
   LAYERS_UPDATED                   = 10, // Sent from the compositor when any layer has been updated
-  COMPOSITOR_CONTROLLER_OPEN       = 20  // Compositor controller IPC is open
+  TOOLBAR_SNAPSHOT_FAILED          = 11, // Sent to compositor when the toolbar snapshot fails.
+  COMPOSITOR_CONTROLLER_OPEN       = 20, // Compositor controller IPC is open
+  IS_COMPOSITOR_CONTROLLER_OPEN    = 21  // Special message sent from controller to query if the compositor controller is open
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // include_gfx_ipc_UiCompositorControllerMessageTypes_h
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -2024,66 +2024,28 @@ MessageChannel::DispatchAsyncMessage(con
 void
 MessageChannel::DispatchInterruptMessage(Message&& aMsg, size_t stackDepth)
 {
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
     IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
 
-    // Race detection: see the long comment near mRemoteStackDepthGuess in
-    // MessageChannel.h. "Remote" stack depth means our side, and "local" means
-    // the other side.
-    if (aMsg.interrupt_remote_stack_depth_guess() != RemoteViewOfStackDepth(stackDepth)) {
-        // Interrupt in-calls have raced. The winner, if there is one, gets to defer
-        // processing of the other side's in-call.
-        bool defer;
-        const char* winner;
-        const MessageInfo parentMsgInfo =
-          (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
-        const MessageInfo childMsgInfo =
-          (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
-        switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo))
-        {
-          case RIPChildWins:
-            winner = "child";
-            defer = (mSide == ChildSide);
-            break;
-          case RIPParentWins:
-            winner = "parent";
-            defer = (mSide != ChildSide);
-            break;
-          case RIPError:
-            MOZ_CRASH("NYI: 'Error' Interrupt race policy");
-            return;
-          default:
-            MOZ_CRASH("not reached");
-            return;
-        }
-
-        if (LoggingEnabled()) {
-            printf_stderr("  (%s: %s won, so we're%sdeferring)\n",
-                          (mSide == ChildSide) ? "child" : "parent",
-                          winner,
-                          defer ? " " : " not ");
-        }
-
-        if (defer) {
-            // We now know the other side's stack has one more frame
-            // than we thought.
-            ++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred()
-            mDeferred.push(Move(aMsg));
-            return;
-        }
-
-        // We "lost" and need to process the other side's in-call. Don't need
-        // to fix up the mRemoteStackDepthGuess here, because we're just about
-        // to increment it in DispatchCall(), which will make it correct again.
+    if (ShouldDeferInterruptMessage(aMsg, stackDepth)) {
+        // We now know the other side's stack has one more frame
+        // than we thought.
+        ++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred()
+        mDeferred.push(Move(aMsg));
+        return;
     }
 
+    // If we "lost" a race and need to process the other side's in-call, we
+    // don't need to fix up the mRemoteStackDepthGuess here, because we're just
+    // about to increment it, which will make it correct again.
+
 #ifdef OS_WIN
     SyncStackFrame frame(this, true);
 #endif
 
     nsAutoPtr<Message> reply;
 
     ++mRemoteStackDepthGuess;
     Result rv = mListener->OnCallReceived(aMsg, *getter_Transfers(reply));
@@ -2098,33 +2060,87 @@ MessageChannel::DispatchInterruptMessage
     reply->set_seqno(aMsg.seqno());
 
     MonitorAutoLock lock(*mMonitor);
     if (ChannelConnected == mChannelState) {
         mLink->SendMessage(reply.forget());
     }
 }
 
+bool
+MessageChannel::ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth)
+{
+    AssertWorkerThread();
+
+    // We may or may not own the lock in this function, so don't access any
+    // channel state.
+
+    IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
+
+    // Race detection: see the long comment near mRemoteStackDepthGuess in
+    // MessageChannel.h. "Remote" stack depth means our side, and "local" means
+    // the other side.
+    if (aMsg.interrupt_remote_stack_depth_guess() == RemoteViewOfStackDepth(aStackDepth)) {
+        return false;
+    }
+
+    // Interrupt in-calls have raced. The winner, if there is one, gets to defer
+    // processing of the other side's in-call.
+    bool defer;
+    const char* winner;
+    const MessageInfo parentMsgInfo =
+        (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
+    const MessageInfo childMsgInfo =
+        (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
+    switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo))
+    {
+        case RIPChildWins:
+            winner = "child";
+            defer = (mSide == ChildSide);
+            break;
+        case RIPParentWins:
+            winner = "parent";
+            defer = (mSide != ChildSide);
+            break;
+        case RIPError:
+            MOZ_CRASH("NYI: 'Error' Interrupt race policy");
+        default:
+            MOZ_CRASH("not reached");
+    }
+
+    IPC_LOG("race in %s: %s won",
+            (mSide == ChildSide) ? "child" : "parent",
+            winner);
+
+    return defer;
+}
+
 void
 MessageChannel::MaybeUndeferIncall()
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
     if (mDeferred.empty())
         return;
 
     size_t stackDepth = InterruptStackDepth();
 
+    Message& deferred = mDeferred.top();
+
     // the other side can only *under*-estimate our actual stack depth
-    IPC_ASSERT(mDeferred.top().interrupt_remote_stack_depth_guess() <= stackDepth,
+    IPC_ASSERT(deferred.interrupt_remote_stack_depth_guess() <= stackDepth,
                "fatal logic error");
 
+    if (ShouldDeferInterruptMessage(deferred, stackDepth)) {
+        return;
+    }
+
     // maybe time to process this message
-    Message call(Move(mDeferred.top()));
+    Message call(Move(deferred));
     mDeferred.pop();
 
     // fix up fudge factor we added to account for race
     IPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error");
     --mRemoteStackDepthGuess;
 
     MOZ_RELEASE_ASSERT(call.nested_level() == IPC::Message::NOT_NESTED);
     RefPtr<MessageTask> task = new MessageTask(this, Move(call));
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -471,16 +471,17 @@ class MessageChannel : HasResultCodes, M
 
     // Returns true if ShouldDeferMessage(aMsg) is guaranteed to return true.
     // Otherwise, the result of ShouldDeferMessage(aMsg) may be true or false,
     // depending on context.
     static bool IsAlwaysDeferred(const Message& aMsg);
 
     bool WasTransactionCanceled(int transaction);
     bool ShouldDeferMessage(const Message& aMsg);
+    bool ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth);
     void OnMessageReceivedFromLink(Message&& aMsg);
     void OnChannelErrorFromLink();
 
   private:
     // Run on the not current thread.
     void NotifyChannelClosed();
     void NotifyMaybeChannelError();
 
--- a/layout/printing/nsPagePrintTimer.h
+++ b/layout/printing/nsPagePrintTimer.h
@@ -83,13 +83,9 @@ private:
     // Debug builds are very slow (on Mac at least) and can need extra time
                                               30
 #else
                                               10
 #endif
   ;
 };
 
-
-nsresult
-NS_NewPagePrintTimer(nsPagePrintTimer **aResult);
-
 #endif /* nsPagePrintTimer_h___ */
--- a/layout/svg/SVGImageContext.cpp
+++ b/layout/svg/SVGImageContext.cpp
@@ -5,16 +5,17 @@
 
 
 // Main header first:
 #include "SVGImageContext.h"
 
 // Keep others in (case-insensitive) order:
 #include "gfxUtils.h"
 #include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
 #include "nsIFrame.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
 
 /* static */ void
 SVGImageContext::MaybeStoreContextPaint(Maybe<SVGImageContext>& aContext,
                                         nsIFrame* aFromFrame,
@@ -25,17 +26,17 @@ SVGImageContext::MaybeStoreContextPaint(
 
   if (!sEnabledForContentCached) {
     Preferences::AddBoolVarCache(&sEnabledForContent,
                                  "svg.context-properties.content.enabled", false);
     sEnabledForContentCached = true;
   }
 
   if (!sEnabledForContent &&
-      !aFromFrame->PresContext()->IsChrome()) {
+      !nsContentUtils::IsChromeDoc(aFromFrame->PresContext()->Document())) {
     // Context paint is pref'ed off for content and this is a content doc.
     return;
   }
 
   if (aImgContainer->GetType() != imgIContainer::TYPE_VECTOR) {
     // Avoid this overhead for raster images.
     return;
   }
--- a/mfbt/ThreadLocal.h
+++ b/mfbt/ThreadLocal.h
@@ -16,17 +16,25 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TypeTraits.h"
 
 namespace mozilla {
 
 namespace detail {
 
-#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN) || defined(XP_MACOSX)
+#ifdef XP_MACOSX
+#  if defined(__has_feature)
+#    if __has_feature(cxx_thread_local)
+#      define MACOSX_HAS_THREAD_LOCAL
+#    endif
+#  endif
+#endif
+
+#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)
 #define MOZ_HAS_THREAD_LOCAL
 #endif
 
 /*
  * Thread Local Storage helpers.
  *
  * Usage:
  *
@@ -166,17 +174,17 @@ ThreadLocal<T>::set(const T aValue)
   bool succeeded = !pthread_setspecific(mKey, h);
   if (!succeeded) {
     MOZ_CRASH();
   }
 #endif
 }
 
 #ifdef MOZ_HAS_THREAD_LOCAL
-#if defined(XP_WIN) || defined(XP_MACOSX)
+#if defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)
 #define MOZ_THREAD_LOCAL(TYPE) thread_local mozilla::detail::ThreadLocal<TYPE>
 #else
 #define MOZ_THREAD_LOCAL(TYPE) __thread mozilla::detail::ThreadLocal<TYPE>
 #endif
 #else
 #define MOZ_THREAD_LOCAL(TYPE) mozilla::detail::ThreadLocal<TYPE>
 #endif
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -253,16 +253,22 @@ public class BrowserApp extends GeckoApp
     private int mCachedRecentTabsCount;
     private ActionModeCompat mActionMode;
     private TabHistoryController tabHistoryController;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
     public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
 
+    // When the static action bar is shown, only the real toolbar chrome should be
+    // shown when the toolbar is visible. Causing the toolbar animator to also
+    // show the snapshot causes the content to shift under the users finger.
+    // See: Bug 1358554
+    private boolean mShowingToolbarChromeForActionBar;
+
     private static class MenuItemInfo {
         public int id;
         public String label;
         public boolean checkable;
         public boolean checked;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
@@ -381,17 +387,20 @@ public class BrowserApp extends GeckoApp
                     tab.setShouldShowToolbarWithoutAnimationOnFirstSelection(false);
                 }
                 // fall through
             case LOCATION_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     updateHomePagerForTab(tab);
                 }
 
-                mDynamicToolbar.persistTemporaryVisibility();
+                if (mShowingToolbarChromeForActionBar) {
+                    mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
+                    mShowingToolbarChromeForActionBar = false;
+                }
                 break;
             case START:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
 
                     if (mDynamicToolbar.isEnabled()) {
                         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
                     }
@@ -4031,27 +4040,30 @@ public class BrowserApp extends GeckoApp
         return mReadingListHelper;
     }
 
     @Override
     protected ActionModePresenter getTextSelectPresenter() {
         return this;
     }
 
+
     /* Implementing ActionModeCompat.Presenter */
     @Override
     public void startActionMode(final ActionModeCompat.Callback callback) {
         // If actionMode is null, we're not currently showing one. Flip to the action mode view
         if (mActionMode == null) {
             mActionBarFlipper.showNext();
             DynamicToolbarAnimator toolbar = mLayerView.getDynamicToolbarAnimator();
 
-            // If the toolbar is dynamic and not currently showing, just slide it in
+            // If the toolbar is dynamic and not currently showing, just show the real toolbar
+            // and keep the animated snapshot hidden
             if (mDynamicToolbar.isEnabled() && toolbar.getCurrentToolbarHeight() == 0) {
-                mDynamicToolbar.setTemporarilyVisible(true, VisibilityTransition.ANIMATE);
+                toggleToolbarChrome(true);
+                mShowingToolbarChromeForActionBar = true;
             }
             mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
 
         } else {
             // Otherwise, we're already showing an action mode. Just finish it and show the new one
             mActionMode.finish();
         }
 
@@ -4070,19 +4082,22 @@ public class BrowserApp extends GeckoApp
         }
 
         mActionMode.finish();
         mActionMode = null;
         mDynamicToolbar.setPinned(false, PinReason.ACTION_MODE);
 
         mActionBarFlipper.showPrevious();
 
-        // Only slide the urlbar out if it was hidden when the action mode started
-        // Don't animate hiding it so that there's no flash as we switch back to url mode
-        mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
+        // Hide the real toolbar chrome if it was hidden before the action bar
+        // was shown.
+        if (mShowingToolbarChromeForActionBar) {
+            toggleToolbarChrome(false);
+            mShowingToolbarChromeForActionBar = false;
+        }
     }
 
     public static interface TabStripInterface {
         public void refresh();
         /** Called to let the tab strip know it is now, or is now no longer, being hidden by
          *  something being drawn over it.
          */
         void tabStripIsCovered(boolean covered);
--- a/mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java
@@ -144,46 +144,16 @@ public class DynamicToolbar {
         final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
         if (visible) {
             layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
         } else {
             layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
         }
     }
 
-    public void setTemporarilyVisible(boolean visible, VisibilityTransition transition) {
-        ThreadUtils.assertOnUiThread();
-
-        if (layerView == null) {
-            return;
-        }
-
-        if (visible == temporarilyVisible) {
-            // nothing to do
-            return;
-        }
-
-        temporarilyVisible = visible;
-        final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
-        if (visible) {
-            layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
-        } else {
-            layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
-        }
-    }
-
-    public void persistTemporaryVisibility() {
-        ThreadUtils.assertOnUiThread();
-
-        if (temporarilyVisible) {
-            temporarilyVisible = false;
-            setVisible(true, VisibilityTransition.IMMEDIATE);
-        }
-    }
-
     public void setPinned(boolean pinned, PinReason reason) {
         ThreadUtils.assertOnUiThread();
         if (layerView == null) {
             return;
         }
 
         layerView.getDynamicToolbarAnimator().setPinned(pinned, reason);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -34,16 +34,18 @@ import android.widget.ProgressBar;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
+import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
@@ -211,16 +213,24 @@ public class CustomTabsActivity extends 
             if (tab == null) {
                 finish();
             } else {
                 // we are restoring
                 actionBarPresenter.update(tab);
             }
         }
         super.onResume();
+
+        mLayerView.getDynamicToolbarAnimator().setPinned(true, PinReason.CUSTOM_TAB);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mLayerView.getDynamicToolbarAnimator().setPinned(false, PinReason.CUSTOM_TAB);
     }
 
     // Usually should use onCreateOptionsMenu() to initialize menu items. But GeckoApp overwrite
     // it to support custom menu(Bug 739412). Then the parameter *menu* in this.onCreateOptionsMenu()
     // and this.onPrepareOptionsMenu() are different instances - GeckoApp.onCreatePanelMenu() changed it.
     // CustomTabsActivity only use standard menu in ActionBar, so initialize menu here.
     @Override
     public boolean onCreatePanelMenu(final int id, final Menu menu) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -22,17 +22,18 @@ public class DynamicToolbarAnimator {
     private static final String LOGTAG = "GeckoDynamicToolbarAnimator";
 
     public static enum PinReason {
         DISABLED(0),
         RELAYOUT(1),
         ACTION_MODE(2),
         FULL_SCREEN(3),
         CARET_DRAG(4),
-        PAGE_LOADING(5);
+        PAGE_LOADING(5),
+        CUSTOM_TAB(6);
 
         public final int mValue;
         PinReason(final int value) {
             mValue = value;
         }
     }
 
     public interface MetricsListener {
@@ -149,19 +150,29 @@ public class DynamicToolbarAnimator {
     public PointF getVisibleEndOfLayerView() {
         return new PointF(mTarget.getView().getWidth(),
             mTarget.getView().getHeight());
     }
 
     private void dumpStateToCompositor() {
         if ((mCompositor != null) && mCompositorControllerOpen) {
             mCompositor.setMaxToolbarHeight(mMaxToolbarHeight);
+            if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
+                mCompositor.sendToolbarAnimatorMessage(LayerView.REQUEST_SHOW_TOOLBAR_IMMEDIATELY);
+            } else {
+                mCompositor.sendToolbarAnimatorMessage(LayerView.REQUEST_HIDE_TOOLBAR_IMMEDIATELY);
+            }
             for (PinReason reason : pinFlags) {
               mCompositor.setPinned(true, reason.mValue);
             }
+        } else if ((mCompositor != null) && !mCompositorControllerOpen) {
+            // Ask the UiCompositorControllerChild if it is open since the open message can
+            // sometimes be sent to a different instance of the LayerView such as when
+            // Fennec is being used in custom tabs.
+            mCompositor.sendToolbarAnimatorMessage(LayerView.IS_COMPOSITOR_CONTROLLER_OPEN);
         }
     }
 
     /* package-private */ void notifyCompositorCreated(LayerView.Compositor aCompositor) {
         ThreadUtils.assertOnUiThread();
         mCompositor = aCompositor;
         dumpStateToCompositor();
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -83,18 +83,20 @@ public class LayerView extends FrameLayo
     /* package */ final static int TOOLBAR_HIDDEN                   = 2;  // Sent to compositor when the real toolbar has been hidden.
     /* package */ final static int TOOLBAR_VISIBLE                  = 3;  // Sent to compositor when the real toolbar is visible.
     /* package */ final static int TOOLBAR_SHOW                     = 4;  // Sent from compositor when the static toolbar has been made visible so the real toolbar should be shown.
     /* package */ final static int FIRST_PAINT                      = 5;  // Sent from compositor after first paint
     /* package */ final static int REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6;  // Sent to compositor requesting toolbar be shown immediately
     /* package */ final static int REQUEST_SHOW_TOOLBAR_ANIMATED    = 7;  // Sent to compositor requesting toolbar be shown animated
     /* package */ final static int REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8;  // Sent to compositor requesting toolbar be hidden immediately
     /* package */ final static int REQUEST_HIDE_TOOLBAR_ANIMATED    = 9;  // Sent to compositor requesting toolbar be hidden animated
-    /* package */ final static int LAYERS_UPDATED                    = 10; // Sent from compositor when a layer has been updated
+    /* package */ final static int LAYERS_UPDATED                   = 10; // Sent from compositor when a layer has been updated
+    /* package */ final static int TOOLBAR_SNAPSHOT_FAILED          = 11; // Sent to compositor when the toolbar snapshot fails.
     /* package */ final static int COMPOSITOR_CONTROLLER_OPEN       = 20; // Special message sent from UiCompositorControllerChild once it is open
+    /* package */ final static int IS_COMPOSITOR_CONTROLLER_OPEN    = 21; // Special message sent from controller to query if the compositor controller is open
 
     private void postCompositorMessage(final int message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 mCompositor.sendToolbarAnimatorMessage(message);
             }
         });
@@ -201,16 +203,17 @@ public class LayerView extends FrameLayo
     }
 
     /* package */ void handleToolbarAnimatorMessage(int message) {
         switch(message) {
             case STATIC_TOOLBAR_NEEDS_UPDATE:
                 // Send updated toolbar image to compositor.
                 Bitmap bm = mToolbarAnimator.getBitmapOfToolbarChrome();
                 if (bm == null) {
+                    postCompositorMessage(TOOLBAR_SNAPSHOT_FAILED);
                     break;
                 }
                 final int width = bm.getWidth();
                 final int height = bm.getHeight();
                 int[] pixels = new int[bm.getByteCount() / 4];
                 try {
                     bm.getPixels(pixels, /* offset */ 0, /* stride */ width, /* x */ 0, /* y */ 0, width, height);
                     mCompositor.sendToolbarPixelsToCompositor(width, height, pixels);
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -12,16 +12,17 @@ support-files =
   insecure_test_subframe.html
   multiple_forms.html
   streamConverter_content.sjs
 
 [browser_autocomplete_insecure_warning.js]
 support-files =
   form_cross_origin_insecure_action.html
 [browser_capture_doorhanger.js]
+skip-if = os == "linux" && debug # Bug 1334336
 support-files =
   subtst_notifications_1.html
   subtst_notifications_2.html
   subtst_notifications_2pw_0un.html
   subtst_notifications_2pw_1un_1text.html
   subtst_notifications_3.html
   subtst_notifications_4.html
   subtst_notifications_5.html
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5618,40 +5618,24 @@
   "FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS": {
     "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 10,
     "description": "Session restore: Time to collect all window data (ms)"
   },
-  "FX_SESSION_RESTORE_COLLECT_COOKIES_MS": {
-    "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 30000,
-    "n_buckets": 10,
-    "description": "Session restore: Time to collect cookies (ms)"
-  },
   "FX_SESSION_RESTORE_COLLECT_DATA_MS": {
     "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
     "expires_in_version": "default",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 10,
     "description": "Session restore: Time to collect all window and tab data (ms)"
   },
-  "FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS": {
-    "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 30000,
-    "n_buckets": 10,
-    "description": "Session restore: Duration of the longest uninterruptible operation while collecting all window and tab data (ms)"
-  },
   "FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS": {
     "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
     "expires_in_version": "default",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 10,
     "description": "Session restore: Duration of the longest uninterruptible operation while collecting data in the content process (ms)"
   },
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -935,18 +935,16 @@
     "FX_SANITIZE_HISTORY",
     "FX_SANITIZE_OPENWINDOWS",
     "FX_SANITIZE_SESSIONS",
     "FX_SANITIZE_SITESETTINGS",
     "FX_SANITIZE_TOTAL",
     "FX_SESSION_RESTORE_ALL_FILES_CORRUPT",
     "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS",
     "FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS",
-    "FX_SESSION_RESTORE_COLLECT_COOKIES_MS",
-    "FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS",
     "FX_SESSION_RESTORE_COLLECT_DATA_MS",
     "FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS",
     "FX_SESSION_RESTORE_CORRUPT_FILE",
     "FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS",
     "FX_SESSION_RESTORE_FILE_SIZE_BYTES",
     "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS",
     "FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED",
     "FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED",
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -85,18 +85,18 @@ private:
   // into the final stream.
   mozilla::UniquePtr<char[]> mSavedStreamedSamples;
   mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
   mozilla::Maybe<UniqueStacks> mUniqueStacks;
 
   // This is only used for the main thread.
   mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
 
-  // When sampling, this holds the generation number and offset in
-  // ProfilerState::mBuffer of the most recent sample for this thread.
+  // When sampling, this holds the generation number and offset in PS::mBuffer
+  // of the most recent sample for this thread.
   ProfileBuffer::LastSample mLastSample;
 };
 
 void
 StreamSamplesAndMarkers(const char* aName, int aThreadId,
                         ProfileBuffer* aBuffer,
                         SpliceableJSONWriter& aWriter,
                         const mozilla::TimeStamp& aStartTime,
--- a/tools/profiler/core/platform-linux-android.cpp
+++ b/tools/profiler/core/platform-linux-android.cpp
@@ -274,17 +274,17 @@ ThreadEntry(void* aArg)
 {
   auto thread = static_cast<SamplerThread*>(aArg);
   prctl(PR_SET_NAME, "SamplerThread", 0, 0, 0);
   thread->mSamplerTid = gettid();
   thread->Run();
   return nullptr;
 }
 
-SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
+SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
                              double aIntervalMilliseconds)
   : mActivityGeneration(aActivityGeneration)
   , mIntervalMicroseconds(
       std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
   , mMyPid(getpid())
   // We don't know what the sampler thread's ID will be until it runs, so set
   // mSamplerTid to a dummy value and fill it in for real in ThreadEntry().
   , mSamplerTid(-1)
@@ -338,30 +338,30 @@ SamplerThread::SamplerThread(PS::LockRef
 }
 
 SamplerThread::~SamplerThread()
 {
   pthread_join(mThread, nullptr);
 }
 
 void
-SamplerThread::Stop(PS::LockRef aLock)
+SamplerThread::Stop(PSLockRef aLock)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // Restore old signal handler. This is global state so it's important that
   // we do it now, while gPSMutex is locked. It's safe to do this now even
   // though this SamplerThread is still alive, because the next time the main
   // loop of Run() iterates it won't get past the mActivityGeneration check,
   // and so won't send any signals.
   sigaction(SIGPROF, &mOldSigprofHandler, 0);
 }
 
 void
-SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
+SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
                                                TickSample& aSample)
 {
   // Only one sampler thread can be sampling at once.  So we expect to have
   // complete control over |sSigHandlerCoordinator|.
   MOZ_ASSERT(!sSigHandlerCoordinator);
 
   int sampleeTid = aSample.mThreadId;
   MOZ_RELEASE_ASSERT(sampleeTid != mSamplerTid);
@@ -461,47 +461,47 @@ SamplerThread::SuspendAndSampleAndResume
 // In the parent, before the fork, record IsPaused, and then pause.
 static void
 paf_prepare()
 {
   // This function can run off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   gPS->SetWasPaused(lock, gPS->IsPaused(lock));
   gPS->SetIsPaused(lock, true);
 }
 
 // In the parent, after the fork, return IsPaused to the pre-fork state.
 static void
 paf_parent()
 {
   // This function can run off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   gPS->SetIsPaused(lock, gPS->WasPaused(lock));
   gPS->SetWasPaused(lock, false);
 }
 
 static void
-PlatformInit(PS::LockRef aLock)
+PlatformInit(PSLockRef aLock)
 {
   // Set up the fork handlers.
   pthread_atfork(paf_prepare, paf_parent, nullptr);
 }
 
 #else
 
 static void
-PlatformInit(PS::LockRef aLock)
+PlatformInit(PSLockRef aLock)
 {
 }
 
 #endif
 
 void
 TickSample::PopulateContext(ucontext_t* aContext)
 {
--- a/tools/profiler/core/platform-macos.cpp
+++ b/tools/profiler/core/platform-macos.cpp
@@ -96,17 +96,17 @@ static void*
 ThreadEntry(void* aArg)
 {
   auto thread = static_cast<SamplerThread*>(aArg);
   SetThreadName();
   thread->Run();
   return nullptr;
 }
 
-SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
+SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
                              double aIntervalMilliseconds)
   : mActivityGeneration(aActivityGeneration)
   , mIntervalMicroseconds(
       std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   pthread_attr_t* attr_ptr = nullptr;
@@ -116,23 +116,23 @@ SamplerThread::SamplerThread(PS::LockRef
 }
 
 SamplerThread::~SamplerThread()
 {
   pthread_join(mThread, nullptr);
 }
 
 void
-SamplerThread::Stop(PS::LockRef aLock)
+SamplerThread::Stop(PSLockRef aLock)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 }
 
 void
-SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
+SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
                                                TickSample& aSample)
 {
   thread_act_t samplee_thread = aSample.mPlatformData->ProfiledThread();
 
   //----------------------------------------------------------------//
   // Suspend the samplee thread and get its context.
 
   // We're using thread_suspend on OS X because pthread_kill (which is what we
@@ -199,17 +199,17 @@ SamplerThread::SuspendAndSampleAndResume
   //
   // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
 }
 
 // END SamplerThread target specifics
 ////////////////////////////////////////////////////////////////////////
 
 static void
-PlatformInit(PS::LockRef aLock)
+PlatformInit(PSLockRef aLock)
 {
 }
 
 void
 TickSample::PopulateContext(void* aContext)
 {
   MOZ_ASSERT(mIsSynchronous);
   MOZ_ASSERT(!aContext);
--- a/tools/profiler/core/platform-win32.cpp
+++ b/tools/profiler/core/platform-win32.cpp
@@ -93,17 +93,17 @@ static const HANDLE kNoThread = INVALID_
 static unsigned int __stdcall
 ThreadEntry(void* aArg)
 {
   auto thread = static_cast<SamplerThread*>(aArg);
   thread->Run();
   return 0;
 }
 
-SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
+SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
                              double aIntervalMilliseconds)
     : mActivityGeneration(aActivityGeneration)
     , mIntervalMicroseconds(
         std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // By default we'll not adjust the timer resolution which tends to be
@@ -134,17 +134,17 @@ SamplerThread::~SamplerThread()
 
   // Close our own handle for the thread.
   if (mThread != kNoThread) {
     CloseHandle(mThread);
   }
 }
 
 void
-SamplerThread::Stop(PS::LockRef aLock)
+SamplerThread::Stop(PSLockRef aLock)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // Disable any timer resolution changes we've made. Do it now while
   // gPSMutex is locked, i.e. before any other SamplerThread can be created
   // and call ::timeBeginPeriod().
   //
   // It's safe to do this now even though this SamplerThread is still alive,
@@ -152,17 +152,17 @@ SamplerThread::Stop(PS::LockRef aLock)
   // the mActivityGeneration check, and so it won't make any more ::Sleep()
   // calls.
   if (mIntervalMicroseconds < 10 * 1000) {
     ::timeEndPeriod(mIntervalMicroseconds / 1000);
   }
 }
 
 void
-SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
+SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
                                                TickSample& aSample)
 {
   HANDLE profiled_thread = aSample.mPlatformData->ProfiledThread();
   if (profiled_thread == nullptr)
     return;
 
   // Context used for sampling the register state of the profiled thread.
   CONTEXT context;
@@ -224,17 +224,17 @@ SamplerThread::SuspendAndSampleAndResume
   //
   // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
 }
 
 // END SamplerThread target specifics
 ////////////////////////////////////////////////////////////////////////
 
 static void
-PlatformInit(PS::LockRef aLock)
+PlatformInit(PSLockRef aLock)
 {
 }
 
 void
 TickSample::PopulateContext(CONTEXT* aContext)
 {
   MOZ_ASSERT(mIsSynchronous);
   MOZ_ASSERT(aContext);
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -105,44 +105,43 @@ public:
 };
 #endif
 
 class SamplerThread;
 
 // Per-thread state.
 MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
 
+class PSMutex : public mozilla::StaticMutex {};
+
+typedef mozilla::BaseAutoLock<PSMutex> PSAutoLock;
+
+// Only functions that take a PSLockRef arg can modify this class's fields.
+typedef const PSAutoLock& PSLockRef;
+
 // This class contains most of the profiler's global state. gPS is the single
 // instance. Most profile operations can't do anything useful when gPS is not
 // instantiated, so we release-assert its non-nullness in all such operations.
 //
 // Accesses to gPS are guarded by gPSMutex. Every getter and setter takes a
-// PS::AutoLock reference as an argument as proof that the gPSMutex is
-// currently locked. This makes it clear when gPSMutex is locked and helps
-// avoid accidental unlocked accesses to global state. There are ways to
-// circumvent this mechanism, but please don't do so without *very* good reason
-// and a detailed explanation.
+// PSAutoLock reference as an argument as proof that the gPSMutex is currently
+// locked. This makes it clear when gPSMutex is locked and helps avoid
+// accidental unlocked accesses to global state. There are ways to circumvent
+// this mechanism, but please don't do so without *very* good reason and a
+// detailed explanation.
 //
 // Other from the lock protection, this class is essentially a thin wrapper and
 // contains very little "smarts" itself.
 //
-class ProfilerState
+class PS
 {
 public:
-  // Shorter names for local use.
-  class Mutex : public mozilla::StaticMutex {};
-
-  typedef mozilla::BaseAutoLock<Mutex> AutoLock;
-
-  // Only functions that take a LockRef arg can modify this class's fields.
-  typedef const AutoLock& LockRef;
-
   typedef std::vector<ThreadInfo*> ThreadVector;
 
-  ProfilerState()
+  PS()
     : mEntries(0)
     , mInterval(0)
     , mFeatureDisplayListDump(false)
     , mFeatureGPU(false)
     , mFeatureJava(false)
     , mFeatureJS(false)
     , mFeatureLayersDump(false)
     , mFeatureLeaf(false)
@@ -150,41 +149,41 @@ public:
     , mFeatureMemory(false)
     , mFeaturePrivacy(false)
     , mFeatureRestyle(false)
     , mFeatureStackWalk(false)
     , mFeatureTaskTracer(false)
     , mFeatureThreads(false)
     , mBuffer(nullptr)
     , mIsPaused(false)
-#if defined(GP_OS_linux) || defined(GP_OS_android)
+#if defined(GP_OS_linux)
     , mWasPaused(false)
 #endif
     , mSamplerThread(nullptr)
 #ifdef USE_LUL_STACKWALK
     , mLUL(nullptr)
 #endif
     , mInterposeObserver(nullptr)
     , mFrameNumber(0)
     , mLatestRecordedFrameNumber(0)
   {}
 
   #define GET_AND_SET(type_, name_) \
-    type_ name_(LockRef) const { return m##name_; } \
-    void Set##name_(LockRef, type_ a##name_) { m##name_ = a##name_; }
-
-  GET_AND_SET(TimeStamp, StartTime)
+    type_ name_(PSLockRef) const { return m##name_; } \
+    void Set##name_(PSLockRef, type_ a##name_) { m##name_ = a##name_; }
+
+  GET_AND_SET(TimeStamp, ProcessStartTime)
 
   GET_AND_SET(int, Entries)
 
   GET_AND_SET(double, Interval)
 
-  Vector<std::string>& Features(LockRef) { return mFeatures; }
-
-  Vector<std::string>& ThreadNameFilters(LockRef) { return mThreadNameFilters; }
+  Vector<std::string>& Features(PSLockRef) { return mFeatures; }
+
+  Vector<std::string>& Filters(PSLockRef) { return mFilters; }
 
   GET_AND_SET(bool, FeatureDisplayListDump)
   GET_AND_SET(bool, FeatureGPU)
   GET_AND_SET(bool, FeatureJava)
   GET_AND_SET(bool, FeatureJS)
   GET_AND_SET(bool, FeatureLayersDump)
   GET_AND_SET(bool, FeatureLeaf)
   GET_AND_SET(bool, FeatureMainThreadIO)
@@ -192,34 +191,34 @@ public:
   GET_AND_SET(bool, FeaturePrivacy)
   GET_AND_SET(bool, FeatureRestyle)
   GET_AND_SET(bool, FeatureStackWalk)
   GET_AND_SET(bool, FeatureTaskTracer)
   GET_AND_SET(bool, FeatureThreads)
 
   GET_AND_SET(ProfileBuffer*, Buffer)
 
-  ThreadVector& LiveThreads(LockRef) { return mLiveThreads; }
-  ThreadVector& DeadThreads(LockRef) { return mDeadThreads; }
-
-  static bool IsActive(LockRef) { return sActivityGeneration > 0; }
-  static uint32_t ActivityGeneration(LockRef) { return sActivityGeneration; }
-  static void SetInactive(LockRef) { sActivityGeneration = 0; }
-  static void SetActive(LockRef)
+  ThreadVector& LiveThreads(PSLockRef) { return mLiveThreads; }
+  ThreadVector& DeadThreads(PSLockRef) { return mDeadThreads; }
+
+  static bool IsActive(PSLockRef) { return sActivityGeneration > 0; }
+  static uint32_t ActivityGeneration(PSLockRef) { return sActivityGeneration; }
+  static void SetInactive(PSLockRef) { sActivityGeneration = 0; }
+  static void SetActive(PSLockRef)
   {
     sActivityGeneration = sNextActivityGeneration;
     // On overflow, reset to 1 instead of 0, because 0 means inactive.
     sNextActivityGeneration = (sNextActivityGeneration == 0xffffffff)
                             ? 1
                             : sNextActivityGeneration + 1;
   }
 
   GET_AND_SET(bool, IsPaused)
 
-#if defined(GP_OS_linux) || defined(GP_OS_android)
+#if defined(GP_OS_linux)
   GET_AND_SET(bool, WasPaused)
 #endif
 
   GET_AND_SET(class SamplerThread*, SamplerThread)
 
 #ifdef USE_LUL_STACKWALK
   GET_AND_SET(lul::LUL*, LUL)
 #endif
@@ -227,33 +226,33 @@ public:
   GET_AND_SET(mozilla::ProfilerIOInterposeObserver*, InterposeObserver)
 
   GET_AND_SET(int, FrameNumber)
   GET_AND_SET(int, LatestRecordedFrameNumber)
 
   #undef GET_AND_SET
 
 private:
-  // When profiler_init() or profiler_start() was most recently called.
-  mozilla::TimeStamp mStartTime;
+  // The time that the process started.
+  mozilla::TimeStamp mProcessStartTime;
 
   // The number of entries in mBuffer. Zeroed when the profiler is inactive.
   int mEntries;
 
   // The interval between samples, measured in milliseconds. Zeroed when the
   // profiler is inactive.
   double mInterval;
 
   // The profile features that are enabled. Cleared when the profiler is
   // inactive.
   Vector<std::string> mFeatures;
 
   // Substrings of names of threads we want to profile. Cleared when the
   // profiler is inactive
-  Vector<std::string> mThreadNameFilters;
+  Vector<std::string> mFilters;
 
   // Configuration flags derived from mFeatures. Cleared when the profiler is
   // inactive.
   bool mFeatureDisplayListDump;
   bool mFeatureGPU;
   bool mFeatureJava;
   bool mFeatureJS;
   bool mFeatureLayersDump;
@@ -301,17 +300,17 @@ private:
   // SamplerThread::Run() even after gPS has been destroyed by
   // profiler_shutdown().
   static uint32_t sActivityGeneration;
   static uint32_t sNextActivityGeneration;
 
   // Is the profiler paused? False when the profiler is inactive.
   bool mIsPaused;
 
-#if defined(GP_OS_linux) || defined(GP_OS_android)
+#if defined(GP_OS_linux)
   // Used to record whether the profiler was paused just before forking. False
   // at all times except just before/after forking.
   bool mWasPaused;
 #endif
 
   // The current sampler thread. Null when the profiler is inactive.
   class SamplerThread* mSamplerThread;
 
@@ -325,27 +324,27 @@ private:
   mozilla::ProfilerIOInterposeObserver* mInterposeObserver;
 
   // The current frame number and the most recent frame number recorded in a
   // sample.
   int mFrameNumber;
   int mLatestRecordedFrameNumber;
 };
 
-// A shorter name for use within this compilation unit.
-typedef ProfilerState PS;
-
 uint32_t PS::sActivityGeneration = 0;
 uint32_t PS::sNextActivityGeneration = 1;
 
-// The profiler state. Set by profiler_init(), cleared by profiler_shutdown().
-PS* gPS = nullptr;
+// The core profiler state. Null at process startup, it is set to a non-null
+// value in profiler_init() and stays that way until profiler_shutdown() is
+// called. Therefore it can be checked to determine if the profiler has been
+// initialized but not yet shut down.
+static PS* gPS = nullptr;
 
 // The mutex that guards accesses to gPS.
-static PS::Mutex gPSMutex;
+static PSMutex gPSMutex;
 
 // The name of the main thread.
 static const char* const kMainThreadName = "GeckoMain";
 
 static bool
 CanNotifyObservers()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -468,17 +467,17 @@ AddDynamicCodeLocationTag(ProfileBuffer*
     // Cast to *((void**) to pass the text data to a void*.
     aBuffer->addTag(ProfileBufferEntry::EmbeddedString(*((void**)(&text[0]))));
   }
 }
 
 static const int SAMPLER_MAX_STRING_LENGTH = 128;
 
 static void
-AddPseudoEntry(PS::LockRef aLock, ProfileBuffer* aBuffer,
+AddPseudoEntry(PSLockRef aLock, ProfileBuffer* aBuffer,
                volatile js::ProfileEntry& entry, PseudoStack* stack,
                void* lastpc)
 {
   // Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations and
   // should not be recorded in the profile.
   if (entry.hasFlag(js::ProfileEntry::BEGIN_PSEUDO_JS)) {
     return;
   }
@@ -570,17 +569,17 @@ struct AutoWalkJSStack
   ~AutoWalkJSStack() {
     if (walkAllowed) {
       WALKING_JS_STACK = false;
     }
   }
 };
 
 static void
-MergeStacksIntoProfile(PS::LockRef aLock, ProfileBuffer* aBuffer,
+MergeStacksIntoProfile(PSLockRef aLock, ProfileBuffer* aBuffer,
                        const TickSample& aSample, NativeStack& aNativeStack)
 {
   NotNull<PseudoStack*> pseudoStack = aSample.mPseudoStack;
   volatile js::ProfileEntry* pseudoFrames = pseudoStack->mStack;
   uint32_t pseudoCount = pseudoStack->stackSize();
 
   // Make a copy of the JS stack into a JSFrame array. This is necessary since,
   // like the native stack, the JS stack is iterated youngest-to-oldest and we
@@ -754,17 +753,17 @@ MergeStacksIntoProfile(PS::LockRef aLock
     if (nativeIndex >= 0) {
       nativeIndex--;
     }
   }
 
   // Update the JS context with the current profile sample buffer generation.
   //
   // Do not do this for synchronous samples, which use their own
-  // ProfileBuffers instead of the global one in ProfilerState.
+  // ProfileBuffers instead of the global one in PS.
   if (!aSample.mIsSynchronous && pseudoStack->mContext) {
     MOZ_ASSERT(aBuffer->mGeneration >= startBufferGen);
     uint32_t lapCount = aBuffer->mGeneration - startBufferGen;
     JS::UpdateJSContextProfilerSampleBufferGen(pseudoStack->mContext,
                                                aBuffer->mGeneration,
                                                lapCount);
   }
 }
@@ -780,17 +779,17 @@ StackWalkCallback(uint32_t aFrameNumber,
   NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
   MOZ_ASSERT(nativeStack->count < nativeStack->size);
   nativeStack->sp_array[nativeStack->count] = aSP;
   nativeStack->pc_array[nativeStack->count] = aPC;
   nativeStack->count++;
 }
 
 static void
-DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
+DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
                   const TickSample& aSample)
 {
   void* pc_array[1000];
   void* sp_array[1000];
   NativeStack nativeStack = {
     pc_array,
     sp_array,
     mozilla::ArrayLength(pc_array),
@@ -822,17 +821,17 @@ DoNativeBacktrace(PS::LockRef aLock, Pro
 #endif
 
   MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
 }
 #endif
 
 #ifdef USE_EHABI_STACKWALK
 static void
-DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
+DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
                   const TickSample& aSample)
 {
   void* pc_array[1000];
   void* sp_array[1000];
   NativeStack nativeStack = {
     pc_array,
     sp_array,
     mozilla::ArrayLength(pc_array),
@@ -911,17 +910,17 @@ ASAN_memcpy(void* aDst, const void* aSrc
 
   for (size_t i = 0; i < aLen; i++) {
     dst[i] = src[i];
   }
 }
 #endif
 
 static void
-DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
+DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
                   const TickSample& aSample)
 {
   const mcontext_t* mc =
     &reinterpret_cast<ucontext_t*>(aSample.mContext)->uc_mcontext;
 
   lul::UnwindRegs startRegs;
   memset(&startRegs, 0, sizeof(startRegs));
 
@@ -1028,61 +1027,64 @@ DoNativeBacktrace(PS::LockRef aLock, Pro
   const int MAX_NATIVE_FRAMES = 256;
 
   size_t scannedFramesAllowed = 0;
 
   uintptr_t framePCs[MAX_NATIVE_FRAMES];
   uintptr_t frameSPs[MAX_NATIVE_FRAMES];
   size_t framesAvail = mozilla::ArrayLength(framePCs);
   size_t framesUsed  = 0;
-  size_t scannedFramesAcquired = 0;
+  size_t scannedFramesAcquired = 0, framePointerFramesAcquired = 0;
   lul::LUL* lul = gPS->LUL(aLock);
   lul->Unwind(&framePCs[0], &frameSPs[0],
-              &framesUsed, &scannedFramesAcquired,
+              &framesUsed, &framePointerFramesAcquired, &scannedFramesAcquired,
               framesAvail, scannedFramesAllowed,
               &startRegs, &stackImg);
 
   NativeStack nativeStack = {
     reinterpret_cast<void**>(framePCs),
     reinterpret_cast<void**>(frameSPs),
     mozilla::ArrayLength(framePCs),
     framesUsed
   };
 
   MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
 
   // Update stats in the LUL stats object.  Unfortunately this requires
   // three global memory operations.
   lul->mStats.mContext += 1;
-  lul->mStats.mCFI     += framesUsed - 1 - scannedFramesAcquired;
+  lul->mStats.mCFI     += framesUsed - 1 - framePointerFramesAcquired -
+                                           scannedFramesAcquired;
+  lul->mStats.mFP      += framePointerFramesAcquired;
   lul->mStats.mScanned += scannedFramesAcquired;
 }
 
 #endif
 
 static void
-DoSampleStackTrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
+DoSampleStackTrace(PSLockRef aLock, ProfileBuffer* aBuffer,
                    const TickSample& aSample)
 {
   NativeStack nativeStack = { nullptr, nullptr, 0, 0 };
   MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
 
   if (gPS->FeatureLeaf(aLock)) {
     aBuffer->addTag(ProfileBufferEntry::NativeLeafAddr((void*)aSample.mPC));
   }
 }
 
 // This function is called for each sampling period with the current program
 // counter. It is called within a signal and so must be re-entrant.
 static void
-Tick(PS::LockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
+Tick(PSLockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
 {
   aBuffer->addTagThreadId(aSample.mThreadId, aSample.mLastSample);
 
-  mozilla::TimeDuration delta = aSample.mTimeStamp - gPS->StartTime(aLock);
+  mozilla::TimeDuration delta =
+    aSample.mTimeStamp - gPS->ProcessStartTime(aLock);
   aBuffer->addTag(ProfileBufferEntry::Time(delta.ToMilliseconds()));
 
   NotNull<PseudoStack*> pseudoStack = aSample.mPseudoStack;
 
 #if defined(HAVE_NATIVE_UNWIND)
   if (gPS->FeatureStackWalk(aLock)) {
     DoNativeBacktrace(aLock, aBuffer, aSample);
   } else
@@ -1181,23 +1183,23 @@ StreamNameAndThreadId(JSONWriter& aWrite
     }
     aWriter.IntProperty("tid", aThreadId);
   }
   aWriter.EndObject();
 }
 #endif
 
 static void
-StreamTaskTracer(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
+StreamTaskTracer(PSLockRef aLock, SpliceableJSONWriter& aWriter)
 {
 #ifdef MOZ_TASK_TRACER
   aWriter.StartArrayProperty("data");
   {
     UniquePtr<nsTArray<nsCString>> data =
-      mozilla::tasktracer::GetLoggedData(gPS->StartTime(aLock));
+      mozilla::tasktracer::GetLoggedData(gPS->ProcessStartTime(aLock));
     for (uint32_t i = 0; i < data->Length(); ++i) {
       aWriter.StringElement((data->ElementAt(i)).get());
     }
   }
   aWriter.EndArray();
 
   aWriter.StartArrayProperty("threads");
   {
@@ -1216,17 +1218,17 @@ StreamTaskTracer(PS::LockRef aLock, Spli
   aWriter.EndArray();
 
   aWriter.DoubleProperty(
     "start", static_cast<double>(mozilla::tasktracer::GetStartTime()));
 #endif
 }
 
 static void
-StreamMetaJSCustomObject(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
+StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   aWriter.IntProperty("version", 5);
   aWriter.DoubleProperty("interval", gPS->Interval(aLock));
   aWriter.IntProperty("stackwalk", gPS->FeatureStackWalk(aLock));
 
 #ifdef DEBUG
@@ -1236,20 +1238,20 @@ StreamMetaJSCustomObject(PS::LockRef aLo
 #endif
 
   aWriter.IntProperty("gcpoison", JS::IsGCPoisoning() ? 1 : 0);
 
   bool asyncStacks = Preferences::GetBool("javascript.options.asyncstack");
   aWriter.IntProperty("asyncstack", asyncStacks);
 
   // The "startTime" field holds the number of milliseconds since midnight
-  // January 1, 1970 GMT. This grotty code computes (Now - (Now - StartTime))
-  // to convert gPS->StartTime() into that form.
+  // January 1, 1970 GMT. This grotty code computes (Now - (Now -
+  // ProcessStartTime)) to convert gPS->ProcessStartTime() into that form.
   mozilla::TimeDuration delta =
-    mozilla::TimeStamp::Now() - gPS->StartTime(aLock);
+    mozilla::TimeStamp::Now() - gPS->ProcessStartTime(aLock);
   aWriter.DoubleProperty(
     "startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
 
   aWriter.IntProperty("processType", XRE_GetProcessType());
 
   nsresult res;
   nsCOMPtr<nsIHttpProtocolHandler> http =
     do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
@@ -1348,17 +1350,19 @@ BuildJavaThreadJSObject(SpliceableJSONWr
       }
     }
   }
   aWriter.EndArray();
 }
 #endif
 
 static void
-locked_profiler_stream_json_for_this_process(PS::LockRef aLock, SpliceableJSONWriter& aWriter, double aSinceTime)
+locked_profiler_stream_json_for_this_process(PSLockRef aLock,
+                                             SpliceableJSONWriter& aWriter,
+                                             double aSinceTime)
 {
   LOG("locked_profiler_stream_json_for_this_process");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS && gPS->IsActive(aLock));
 
   // Put shared library info
   aWriter.StartArrayProperty("libs");
@@ -1379,33 +1383,33 @@ locked_profiler_stream_json_for_this_pro
     aWriter.EndObject();
   }
 
   // Lists the samples for each thread profile
   aWriter.StartArrayProperty("threads");
   {
     gPS->SetIsPaused(aLock, true);
 
-      const PS::ThreadVector& liveThreads = gPS->LiveThreads(aLock);
-      for (size_t i = 0; i < liveThreads.size(); i++) {
-        ThreadInfo* info = liveThreads.at(i);
-        if (!info->IsBeingProfiled()) {
-          continue;
-        }
-        info->StreamJSON(gPS->Buffer(aLock), aWriter, gPS->StartTime(aLock),
-                         aSinceTime);
+    const PS::ThreadVector& liveThreads = gPS->LiveThreads(aLock);
+    for (size_t i = 0; i < liveThreads.size(); i++) {
+      ThreadInfo* info = liveThreads.at(i);
+      if (!info->IsBeingProfiled()) {
+        continue;
       }
-
-      const PS::ThreadVector& deadThreads = gPS->DeadThreads(aLock);
-      for (size_t i = 0; i < deadThreads.size(); i++) {
-        ThreadInfo* info = deadThreads.at(i);
-        MOZ_ASSERT(info->IsBeingProfiled());
-        info->StreamJSON(gPS->Buffer(aLock), aWriter, gPS->StartTime(aLock),
-                         aSinceTime);
-      }
+      info->StreamJSON(gPS->Buffer(aLock), aWriter,
+                       gPS->ProcessStartTime(aLock), aSinceTime);
+    }
+
+    const PS::ThreadVector& deadThreads = gPS->DeadThreads(aLock);
+    for (size_t i = 0; i < deadThreads.size(); i++) {
+      ThreadInfo* info = deadThreads.at(i);
+      MOZ_ASSERT(info->IsBeingProfiled());
+      info->StreamJSON(gPS->Buffer(aLock), aWriter,
+                       gPS->ProcessStartTime(aLock), aSinceTime);
+    }
 
 #if defined(PROFILE_JAVA)
     if (gPS->FeatureJava(aLock)) {
       java::GeckoJavaSampler::Pause();
 
       aWriter.Start();
       {
         BuildJavaThreadJSObject(aWriter);
@@ -1424,17 +1428,17 @@ locked_profiler_stream_json_for_this_pro
 bool
 profiler_stream_json_for_this_process(SpliceableJSONWriter& aWriter, double aSinceTime)
 {
   LOG("profiler_stream_json_for_this_process");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock)) {
     return false;
   }
 
   locked_profiler_stream_json_for_this_process(lock, aWriter, aSinceTime);
   return true;
 }
@@ -1462,33 +1466,33 @@ ProfilerMarker::SetGeneration(uint32_t a
 }
 
 double
 ProfilerMarker::GetTime() const {
   return mTime;
 }
 
 void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter,
-                                const TimeStamp& aStartTime,
+                                const TimeStamp& aProcessStartTime,
                                 UniqueStacks& aUniqueStacks) const
 {
   // Schema:
   //   [name, time, data]
 
   aWriter.StartArrayElement();
   {
     aUniqueStacks.mUniqueStrings.WriteElement(aWriter, GetMarkerName());
     aWriter.DoubleElement(mTime);
     // TODO: Store the callsite for this marker if available:
     // if have location data
     //   b.NameValue(marker, "location", ...);
     if (mPayload) {
       aWriter.StartObjectElement();
       {
-          mPayload->StreamPayload(aWriter, aStartTime, aUniqueStacks);
+        mPayload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks);
       }
       aWriter.EndObject();
     }
   }
   aWriter.EndArray();
 }
 
 static void
@@ -1553,29 +1557,29 @@ struct SigHandlerCoordinator;
 // The sampler thread controls sampling and runs whenever the profiler is
 // active. It periodically runs through all registered threads, finds those
 // that should be sampled, then pauses and samples them.
 
 class SamplerThread
 {
 public:
   // Creates a sampler thread, but doesn't start it.
-  SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
+  SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
                 double aIntervalMilliseconds);
   ~SamplerThread();
 
   // This runs on the sampler thread.  It suspends and resumes the samplee
   // threads.
-  void SuspendAndSampleAndResumeThread(PS::LockRef aLock, TickSample& aSample);
+  void SuspendAndSampleAndResumeThread(PSLockRef aLock, TickSample& aSample);
 
   // This runs on (is!) the sampler thread.
   void Run();
 
   // This runs on the main thread.
-  void Stop(PS::LockRef aLock);
+  void Stop(PSLockRef aLock);
 
 private:
   // The activity generation, for detecting when the sampler thread must stop.
   const uint32_t mActivityGeneration;
 
   // The interval between samples, measured in microseconds.
   const int mIntervalMicroseconds;
 
@@ -1619,17 +1623,17 @@ SamplerThread::Run()
   // This will be positive if we are running behind schedule (sampling less
   // frequently than desired) and negative if we are ahead of schedule.
   TimeDuration lastSleepOvershoot = 0;
   TimeStamp sampleStart = TimeStamp::Now();
 
   while (true) {
     // This scope is for |lock|. It ends before we sleep below.
     {
-      PS::AutoLock lock(gPSMutex);
+      PSAutoLock lock(gPSMutex);
 
       // At this point profiler_stop() might have been called, and
       // profiler_start() might have been called on another thread.
       // Alternatively, profiler_shutdown() might have been called and gPS
       // may be null. In all these cases, PS::sActivityGeneration will no
       // longer equal mActivityGeneration, so we must exit immediately, but
       // without touching gPS. (This is why PS::sActivityGeneration must be
       // static.)
@@ -1649,19 +1653,19 @@ SamplerThread::Run()
             continue;
           }
 
           // If the thread is asleep and has been sampled before in the same
           // sleep episode, find and copy the previous sample, as that's
           // cheaper than taking a new sample.
           if (info->Stack()->CanDuplicateLastSampleDueToSleep()) {
             bool dup_ok =
-              gPS->Buffer(lock)->DuplicateLastSample(info->ThreadId(),
-                                                     gPS->StartTime(lock),
-                                                     info->LastSample());
+              gPS->Buffer(lock)->DuplicateLastSample(
+                info->ThreadId(), gPS->ProcessStartTime(lock),
+                info->LastSample());
             if (dup_ok) {
               continue;
             }
           }
 
           // We only track responsiveness for the main thread.
           if (info->IsMainThread()) {
             info->GetThreadResponsiveness()->Update();
@@ -1752,17 +1756,17 @@ GeckoProfilerReporter::CollectReports(ns
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   size_t profSize = 0;
 #if defined(USE_LUL_STACKWALK)
   size_t lulSize = 0;
 #endif
 
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     if (gPS) {
       profSize = GeckoProfilerMallocSizeOf(gPS);
 
       const PS::ThreadVector& liveThreads = gPS->LiveThreads(lock);
       for (uint32_t i = 0; i < liveThreads.size(); i++) {
         ThreadInfo* info = liveThreads.at(i);
         profSize += info->SizeOfIncludingThis(GeckoProfilerMallocSizeOf);
@@ -1777,88 +1781,88 @@ GeckoProfilerReporter::CollectReports(ns
       if (gPS->IsActive(lock)) {
         profSize +=
           gPS->Buffer(lock)->SizeOfIncludingThis(GeckoProfilerMallocSizeOf);
       }
 
       // Measurement of the following things may be added later if DMD finds it
       // is worthwhile:
       // - gPS->mFeatures
-      // - gPS->mThreadNameFilters
+      // - gPS->mFilters
       // - gPS->mLiveThreads itself (its elements' children are measured above)
       // - gPS->mDeadThreads itself (ditto)
       // - gPS->mInterposeObserver
 
 #if defined(USE_LUL_STACKWALK)
       lul::LUL* lul = gPS->LUL(lock);
       lulSize = lul ? lul->SizeOfIncludingThis(GeckoProfilerMallocSizeOf) : 0;
 #endif
     }
   }
 
   MOZ_COLLECT_REPORT(
     "explicit/profiler/profiler-state", KIND_HEAP, UNITS_BYTES, profSize,
-    "Memory used by the Gecko Profiler's ProfilerState object (excluding "
-    "memory used by LUL).");
+    "Memory used by the Gecko Profiler's global state (excluding memory used "
+    "by LUL).");
 
 #if defined(USE_LUL_STACKWALK)
   MOZ_COLLECT_REPORT(
     "explicit/profiler/lul", KIND_HEAP, UNITS_BYTES, lulSize,
     "Memory used by LUL, a stack unwinder used by the Gecko Profiler.");
 #endif
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(GeckoProfilerReporter, nsIMemoryReporter)
 
 static bool
-ThreadSelected(PS::LockRef aLock, const char* aThreadName)
+ThreadSelected(PSLockRef aLock, const char* aThreadName)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  const Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(aLock);
-
-  if (threadNameFilters.empty()) {
+  const Vector<std::string>& filters = gPS->Filters(aLock);
+
+  if (filters.empty()) {
     return true;
   }
 
   std::string name = aThreadName;
   std::transform(name.begin(), name.end(), name.begin(), ::tolower);
 
-  for (uint32_t i = 0; i < threadNameFilters.length(); ++i) {
-    std::string filter = threadNameFilters[i];
+  for (uint32_t i = 0; i < filters.length(); ++i) {
+    std::string filter = filters[i];
     std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
 
     // Crude, non UTF-8 compatible, case insensitive substring search
     if (name.find(filter) != std::string::npos) {
       return true;
     }
   }
 
   return false;
 }
 
 static bool
-ShouldProfileThread(PS::LockRef aLock, ThreadInfo* aInfo)
+ShouldProfileThread(PSLockRef aLock, ThreadInfo* aInfo)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
   return ((aInfo->IsMainThread() || gPS->FeatureThreads(aLock)) &&
           ThreadSelected(aLock, aInfo->Name()));
 }
 
 // Find the ThreadInfo for the current thread. On success, *aIndexOut is set to
 // the index if it is non-null.
 static ThreadInfo*
-FindLiveThreadInfo(PS::LockRef aLock, int* aIndexOut = nullptr)
+FindLiveThreadInfo(PSLockRef aLock, int* aIndexOut = nullptr)
 {
   // This function runs both on and off the main thread.
 
   Thread::tid_t id = Thread::GetCurrentId();
   const PS::ThreadVector& liveThreads = gPS->LiveThreads(aLock);
   for (uint32_t i = 0; i < liveThreads.size(); i++) {
     ThreadInfo* info = liveThreads.at(i);
     if (info->ThreadId() == id) {
@@ -1867,17 +1871,17 @@ FindLiveThreadInfo(PS::LockRef aLock, in
       }
       return info;
     }
   }
   return nullptr;
 }
 
 static void
-locked_register_thread(PS::LockRef aLock, const char* aName, void* stackTop)
+locked_register_thread(PSLockRef aLock, const char* aName, void* stackTop)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
   MOZ_RELEASE_ASSERT(!FindLiveThreadInfo(aLock));
 
   if (!tlsPseudoStack.init()) {
@@ -1901,40 +1905,39 @@ locked_register_thread(PS::LockRef aLock
   }
 
   gPS->LiveThreads(aLock).push_back(info);
 }
 
 static void
 NotifyProfilerStarted(const int aEntries, double aInterval,
                       const char** aFeatures, uint32_t aFeatureCount,
-                      const char** aThreadNameFilters, uint32_t aFilterCount)
+                      const char** aFilters, uint32_t aFilterCount)
 {
   if (!CanNotifyObservers()) {
     return;
   }
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (!os) {
     return;
   }
 
   nsTArray<nsCString> featuresArray;
   for (size_t i = 0; i < aFeatureCount; ++i) {
     featuresArray.AppendElement(aFeatures[i]);
   }
 
-  nsTArray<nsCString> threadNameFiltersArray;
+  nsTArray<nsCString> filtersArray;
   for (size_t i = 0; i < aFilterCount; ++i) {
-    threadNameFiltersArray.AppendElement(aThreadNameFilters[i]);
+    filtersArray.AppendElement(aFilters[i]);
   }
 
   nsCOMPtr<nsIProfilerStartParams> params =
-    new nsProfilerStartParams(aEntries, aInterval, featuresArray,
-                              threadNameFiltersArray);
+    new nsProfilerStartParams(aEntries, aInterval, featuresArray, filtersArray);
 
   os->NotifyObservers(params, "profiler-started", nullptr);
 }
 
 static void
 NotifyObservers(const char* aTopic)
 {
   if (!CanNotifyObservers()) {
@@ -1945,19 +1948,19 @@ NotifyObservers(const char* aTopic)
   if (!os) {
     return;
   }
 
   os->NotifyObservers(nullptr, aTopic, nullptr);
 }
 
 static void
-locked_profiler_start(PS::LockRef aLock, const int aEntries, double aInterval,
+locked_profiler_start(PSLockRef aLock, const int aEntries, double aInterval,
                       const char** aFeatures, uint32_t aFeatureCount,
-                      const char** aThreadNameFilters, uint32_t aFilterCount);
+                      const char** aFilters, uint32_t aFilterCount);
 
 void
 profiler_init(void* aStackTop)
 {
   LOG("profiler_init");
 
   MOZ_RELEASE_ASSERT(!gPS);
 
@@ -1974,24 +1977,24 @@ profiler_init(void* aStackTop)
 
   const char* threadFilters[] = { "GeckoMain", "Compositor" };
 
   if (getenv("MOZ_PROFILER_HELP")) {
     PrintUsageThenExit(0); // terminates execution
   }
 
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     // We've passed the possible failure point. Instantiate gPS, which
     // indicates that the profiler has initialized successfully.
     gPS = new PS();
 
     bool ignore;
-    gPS->SetStartTime(lock, mozilla::TimeStamp::ProcessCreation(ignore));
+    gPS->SetProcessStartTime(lock, mozilla::TimeStamp::ProcessCreation(ignore));
 
     locked_register_thread(lock, kMainThreadName, aStackTop);
 
     // Platform-specific initialization.
     PlatformInit(lock);
 
 #ifdef MOZ_TASK_TRACER
     mozilla::tasktracer::InitTaskTracer();
@@ -2047,34 +2050,34 @@ profiler_init(void* aStackTop)
   // We do this with gPSMutex unlocked. The comment in profiler_stop() explains
   // why.
   NotifyProfilerStarted(PROFILE_DEFAULT_ENTRIES, PROFILE_DEFAULT_INTERVAL,
                         features, MOZ_ARRAY_LENGTH(features),
                         threadFilters, MOZ_ARRAY_LENGTH(threadFilters));
 }
 
 static void
-locked_profiler_save_profile_to_file(PS::LockRef aLock, const char* aFilename);
+locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename);
 
 static SamplerThread*
-locked_profiler_stop(PS::LockRef aLock);
+locked_profiler_stop(PSLockRef aLock);
 
 void
 profiler_shutdown()
 {
   LOG("profiler_shutdown");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
   // If the profiler is active we must get a handle to the SamplerThread before
   // gPS is destroyed, in order to delete it.
   SamplerThread* samplerThread = nullptr;
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     // Save the profile on shutdown if requested.
     if (gPS->IsActive(lock)) {
       const char* filename = getenv("MOZ_PROFILER_SHUTDOWN");
       if (filename) {
         locked_profiler_save_profile_to_file(lock, filename);
       }
 
@@ -2155,36 +2158,36 @@ profiler_get_start_params(int* aEntries,
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
   if (NS_WARN_IF(!aEntries) || NS_WARN_IF(!aInterval) ||
       NS_WARN_IF(!aFeatures) || NS_WARN_IF(!aFilters)) {
     return;
   }
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   *aEntries = gPS->Entries(lock);
   *aInterval = gPS->Interval(lock);
 
   const Vector<std::string>& features = gPS->Features(lock);
   MOZ_ALWAYS_TRUE(aFeatures->resize(features.length()));
   for (size_t i = 0; i < features.length(); ++i) {
     (*aFeatures)[i] = features[i].c_str();
   }
 
-  const Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(lock);
-  MOZ_ALWAYS_TRUE(aFilters->resize(threadNameFilters.length()));
-  for (uint32_t i = 0; i < threadNameFilters.length(); ++i) {
-    (*aFilters)[i] = threadNameFilters[i].c_str();
+  const Vector<std::string>& filters = gPS->Filters(lock);
+  MOZ_ALWAYS_TRUE(aFilters->resize(filters.length()));
+  for (uint32_t i = 0; i < filters.length(); ++i) {
+    (*aFilters)[i] = filters[i].c_str();
   }
 }
 
 static void
-locked_profiler_save_profile_to_file(PS::LockRef aLock, const char* aFilename)
+locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename)
 {
   LOG("locked_profiler_save_profile_to_file(%s)", aFilename);
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS && gPS->IsActive(aLock));
 
   std::ofstream stream;
   stream.open(aFilename);
@@ -2208,17 +2211,17 @@ locked_profiler_save_profile_to_file(PS:
 void
 profiler_save_profile_to_file(const char* aFilename)
 {
   LOG("profiler_save_profile_to_file(%s)", aFilename);
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock)) {
     return;
   }
 
   locked_profiler_save_profile_to_file(lock, aFilename);
 }
 
@@ -2272,17 +2275,17 @@ profiler_get_buffer_info_helper(uint32_t
                                 uint32_t* aGeneration)
 {
   // This function is called by profiler_get_buffer_info(), which has already
   // zeroed the outparams.
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock)) {
     return;
   }
 
   *aCurrentPosition = gPS->Buffer(lock)->mWritePos;
   *aEntries = gPS->Entries(lock);
   *aGeneration = gPS->Buffer(lock)->mGeneration;
@@ -2295,59 +2298,55 @@ hasFeature(const char** aFeatures, uint3
     if (strcmp(aFeatures[i], aFeature) == 0) {
       return true;
     }
   }
   return false;
 }
 
 static void
-locked_profiler_start(PS::LockRef aLock, int aEntries, double aInterval,
+locked_profiler_start(PSLockRef aLock, int aEntries, double aInterval,
                       const char** aFeatures, uint32_t aFeatureCount,
-                      const char** aThreadNameFilters, uint32_t aFilterCount)
+                      const char** aFilters, uint32_t aFilterCount)
 {
   if (LOG_TEST) {
     LOG("locked_profiler_start");
     LOG("- entries  = %d", aEntries);
     LOG("- interval = %.2f", aInterval);
     for (uint32_t i = 0; i < aFeatureCount; i++) {
       LOG("- feature  = %s", aFeatures[i]);
     }
     for (uint32_t i = 0; i < aFilterCount; i++) {
-      LOG("- threads  = %s", aThreadNameFilters[i]);
+      LOG("- threads  = %s", aFilters[i]);
     }
   }
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS && !gPS->IsActive(aLock));
 
-  bool ignore;
-  gPS->SetStartTime(aLock, mozilla::TimeStamp::ProcessCreation(ignore));
-
   // Fall back to the default value if the passed-in value is unreasonable.
   int entries = aEntries > 0 ? aEntries : PROFILE_DEFAULT_ENTRIES;
   gPS->SetEntries(aLock, entries);
 
   // Ditto.
   double interval = aInterval > 0 ? aInterval : PROFILE_DEFAULT_INTERVAL;
   gPS->SetInterval(aLock, interval);
 
   // Deep copy aFeatures. Must precede the ShouldProfileThread() call below.
   Vector<std::string>& features = gPS->Features(aLock);
   MOZ_ALWAYS_TRUE(features.resize(aFeatureCount));
   for (uint32_t i = 0; i < aFeatureCount; ++i) {
     features[i] = aFeatures[i];
   }
 
-  // Deep copy aThreadNameFilters. Must precede the ShouldProfileThread() call
-  // below.
-  Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(aLock);
-  MOZ_ALWAYS_TRUE(threadNameFilters.resize(aFilterCount));
+  // Deep copy aFilters. Must precede the ShouldProfileThread() call below.
+  Vector<std::string>& filters = gPS->Filters(aLock);
+  MOZ_ALWAYS_TRUE(filters.resize(aFilterCount));
   for (uint32_t i = 0; i < aFilterCount; ++i) {
-    threadNameFilters[i] = aThreadNameFilters[i];
+    filters[i] = aFilters[i];
   }
 
 #define HAS_FEATURE(feature) hasFeature(aFeatures, aFeatureCount, feature)
 
   gPS->SetFeatureDisplayListDump(aLock, HAS_FEATURE("displaylistdump"));
   gPS->SetFeatureGPU(aLock, HAS_FEATURE("gpu"));
 #if defined(PROFILE_JAVA)
   gPS->SetFeatureJava(aLock, mozilla::jni::IsFennec() && HAS_FEATURE("java"));
@@ -2436,52 +2435,52 @@ locked_profiler_start(PS::LockRef aLock,
     mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll,
                                     interposeObserver);
   }
 }
 
 void
 profiler_start(int aEntries, double aInterval,
                const char** aFeatures, uint32_t aFeatureCount,
-               const char** aThreadNameFilters, uint32_t aFilterCount)
+               const char** aFilters, uint32_t aFilterCount)
 {
   LOG("profiler_start");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   SamplerThread* samplerThread = nullptr;
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     // Initialize if necessary.
     if (!gPS) {
       profiler_init(nullptr);
     }
 
     // Reset the current state if the profiler is running.
     if (gPS->IsActive(lock)) {
       samplerThread = locked_profiler_stop(lock);
     }
 
     locked_profiler_start(lock, aEntries, aInterval, aFeatures, aFeatureCount,
-                          aThreadNameFilters, aFilterCount);
+                          aFilters, aFilterCount);
   }
 
   // We do these operations with gPSMutex unlocked. The comments in
   // profiler_stop() explain why.
   if (samplerThread) {
     NotifyObservers("profiler-stopped");
     delete samplerThread;
   }
   NotifyProfilerStarted(aEntries, aInterval, aFeatures, aFeatureCount,
-                        aThreadNameFilters, aFilterCount);
+                        aFilters, aFilterCount);
 }
 
 static MOZ_MUST_USE SamplerThread*
-locked_profiler_stop(PS::LockRef aLock)
+locked_profiler_stop(PSLockRef aLock)
 {
   LOG("locked_profiler_stop");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS && gPS->IsActive(aLock));
 
   // We clear things in roughly reverse order to their setting in
   // locked_profiler_start().
@@ -2549,17 +2548,17 @@ locked_profiler_stop(PS::LockRef aLock)
   gPS->SetFeatureLeaf(aLock, false);
   gPS->SetFeatureMemory(aLock, false);
   gPS->SetFeaturePrivacy(aLock, false);
   gPS->SetFeatureRestyle(aLock, false);
   gPS->SetFeatureStackWalk(aLock, false);
   gPS->SetFeatureTaskTracer(aLock, false);
   gPS->SetFeatureThreads(aLock, false);
 
-  gPS->ThreadNameFilters(aLock).clear();
+  gPS->Filters(aLock).clear();
 
   gPS->Features(aLock).clear();
 
   gPS->SetInterval(aLock, 0.0);
 
   gPS->SetEntries(aLock, 0);
 
   return samplerThread;
@@ -2570,17 +2569,17 @@ profiler_stop()
 {
   LOG("profiler_stop");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
   SamplerThread* samplerThread;
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     if (!gPS->IsActive(lock)) {
       return;
     }
 
     samplerThread = locked_profiler_stop(lock);
   }
 
@@ -2602,17 +2601,17 @@ profiler_stop()
 }
 
 bool
 profiler_is_paused()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock)) {
     return false;
   }
 
   return gPS->IsPaused(lock);
 }
 
@@ -2620,17 +2619,17 @@ void
 profiler_pause()
 {
   LOG("profiler_pause");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
 
     if (!gPS->IsActive(lock)) {
       return;
     }
 
     gPS->SetIsPaused(lock, true);
   }
 
@@ -2641,17 +2640,17 @@ profiler_pause()
 void
 profiler_resume()
 {
   LOG("profiler_resume");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   {
     if (!gPS->IsActive(lock)) {
       return;
     }
 
     gPS->SetIsPaused(lock, false);
   }
@@ -2662,17 +2661,17 @@ profiler_resume()
 
 bool
 profiler_feature_active(const char* aName)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock)) {
     return false;
   }
 
   if (strcmp(aName, "displaylistdump") == 0) {
     return gPS->FeatureDisplayListDump(lock);
   }
@@ -2694,54 +2693,54 @@ profiler_feature_active(const char* aNam
 
 bool
 profiler_is_active()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   return gPS->IsActive(lock);
 }
 
 void
 profiler_set_frame_number(int aFrameNumber)
 {
   // This function runs both on (via tests) and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   gPS->SetFrameNumber(lock, aFrameNumber);
 }
 
 void
 profiler_register_thread(const char* aName, void* aGuessStackTop)
 {
   DEBUG_LOG("profiler_register_thread(%s)", aName);
 
   MOZ_RELEASE_ASSERT(!NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   void* stackTop = GetStackTop(aGuessStackTop);
   locked_register_thread(lock, aName, stackTop);
 }
 
 void
 profiler_unregister_thread()
 {
   MOZ_RELEASE_ASSERT(!NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   // We don't call PseudoStack::stopJSSampling() here; there's no point doing
   // that for a JS thread that is in the process of disappearing.
 
   int i;
   ThreadInfo* info = FindLiveThreadInfo(lock, &i);
   if (info) {
     DEBUG_LOG("profiler_unregister_thread: %s", info->Name());
@@ -2829,30 +2828,30 @@ profiler_js_interrupt_callback()
 
 double
 profiler_time()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   mozilla::TimeDuration delta =
-    mozilla::TimeStamp::Now() - gPS->StartTime(lock);
+    mozilla::TimeStamp::Now() - gPS->ProcessStartTime(lock);
   return delta.ToMilliseconds();
 }
 
 UniqueProfilerBacktrace
 profiler_get_backtrace()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
     return nullptr;
   }
 
   PseudoStack* stack = tlsPseudoStack.get();
   if (!stack) {
     MOZ_ASSERT(stack);
@@ -2905,17 +2904,17 @@ profiler_get_backtrace_noalloc(char *out
 
   PseudoStack *pseudoStack = tlsPseudoStack.get();
   if (!pseudoStack) {
     return;
   }
 
   bool includeDynamicString = true;
   {
-    PS::AutoLock lock(gPSMutex);
+    PSAutoLock lock(gPSMutex);
     includeDynamicString = !gPS->FeaturePrivacy(lock);
   }
 
   volatile js::ProfileEntry *pseudoFrames = pseudoStack->mStack;
   uint32_t pseudoCount = pseudoStack->stackSize();
 
   for (uint32_t i = 0; i < pseudoCount; i++) {
     const char* label = pseudoFrames[i].label();
@@ -2942,17 +2941,17 @@ profiler_get_backtrace_noalloc(char *out
       output += labelLength;
     }
     *output++ = '\0';
     *output = '\0';
   }
 }
 
 static void
-locked_profiler_add_marker(PS::LockRef aLock, const char* aMarker,
+locked_profiler_add_marker(PSLockRef aLock, const char* aMarker,
                            ProfilerMarkerPayload* aPayload)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
   MOZ_RELEASE_ASSERT(gPS->IsActive(aLock) && !gPS->FeaturePrivacy(aLock));
 
   // aPayload must be freed if we return early.
@@ -2961,28 +2960,28 @@ locked_profiler_add_marker(PS::LockRef a
   PseudoStack *stack = tlsPseudoStack.get();
   if (!stack) {
     return;
   }
 
   mozilla::TimeStamp origin = (payload && !payload->GetStartTime().IsNull())
                             ? payload->GetStartTime()
                             : mozilla::TimeStamp::Now();
-  mozilla::TimeDuration delta = origin - gPS->StartTime(aLock);
+  mozilla::TimeDuration delta = origin - gPS->ProcessStartTime(aLock);
   stack->addMarker(aMarker, payload.release(), delta.ToMilliseconds());
 }
 
 void
 profiler_add_marker(const char* aMarker, ProfilerMarkerPayload* aPayload)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   // aPayload must be freed if we return early.
   mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
 
   if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
     return;
   }
 
@@ -2992,17 +2991,17 @@ profiler_add_marker(const char* aMarker,
 void
 profiler_tracing(const char* aCategory, const char* aInfo,
                  TracingKind aKind)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
     return;
   }
 
   auto marker = new ProfilerMarkerTracing(aCategory, aKind);
   locked_profiler_add_marker(lock, aInfo, marker);
 }
@@ -3010,17 +3009,17 @@ profiler_tracing(const char* aCategory, 
 void
 profiler_tracing(const char* aCategory, const char* aInfo,
                  UniqueProfilerBacktrace aCause, TracingKind aKind)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
     return;
   }
 
   auto marker =
     new ProfilerMarkerTracing(aCategory, aKind, mozilla::Move(aCause));
   locked_profiler_add_marker(lock, aInfo, marker);
@@ -3063,26 +3062,27 @@ profiler_clear_js_context()
 
   if (!stack->mContext) {
     return;
   }
 
   // On JS shut down, flush the current buffer as stringifying JIT samples
   // requires a live JSContext.
 
-  PS::AutoLock lock(gPSMutex);
+  PSAutoLock lock(gPSMutex);
 
   if (gPS->IsActive(lock)) {
     gPS->SetIsPaused(lock, true);
 
     // Flush this thread's ThreadInfo, if it is being profiled.
     ThreadInfo* info = FindLiveThreadInfo(lock);
     MOZ_RELEASE_ASSERT(info);
     if (info->IsBeingProfiled()) {
-      info->FlushSamplesAndMarkers(gPS->Buffer(lock), gPS->StartTime(lock));
+      info->FlushSamplesAndMarkers(gPS->Buffer(lock),
+                                   gPS->ProcessStartTime(lock));
     }
 
     gPS->SetIsPaused(lock, false);
   }
 
   // We don't call stack->stopJSSampling() here; there's no point doing
   // that for a JS thread that is in the process of disappearing.
 
--- a/tools/profiler/lul/LulMain.cpp
+++ b/tools/profiler/lul/LulMain.cpp
@@ -891,23 +891,24 @@ LUL::MaybeShowStats()
   //   n_new == n_new_Context + n_new_CFI + n_new_Scanned
   // if it should happen that mStats is updated by some other thread
   // in between computation of n_new and n_new_{Context,CFI,Scanned}.
   // But it's just stats printing, so we don't really care.
   uint32_t n_new = mStats - mStatsPrevious;
   if (n_new >= 5000) {
     uint32_t n_new_Context = mStats.mContext - mStatsPrevious.mContext;
     uint32_t n_new_CFI     = mStats.mCFI     - mStatsPrevious.mCFI;
+    uint32_t n_new_FP      = mStats.mFP      - mStatsPrevious.mFP;
     uint32_t n_new_Scanned = mStats.mScanned - mStatsPrevious.mScanned;
     mStatsPrevious = mStats;
     char buf[200];
     SprintfLiteral(buf,
                    "LUL frame stats: TOTAL %5u"
-                   "    CTX %4u    CFI %4u    SCAN %4u",
-                   n_new, n_new_Context, n_new_CFI, n_new_Scanned);
+                   "    CTX %4u    CFI %4u    FP %4u    SCAN %4u",
+                   n_new, n_new_Context, n_new_CFI, n_new_FP, n_new_Scanned);
     buf[sizeof(buf)-1] = 0;
     mLog(buf);
   }
 }
 
 
 size_t
 LUL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
@@ -1341,16 +1342,17 @@ void UseRuleSet(/*MOD*/UnwindRegs* aRegs
   // new value will now be marked as invalid.
 }
 
 // RUNS IN NO-MALLOC CONTEXT
 void
 LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
             /*OUT*/uintptr_t* aFrameSPs,
             /*OUT*/size_t* aFramesUsed, 
+            /*OUT*/size_t* aFramePointerFramesAcquired,
             /*OUT*/size_t* aScannedFramesAcquired,
             size_t aFramesAvail,
             size_t aScannedFramesAllowed,
             UnwindRegs* aStartRegs, StackImage* aStackImg)
 {
   MOZ_ASSERT(!mAdminMode);
 
   /////////////////////////////////////////////////////////
@@ -1540,18 +1542,72 @@ LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
         ruleset->Print(mLog); mLog("\n");
       }
       // Use the RuleSet to compute the registers for the previous
       // frame.  |regs| is modified in-place.
       UseRuleSet(&regs, aStackImg, ruleset, pfxinstrs);
 
     } else {
 
-      // There's no RuleSet for the specified address, so see if
-      // it's possible to get anywhere by stack-scanning.
+      // There's no RuleSet for the specified address.  On amd64_linux, see if
+      // it's possible to recover the caller's frame by using the frame pointer.
+      // This would probably work for the 32-bit case too, but hasn't been
+      // tested for that case.
+
+#if defined(GP_PLAT_amd64_linux)
+      // We seek to compute (new_IP, new_SP, new_BP) from (old_BP, stack image),
+      // and assume the following layout:
+      //
+      //                 <--- new_SP
+      //   +----------+
+      //   |  new_IP  |  (return address)
+      //   +----------+
+      //   |  new_BP  |  <--- old_BP
+      //   +----------+
+      //   |   ....   |
+      //   |   ....   |
+      //   |   ....   |
+      //   +----------+  <---- old_SP (arbitrary, but must be <= old_BP)
+
+      const size_t wordSzB = sizeof(uintptr_t);
+      TaggedUWord old_xsp = regs.xsp;
+
+      // points at new_BP ?
+      TaggedUWord old_xbp = regs.xbp;
+      // points at new_IP ?
+      TaggedUWord old_xbp_plus1 = regs.xbp + TaggedUWord(1 * wordSzB);
+      // is the new_SP ?
+      TaggedUWord old_xbp_plus2 = regs.xbp + TaggedUWord(2 * wordSzB);
+
+      if (old_xbp.Valid() && old_xbp.IsAligned() &&
+          old_xsp.Valid() && old_xsp.IsAligned() &&
+          old_xsp.Value() <= old_xbp.Value()) {
+        // We don't need to do any range, alignment or validity checks for
+        // addresses passed to DerefTUW, since that performs them itself, and
+        // returns an invalid value on failure.  Any such value will poison
+        // subsequent uses, and we do a final check for validity before putting
+        // the computed values into |regs|.
+        TaggedUWord new_xbp = DerefTUW(old_xbp, aStackImg);
+        if (new_xbp.Valid() && new_xbp.IsAligned() &&
+            old_xbp.Value() < new_xbp.Value()) {
+          TaggedUWord new_xip = DerefTUW(old_xbp_plus1, aStackImg);
+          TaggedUWord new_xsp = old_xbp_plus2;
+          if (new_xbp.Valid() && new_xip.Valid() && new_xsp.Valid()) {
+            regs.xbp = new_xbp;
+            regs.xip = new_xip;
+            regs.xsp = new_xsp;
+            (*aFramePointerFramesAcquired)++;
+            continue;
+          }
+        }
+      }
+#endif
+
+      // As a last-ditch resort, see if it's possible to get anywhere by
+      // stack-scanning.
 
       // Use stack scanning frugally.
       if (n_scanned_frames++ >= aScannedFramesAllowed) {
         break;
       }
 
       // We can't scan the stack without a valid, aligned stack pointer.
       if (!sp.IsAligned()) {
@@ -1763,19 +1819,20 @@ bool GetAndCheckStackTrace(LUL* aLUL, co
 
   // Unwind it.
   const int MAX_TEST_FRAMES = 64;
   uintptr_t framePCs[MAX_TEST_FRAMES];
   uintptr_t frameSPs[MAX_TEST_FRAMES];
   size_t framesAvail = mozilla::ArrayLength(framePCs);
   size_t framesUsed  = 0;
   size_t scannedFramesAllowed = 0;
-  size_t scannedFramesAcquired = 0;
+  size_t scannedFramesAcquired = 0, framePointerFramesAcquired = 0;
   aLUL->Unwind( &framePCs[0], &frameSPs[0],
-                &framesUsed, &scannedFramesAcquired,
+                &framesUsed,
+                &framePointerFramesAcquired, &scannedFramesAcquired,
                 framesAvail, scannedFramesAllowed,
                 &startRegs, stackImg );
 
   delete stackImg;
 
   //if (0) {
   //  // Show what we have.
   //  fprintf(stderr, "Got %d frames:\n", (int)framesUsed);
--- a/tools/profiler/lul/LulMain.h
+++ b/tools/profiler/lul/LulMain.h
@@ -178,43 +178,48 @@ struct StackImage {
 
 // Statistics collection for the unwinder.
 template<typename T>
 class LULStats {
 public:
   LULStats()
     : mContext(0)
     , mCFI(0)
+    , mFP(0)
     , mScanned(0)
   {}
 
   template <typename S>
   explicit LULStats(const LULStats<S>& aOther)
     : mContext(aOther.mContext)
     , mCFI(aOther.mCFI)
+    , mFP(aOther.mFP)
     , mScanned(aOther.mScanned)
   {}
 
   template <typename S>
   LULStats<T>& operator=(const LULStats<S>& aOther)
   {
     mContext = aOther.mContext;
     mCFI     = aOther.mCFI;
+    mFP      = aOther.mFP;
     mScanned = aOther.mScanned;
     return *this;
   }
 
   template <typename S>
   uint32_t operator-(const LULStats<S>& aOther) {
     return (mContext - aOther.mContext) +
-           (mCFI - aOther.mCFI) + (mScanned - aOther.mScanned);
+           (mCFI - aOther.mCFI) + (mFP - aOther.mFP) +
+           (mScanned - aOther.mScanned);
   }
 
   T mContext; // Number of context frames
   T mCFI;     // Number of CFI/EXIDX frames
+  T mFP;      // Number of frame-pointer recovered frames
   T mScanned; // Number of scanned frames
 };
 
 
 // The core unwinder library class.  Just one of these is needed, and
 // it can be shared by multiple unwinder threads.
 //
 // The library operates in one of two modes.
@@ -333,16 +338,17 @@ public:
   //
   // Up to aScannedFramesAllowed stack-scanned frames may be recovered.
   //
   // The calling thread must previously have been registered via a call to
   // RegisterSampledThread.
   void Unwind(/*OUT*/uintptr_t* aFramePCs,
               /*OUT*/uintptr_t* aFrameSPs,
               /*OUT*/size_t* aFramesUsed,
+              /*OUT*/size_t* aFramePointerFramesAcquired,
               /*OUT*/size_t* aScannedFramesAcquired,
               size_t aFramesAvail,
               size_t aScannedFramesAllowed,
               UnwindRegs* aStartRegs, StackImage* aStackImg);
 
   // The logging sink.  Call to send debug strings to the caller-
   // specified destination.  Can only be called by the Admin thread.
   void (*mLog)(const char*);
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -284,18 +284,18 @@ PROFILER_FUNC_VOID(profiler_thread_sleep
 PROFILER_FUNC_VOID(profiler_thread_wake())
 PROFILER_FUNC(bool profiler_thread_is_sleeping(), false)
 
 // Call by the JSRuntime's operation callback. This is used to start profiling
 // on auxiliary threads. Operates the same whether the profiler is active or
 // not.
 PROFILER_FUNC_VOID(profiler_js_interrupt_callback())
 
-// Gets the time since the last profiler_init() or profiler_start() call.
-// Operates the same whether the profiler is active or inactive.
+// The number of milliseconds since the process started. Operates the same
+// whether the profiler is active or inactive.
 PROFILER_FUNC(double profiler_time(), 0)
 
 PROFILER_FUNC_VOID(profiler_log(const char *str))
 
 // End of the functions defined whether the profiler is enabled or not.
 
 #if defined(MOZ_GECKO_PROFILER)
 
@@ -324,24 +324,16 @@ class ProfilerMarkerPayload;
 // Non-owning PseudoStack references are also temporarily used by
 // profiler_call_{enter,exit}() pairs. RAII classes ensure these calls are
 // balanced, and they occur on the thread itself, which means they are
 // necessarily bounded by the lifetime of the thread, which ensures they can't
 // be used after the PseudoStack is destroyed.
 //
 extern MOZ_THREAD_LOCAL(PseudoStack*) tlsPseudoStack;
 
-class ProfilerState;
-
-// The core profiler state. Null at process startup, it is set to a non-null
-// value in profiler_init() and stays that way until profiler_shutdown() is
-// called. Therefore it can be checked to determine if the profiler has been
-// initialized but not yet shut down.
-extern ProfilerState* gPS;
-
 #ifndef SAMPLE_FUNCTION_NAME
 # if defined(__GNUC__) || defined(_MSC_VER)
 #  define SAMPLE_FUNCTION_NAME __FUNCTION__
 # else
 #  define SAMPLE_FUNCTION_NAME __func__  // defined in C99, supported in various C++ compilers. Just raw function name.
 # endif
 #endif
 
@@ -350,36 +342,32 @@ extern ProfilerState* gPS;
 static inline void*
 profiler_call_enter(const char* aInfo,
                     js::ProfileEntry::Category aCategory,
                     void *aFrameAddress, bool aCopy, uint32_t line,
                     const char* aDynamicString = nullptr)
 {
   // This function runs both on and off the main thread.
 
-  MOZ_RELEASE_ASSERT(gPS);
-
   PseudoStack* stack = tlsPseudoStack.get();
   if (!stack) {
     return stack;
   }
   stack->push(aInfo, aCategory, aFrameAddress, aCopy, line, aDynamicString);
 
   // The handle is meant to support future changes but for now it is simply
   // used to avoid having to call tlsPseudoStack.get() in profiler_call_exit().
   return stack;
 }
 
 static inline void
 profiler_call_exit(void* aHandle)
 {
   // This function runs both on and off the main thread.
 
-  MOZ_RELEASE_ASSERT(gPS);
-
   if (!aHandle) {
     return;
   }
 
   PseudoStack *stack = (PseudoStack*)aHandle;
   stack->pop();
 }
 
@@ -493,18 +481,16 @@ private:
 
 } // namespace mozilla
 
 inline PseudoStack*
 profiler_get_pseudo_stack(void)
 {
   // This function runs both on and off the main thread.
 
-  MOZ_RELEASE_ASSERT(gPS);
-
   return tlsPseudoStack.get();
 }
 
 void profiler_set_js_context(JSContext* aCx);
 void profiler_clear_js_context();
 
 class GeckoProfilerReporter final : public nsIMemoryReporter
 {