Bug 1405693 - Use dev build of React for Activity Stream if browser.newtabpage.activity-stream.debug is true draft bug1405693
authork88hudson <khudson@mozilla.com>
Wed, 04 Oct 2017 10:59:45 -0400
changeset 675837 bd96c617c9e2221a08b0f3908a3b1d708af42c56
parent 674389 bb2a1f41d82a1fc2414c6f0d435fa6c054244d62
child 734728 3d003f436415e741603f32a88f6d537ba7743a89
push id83261
push userkhudson@mozilla.com
push dateThu, 05 Oct 2017 23:46:11 +0000
bugs1405693
milestone58.0a1
Bug 1405693 - Use dev build of React for Activity Stream if browser.newtabpage.activity-stream.debug is true MozReview-Commit-ID: 13aqAEVUMNs
browser/app/profile/firefox.js
browser/components/newtab/aboutNewTabService.js
browser/components/newtab/nsIAboutNewTabService.idl
browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
browser/extensions/activity-stream/data/content/activity-stream-debug.html
browser/extensions/activity-stream/data/content/activity-stream-prerendered-debug.html
browser/extensions/activity-stream/jar.mn
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1265,16 +1265,19 @@ pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
 pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // activates Activity Stream
 pref("browser.newtabpage.activity-stream.enabled", true);
 pref("browser.newtabpage.activity-stream.prerender", true);
 pref("browser.newtabpage.activity-stream.aboutHome.enabled", true);
+#ifndef RELEASE_OR_BETA
+pref("browser.newtabpage.activity-stream.debug", false);
+#endif
 
 pref("browser.library.activity-stream.enabled", true);
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -5,38 +5,55 @@
 */
 
 "use strict";
 
 const {utils: Cu, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "AboutNewTab",
                                   "resource:///modules/AboutNewTab.jsm");
 
 const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
 
-const ACTIVITY_STREAM_URL = "resource://activity-stream/data/content/activity-stream.html";
-
-const ACTIVITY_STREAM_PRERENDER_URL = "resource://activity-stream/data/content/activity-stream-prerendered.html";
+// Debug versions are only available in Nightly
+const ACTIVITY_STREAM_URLS = {
+  "": "resource://activity-stream/data/content/activity-stream.html",
+  "debug": "resource://activity-stream/data/content/activity-stream-debug.html",
+  "prerender": "resource://activity-stream/data/content/activity-stream-prerendered.html",
+  "prerenderdebug": "resource://activity-stream/data/content/activity-stream-prerendered-debug.html",
+};
 
 const ABOUT_URL = "about:newtab";
 
 const IS_MAIN_PROCESS = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
 
+const IS_RELEASE_OR_BETA = AppConstants.RELEASE_OR_BETA;
+
 // Pref that tells if activity stream is enabled
 const PREF_ACTIVITY_STREAM_ENABLED = "browser.newtabpage.activity-stream.enabled";
 const PREF_ACTIVITY_STREAM_PRERENDER_ENABLED = "browser.newtabpage.activity-stream.prerender";
