Bug 1402529 - Fix broken story dismissing, unintended telemetry and bug fixes to Activity Stream. r?dmose draft
authorEd Lee <edilee@mozilla.com>
Fri, 22 Sep 2017 16:13:36 -0700
changeset 669357 9f111923f2886809ec09b90483acb3d8b829e823
parent 669337 61e58a7d800b8a84d3da7c3db977830e206428a3
child 732936 a36a7172a1339d6a8e1160e51dc4bcd68fd96b0d
push id81309
push userbmo:edilee@mozilla.com
push dateFri, 22 Sep 2017 23:13:49 +0000
reviewersdmose
bugs1402529
milestone58.0a1
Bug 1402529 - Fix broken story dismissing, unintended telemetry and bug fixes to Activity Stream. r?dmose MozReview-Commit-ID: F0mYjCJcg0u
browser/extensions/activity-stream/common/Actions.jsm
browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
browser/extensions/activity-stream/data/content/activity-stream.bundle.js
browser/extensions/activity-stream/data/content/activity-stream.css
browser/extensions/activity-stream/data/locales.json
browser/extensions/activity-stream/install.rdf.in
browser/extensions/activity-stream/lib/SectionsManager.jsm
browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
browser/extensions/activity-stream/test/unit/lib/TelemetryFeed.test.js
browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -54,16 +54,17 @@ for (const type of [
   "PREFS_INITIAL_VALUES",
   "PREF_CHANGED",
   "SAVE_SESSION_PERF_DATA",
   "SAVE_TO_POCKET",
   "SCREENSHOT_UPDATED",
   "SECTION_DEREGISTER",
   "SECTION_DISABLE",
   "SECTION_ENABLE",
+  "SECTION_OPTIONS_CHANGED",
   "SECTION_REGISTER",
   "SECTION_UPDATE",
   "SECTION_UPDATE_CARD",
   "SET_PREF",
   "SHOW_FIREFOX_ACCOUNTS",
   "SNIPPETS_DATA",
   "SNIPPETS_RESET",
   "SYSTEM_TICK",
--- a/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
@@ -4,17 +4,17 @@
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
     <title></title>
     <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
   </head>
   <body class="activity-stream">
-    <div id="root"><div class="outer-wrapper fixed-to-top" data-reactroot="" data-reactid="1" data-react-checksum="-1132846255"><main data-reactid="2"><div class="search-wrapper" data-reactid="3"><label for="newtab-search-text" class="search-label" data-reactid="4"><span class="sr-only" data-reactid="5"><span data-reactid="6">Search the Web</span></span></label><input type="search" id="newtab-search-text" maxlength="256" placeholder="Search the Web" title="Search the Web" data-reactid="7"/><button id="searchSubmit" class="search-button" title=" " data-reactid="8"><span class="sr-only" data-reactid="9"><span data-reactid="10"> </span></span></button></div><div class="body-wrapper" data-reactid="11"><section class="top-sites" data-reactid="12"><h3 class="section-title" data-reactid="13"><span class="icon icon-small-spacer icon-topsites" data-reactid="14"></span><span data-reactid="15"> </span></h3><ul class="top-sites-list" data-reactid="16"><li class="top-site-outer placeholder" data-reactid="17"><a data-reactid="18"><div class="tile" aria-hidden="true" data-reactid="19"><span class="letter-fallback" data-reactid="20"></span><div class="screenshot" style="background-image:none;" data-reactid="21"></div></div><div class="title " data-reactid="22"><span dir="auto" data-reactid="23"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="24"><a data-reactid="25"><div class="tile" aria-hidden="true" data-reactid="26"><span class="letter-fallback" data-reactid="27"></span><div class="screenshot" style="background-image:none;" data-reactid="28"></div></div><div class="title " data-reactid="29"><span dir="auto" data-reactid="30"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="31"><a data-reactid="32"><div class="tile" aria-hidden="true" data-reactid="33"><span class="letter-fallback" data-reactid="34"></span><div class="screenshot" style="background-image:none;" data-reactid="35"></div></div><div class="title " data-reactid="36"><span dir="auto" data-reactid="37"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="38"><a data-reactid="39"><div class="tile" aria-hidden="true" data-reactid="40"><span class="letter-fallback" data-reactid="41"></span><div class="screenshot" style="background-image:none;" data-reactid="42"></div></div><div class="title " data-reactid="43"><span dir="auto" data-reactid="44"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="45"><a data-reactid="46"><div class="tile" aria-hidden="true" data-reactid="47"><span class="letter-fallback" data-reactid="48"></span><div class="screenshot" style="background-image:none;" data-reactid="49"></div></div><div class="title " data-reactid="50"><span dir="auto" data-reactid="51"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="52"><a data-reactid="53"><div class="tile" aria-hidden="true" data-reactid="54"><span class="letter-fallback" data-reactid="55"></span><div class="screenshot" style="background-image:none;" data-reactid="56"></div></div><div class="title " data-reactid="57"><span dir="auto" data-reactid="58"></span></div></a></li></ul><div class="edit-topsites-wrapper" data-reactid="59"><div class="edit-topsites-button" data-reactid="60"><button class="edit" title=" " data-reactid="61"><span data-reactid="62"> </span></button></div></div></section><div class="sections-list" data-reactid="63"><section data-reactid="64"><div class="section-top-bar" data-reactid="65"><h3 class="section-title" data-reactid="66"><span class="icon icon-small-spacer icon-pocket" data-reactid="67"></span><span data-reactid="68"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="69"><li class="card-outer placeholder" data-reactid="70"><a data-reactid="71"><div class="card" data-reactid="72"><div class="card-details no-image" data-reactid="73"><div class="card-text no-context no-description no-host-name no-image" data-reactid="74"><h4 class="card-title" dir="auto" data-reactid="75"></h4><p class="card-description" dir="auto" data-reactid="76"></p></div><div class="card-context" data-reactid="77"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="78"><a data-reactid="79"><div class="card" data-reactid="80"><div class="card-details no-image" data-reactid="81"><div class="card-text no-context no-description no-host-name no-image" data-reactid="82"><h4 class="card-title" dir="auto" data-reactid="83"></h4><p class="card-description" dir="auto" data-reactid="84"></p></div><div class="card-context" data-reactid="85"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="86"><a data-reactid="87"><div class="card" data-reactid="88"><div class="card-details no-image" data-reactid="89"><div class="card-text no-context no-description no-host-name no-image" data-reactid="90"><h4 class="card-title" dir="auto" data-reactid="91"></h4><p class="card-description" dir="auto" data-reactid="92"></p></div><div class="card-context" data-reactid="93"></div></div></div></a></li></ul><div class="topic" data-reactid="94"><span data-reactid="95"><span data-reactid="96"> </span></span><ul data-reactid="97"><li data-reactid="98"><a class="topic-link" data-reactid="99"></a></li></ul></div></section><section data-reactid="100"><div class="section-top-bar" data-reactid="101"><h3 class="section-title" data-reactid="102"><span class="icon icon-small-spacer icon-highlights" data-reactid="103"></span><span data-reactid="104"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="105"><li class="card-outer placeholder" data-reactid="106"><a data-reactid="107"><div class="card" data-reactid="108"><div class="card-details no-image" data-reactid="109"><div class="card-text no-context no-description no-host-name no-image" data-reactid="110"><h4 class="card-title" dir="auto" data-reactid="111"></h4><p class="card-description" dir="auto" data-reactid="112"></p></div><div class="card-context" data-reactid="113"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="114"><a data-reactid="115"><div class="card" data-reactid="116"><div class="card-details no-image" data-reactid="117"><div class="card-text no-context no-description no-host-name no-image" data-reactid="118"><h4 class="card-title" dir="auto" data-reactid="119"></h4><p class="card-description" dir="auto" data-reactid="120"></p></div><div class="card-context" data-reactid="121"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="122"><a data-reactid="123"><div class="card" data-reactid="124"><div class="card-details no-image" data-reactid="125"><div class="card-text no-context no-description no-host-name no-image" data-reactid="126"><h4 class="card-title" dir="auto" data-reactid="127"></h4><p class="card-description" dir="auto" data-reactid="128"></p></div><div class="card-context" data-reactid="129"></div></div></div></a></li></ul></section></div></div><!-- react-empty: 130 --></main></div></div>
+    <div id="root"><div class="outer-wrapper fixed-to-top" data-reactroot="" data-reactid="1" data-react-checksum="-1229441695"><main data-reactid="2"><div class="search-wrapper" data-reactid="3"><label for="newtab-search-text" class="search-label" data-reactid="4"><span class="sr-only" data-reactid="5"><span data-reactid="6">Search the Web</span></span></label><input type="search" id="newtab-search-text" maxlength="256" placeholder="Search the Web" title="Search the Web" data-reactid="7"/><button id="searchSubmit" class="search-button" title=" " data-reactid="8"><span class="sr-only" data-reactid="9"><span data-reactid="10"> </span></span></button></div><div class="body-wrapper" data-reactid="11"><section class="top-sites" data-reactid="12"><div class="section-top-bar" data-reactid="13"><h3 class="section-title" data-reactid="14"><span class="icon icon-small-spacer icon-topsites" data-reactid="15"></span><span data-reactid="16"> </span></h3></div><ul class="top-sites-list" data-reactid="17"><li class="top-site-outer placeholder" data-reactid="18"><a data-reactid="19"><div class="tile" aria-hidden="true" data-reactid="20"><span class="letter-fallback" data-reactid="21"></span><div class="screenshot" style="background-image:none;" data-reactid="22"></div></div><div class="title " data-reactid="23"><span dir="auto" data-reactid="24"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="25"><a data-reactid="26"><div class="tile" aria-hidden="true" data-reactid="27"><span class="letter-fallback" data-reactid="28"></span><div class="screenshot" style="background-image:none;" data-reactid="29"></div></div><div class="title " data-reactid="30"><span dir="auto" data-reactid="31"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="32"><a data-reactid="33"><div class="tile" aria-hidden="true" data-reactid="34"><span class="letter-fallback" data-reactid="35"></span><div class="screenshot" style="background-image:none;" data-reactid="36"></div></div><div class="title " data-reactid="37"><span dir="auto" data-reactid="38"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="39"><a data-reactid="40"><div class="tile" aria-hidden="true" data-reactid="41"><span class="letter-fallback" data-reactid="42"></span><div class="screenshot" style="background-image:none;" data-reactid="43"></div></div><div class="title " data-reactid="44"><span dir="auto" data-reactid="45"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="46"><a data-reactid="47"><div class="tile" aria-hidden="true" data-reactid="48"><span class="letter-fallback" data-reactid="49"></span><div class="screenshot" style="background-image:none;" data-reactid="50"></div></div><div class="title " data-reactid="51"><span dir="auto" data-reactid="52"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="53"><a data-reactid="54"><div class="tile" aria-hidden="true" data-reactid="55"><span class="letter-fallback" data-reactid="56"></span><div class="screenshot" style="background-image:none;" data-reactid="57"></div></div><div class="title " data-reactid="58"><span dir="auto" data-reactid="59"></span></div></a></li></ul><div class="edit-topsites-wrapper" data-reactid="60"><div class="edit-topsites-button" data-reactid="61"><button class="edit" title=" " data-reactid="62"><span data-reactid="63"> </span></button></div></div></section><div class="sections-list" data-reactid="64"><section data-reactid="65"><div class="section-top-bar" data-reactid="66"><h3 class="section-title" data-reactid="67"><span class="icon icon-small-spacer icon-pocket" data-reactid="68"></span><span data-reactid="69"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="70"><li class="card-outer placeholder" data-reactid="71"><a data-reactid="72"><div class="card" data-reactid="73"><div class="card-details no-image" data-reactid="74"><div class="card-text no-context no-description no-host-name no-image" data-reactid="75"><h4 class="card-title" dir="auto" data-reactid="76"></h4><p class="card-description" dir="auto" data-reactid="77"></p></div><div class="card-context" data-reactid="78"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="79"><a data-reactid="80"><div class="card" data-reactid="81"><div class="card-details no-image" data-reactid="82"><div class="card-text no-context no-description no-host-name no-image" data-reactid="83"><h4 class="card-title" dir="auto" data-reactid="84"></h4><p class="card-description" dir="auto" data-reactid="85"></p></div><div class="card-context" data-reactid="86"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="87"><a data-reactid="88"><div class="card" data-reactid="89"><div class="card-details no-image" data-reactid="90"><div class="card-text no-context no-description no-host-name no-image" data-reactid="91"><h4 class="card-title" dir="auto" data-reactid="92"></h4><p class="card-description" dir="auto" data-reactid="93"></p></div><div class="card-context" data-reactid="94"></div></div></div></a></li></ul><div class="topic" data-reactid="95"><span data-reactid="96"><span data-reactid="97"> </span></span><ul data-reactid="98"><li data-reactid="99"><a class="topic-link" data-reactid="100"></a></li></ul></div></section><section data-reactid="101"><div class="section-top-bar" data-reactid="102"><h3 class="section-title" data-reactid="103"><span class="icon icon-small-spacer icon-highlights" data-reactid="104"></span><span data-reactid="105"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="106"><li class="card-outer placeholder" data-reactid="107"><a data-reactid="108"><div class="card" data-reactid="109"><div class="card-details no-image" data-reactid="110"><div class="card-text no-context no-description no-host-name no-image" data-reactid="111"><h4 class="card-title" dir="auto" data-reactid="112"></h4><p class="card-description" dir="auto" data-reactid="113"></p></div><div class="card-context" data-reactid="114"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="115"><a data-reactid="116"><div class="card" data-reactid="117"><div class="card-details no-image" data-reactid="118"><div class="card-text no-context no-description no-host-name no-image" data-reactid="119"><h4 class="card-title" dir="auto" data-reactid="120"></h4><p class="card-description" dir="auto" data-reactid="121"></p></div><div class="card-context" data-reactid="122"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="123"><a data-reactid="124"><div class="card" data-reactid="125"><div class="card-details no-image" data-reactid="126"><div class="card-text no-context no-description no-host-name no-image" data-reactid="127"><h4 class="card-title" dir="auto" data-reactid="128"></h4><p class="card-description" dir="auto" data-reactid="129"></p></div><div class="card-context" data-reactid="130"></div></div></div></a></li></ul></section></div></div><!-- react-empty: 131 --></main></div></div>
     <div id="snippets-container">
       <div id="snippets"></div>
     </div>
     <script>
 // Don't directly load the following scripts as part of html to let the page
 // finish loading to render the content sooner.
 for (const src of [
   "resource://activity-stream/data/content/activity-stream-initial-state.js",
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -89,17 +89,17 @@ const globalImportContext = typeof Windo
 
 
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
-for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
+for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
   actionTypes[type] = type;
 }
 
 // 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) {
@@ -1046,17 +1046,17 @@ class LinkMenu extends React.PureCompone
           props.dispatch(action);
           if (userEvent) {
             props.dispatch(ac.UserEvent({
               event: userEvent,
               source,
               action_position: index
             }));
           }
-          if (impression) {
+          if (impression && props.shouldSendImpressionStats) {
             props.dispatch(impression);
           }
         };
       }
       return option;
     });
 
     // This is for accessibility to support making each item tabbable.
@@ -1249,20 +1249,24 @@ const TopSites = props => {
   const placeholderCount = props.TopSitesCount - realTopSites.length;
   return React.createElement(
     TopSitesPerfTimer,
     null,
     React.createElement(
       "section",
       { className: "top-sites" },
       React.createElement(
-        "h3",
-        { className: "section-title" },
-        React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }),
-        React.createElement(FormattedMessage, { id: "header_top_sites" })
+        "div",
+        { className: "section-top-bar" },
+        React.createElement(
+          "h3",
+          { className: "section-title" },
+          React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }),
+          React.createElement(FormattedMessage, { id: "header_top_sites" })
+        )
       ),
       React.createElement(
         "ul",
         { className: "top-sites-list" },
         realTopSites.map((link, index) => link && React.createElement(TopSite, {
           key: link.guid || link.url,
           dispatch: props.dispatch,
           link: link,
@@ -2469,21 +2473,26 @@ class Section extends React.PureComponen
       null,
       message
     ) : React.createElement(FormattedMessage, message);
   }
 
   _dispatchImpressionStats() {
     const { props } = this;
     const maxCards = 3 * props.maxRows;
-    props.dispatch(ac.ImpressionStats({
-      source: props.eventSource,
-      tiles: props.rows.slice(0, maxCards).map(link => ({ id: link.guid })),
-      incognito: props.options && props.options.personalized
-    }));
+    const cards = props.rows.slice(0, maxCards);
+
+    if (this.needsImpressionStats(cards)) {
+      props.dispatch(ac.ImpressionStats({
+        source: props.eventSource,
+        tiles: cards.map(link => ({ id: link.guid })),
+        incognito: props.options && props.options.personalized
+      }));
+      this.impressionCardGuids = cards.map(link => link.guid);
+    }
   }
 
   // This sends an event when a user sees a set of new content. If content
   // changes while the page is hidden (i.e. preloaded or on a hidden tab),
   // only send the event if the page becomes visible again.
   sendImpressionStatsOrAddListener() {
     const { props } = this;
 
@@ -2523,16 +2532,30 @@ class Section extends React.PureComponen
     // Don't send impression stats for the empty state
     props.rows.length &&
     // We only want to send impression stats if the content of the cards has changed
     props.rows !== prevProps.rows) {
       this.sendImpressionStatsOrAddListener();
     }
   }
 
+  needsImpressionStats(cards) {
+    if (!this.impressionCardGuids || this.impressionCardGuids.length !== cards.length) {
+      return true;
+    }
+
+    for (let i = 0; i < cards.length; i++) {
+      if (cards[i].guid !== this.impressionCardGuids[i]) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   numberOfPlaceholders(items) {
     if (items === 0) {
       return CARDS_PER_ROW;
     }
     const remainder = items % CARDS_PER_ROW;
     if (remainder === 0) {
       return 0;
     }
@@ -2607,17 +2630,18 @@ class Section extends React.PureComponen
               this.getFormattedMessage(infoOption.link.title || infoOption.link)
             )
           )
         )
       ),
       !shouldShowEmptyState && React.createElement(
         "ul",
         { className: "section-list", style: { padding: 0 } },
-        realRows.map((link, index) => link && React.createElement(Card, { key: index, index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions, eventSource: eventSource })),
+        realRows.map((link, index) => link && React.createElement(Card, { key: index, index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions,
+          eventSource: eventSource, shouldSendImpressionStats: this.props.shouldSendImpressionStats })),
         placeholders > 0 && [...new Array(placeholders)].map((_, i) => React.createElement(PlaceholderCard, { key: i }))
       ),
       shouldShowEmptyState && React.createElement(
         "div",
         { className: "section-empty-state" },
         React.createElement(
           "div",
           { className: "empty-state" },
@@ -2701,28 +2725,30 @@ class Card extends React.PureComponent {
       type: at.OPEN_LINK,
       data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
     }));
     this.props.dispatch(ac.UserEvent({
       event: "CLICK",
       source: this.props.eventSource,
       action_position: this.props.index
     }));
