summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/bin
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/bin')
-rw-r--r--browser/components/newtab/bin/render-activity-stream-html.js188
-rw-r--r--browser/components/newtab/bin/try-runner.js366
-rw-r--r--browser/components/newtab/bin/vendor.js38
3 files changed, 592 insertions, 0 deletions
diff --git a/browser/components/newtab/bin/render-activity-stream-html.js b/browser/components/newtab/bin/render-activity-stream-html.js
new file mode 100644
index 0000000000..41b77c35db
--- /dev/null
+++ b/browser/components/newtab/bin/render-activity-stream-html.js
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-disable no-console */
+const fs = require("fs");
+const { mkdir } = require("shelljs");
+const path = require("path");
+const { pathToFileURL } = require("url");
+const chalk = require("chalk");
+
+const DEFAULT_OPTIONS = {
+ // Glob leading from CWD to the parent of the intended prerendered directory.
+ // Starting in newtab/bin/ and we want to write to newtab/prerendered/ so we
+ // go up one level.
+ addonPath: "..",
+ // depends on the registration in browser/components/newtab/jar.mn
+ baseUrl: "resource://activity-stream/",
+};
+
+/**
+ * templateHTML - Generates HTML for activity stream, given some options and
+ * prerendered HTML if necessary.
+ *
+ * @param {obj} options
+ * {str} options.baseUrl The base URL for all local assets
+ * {bool} options.debug Should we use dev versions of JS libraries?
+ * {bool} options.noscripts Should we include scripts in the prerendered files?
+ * @return {str} An HTML document as a string
+ */
+function templateHTML(options) {
+ const debugString = options.debug ? "-dev" : "";
+ // This list must match any similar ones in AboutNewTabChild.sys.mjs
+ const scripts = [
+ "chrome://browser/content/contentSearchUI.js",
+ "chrome://browser/content/contentSearchHandoffUI.js",
+ "chrome://browser/content/contentTheme.js",
+ `${options.baseUrl}vendor/react${debugString}.js`,
+ `${options.baseUrl}vendor/react-dom${debugString}.js`,
+ `${options.baseUrl}vendor/prop-types.js`,
+ `${options.baseUrl}vendor/redux.js`,
+ `${options.baseUrl}vendor/react-redux.js`,
+ `${options.baseUrl}vendor/react-transition-group.js`,
+ `${options.baseUrl}data/content/activity-stream.bundle.js`,
+ `${options.baseUrl}data/content/newtab-render.js`,
+ ];
+
+ // Add spacing and script tags
+ const scriptRender = `\n${scripts
+ .map(script => ` <script src="${script}"></script>`)
+ .join("\n")}`;
+
+ // The markup below needs to be formatted by Prettier. But any diff after
+ // running this script should be caught by try-runnner.js
+ return `
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta
+ http-equiv="Content-Security-Policy"
+ content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob: chrome:; style-src 'unsafe-inline';"
+ />
+ <meta name="color-scheme" content="light dark" />
+ <title data-l10n-id="newtab-page-title"></title>
+ <link
+ rel="icon"
+ type="image/png"
+ href="chrome://branding/content/icon32.png"
+ />
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="toolkit/branding/brandings.ftl" />
+ <link rel="localization" href="browser/newtab/newtab.ftl" />
+ <link
+ rel="stylesheet"
+ href="chrome://global/skin/design-system/tokens-brand.css"
+ />
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/contentSearchUI.css"
+ />
+ <link
+ rel="stylesheet"
+ href="chrome://activity-stream/content/css/activity-stream.css"
+ />
+ </head>
+ <body class="activity-stream">
+ <div id="root"></div>${options.noscripts ? "" : scriptRender}
+ <script
+ async
+ type="module"
+ src="chrome://global/content/elements/moz-toggle.mjs"
+ ></script>
+ </body>
+</html>
+`.trimLeft();
+}
+
+/**
+ * writeFiles - Writes to the desired files the result of a template given
+ * various prerendered data and options.
+ *
+ * @param {string} destPath Path to write the files to
+ * @param {Map} filesMap Mapping of a string file name to templater
+ * @param {Object} options Various options for the templater
+ */
+function writeFiles(destPath, filesMap, options) {
+ for (const [file, templater] of filesMap) {
+ fs.writeFileSync(path.join(destPath, file), templater({ options }));
+ console.log(chalk.green(`✓ ${file}`));
+ }
+}
+
+const STATIC_FILES = new Map([
+ ["activity-stream.html", ({ options }) => templateHTML(options)],
+ [
+ "activity-stream-debug.html",
+ ({ options }) => templateHTML(Object.assign({}, options, { debug: true })),
+ ],
+ [
+ "activity-stream-noscripts.html",
+ ({ options }) =>
+ templateHTML(Object.assign({}, options, { noscripts: true })),
+ ],
+]);
+
+/**
+ * main - Parses command line arguments, generates html and js with templates,
+ * and writes files to their specified locations.
+ */
+async function main() {
+ const { default: meow } = await import("meow");
+ const fileUrl = pathToFileURL(__filename);
+ const cli = meow(
+ `
+ Usage
+ $ node ./bin/render-activity-stream-html.js [options]
+
+ Options
+ -a PATH, --addon-path PATH Path to the parent of the target directory.
+ default: "${DEFAULT_OPTIONS.addonPath}"
+ -b URL, --base-url URL Base URL for assets.
+ default: "${DEFAULT_OPTIONS.baseUrl}"
+ --help Show this help message.
+`,
+ {
+ description: false,
+ // `pkg` is a tiny optimization. It prevents meow from looking for a package
+ // that doesn't technically exist. meow searches for a package and changes
+ // the process name to the package name. It resolves to the newtab
+ // package.json, which would give a confusing name and be wasteful.
+ pkg: {
+ name: "render-activity-stream-html",
+ version: "0.0.0",
+ },
+ // `importMeta` is required by meow 10+. It was added to support ESM, but
+ // meow now requires it, and no longer supports CJS style imports. But it
+ // only uses import.meta.url, which can be polyfilled like this:
+ importMeta: { url: fileUrl },
+ flags: {
+ addonPath: {
+ type: "string",
+ alias: "a",
+ default: DEFAULT_OPTIONS.addonPath,
+ },
+ baseUrl: {
+ type: "string",
+ alias: "b",
+ default: DEFAULT_OPTIONS.baseUrl,
+ },
+ },
+ }
+ );
+
+ const options = Object.assign({ debug: false }, cli.flags || {});
+ const addonPath = path.resolve(__dirname, options.addonPath);
+ const prerenderedPath = path.join(addonPath, "prerendered");
+ console.log(`Writing prerendered files to ${prerenderedPath}:`);
+
+ mkdir("-p", prerenderedPath);
+ writeFiles(prerenderedPath, STATIC_FILES, options);
+}
+
+main();
diff --git a/browser/components/newtab/bin/try-runner.js b/browser/components/newtab/bin/try-runner.js
new file mode 100644
index 0000000000..93b88fac23
--- /dev/null
+++ b/browser/components/newtab/bin/try-runner.js
@@ -0,0 +1,366 @@
+/* eslint-disable no-console */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+/*
+ * A small test runner/reporter for node-based tests,
+ * which are run via taskcluster node(debugger).
+ *
+ * Forked from
+ * https://searchfox.org/mozilla-central/rev/c3453c7a0427eb27d467e1582f821f402aed9850/devtools/client/debugger/bin/try-runner.js
+ */
+
+const { execFileSync } = require("child_process");
+const { readFileSync, writeFileSync } = require("fs");
+const path = require("path");
+const { pathToFileURL } = require("url");
+const chalk = require("chalk");
+
+function logErrors(tool, errors) {
+ for (const error of errors) {
+ console.log(`TEST-UNEXPECTED-FAIL | ${tool} | ${error}`);
+ }
+ return errors;
+}
+
+function execOut(...args) {
+ let exitCode = 0;
+ let out;
+ let err;
+
+ try {
+ out = execFileSync(...args, {
+ silent: false,
+ });
+ } catch (e) {
+ // For debugging on (eg) try server...
+ //
+ // if (e) {
+ // logErrors("execOut", ["execFileSync returned exception: ", e]);
+ // }
+
+ out = e && e.stdout;
+ err = e && e.stderr;
+ exitCode = e && e.status;
+ }
+ return { exitCode, out: out && out.toString(), err: err && err.toString() };
+}
+
+function logStart(name) {
+ console.log(`TEST-START | ${name}`);
+}
+
+function logSkip(name) {
+ console.log(`TEST-SKIP | ${name}`);
+}
+
+const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
+
+const tests = {
+ bundles() {
+ logStart("bundles");
+
+ const items = {
+ "Activity Stream bundle": {
+ path: path.join("data", "content", "activity-stream.bundle.js"),
+ },
+ "activity-stream.html": {
+ path: path.join("prerendered", "activity-stream.html"),
+ },
+ "activity-stream-debug.html": {
+ path: path.join("prerendered", "activity-stream-debug.html"),
+ },
+ "activity-stream-noscripts.html": {
+ path: path.join("prerendered", "activity-stream-noscripts.html"),
+ },
+ "activity-stream-linux.css": {
+ path: path.join("css", "activity-stream-linux.css"),
+ },
+ "activity-stream-mac.css": {
+ path: path.join("css", "activity-stream-mac.css"),
+ },
+ "activity-stream-windows.css": {
+ path: path.join("css", "activity-stream-windows.css"),
+ },
+ // These should get split out to their own try-runner eventually (bug 1866170).
+ "about:welcome bundle": {
+ path: path.join(
+ "../",
+ "aboutwelcome",
+ "content",
+ "aboutwelcome.bundle.js"
+ ),
+ },
+ "aboutwelcome.css": {
+ path: path.join("../", "aboutwelcome", "content", "aboutwelcome.css"),
+ extraCheck: content => {
+ if (content.match(/^\s*@import/m)) {
+ return "aboutwelcome.css contains an @import statement. We should not import styles through the stylesheet, because it is loaded in multiple environments, including the browser chrome for feature callouts. To add other stylesheets to about:welcome or spotlight, add them to aboutwelcome.html or spotlight.html instead.";
+ }
+ return null;
+ },
+ },
+ // These should get split out to their own try-runner eventually (bug 1866170).
+ "about:asrouter bundle": {
+ path: path.join(
+ "../",
+ "asrouter",
+ "content",
+ "asrouter-admin.bundle.js"
+ ),
+ },
+ "ASRouterAdmin.css": {
+ path: path.join(
+ "../",
+ "asrouter",
+ "content",
+ "components",
+ "ASRouterAdmin",
+ "ASRouterAdmin.css"
+ ),
+ },
+ };
+ const errors = [];
+
+ for (const name of Object.keys(items)) {
+ const item = items[name];
+ item.before = readFileSync(item.path, item.encoding || "utf8");
+ }
+
+ let newtabBundleExitCode = execOut(npmCommand, ["run", "bundle"]).exitCode;
+
+ // Until we split out the try runner for about:welcome out into its own
+ // script, we manually run its bundle script.
+ let cwd = process.cwd();
+ process.chdir("../aboutwelcome");
+ let welcomeBundleExitCode = execOut(npmCommand, ["run", "bundle"]).exitCode;
+ process.chdir(cwd);
+
+ // Same thing for about:asrouter
+ process.chdir("../asrouter");
+ let asrouterBundleExitCode = execOut(npmCommand, [
+ "run",
+ "bundle",
+ ]).exitCode;
+ process.chdir(cwd);
+
+ for (const name of Object.keys(items)) {
+ const item = items[name];
+ const after = readFileSync(item.path, item.encoding || "utf8");
+
+ if (item.before !== after) {
+ errors.push(`${name} out of date`);
+ }
+
+ if (item.extraCheck) {
+ const extraError = item.extraCheck(after);
+ if (extraError) {
+ errors.push(extraError);
+ }
+ }
+ }
+
+ if (newtabBundleExitCode !== 0) {
+ errors.push("newtab npm:bundle did not run successfully");
+ }
+
+ if (welcomeBundleExitCode !== 0) {
+ errors.push("about:welcome npm:bundle did not run successfully");
+ }
+
+ if (asrouterBundleExitCode !== 0) {
+ errors.push("about:asrouter npm:bundle did not run successfully");
+ }
+
+ logErrors("bundles", errors);
+ return errors.length === 0;
+ },
+
+ karma() {
+ logStart(`karma ${process.cwd()}`);
+
+ const errors = [];
+ const { exitCode, out } = execOut(npmCommand, [
+ "run",
+ "testmc:unit",
+ // , "--", "--log-level", "--verbose",
+ // to debug the karma integration, uncomment the above line
+ ]);
+
+ // karma spits everything to stdout, not stderr, so if nothing came back on
+ // stdout, give up now.
+ if (!out) {
+ return false;
+ }
+
+ // Detect mocha failures
+ let jsonContent;
+ try {
+ // Note that this will be overwritten at each run, but that shouldn't
+ // matter.
+ jsonContent = readFileSync(path.join("logs", "karma-run-results.json"));
+ } catch (ex) {
+ console.error("exception reading karma-run-results.json: ", ex);
+ return false;
+ }
+ const results = JSON.parse(jsonContent);
+ // eslint-disable-next-line guard-for-in
+ for (let testArray in results.result) {
+ let failedTests = Array.from(results.result[testArray]).filter(
+ test => !test.success && !test.skipped
+ );
+
+ errors.push(
+ ...failedTests.map(
+ test => `${test.suite.join(":")} ${test.description}: ${test.log[0]}`
+ )
+ );
+ }
+
+ // Detect istanbul failures (coverage thresholds set in karma config)
+ const coverage = out.match(/ERROR.+coverage-istanbul.+/g);
+ if (coverage) {
+ errors.push(...coverage.map(line => line.match(/Coverage.+/)[0]));
+ }
+
+ logErrors(`karma ${process.cwd()}`, errors);
+
+ console.log("-----karma stdout below this line---");
+ console.log(out);
+ console.log("-----karma stdout above this line---");
+
+ // Pass if there's no detected errors and nothing unexpected.
+ return errors.length === 0 && !exitCode;
+ },
+
+ welcomekarma() {
+ let cwd = process.cwd();
+ process.chdir("../aboutwelcome");
+ const result = this.karma();
+ process.chdir(cwd);
+ return result;
+ },
+
+ asrouterkarma() {
+ let cwd = process.cwd();
+ process.chdir("../asrouter");
+ const result = this.karma();
+ process.chdir(cwd);
+ return result;
+ },
+
+ zipCodeCoverage() {
+ logStart("zipCodeCoverage");
+
+ const newtabCoveragePath = "logs/coverage/lcov.info";
+ const welcomeCoveragePath = "../aboutwelcome/logs/coverage/lcov.info";
+ const asrouterCoveragePath = "../asrouter/logs/coverage/lcov.info";
+
+ let newtabCoverage = readFileSync(newtabCoveragePath, "utf8");
+ const welcomeCoverage = readFileSync(welcomeCoveragePath, "utf8");
+ const asrouterCoverage = readFileSync(asrouterCoveragePath, "utf8");
+
+ newtabCoverage = `${newtabCoverage}${welcomeCoverage}${asrouterCoverage}`;
+ writeFileSync(newtabCoveragePath, newtabCoverage, "utf8");
+
+ const { exitCode, out } = execOut("zip", [
+ "-j",
+ "logs/coverage/code-coverage-grcov",
+ "logs/coverage/lcov.info",
+ ]);
+
+ console.log("zipCodeCoverage log output: ", out);
+
+ if (!exitCode) {
+ return true;
+ }
+
+ return false;
+ },
+};
+
+async function main() {
+ const { default: meow } = await import("meow");
+ const fileUrl = pathToFileURL(__filename);
+ const cli = meow(
+ `
+ Usage
+ $ node bin/try-runner.js <tests> [options]
+
+ Options
+ -t NAME, --test NAME Run only the specified test. If not specified,
+ all tests will be run. Argument can be passed
+ multiple times to run multiple tests.
+ --help Show this help message.
+
+ Examples
+ $ node bin/try-runner.js bundles karma
+ $ node bin/try-runner.js -t karma -t zip
+`,
+ {
+ description: false,
+ // `pkg` is a tiny optimization. It prevents meow from looking for a package
+ // that doesn't technically exist. meow searches for a package and changes
+ // the process name to the package name. It resolves to the newtab
+ // package.json, which would give a confusing name and be wasteful.
+ pkg: {
+ name: "try-runner",
+ version: "1.0.0",
+ },
+ // `importMeta` is required by meow 10+. It was added to support ESM, but
+ // meow now requires it, and no longer supports CJS style imports. But it
+ // only uses import.meta.url, which can be polyfilled like this:
+ importMeta: { url: fileUrl },
+ flags: {
+ test: {
+ type: "string",
+ isMultiple: true,
+ alias: "t",
+ },
+ },
+ }
+ );
+ const aliases = {
+ bundle: "bundles",
+ build: "bundles",
+ coverage: "karma",
+ cov: "karma",
+ zip: "zipCodeCoverage",
+ welcomecoverage: "welcomekarma",
+ welcomecov: "welcomekarma",
+ asroutercoverage: "asrouterkarma",
+ asroutercov: "asrouterkarma",
+ };
+
+ const inputs = [...cli.input, ...cli.flags.test].map(input =>
+ (aliases[input] || input).toLowerCase()
+ );
+
+ function shouldRunTest(name) {
+ if (inputs.length) {
+ return inputs.includes(name.toLowerCase());
+ }
+ return true;
+ }
+
+ const results = [];
+ for (const name of Object.keys(tests)) {
+ if (shouldRunTest(name)) {
+ results.push([name, tests[name]()]);
+ } else {
+ logSkip(name);
+ }
+ }
+
+ for (const [name, result] of results) {
+ // colorize output based on result
+ console.log(result ? chalk.green(`✓ ${name}`) : chalk.red(`✗ ${name}`));
+ }
+
+ const success = results.every(([, result]) => result);
+ process.exitCode = success ? 0 : 1;
+ console.log("CODE", process.exitCode);
+}
+
+main();
diff --git a/browser/components/newtab/bin/vendor.js b/browser/components/newtab/bin/vendor.js
new file mode 100644
index 0000000000..3d929dcf4b
--- /dev/null
+++ b/browser/components/newtab/bin/vendor.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-disable no-console */
+
+const { cp, set } = require("shelljs");
+const path = require("path");
+
+const filesToVendor = {
+ // XXX currently these two licenses are identical. Perhaps we should check
+ // in case that changes at some point in the future.
+ "react/LICENSE": "REACT_AND_REACT_DOM_LICENSE",
+ "react/umd/react.production.min.js": "react.js",
+ "react/umd/react.development.js": "react-dev.js",
+ "react-dom/umd/react-dom.production.min.js": "react-dom.js",
+ "react-dom/umd/react-dom.development.js": "react-dom-dev.js",
+ "react-dom/umd/react-dom-server.browser.production.min.js":
+ "react-dom-server.js",
+ "react-redux/LICENSE.md": "REACT_REDUX_LICENSE",
+ "react-redux/dist/react-redux.min.js": "react-redux.js",
+ "react-transition-group/dist/react-transition-group.min.js":
+ "react-transition-group.js",
+ "react-transition-group/LICENSE": "REACT_TRANSITION_GROUP_LICENSE",
+};
+
+set("-v"); // Echo all the copy commands so the user can see what's going on
+for (let srcPath of Object.keys(filesToVendor)) {
+ cp(
+ path.join("node_modules", srcPath),
+ path.join("vendor", filesToVendor[srcPath])
+ );
+}
+
+console.log(`
+Check to see if any license files have changed, and, if so, be sure to update
+https://searchfox.org/mozilla-central/source/toolkit/content/license.html`);