+const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug";
+
 
 function AboutNewTabService() {
   Services.obs.addObserver(this, "quit-application-granted");
   Services.prefs.addObserver(PREF_ACTIVITY_STREAM_ENABLED, this);
   Services.prefs.addObserver(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED, this);
+  if (!IS_RELEASE_OR_BETA) {
+    Services.prefs.addObserver(PREF_ACTIVITY_STREAM_DEBUG, this);
+  }
+
+  // More initialization happens here
   this.toggleActivityStream();
+  this.initialized = true;
+
   if (IS_MAIN_PROCESS) {
     AboutNewTab.init();
   }
 }
 
 /*
  * A service that allows for the overriding, at runtime, of the newtab page's url.
  * Additionally, the service manages pref state between a activity stream, or the regular
@@ -71,16 +88,17 @@ function AboutNewTabService() {
  * LOAD_NORMAL or LOAD_REPLACE flags yield unexpected behaviors, so a roundtrip
  * to the redirector from browser chrome is avoided.
  */
 AboutNewTabService.prototype = {
 
   _newTabURL: ABOUT_URL,
   _activityStreamEnabled: false,
   _activityStreamPrerender: false,
+  _activityStreamDebug: false,
   _overridden: false,
 
   classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIAboutNewTabService,
     Ci.nsIObserver
   ]),
   _xpcom_categories: [{
@@ -92,22 +110,23 @@ AboutNewTabService.prototype = {
       case "nsPref:changed":
         if (data === PREF_ACTIVITY_STREAM_ENABLED) {
           if (this.toggleActivityStream()) {
             Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
           }
         } else if (data === PREF_ACTIVITY_STREAM_PRERENDER_ENABLED) {
           this._activityStreamPrerender = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED);
           Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
+        } else if (!IS_RELEASE_OR_BETA && data === PREF_ACTIVITY_STREAM_DEBUG) {
+          this._activityStreamDebug = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_DEBUG, false);
+          Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
         }
         break;
       case "quit-application-granted":
-        Services.obs.removeObserver(this, "quit-application-granted");
-        Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_ENABLED, this);
-        Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED, this);
+        this.uninit();
         if (IS_MAIN_PROCESS) {
           AboutNewTab.uninit();
         }
         break;
     }
   },
 
   /**
@@ -132,31 +151,36 @@ AboutNewTabService.prototype = {
       return false;
     }
     if (stateEnabled) {
       this._activityStreamEnabled = true;
     } else {
       this._activityStreamEnabled = false;
     }
     this._activityStreamPrerender = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED);
+    if (!IS_RELEASE_OR_BETA) {
+      this._activityStreamDebug = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_DEBUG, false);
+    }
     this._newtabURL = ABOUT_URL;
     return true;
   },
 
   /*
    * Returns the default URL.
    *
    * This URL only depends on the browser.newtabpage.activity-stream.enabled pref. Overriding
    * the newtab page has no effect on the result of this function.
    *
    * @returns {String} the default newtab URL, activity-stream or regular depending on browser.newtabpage.activity-stream.enabled
    */
   get defaultURL() {
     if (this.activityStreamEnabled) {
-      return this.activityStreamPrerender ? this.activityStreamPrerenderURL : this.activityStreamURL;
+      const prerender = this.activityStreamPrerender ? "prerender" : "";
+      const debug = this.activityStreamDebug ? "debug" : "";
+      return ACTIVITY_STREAM_URLS[prerender + debug];
     }
     return LOCAL_NEWTAB_URL;
   },
 
   get newTabURL() {
     return this._newTabURL;
   },
 
@@ -183,25 +207,34 @@ AboutNewTabService.prototype = {
   get activityStreamEnabled() {
     return this._activityStreamEnabled;
   },
 
   get activityStreamPrerender() {
     return this._activityStreamPrerender;
   },
 
-  get activityStreamURL() {
-    return ACTIVITY_STREAM_URL;
-  },
-
-  get activityStreamPrerenderURL() {
-    return ACTIVITY_STREAM_PRERENDER_URL;
+  get activityStreamDebug() {
+    return this._activityStreamDebug;
   },
 
   resetNewTabURL() {
     this._overridden = false;
     this._newTabURL = ABOUT_URL;
     this.toggleActivityStream(undefined, true);
     Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
+  },
+
+  uninit() {
+    if (!this.initialized) {
+      return;
+    }
+    Services.obs.removeObserver(this, "quit-application-granted");
+    Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_ENABLED, this);
+    Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED, this);
+    if (!IS_RELEASE_OR_BETA) {
+      Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_DEBUG, this);
+    }
+    this.initialized = false;
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]);
--- a/browser/components/newtab/nsIAboutNewTabService.idl
+++ b/browser/components/newtab/nsIAboutNewTabService.idl
@@ -35,23 +35,18 @@ interface nsIAboutNewTabService : nsISup
   readonly attribute bool activityStreamEnabled;
 
   /**
    * Returns true if the the prerendering pref for activity stream is true
    */
   readonly attribute bool activityStreamPrerender;
 
   /**
-   * Returns the activity stream resource URL for the newtab page
+   * Returns true if the the debug pref for activity stream is true
    */
-  readonly attribute ACString activityStreamURL;
-
-  /**
-   * Returns the prerendered activity stream resource URL for the newtab page
-   */
-  readonly attribute ACString activityStreamPrerenderURL;
+  readonly attribute bool activityStreamDebug;
 
   /**
    * Resets to the default resource and also resets the
    * overridden attribute to false.
    */
   void resetNewTabURL();
 };
--- a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
+++ b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
@@ -7,37 +7,43 @@
 const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