-    this.props.dispatch(ac.ImpressionStats({
-      source: this.props.eventSource,
-      click: 0,
-      incognito: true,
-      tiles: [{ id: this.props.link.guid, pos: this.props.index }]
-    }));
+    if (this.props.shouldSendImpressionStats) {
+      this.props.dispatch(ac.ImpressionStats({
+        source: this.props.eventSource,
+        click: 0,
+        incognito: true,
+        tiles: [{ id: this.props.link.guid, pos: this.props.index }]
+      }));
+    }
   }
   onMenuUpdate(showContextMenu) {
     this.setState({ showContextMenu });
   }
   render() {
-    const { index, link, dispatch, contextMenuOptions, eventSource } = this.props;
+    const { index, link, dispatch, contextMenuOptions, eventSource, shouldSendImpressionStats } = this.props;
     const { props } = this;
     const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
     // Display "now" as "trending" until we have new strings #3402
     const { icon, intlID } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
     const hasImage = link.image || link.hasImage;
     const imageStyle = { backgroundImage: link.image ? `url(${link.image})` : "none" };
 
     return React.createElement(
@@ -2792,17 +2818,18 @@ class Card extends React.PureComponent {
       ),
       !props.placeholder && React.createElement(LinkMenu, {
         dispatch: dispatch,
         index: index,
         source: eventSource,
         onUpdate: this.onMenuUpdate,
         options: link.contextMenuOptions || contextMenuOptions,
         site: link,
-        visible: isContextMenuOpen })
+        visible: isContextMenuOpen,
+        shouldSendImpressionStats: shouldSendImpressionStats })
     );
   }
 }
 Card.defaultProps = { link: {} };
 
 const PlaceholderCard = () => React.createElement(Card, { placeholder: true });
 
 module.exports = Card;
