merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 26 Apr 2017 08:41:31 +0200
changeset 568509 0f5ba06c4c5959030a05cb852656d854065e2226
parent 568508 08a5a97f615fb65b3455b435c3e56d96c4c12208 (current diff)
parent 568354 3e34e56f5e7adb50f3827125c25f121170ed071b (diff)
child 568510 ec901427174b747e1cc458f3076b31284655e767
child 568517 67294756b2c3b814b1b93aac3b1321d059ca5b11
child 568541 e4c20da0ccb2a7de11db066a339348bd8b4ffb4d
child 568542 2c0d6ec54fab35d4295cd33e6ef796edebb0bf8f
child 568543 cb6ce74289500911d5464740403b5df02374656a
child 568546 0c1bb81b7a6bf276af0771929b24a36ad8c77d69
child 568559 07f8391406cbe6994a70d8ae816cf68ccb38dbdb
child 568565 0c043b5460e8d342f886205dab0b00c00d408d0f
child 568580 bbe63f637542c3c3896dac9ddf9f6839297080cc
child 568593 49dc7b165eaada484576c5583969e259ff2a4544
child 568596 68abccf359706ec23be78fe8eac90f556d92c8ff
child 568600 578bd41326e08e96bcf42edfbba0b2a5b91a460b
child 568602 f6283a992ae1cff13c2d9a382698f798dc5e176c
child 568606 287dced5a94f509d10b505c0db44583d82d7fdd5
child 568609 81d8e78682d88250f168911db2877fea21577574
child 568613 41f52b6c53a59a514a464f39d7d74440a58f62c1
child 568627 a99178d1573b1535b46de618bda4f7d6c914c0ed
child 568640 b9703423dc88617111d974debbff4605fec25c81
child 568641 d236a7185f61f288a9983a8abec19a1c34b81613
child 568651 e2410aa4edd65f287090a0cd0491006974d8ebe1
child 568660 abf44d103b419be9a8ce44a4f1b5eba77c70e72f
child 568662 abfd7da4851dc876a15b8878bd85709e45b0181a
child 568663 a6232f0377c8878e56efec141b6ac809b497b5cf
child 568666 ff6d22bb158cf7e4823d593ffc6a82cec03a09dc
child 568679 a1cf7ff38d2ddbed9a62bd7db02a7ceecb79cb73
child 568712 0dfc3bd141e6cc6c3157fc0086b719fbf80a48e5
child 568736 bd11561eb861c449b6a61cbdf458d7766f31fb97
child 568737 198cac09dce05f20c1d11fcbda7e4c6cfb69ec02
child 568764 45c2aad0e684e5608481cccd408ae3eb1afab256
child 568788 ab0087b6f8ff4db7d9f9bdae9d528b272f6c5de2
child 568790 5ef1fdb51ae9b4397945f195a8ff55bb7462f069
child 568792 3fac258865662992c908649be3c56a47477a12b4
child 568799 5e2459d04640f56de373a3e0e3805c13f382b7ea
child 568800 c8ba431a5dd03299fa16555dbfa04b0e2d79cdaa
child 568806 46ed496bf8cb095c037b4e96903d535acfed9cf5
child 568825 c85b44a0b45b34f756d783016e9a9f43c7aa4dc0
child 568846 cb321e236ecf4c5a14f6d82a94579ac5bbe7f312
child 568856 175e5511c6ac6f2827cec68f5647a9d48ce8543e
child 568857 6bfa775f14847a83d4e713e156f9e42d1b100db9
child 568867 093a8b181555711d1a6f73104047d049c9655efd
child 568896 28ac3df85281f0a706f5b9725e11aa7095c6f75b
child 568898 8acee07a60dc4a322d51d981f34d7d950c1d6ea2
child 568900 f15b077cbc604c892824d864923547a930e79247
child 568923 0a03c2222193a1d07909ee534876166fd7019f85
child 568947 13fbe9a708fdff7b43fc3adde311529398415e35
child 568988 f7c558543205b870a09eedc85c45f85b2c2e187c
child 569001 cbbc4e785fcdc8ebfe73a1d6bb6106b9ab1313c0
child 569002 68dea0b5b2a2fc1b65872277e671794d5600d8f5
child 569003 4beba0212411a0f5867feb506bbf732f5a934fa9
child 569059 01e6de746531396ca9c23ffa2ae064970bafeb4f
child 569156 790d70605139327a8f86d18e27b33dbf8728ad00
child 569157 c430ba59c1e554201d8e851faa8606777bd368fd
child 569160 c918203477832dfaaa593ea58ac95814bc90e295
child 569161 92870f1adc7ae68e58b15443e4223012bdf0e39a
child 569177 5c1a0bff090a7bcd27660fe0a0938b99964fc8e7
child 569204 0236c6f67ce69d5f532026976d8ea1b521b78f13
child 569205 20ee21e29263ef8ca2530aeaf5498687e4c8e67a
child 569206 ed75d187f7673f29011d74e4b15f233957fc5e34
child 569207 81a11dc16f58dab69786df4d8181e48ccb9118a3
child 569212 f22e0ac7e590de4f55fddc7532aaf19840bcf4b4
child 569219 41837fc3c0a7462432fca312e8924b9ca6e6c00f
child 569256 003bd411b50ec42e84e401fe388af5194fb2ca06
child 569266 6f0034a9589b4cb68d932a5fee99e40c3b8a3f18
child 569269 9deaf76548426b2b0b2413d0161959c69bcac583
child 569278 1c02f6587e1a2a5453d5cebefe4b2571689f9e93
child 569281 ab1689f4aae9a3caaebc5e29d111bb2eaa9c17ae
child 569306 dd11cdc7d1f2c1429986d34bc6cb468f68b6c12e
child 569357 29b51dbfca5c7bcb3a90cf8e66e185f9f720e4da
child 569366 07c91d233304c0ec23fd81cde267751f11d39b7c
child 569447 45e911958025757a3071c0c3d2c4dfa308410955
child 569503 622115120ac1349b63175b6de088269e9f17e1ed
child 569575 684f22011d8015ada513d9b2050c716a9a07b679
child 569611 a4437bfc53999877add5066b70ebc6b1d1e09b9f
child 569669 082806be4c4efbc477a508bcb71d77013788b3bd
child 569733 05ae32f6f2886808982aa0d1fabb132c936566b4
child 569921 99fa160d95167379a53c3c1ac81e20ccfebfb125
child 569922 1d30bc32c0b280b2e1f5f9c979c624479d9a6433
child 569943 1238cab16429c5c19eecd0dee28657b9e0d12144
child 569966 79c5f3d91607eb6c7e272ee1a47b08546274f6de
child 569970 2955f9c5df121b6757da6fd45632b970307dfcf9
child 569972 222e73c84d5a56fd689a90e4a615682121605777
child 570092 9057746ff921820a5f19aac9021dd634dd51a887
child 570142 33fd9b4a7106f18fad7d9dced7e5515ecb3fd8da
child 570189 feae4b880bd05b8d23d3c9ae45a84dcd01e80fef
child 570381 c7fcbbae4712e21b79793c2a41c4580f02bec366
child 570504 f9df335776e3fa5177bfda73c089492223e63d95
child 571112 ccc0789e90a44c04e8669b0f4d9d3caf38faf0a8
child 572387 ffec8c5f215aa1b5f7bda82a01113168f1affe0b
child 572389 7d6cfb24d3080e812e594df85baea5d7aeceb8b5
child 572395 bbc51c445adc77fe41a89cc5e9c9b72b3354444d
child 572396 895178775f143994af7032f88c09f121b305998a
child 572934 f56d98adde8fead8a054bd7033ac995b17970a6e
child 573004 26a92b0a70a0206ae9b1ac0d56fa7febd3e0227a
child 574078 85d3bb217dfb27e286fdc064481a9adcf1ad4d4d
child 580181 9294d6027a64685c96028e2b5dbe7a77a160b012
child 580345 06d850baa212e5df74604389c31fefaf0821f079
push id55881
push userjichen@mozilla.com
push dateWed, 26 Apr 2017 07:22:02 +0000
reviewersmerge
milestone55.0a1
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
 {