-const ACTIVITY_STREAM_PRERENDER_URL = aboutNewTabService.activityStreamPrerenderURL;
+const ACTIVITY_STREAM_PRERENDER_URL = "resource://activity-stream/data/content/activity-stream-prerendered.html";
+const ACTIVITY_STREAM_PRERENDER_DEBUG_URL = "resource://activity-stream/data/content/activity-stream-prerendered-debug.html";
+const ACTIVITY_STREAM_URL = "resource://activity-stream/data/content/activity-stream.html";
+const ACTIVITY_STREAM_DEBUG_URL = "resource://activity-stream/data/content/activity-stream-debug.html";
 
 const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
 const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
 const ACTIVITY_STREAM_PREF = "browser.newtabpage.activity-stream.enabled";
 const ACTIVITY_STREAM_PRERENDER_PREF = "browser.newtabpage.activity-stream.prerender";
+const ACTIVITY_STREAM_DEBUG_PREF = "browser.newtabpage.activity-stream.debug";
 
 function cleanup() {
   Services.prefs.clearUserPref(ACTIVITY_STREAM_PREF);
   Services.prefs.clearUserPref(ACTIVITY_STREAM_PRERENDER_PREF);
+  Services.prefs.clearUserPref(ACTIVITY_STREAM_DEBUG_PREF);
   aboutNewTabService.resetNewTabURL();
 }
 
 do_register_cleanup(cleanup);
 
 add_task(async function test_as_and_prerender_initialized() {
   Assert.equal(aboutNewTabService.activityStreamEnabled, Services.prefs.getBoolPref(ACTIVITY_STREAM_PREF),
     ".activityStreamEnabled should be set to the correct initial value");
   Assert.equal(aboutNewTabService.activityStreamPrerender, Services.prefs.getBoolPref(ACTIVITY_STREAM_PRERENDER_PREF),
     ".activityStreamPrerender should be set to the correct initial value");
-  Services.prefs.getBoolPref(ACTIVITY_STREAM_PREF);
+  Assert.equal(aboutNewTabService.activityStreamDebug, Services.prefs.getBoolPref(ACTIVITY_STREAM_DEBUG_PREF),
+    ".activityStreamDebug should be set to the correct initial value");
 });
 
 /**
  * Test the overriding of the default URL
  */
 add_task(async function test_override_activity_stream_disabled() {
   let notificationPromise;
   Services.prefs.setBoolPref(ACTIVITY_STREAM_PREF, false);
@@ -92,27 +98,35 @@ add_task(async function test_override_ac
   Assert.ok(!aboutNewTabService.activityStreamEnabled, "Activity Stream should not be enabled");
 
   cleanup();
 });
 
 add_task(async function test_default_url() {
   await setupASPrerendered();
   Assert.equal(aboutNewTabService.defaultURL, ACTIVITY_STREAM_PRERENDER_URL,
-               "Newtab defaultURL initially set to prerendered AS url");
+    "Newtab defaultURL initially set to prerendered AS url");
+
+  await setBoolPrefAndWaitForChange(ACTIVITY_STREAM_DEBUG_PREF, true,
+    "A notification occurs after changing the debug pref to true");
+
+  Assert.equal(aboutNewTabService.defaultURL, ACTIVITY_STREAM_PRERENDER_DEBUG_URL,
+    "Newtab defaultURL set to debug prerendered AS url after the pref has been changed");
 
-  // Change activity-stream.prerendered to false and wait for the required event to fire
-  const notificationPromise = nextChangeNotificationPromise(
-    "about:newtab", "a notification occurs after changing prerender pref");
-  Services.prefs.setBoolPref(ACTIVITY_STREAM_PRERENDER_PREF, false);
+  await setBoolPrefAndWaitForChange(ACTIVITY_STREAM_PRERENDER_PREF, false,
+    "A notification occurs after changing the prerender pref to false");
+
+  Assert.equal(aboutNewTabService.defaultURL, ACTIVITY_STREAM_DEBUG_URL,
+    "Newtab defaultURL set to un-prerendered AS with debug if prerender is false and debug is true");
 
-  await notificationPromise;
+  await setBoolPrefAndWaitForChange(ACTIVITY_STREAM_DEBUG_PREF, false,
+    "A notification occurs after changing the debug pref to false");
 
-  Assert.equal(aboutNewTabService.defaultURL, aboutNewTabService.activityStreamURL,
-               "Newtab defaultURL set to un-prerendered AS url after the pref has been changed");
+  Assert.equal(aboutNewTabService.defaultURL, ACTIVITY_STREAM_URL,
+    "Newtab defaultURL set to un-prerendered AS if prerender is false and debug is false");
 
   cleanup();
 });
 
 /**
  * Tests reponse to updates to prefs
  */
 add_task(async function test_updates() {
@@ -154,16 +168,29 @@ function nextChangeNotificationPromise(a
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) {  // jshint unused:false
       Services.obs.removeObserver(observer, aTopic);
       Assert.equal(aData, aNewURL, testMessage);
       resolve();
     }, "newtab-url-changed");
   });
 }
 
+function setBoolPrefAndWaitForChange(pref, value, testMessage) {
+  return new Promise(resolve => {
+    Services.obs.addObserver(function observer(aSubject, aTopic, aData) {  // jshint unused:false
+      Services.obs.removeObserver(observer, aTopic);
+      Assert.equal(aData, aboutNewTabService.newTabURL, testMessage);
+      resolve();
+    }, "newtab-url-changed");
+
+    Services.prefs.setBoolPref(pref, value);
+  });
+}
+
+
 function setupASPrerendered() {
   if (Services.prefs.getBoolPref(ACTIVITY_STREAM_PREF) &&
     Services.prefs.getBoolPref(ACTIVITY_STREAM_PRERENDER_PREF)) {
     return Promise.resolve();
   }
 
   let notificationPromise;
   // change newtab page to activity stream
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-debug.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html lang="" dir="ltr">
+  <head>
+    <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>
+    <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 [
+  "chrome://browser/content/contentSearchUI.js",
+  "resource://activity-stream/vendor/react-dev.js",
+  "resource://activity-stream/vendor/react-dom-dev.js",
+  "resource://activity-stream/vendor/react-intl.js",
+  "resource://activity-stream/vendor/redux.js",
+  "resource://activity-stream/vendor/react-redux.js",
+  "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+  // These dynamically inserted scripts by default are async, but we need them
+  // to load in the desired order (i.e., bundle last).
+  const script = document.body.appendChild(document.createElement("script"));
+  script.async = false;
+  script.src = src;
+}
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered-debug.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="" dir="ltr">
+  <head>
+    <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="-1695158303"><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="section 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><span class="section-info-option" data-reactid="17"><img class="info-option-icon" title=" " aria-haspopup="true" aria-controls="info-option" aria-expanded="false" role="note" tabindex="0" data-reactid="18"/><div class="info-option" data-reactid="19"><div class="info-option-header" role="heading" data-reactid="20"><span data-reactid="21"> </span></div><p class="info-option-body" data-reactid="22"><span data-reactid="23"> </span></p><div class="info-option-manage" data-reactid="24"><button data-reactid="25"><span data-reactid="26"> </span></button></div></div></span></div><ul class="top-sites-list" data-reactid="27"><li class="top-site-outer placeholder" data-reactid="28"><a data-reactid="29"><div class="tile" aria-hidden="true" data-reactid="30"><span class="letter-fallback" data-reactid="31"></span><div class="screenshot" style="background-image:none;" data-reactid="32"></div></div><div class="title " data-reactid="33"><span dir="auto" data-reactid="34"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="35"><a data-reactid="36"><div class="tile" aria-hidden="true" data-reactid="37"><span class="letter-fallback" data-reactid="38"></span><div class="screenshot" style="background-image:none;" data-reactid="39"></div></div><div class="title " data-reactid="40"><span dir="auto" data-reactid="41"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="42"><a data-reactid="43"><div class="tile" aria-hidden="true" data-reactid="44"><span class="letter-fallback" data-reactid="45"></span><div class="screenshot" style="background-image:none;" data-reactid="46"></div></div><div class="title " data-reactid="47"><span dir="auto" data-reactid="48"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="49"><a data-reactid="50"><div class="tile" aria-hidden="true" data-reactid="51"><span class="letter-fallback" data-reactid="52"></span><div class="screenshot" style="background-image:none;" data-reactid="53"></div></div><div class="title " data-reactid="54"><span dir="auto" data-reactid="55"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="56"><a data-reactid="57"><div class="tile" aria-hidden="true" data-reactid="58"><span class="letter-fallback" data-reactid="59"></span><div class="screenshot" style="background-image:none;" data-reactid="60"></div></div><div class="title " data-reactid="61"><span dir="auto" data-reactid="62"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="63"><a data-reactid="64"><div class="tile" aria-hidden="true" data-reactid="65"><span class="letter-fallback" data-reactid="66"></span><div class="screenshot" style="background-image:none;" data-reactid="67"></div></div><div class="title " data-reactid="68"><span dir="auto" data-reactid="69"></span></div></a></li></ul><div class="edit-topsites-wrapper" data-reactid="70"><div class="edit-topsites-button" data-reactid="71"><button class="edit" title=" " data-reactid="72"><span data-reactid="73"> </span></button></div></div></section><div class="sections-list" data-reactid="74"><section class="section" data-reactid="75"><div class="section-top-bar" data-reactid="76"><h3 class="section-title" data-reactid="77"><span class="icon icon-small-spacer icon-pocket" data-reactid="78"></span><span data-reactid="79"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="80"><li class="card-outer placeholder" data-reactid="81"><a data-reactid="82"><div class="card" data-reactid="83"><div class="card-details no-image" data-reactid="84"><div class="card-text no-context no-description no-host-name no-image" data-reactid="85"><h4 class="card-title" dir="auto" data-reactid="86"></h4><p class="card-description" dir="auto" data-reactid="87"></p></div><div class="card-context" data-reactid="88"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="89"><a data-reactid="90"><div class="card" data-reactid="91"><div class="card-details no-image" data-reactid="92"><div class="card-text no-context no-description no-host-name no-image" data-reactid="93"><h4 class="card-title" dir="auto" data-reactid="94"></h4><p class="card-description" dir="auto" data-reactid="95"></p></div><div class="card-context" data-reactid="96"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="97"><a data-reactid="98"><div class="card" data-reactid="99"><div class="card-details no-image" data-reactid="100"><div class="card-text no-context no-description no-host-name no-image" data-reactid="101"><h4 class="card-title" dir="auto" data-reactid="102"></h4><p class="card-description" dir="auto" data-reactid="103"></p></div><div class="card-context" data-reactid="104"></div></div></div></a></li></ul><div class="topic" data-reactid="105"><span data-reactid="106"><span data-reactid="107"> </span></span><ul data-reactid="108"></ul></div></section><section class="section" data-reactid="109"><div class="section-top-bar" data-reactid="110"><h3 class="section-title" data-reactid="111"><span class="icon icon-small-spacer icon-highlights" data-reactid="112"></span><span data-reactid="113"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="114"><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><li class="card-outer placeholder" data-reactid="131"><a data-reactid="132"><div class="card" data-reactid="133"><div class="card-details no-image" data-reactid="134"><div class="card-text no-context no-description no-host-name no-image" data-reactid="135"><h4 class="card-title" dir="auto" data-reactid="136"></h4><p class="card-description" dir="auto" data-reactid="137"></p></div><div class="card-context" data-reactid="138"></div></div></div></a></li></ul></section></div></div><!-- react-empty: 139 --></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",
+  "chrome://browser/content/contentSearchUI.js",
+  "resource://activity-stream/vendor/react-dev.js",
+  "resource://activity-stream/vendor/react-dom-dev.js",
+  "resource://activity-stream/vendor/react-intl.js",
+  "resource://activity-stream/vendor/redux.js",
+  "resource://activity-stream/vendor/react-redux.js",
+  "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+  // These dynamically inserted scripts by default are async, but we need them
+  // to load in the desired order (i.e., bundle last).
+  const script = document.body.appendChild(document.createElement("script"));
+  script.async = false;
+  script.src = src;
+}
+    </script>
+  </body>
+</html>
--- a/browser/extensions/activity-stream/jar.mn
+++ b/browser/extensions/activity-stream/jar.mn
@@ -4,12 +4,16 @@
 
 [features/activity-stream@mozilla.org] chrome.jar:
 % resource activity-stream %content/ contentaccessible=yes
   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)
+#ifndef RELEASE_OR_BETA
+  content/vendor/react-dev.js (./vendor/react-dev.js)
+  content/vendor/react-dom-dev.js (./vendor/react-dom-dev.js)
+#endif
   content/vendor/react-intl.js (./vendor/react-intl.js)
   content/vendor/redux.js (./vendor/redux.js)
   content/vendor/react-redux.js (./vendor/react-redux.js)
   content/data/ (./data/*)