--- a/browser/extensions/activity-stream/data/content/activity-stream.css
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -189,16 +189,20 @@ main {
       width: 480px; } }
   @media (min-width: 800px) {
     main {
       width: 736px; } }
   main section {
     margin-bottom: 40px;
     position: relative; }
 
+.section-top-bar {
+  height: 16px;
+  margin-bottom: 12px; }
+
 .section-title {
   font-size: 13px;
   font-weight: bold;
   text-transform: uppercase; }
   .section-title span {
     color: #737373;
     fill: #737373;
     vertical-align: middle; }
@@ -510,19 +514,17 @@ main {
   0% {
     opacity: 0;
     transform: translateY(15px); }
   100% {
     opacity: 1;
     transform: translateY(0); } }
 
 .sections-list .section-top-bar {
-  position: relative;
-  height: 16px;
-  margin-bottom: 18px; }
+  position: relative; }
   .sections-list .section-top-bar .section-info-option {
     offset-inline-end: 0;
     position: absolute;
     top: 0; }
   .sections-list .section-top-bar .info-option-icon {
     background-image: url("assets/glyph-info-option-12.svg");
     background-size: 12px 12px;
     background-repeat: no-repeat;
--- a/browser/extensions/activity-stream/data/locales.json
+++ b/browser/extensions/activity-stream/data/locales.json
@@ -588,17 +588,17 @@
     "header_top_sites": "Llocs principals",
     "header_stories": "Articles populars",
     "header_highlights": "Destacats",
     "header_visit_again": "Torneu a visitar",
     "header_bookmarks": "Adreces d'interès recents",
     "header_recommended_by": "Recomanat per {provider}",
     "header_bookmarks_placeholder": "Encara no teniu cap adreça d'interès.",
     "header_stories_from": "de",
-    "type_label_visited": "Visitats",
+    "type_label_visited": "Visitat",
     "type_label_bookmarked": "A les adreces d'interès",
     "type_label_synced": "Sincronitzat des d'un altre dispositiu",
     "type_label_recommended": "Tendència",
     "type_label_open": "Obert",
     "type_label_topic": "Tema",
     "type_label_now": "Ara",
     "menu_action_bookmark": "Afegeix a les adreces d'interès",
     "menu_action_remove_bookmark": "Elimina l'adreça d'interès",
@@ -2844,64 +2844,84 @@
     "manual_migration_explanation": "נסו את Firefox עם האתרים המועדפים עליך והסימניות שלך מדפדפן אחר.",
     "manual_migration_cancel_button": "לא תודה",
     "manual_migration_import_button": "ייבוא כעת"
   },
   "hi-IN": {
     "newtab_page_title": "नया टैब",
     "default_label_loading": "लोड हो रहा है…",
     "header_top_sites": "सर्वोच्च साइटें",
+    "header_stories": "सर्वोच्च साइटें",
     "header_visit_again": "पुनः पधारें",
     "header_bookmarks": "हाल के पुस्तचिह्न",
+    "header_recommended_by": "{provider} द्वारा अनुशंसित",
+    "header_bookmarks_placeholder": "आपके पास अभी तक कोई भी पुस्तचिन्ह नहीं है.",
     "header_stories_from": "के द्वारा",
     "type_label_visited": "देखी गई",
     "type_label_bookmarked": "पुस्तचिह्न लगाया हुआ",
     "type_label_synced": "किसी अन्य उपकरण से समकालीन किया गया",
     "type_label_recommended": "लोकप्रिय",
     "type_label_open": "खोलें",
     "type_label_topic": "विषय",
+    "type_label_now": "अभी",
     "menu_action_bookmark": "पुस्तचिह्न",
     "menu_action_remove_bookmark": "पुस्तचिह्न हटाएँ",
     "menu_action_copy_address": "पता कॉपी करें",
     "menu_action_email_link": "ईमेल लिंक…",
     "menu_action_open_new_window": "एक नई विंडो में खोलें",
     "menu_action_open_private_window": "एक नई निजी विंडो में खोलें",
     "menu_action_dismiss": "निरस्त करें",
     "menu_action_delete": "इतिहास से मिटाएँ",
+    "menu_action_pin": "पिन करें",
+    "menu_action_unpin": "पिन हटाएँ",
+    "confirm_history_delete_notice_p2": "इस क्रिया को पहले जैसा नहीं किया जा सकता है.",
+    "menu_action_save_to_pocket": "Pocket में सहेजें",
     "search_for_something_with": "इस के साथ {search_term} के लिए खोजें:",
     "search_button": "खोज",
     "search_header": "{search_engine_name} खोज",
     "search_web_placeholder": "वेब पर खोजें",
     "search_settings": "खोज सेटिंग बदलें",
+    "section_info_option": "सूचना",
     "welcome_title": "नए टैब में आपका स्वागत है",
     "welcome_body": "Firefox यह जगह आपके सर्वाधिक प्रासंगिक पुस्तचिन्ह, लेख, वीडियो और पृष्ठों जिनका आपने हाल ही में दौरा किया है उनको दर्शाने के लिए करेगा, ताकि आप बाद में उन तक आसानी से वापस जा सकें.",
     "welcome_label": "आपके प्रमुखताओं की पहचान की जा रही है",
     "time_label_less_than_minute": "<1मि0",
     "time_label_minute": "{number}मि0",
     "time_label_hour": "{number}मि0",
     "time_label_day": "{number}दिन",
     "settings_pane_button_label": "अपने नए टैब पृष्ठ को अनुकूलित करें",
     "settings_pane_header": "नयी टैब वरीयताएँ",
-    "settings_pane_body": "चयन करें कि नया टैब खोलने पर आप क्या देखें.",
     "settings_pane_search_header": "खोज",
     "settings_pane_search_body": "अपने नए टैब से वेब पर खोजें.",
     "settings_pane_topsites_header": "सर्वोच्च साइटें",
     "settings_pane_topsites_body": "आपके द्वारा सबसे ज्यादा खोजी जाने वाली वेबसाइट्स देखें.",
     "settings_pane_topsites_options_showmore": "दो पंक्तियाँ दिखाएँ",
+    "settings_pane_bookmarks_header": "आधुनिक पुस्तचिह्न",
+    "settings_pane_visit_again_header": "पुनः पधारें",
     "settings_pane_done_button": "संपन्न",
     "edit_topsites_button_text": "संपादित करें",
     "edit_topsites_button_label": "अपने शीर्ष साइट्स अनुभाग को अनुकूलित करें",
     "edit_topsites_showmore_button": "अधिक दिखाएँ",
     "edit_topsites_showless_button": "कम दिखाएँ",
     "edit_topsites_done_button": "पूर्ण",
     "edit_topsites_pin_button": "इस साइट को पिन करें",
+    "edit_topsites_unpin_button": "इस साइट को पिन मुक्त करें",
     "edit_topsites_edit_button": "इस साइट को संपादित करें",
     "edit_topsites_dismiss_button": "इस साइट को ख़ारिज करें",
-    "pocket_read_even_more": "और कहानियाँ देखें",
-    "pocket_send_feedback": "प्रतिक्रिया भेजें"
+    "edit_topsites_add_button": "जोड़ें",
+    "topsites_form_add_header": "नई शीर्ष साइट",
+    "topsites_form_edit_header": "शीर्ष साइट संपादित करें",
+    "topsites_form_title_placeholder": "एक शीर्षक दर्ज करें",
+    "topsites_form_url_placeholder": "एक URL टाइप करें अथवा पेस्ट करें",
+    "topsites_form_add_button": "जोड़ें",
+    "topsites_form_save_button": "सहेजें",
+    "topsites_form_cancel_button": "रद्द करें",
+    "topsites_form_url_validation": "मान्य URL आवश्यक",
+    "pocket_read_more": "लोकप्रिय विषय:",
+    "pocket_read_even_more": "और कहानियाँ देखें"
   },
   "hr": {
     "newtab_page_title": "Nova kartica",
     "default_label_loading": "Učitavanje…",
     "header_top_sites": "Najbolje stranice",
     "header_stories": "Najbolje priče",
     "header_highlights": "Istaknuto",
     "header_visit_again": "Posjetite ponovno",
@@ -4615,16 +4635,17 @@
     "manual_migration_cancel_button": "Nei takk",
     "manual_migration_import_button": "Importer nå"
   },
   "ne-NP": {
     "newtab_page_title": "नयाँ ट्याब",
     "default_label_loading": "लोड हुदैँछ...",
     "header_top_sites": "शीर्ष साइटहरु",
     "header_stories": "शीर्ष साइटहरु",
+    "header_highlights": "विशेषताहरू",
     "header_visit_again": "फेरि भ्रमण गर्नुहोस्",
     "header_bookmarks": "भर्खरैका पुस्तकचिनोहरु",
     "header_recommended_by": "{provider} द्वारा सिफारिस गरिएको",
     "header_bookmarks_placeholder": "तपाइँसँग अहिले सम्म कुनै पुस्तकचिनोहरु छैन ।",
     "header_stories_from": "बाट",
     "type_label_visited": "भ्रमण गरिएको",
     "type_label_bookmarked": "पुस्तकचिनो लागाइएको",
     "type_label_synced": "अर्को यण्त्रबाट समक्रमण गरिएको",
@@ -4646,47 +4667,55 @@
     "confirm_history_delete_notice_p2": "यो कार्य पूर्ववत गर्न सकिँदैन ।",
     "menu_action_save_to_pocket": "Pocketमा बचत गर्नुहोस्",
     "search_for_something_with": "{search_term} खोज्न प्रयोग गर्नुहोस्:",
     "search_button": "खोजी गर्नुहोस्",
     "search_header": "{search_engine_name} खोजी",
     "search_web_placeholder": "वेबमा खोज्नुहोस्",
     "search_settings": "खोजी सेटिङ परिवर्तन गर्नुहोस्",
     "section_info_option": "जानकारी",
+    "section_info_send_feedback": "प्रतिक्रिया पठाउनुहोस्",
+    "section_info_privacy_notice": "गोपनीयता नीति",
     "welcome_title": "नयाँ ट्याबमा स्वागत छ",
     "welcome_body": "Firefoxले यस ठाउँको प्रयोग तपाईंको सबैभन्दा सान्दर्भिक पुस्तकचिनो, लेखहरू, भिडियोहरू, र तपाईंले हालै भ्रमण गर्नु भएको पृष्ठहरूलाई राख्न प्रयोग गर्दछ, जसले गर्दा तपाइँ तिनीहरूलाई सजिलै भेटाउन सक्नुहुनेछ ।",
     "welcome_label": "तपाईँका विशेषताहरु पत्ता लगाउँदै",
     "time_label_less_than_minute": "< १ मिनेट",
     "time_label_minute": "{number} मिनेट",
     "time_label_hour": "{number} घण्टा",
     "time_label_day": "{number} दिन",
     "settings_pane_button_label": "तपाईंको नयाँ ट्याब पृष्ठ अनुकूलन गर्नुहोस्",
     "settings_pane_header": "नयाँ ट्याब प्राथमिकताहरू",
-    "settings_pane_body": "तपाईंले नयाँ ट्याब खोल्ने बेलामा के देख्नु चाहनुहुन्छ भन्ने छनौट गर्नुहोस् ।",
     "settings_pane_search_header": "खोजी गर्नुहोस्",
     "settings_pane_search_body": "तपाईंको नयाँ ट्याबबाट वेबमा खोज्नुहोस् ।",
+    "settings_pane_topsites_header": "शीर्ष साइटहरू",
     "settings_pane_topsites_body": "तपाईले धेरै भ्रमण गर्नुभएका वेबसाइटहरूमा पहुँच गर्नुहोस् ।",
     "settings_pane_topsites_options_showmore": "दुई पङ्क्तिहरू देखाउनुहोस्",
     "settings_pane_bookmarks_header": "भर्खरैका पुस्तकचिनोहरु",
     "settings_pane_bookmarks_body": "तपाईंको नयाँ सिर्जना गरिएको पुस्तकचिनोहरुहरू एउटा सजिलो स्थानमा ।",
     "settings_pane_visit_again_header": "फेरि भ्रमण गर्नुहोस्",
-    "settings_pane_pocketstories_header": "शीर्ष कथाहरू",
+    "settings_pane_highlights_header": "विशेषताहरू",
+    "settings_pane_highlights_options_bookmarks": "पुस्तकचिनोहरू",
+    "settings_pane_highlights_options_visited": "भ्रमण गरिएका साईटहरु",
+    "settings_pane_snippets_header": "स्निप्पेटस्",
     "settings_pane_done_button": "सम्पन्न भयो",
     "edit_topsites_button_text": "सम्पादन गर्नुहोस्",
     "edit_topsites_button_label": "तपाईंको शीर्ष साइट खण्ड अनुकूलन गर्नुहोस्",
     "edit_topsites_showmore_button": "थप देखाउनुहोस्",
     "edit_topsites_showless_button": "थोरै देखाउनुहोस्",
     "edit_topsites_done_button": "सम्पन्न भयो",
     "edit_topsites_pin_button": "यस साइटलाई पिन गर्नुहोस्",
     "edit_topsites_unpin_button": "यस साइटलाई अनपिन गर्नुहोस्",
     "edit_topsites_edit_button": "यस साइटलाई सम्पादन गर्नुहोस्",
     "edit_topsites_dismiss_button": "यस साइटलाई खारेज गर्नुहोस्",
     "edit_topsites_add_button": "थप्नुहोस्",
     "topsites_form_add_header": "नयाँ शीर्ष साइट",
     "topsites_form_edit_header": "शीर्ष साइट सम्पादन गर्नुहोस्",
+    "topsites_form_add_button": "थप्नुहोस्",
+    "topsites_form_save_button": "सङ्ग्रह गर्नुहोस्",
+    "topsites_form_cancel_button": "रद्द गर्नुहोस्",
     "manual_migration_cancel_button": "पर्दैन, धन्यबाद",
     "manual_migration_import_button": "अहिले आयात गर्नुहोस्"
   },
   "nl": {
     "newtab_page_title": "Nieuw tabblad",
     "default_label_loading": "Laden…",
     "header_top_sites": "Topwebsites",
     "header_stories": "Topverhalen",
--- a/browser/extensions/activity-stream/install.rdf.in
+++ b/browser/extensions/activity-stream/install.rdf.in
@@ -3,17 +3,17 @@
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>activity-stream@mozilla.org</em:id>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
-    <em:version>2017.09.20.1184-2d88ef77</em:version>
+    <em:version>2017.09.22.1389-2ee94db4</em:version>
     <em:name>Activity Stream</em:name>
     <em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
--- a/browser/extensions/activity-stream/lib/SectionsManager.jsm
+++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm
@@ -273,21 +273,26 @@ class SectionsFeed {
     switch (action.type) {
       case at.INIT:
         SectionsManager.onceInitialized(this.init);
         break;
       // Wait for pref values, as some sections have options stored in prefs
       case at.PREFS_INITIAL_VALUES:
         SectionsManager.init(action.data);
         break;
-      case at.PREF_CHANGED:
-        if (action.data && action.data.name.match(/^feeds.section.(\S+).options$/i)) {
-          SectionsManager.addBuiltInSection(action.data.name.slice(0, -8), action.data.value);
+      case at.PREF_CHANGED: {
+        if (action.data) {
+          const matched = action.data.name.match(/^(feeds.section.(\S+)).options$/i);
+          if (matched) {
+            SectionsManager.addBuiltInSection(matched[1], action.data.value);
+            this.store.dispatch({type: at.SECTION_OPTIONS_CHANGED, data: matched[2]});
+          }
         }
         break;
+      }
       case at.SECTION_DISABLE:
         SectionsManager.disableSection(action.data);
         break;
       case at.SECTION_ENABLE:
         SectionsManager.enableSection(action.data);
         break;
       case at.UNINIT:
         this.uninit();
--- a/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
@@ -20,19 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const STORIES_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const TOPICS_UPDATE_TIME = 3 * 60 * 60 * 1000; // 3 hours
 const DOMAIN_AFFINITY_UPDATE_TIME = 24 * 60 * 60 * 1000; // 24 hours
 const STORIES_NOW_THRESHOLD = 24 * 60 * 60 * 1000; // 24 hours
 const SECTION_ID = "topstories";
 
 this.TopStoriesFeed = class TopStoriesFeed {
   constructor() {
-    this.storiesLastUpdated = 0;
-    this.topicsLastUpdated = 0;
-    this.affinityLastUpdated = 0;
     this.spocsPerNewTabs = 0;
     this.newTabsSinceSpoc = 0;
     this.contentUpdateQueue = [];
   }
 
   init() {
     const initFeed = () => {
       SectionsManager.enableSection(SECTION_ID);
@@ -41,16 +38,19 @@ this.TopStoriesFeed = class TopStoriesFe
         const apiKey = this.getApiKeyFromPref(options.api_key_pref);
         this.stories_endpoint = this.produceFinalEndpointUrl(options.stories_endpoint, apiKey);
         this.topics_endpoint = this.produceFinalEndpointUrl(options.topics_endpoint, apiKey);
         this.read_more_endpoint = options.read_more_endpoint;
         this.stories_referrer = options.stories_referrer;
         this.personalized = options.personalized;
         this.show_spocs = options.show_spocs;
         this.maxHistoryQueryResults = options.maxHistoryQueryResults;
+        this.storiesLastUpdated = 0;
+        this.topicsLastUpdated = 0;
+        this.affinityLastUpdated = 0;
 
         this.fetchStories();
         this.fetchTopics();
       } catch (e) {
         Cu.reportError(`Problem initializing top stories feed: ${e.message}`);
       }
     };
     SectionsManager.onceInitialized(initFeed);
@@ -267,16 +267,35 @@ this.TopStoriesFeed = class TopStoriesFe
         }
         break;
       case at.UNINIT:
         this.uninit();
         break;
       case at.NEW_TAB_REHYDRATED:
         this.maybeAddSpoc(action.meta.fromTarget);
         break;
+      case at.SECTION_OPTIONS_CHANGED:
+        if (action.data === SECTION_ID) {
+          this.uninit();
+          this.init();
+        }
+        break;
+      case at.PLACES_LINK_BLOCKED:
+        if (this.spocs) {
+          this.spocs = this.spocs.filter(s => s.url !== action.data.url);
+        }
+
+        if (this.stories) {
+          const prevStoriesLength = this.stories.length;
+          this.stories = this.stories.filter(s => s.url !== action.data.url);
+          if (prevStoriesLength !== this.stories.length) {
+            SectionsManager.updateSection(SECTION_ID, {rows: this.stories}, true);
+          }
+        }
+        break;
     }
   }
 };
 
 this.STORIES_UPDATE_TIME = STORIES_UPDATE_TIME;
 this.TOPICS_UPDATE_TIME = TOPICS_UPDATE_TIME;
 this.SECTION_ID = SECTION_ID;
 this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "DOMAIN_AFFINITY_UPDATE_TIME", "SECTION_ID"];
