diff options
Diffstat (limited to 'browser/components/newtab/bin')
-rw-r--r-- | browser/components/newtab/bin/render-activity-stream-html.js | 188 | ||||
-rw-r--r-- | browser/components/newtab/bin/try-runner.js | 366 | ||||
-rw-r--r-- | browser/components/newtab/bin/vendor.js | 38 |
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`); |