Skip to content
This repository has been archived by the owner. It is now read-only.

Prerendering #3359

Merged
merged 10 commits into from Sep 8, 2017
Merged

Prerendering #3359

merged 10 commits into from Sep 8, 2017

Conversation

@k88hudson
Copy link
Member

@k88hudson k88hudson commented Sep 6, 2017

This patch adds prerendering in a few commits:

  • a5356f7: This is just some basic refactoring to not crash in node (using global for globals, etc.)
  • 2193572: This changes the state rehydration to work with a new action, NEW_TAB_STATE_REQUEST, instead of NEW_TAB_LOAD. This is needed for prerendering in order to ensure the first render matches the prerendered state.
  • 45357fb: This adds the buildmc:html task, and all the basic prerendering tasks.
  • 686518a: This adds a pref to determine whether or not the pre-rendered version should be used. I need to actually add a patch to aboutNewTabService to make use of this.
  • 209f43dd: This is a small change to <Topics /> to allow it to render but be empty. This is to ensure things don't move around when the pre-rendered version switches to React.
@k88hudson k88hudson force-pushed the k88hudson:gh2813 branch from 686518a to 09f43dd Sep 6, 2017
usablePerfObj = {
now() {},
mark() {}
};

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

I'm assuming this is needed so that it runs under node. A comment indicating that would nice.