--- a/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
@@ -371,16 +371,23 @@ describe("SectionsFeed", () => {
       assert.calledWith(SectionsManager.init, {foo: "bar"});
     });
     it("should call SectionsManager.addBuiltInSection on suitable PREF_CHANGED events", () => {
       sinon.spy(SectionsManager, "addBuiltInSection");
       feed.onAction({type: "PREF_CHANGED", data: {name: "feeds.section.topstories.options", value: "foo"}});
       assert.calledOnce(SectionsManager.addBuiltInSection);
       assert.calledWith(SectionsManager.addBuiltInSection, "feeds.section.topstories", "foo");
     });
+    it("should fire SECTION_OPTIONS_UPDATED on suitable PREF_CHANGED events", () => {
+      feed.onAction({type: "PREF_CHANGED", data: {name: "feeds.section.topstories.options", value: "foo"}});
+      assert.calledOnce(feed.store.dispatch);
+      const action = feed.store.dispatch.firstCall.args[0];
+      assert.equal(action.type, "SECTION_OPTIONS_CHANGED");
+      assert.equal(action.data, "topstories");
+    });
     it("should call SectionsManager.disableSection on SECTION_DISABLE", () => {
       sinon.spy(SectionsManager, "disableSection");
       feed.onAction({type: "SECTION_DISABLE", data: 1234});
       assert.calledOnce(SectionsManager.disableSection);
       assert.calledWith(SectionsManager.disableSection, 1234);
       SectionsManager.disableSection.restore();
     });
     it("should call SectionsManager.enableSection on SECTION_ENABLE", () => {
--- a/browser/extensions/activity-stream/test/unit/lib/TelemetryFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TelemetryFeed.test.js
@@ -137,17 +137,16 @@ describe("TelemetryFeed", () => {
     });
     it("should a valid session ping on the first about:home seen", () => {
       // Add a session
       const portID = "foo";
       const session = instance.addSession(portID, "about:home");
 
       // Create a ping referencing the session
       const ping = instance.createSessionEndEvent(session);
-      console.log("ping: ", JSON.stringify(ping));
       assert.validate(ping, SessionPing);
     });
   });
 
   describe("#browserOpenNewtabStart", () => {
     it("should call perfService.mark with browser-open-newtab-start", () => {
       sandbox.stub(perfService, "mark");
 
--- a/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
@@ -514,10 +514,39 @@ describe("Top Stories Feed", () => {
 
       clock.tick(DOMAIN_AFFINITY_UPDATE_TIME);
       instance.updateSettings(fakeSettings);
       assert.calledOnce(instance.store.dispatch);
       let action = instance.store.dispatch.firstCall.args[0];
       assert.equal(action.type, at.TELEMETRY_PERFORMANCE_EVENT);
       assert.equal(action.data.event, "topstories.domain.affinity.calculation.ms");
     });
+    it("should re-init on options change", () => {
+      instance.storiesLastUpdated = 1;
+      instance.topicsLastUpdated = 1;
+      instance.affinityLastUpdated = 1;
+
+      instance.onAction({type: at.SECTION_OPTIONS_CHANGED, data: "foo"});
+      assert.notCalled(sectionsManagerStub.disableSection);
+      assert.notCalled(sectionsManagerStub.enableSection);
+      assert.equal(instance.storiesLastUpdated, 1);
+      assert.equal(instance.topicsLastUpdated, 1);
+      assert.equal(instance.affinityLastUpdated, 1);
+
+      instance.onAction({type: at.SECTION_OPTIONS_CHANGED, data: "topstories"});
+      assert.calledOnce(sectionsManagerStub.disableSection);
+      assert.calledOnce(sectionsManagerStub.enableSection);
+      assert.equal(instance.storiesLastUpdated, 0);
+      assert.equal(instance.topicsLastUpdated, 0);
+      assert.equal(instance.affinityLastUpdated, 0);
+    });
+    it("should filter recs and spocs when link is blocked", () => {
+      instance.stories = [{"url": "not_blocked"}, {"url": "blocked"}];
+      instance.spocs = [{"url": "not_blocked"}, {"url": "blocked"}];
+      instance.onAction({type: at.PLACES_LINK_BLOCKED, data: {url: "blocked"}});
+
+      assert.deepEqual(instance.stories, [{"url": "not_blocked"}]);
+      assert.deepEqual(instance.spocs, [{"url": "not_blocked"}]);
+      assert.calledOnce(sectionsManagerStub.updateSection);
+      assert.calledWith(sectionsManagerStub.updateSection, SECTION_ID, {rows: instance.stories});
+    });
   });
 });