@@ -1,4 +1,4 @@
module.exports = {
// constant to know if the page is about:newtab or about:home
IS_NEWTAB: document.documentURI === "about:newtab"
IS_NEWTAB: global.document && global.document.documentURI === "about:newtab"

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

What is it that keeps this code running in the browser? Webpack magic? Do we assign global=Window somewhere?

This comment has been minimized.

@k88hudson

k88hudson Sep 6, 2017
Author Member

global is window in webpack, yes

@@ -22,6 +23,9 @@ function addLocaleDataForReactIntl({locale}) {

class Base extends React.Component {
componentDidMount() {
// Request state after the first render
this.props.dispatch(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}));

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

A comment explaining why we do this extra round trip (e.g. for pre-rendering), perhaps even in a bit of details, would likely be useful to future code readers.

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

A unit test for this would be good too; https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md has an example using sinon that should be similar.

@@ -15,7 +15,7 @@ this.NewTabInit = class NewTabInit {
onAction(action) {
let newAction;
switch (action.type) {
case at.NEW_TAB_LOAD:
case at.NEW_TAB_STATE_REQUEST:

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

This wants a unit test, I think. Maybe a look at the code coverage numbers would show other changes that want one too?

This comment has been minimized.

@k88hudson

k88hudson Sep 7, 2017
Author Member

Currently this file isn't being tested (which is an oversight), do you mind if I file a follow-up for this?

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Because of the high-value in getting this into the tree sooner rather than later, yes.


function templateJs(state) {
return `// Note - this is a generated file.
window.gActivityStreamPrerenderedState = ${JSON.stringify(state, null, 2)};

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

This wants a comment describing what it's doing and why. Probably in the rendered file so that people who find that artifact quickly see what is.

`;
}

const args = require("minimist")(process.argv.slice(2), {alias: {baseUrl: "b", title: "t", locale: "l", direction: "d"}});

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

This is a bit hard to skim. Maybe break it up into two lines, and explain the magic constant of 2 in a comment?

fs.writeFileSync(HTML_FILE_PATH, templateHTML(options)); // eslint-disable-line no-sync
fs.writeFileSync(PRERENDERED_HTML_FILE_PATH, templateHTML(options, html)); // eslint-disable-line no-sync
fs.writeFileSync(INITIAL_STATE_JS_FILE_PATH, templateJs(state)); // eslint-disable-line no-sync

This comment has been minimized.

@dmose

dmose Sep 6, 2017
Member

It'd improve readaibility to just put /* eslint-disable no-sync */ at the top of the file.

// Note: the key on IntlProvider must be static in order to not blow away
// all elements on a locale change (such as after preloading).
// See https://github.com/yahoo/react-intl/issues/695 for more info.
return (<IntlProvider key="STATIC" locale={locale} messages={strings}>

This comment has been minimized.

@Mardak

Mardak Sep 6, 2017
Member

This change seems to cause the search placeholder and migration texts to not appear on the first about:home. (You can also test with ./mach run about:newtab) Changing back to key={locale} avoids this:
image

My guess is that LOCALE_UPDATED happens at an unexpected time? Also, guessing that TOP SITES gets the string because TopSites gets re-render-ed.

This comment has been minimized.

@Mardak

Mardak Sep 6, 2017
Member

Looks like passing in a dummy locale prop, e.g., <Search locale={locale} />, makes it render again

@Mardak
Copy link
Member

@Mardak Mardak commented Sep 7, 2017

I pushed to talos moving the activity-stream-prerendered.html to activity-stream.html:
https://treeherder.mozilla.org/perf.html#/compare?originalProject=mozilla-central&originalRevision=c959327c6b75cd4930a6ea087583c38b805e7524&newProject=try&newRevision=099db27cebe866024186aac8f4f0274c60839d09&framework=1&filter=7%20opt

Nothing significant so far although tabpaint shows 6% improvement but low confidence after 10 before and 10 after runs.

<body class="activity-stream">
<div id="root"></div>
<div id="snippets-container">
<div id="snippets"></div>

This comment has been minimized.

@Mardak

Mardak Sep 7, 2017
Member

We'll still need these snippets related div#ids, yeah?

<link rel="icon" type="image/svg+xml" href="${options.baseUrl}img/newtab-icon.svg">
</head>
<body>
<div id="root">${isPrerendered ? html : ""}</div>${isPrerendered ? `

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Having so much of the semantics come from punctuation, and specifically nested template strings, makes this a bit hard to follow. Pull out the <script> for stream-initial-state into a const higher up in the script?

@@ -0,0 +1,59 @@
const fs = require("fs");
const path = require("path");
const prerender = require("./prerender");

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Add a comment here about what this is and how it's built?

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Great!

`;
}

function templateJs(state) {

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

JSDoc here for first-time readers would be helpful, mostly for the info about "state"

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Very nice; thanks!

Object.keys(allStrings["en-US"]).forEach(key => {
strings[key] = " ";
});
}

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

@Mardak, does this need a spin-off bug to properly do individual string fallback correctly?

This comment has been minimized.

@k88hudson

k88hudson Sep 7, 2017
Author Member

I think string fallbacks work decently well at the moment (they fall back to English for any missing strings), although there are some cases where dynamic string ids get injected (e.g. TopStories) that could be handled better on a case-by-case basis

topics: [{name: "Must Reads", category: "World", url: "https://getpocket.com/explore/must-reads?src=ff_new_tab"}]
}
});

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Since each of the above dispatches effectively replaces ones that currently come from chrome, but have a comment above each one talking about how they differ or any assumptions in the interest of future code maintainability.

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Particularly keeping in mind that effectively this code may also need to be updated as the chrome code changes over time.

This comment has been minimized.

@k88hudson

k88hudson Sep 7, 2017
Author Member

It's not really so much that it comes from chrome as that specific actions are needed to set properties in certain reducers

This comment has been minimized.

@k88hudson

k88hudson Sep 7, 2017
Author Member

I can actually add tests to help with this

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

That sounds good.

@@ -0,0 +1,63 @@
const React = require("react");

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

This file definitely wants unit tests.

This comment has been minimized.

@k88hudson

k88hudson Sep 7, 2017
Author Member

Done!


new DetectUserSessionStart().sendEventOrAddListener();

const store = initStore(reducers);
const store = initStore(reducers, global.gActivityStreamPrerenderedState);

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

The changes to this file want unit tests too.

@@ -49,20 +49,23 @@ const messageMiddleware = store => next => action => {
* @param {object} reducers An object containing Redux reducers

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

There should probably be one or two unit tests added here.

const prefConfig = {
// Prefs listed with "invalidates: true" will prevent the prerendered version
// of AS from being used if their value is something other than what is listed
// here.

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

As we were discussing yesterday, some of these have minor consequences, which we currently believe are acceptable. Documenting those consequences and tradeoffs here would help maintainability.

feed.onPrefChanged("foo123", true);
assert.notCalled(feed._prefs.set);
});
it("should set the prerender pref if one of the prefs in PrerenderData.invalidatingPrefs is changed", () => {

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

This wants to be split into two tests for the true -> false change and the false -> true change.

@@ -1,3 +1,4 @@
/* eslint-disable no-sync */ /* eslint-disable no-sync */ /* eslint-disable no-sync */

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Three times? :-)

@@ -82,5 +82,5 @@ class Search extends React.Component {
}

// initialized is passed to props so that Search will rerender when it receives strings

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

This comment needs to be updated too.

@@ -29,6 +29,7 @@ const DEFAULT_OPTIONS = {
*/
function templateHTML(options, html) {
const isPrerendered = !!html;
const scriptForPrerendering = `\n<script src="${options.baseUrl}data/content/activity-stream-initial-state.js"></script>`;

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

nit: It'd be even more readable if you just embedded a new line in the string rather than using /n.

@@ -44,8 +45,7 @@ function templateHTML(options, html) {
<div id="root">${isPrerendered ? html : ""}</div>
<div id="snippets-container">
<div id="snippets"></div>
</div>${isPrerendered ? `
<script src="${options.baseUrl}data/content/activity-stream-initial-state.js"></script>` : ""}
</div>${isPrerendered ? `${scriptForPrerendering}` : ""}

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

Much nicer; thanks!

@k88hudson
Copy link
Member Author

@k88hudson k88hudson commented Sep 7, 2017

Ok, I filed a bunch of follow-ups and added them to this meta-bug: #3372

@k88hudson
Copy link
Member Author

@k88hudson k88hudson commented Sep 7, 2017

Filed a bugzilla ticket for the aboutNewTabService changes that need to land in mc:
https://bugzilla.mozilla.org/show_bug.cgi?id=1397875

});
it("should start uninitialized", () => {
const store = prerenderStore();
const state = store.getState();

This comment has been minimized.

@dmose

dmose Sep 7, 2017
Member

In all of these tests, calling getState wants to be part of the verification block, not the execution block.

@k88hudson k88hudson force-pushed the k88hudson:gh2813 branch from 8ae59ca to 56df179 Sep 8, 2017
@k88hudson
Copy link
Member Author

@k88hudson k88hudson commented Sep 8, 2017

@Mardak @dmose

Ok, I added three more commits;

  • 56df179 resolves the issue where a request can happen before activity stream has initialized;
  • c891f96 restores the class on the body element;
  • 5614734 resolves the issue that @dmose and I talked that causes an empty locale to be rendered in the UI by waiting for LOCALE_UPDATED to be fired before replying
@dmose
Copy link
Member

@dmose dmose commented Sep 8, 2017

Do we need/want to run this through Talos again, given that the performance characteristics have probably been changed a bit by the race fixes?

@Mardak
Copy link
Member

@Mardak Mardak commented Sep 8, 2017

This will need a patch to keep pine / m-c happy:

diff --git a/browser/base/content/test/static/browser_all_files_referenced.js b/browser/base/content/test/static/browser_all_files_referenced.js
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -88,4 +88,8 @@ var whitelist = [
   {file: "resource://app/modules/NewTabWebChannel.jsm"},
 
+  // browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
+  // will be used very soon by bug 1397875
+  {file: "resource://activity-stream/data/content/activity-stream-prerendered.html"},
+
   // layout/mathml/nsMathMLChar.cpp
   {file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties"},
@Mardak
Copy link
Member

@Mardak Mardak commented Sep 8, 2017

Looks like this test is failing with this PR:
browser/components/search/test/browser_searchEngine_behaviors.js
https://treeherder.mozilla.org/logviewer.html#?job_id=129399493&repo=try&lineNumber=3546-3549

 FAIL | browser/components/search/test/browser_searchEngine_behaviors.js | Test timed out [log…] 

Some brief debugging points to it waiting for and never getting content search events:
https://searchfox.org/mozilla-central/source/browser/components/search/test/browser_searchEngine_behaviors.js#96-107

It's failing pretty much everywhere: https://treeherder.mozilla.org/#/jobs?repo=try&revision=1d54a70adfbd8b9f71db2a7062b9a9899725c70c&filter-searchStr=(bc&filter-resultStatus=testfailed&selectedJob=129397977

@k88hudson
Copy link
Member Author

@k88hudson k88hudson commented Sep 8, 2017

Weird ok, I'll take a look

@k88hudson
Copy link
Member Author

@k88hudson k88hudson commented Sep 8, 2017

@Mardak @dmose ok, 5d84448 fixes the race condition where Search was getting loaded to early for the event to fire, as well as the unreferenced file.

- }
- });
- });
+ await ContentTaskUtils.waitForCondition(() => content.wrappedJSObject.gContentSearchController &&

This comment has been minimized.

@dmose

dmose Sep 8, 2017
Member

It'd be more readable if each of the "content.wrappedJSObject" clauses had its own line, eslint-permitting.

This comment has been minimized.

@dmose

dmose Sep 8, 2017
Member

The original style of test seems more robust, though I get that it wasn't quite right. This fix seems ok for now, but I'd suggest also adding an XXX comment suggesting adding an event to the code or some such so that the test depends less on implementation details and is more useful for refactoring.

This comment has been minimized.

@k88hudson

k88hudson Sep 8, 2017
Author Member

I don't really see how this relies more on implementation details, given that that particular event has to be fired (and also in a particular order, i.e. after the listener is added in the test). Additionally, I kind of feel like using the event makes this test way harder to debug/step through 😜

This comment has been minimized.

@dmose

dmose Sep 8, 2017
Member

OK, that's a reasonable enough rationale.

@dmose
Copy link
Member

@dmose dmose commented Sep 8, 2017

The aboutNewTabService patch to firefox.js seems to have bit-rotted. Re-create it, using -U2 so that it will be more robust and likely to last a bit longer?

@k88hudson k88hudson force-pushed the k88hudson:gh2813 branch from 5d84448 to 84534e4 Sep 8, 2017
@Mardak
Copy link
Member

@Mardak Mardak commented Sep 8, 2017

There's a similar browser_google_behavior that has a "promiseContentSearchReady" but isn't failing ?
https://searchfox.org/mozilla-central/search?q=promiseContentSearchReady

@dmose
dmose approved these changes Sep 8, 2017
Copy link
Member

@dmose dmose left a comment

r=dmose

@k88hudson k88hudson merged commit 5779a88 into mozilla:master Sep 8, 2017
1 check passed
1 check passed
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@Mardak
Copy link
Member

@Mardak Mardak commented Sep 12, 2017

I reverted with ade8033 part of 84534e4 that added mozilla-central-patches/fix-search-test-race.diff now that the export is on m-c with https://hg.mozilla.org/mozilla-central/rev/57f1221bf914

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants