Bug 1862309 - Add speedometer3 to the PGO training set r=smaug,sparky,jnicol

This is done for both Android and regular builds, and it brings a nice
1% perf improvement on Windows, and 2% on Android.

Do not get rid of Speedometer2 yet, adding Speedometer3 already brings
an interesting performance boost.

To update to the latest version run

        > ./mach vendor third_party/webkit/PerformanceTests/Speedometer3/moz.yaml

Differential Revision: https://phabricator.services.mozilla.com/D194040
This commit is contained in:
serge-sans-paille 2023-11-28 11:32:56 +00:00
Родитель 017674468d
Коммит 3a2b678746
1091 изменённых файлов: 497487 добавлений и 2 удалений

Просмотреть файл

@ -73,6 +73,7 @@
"talos/tests/perf-reftest-singletons/tiny-traversal-singleton.html",
"talos/tests/perf-reftest-singletons/window-named-property-get.html",
"webkit/PerformanceTests/Speedometer/index.html",
"webkit/PerformanceTests/Speedometer3/index.html?startAutomatically=true",
"webkit/PerformanceTests/webaudio/index.html?raptor&rendering-buffer-length=30",
];
var defaultInterval = 2000;

Просмотреть файл

@ -25,6 +25,7 @@ from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_opt
PAGES = [
"js-input/webkit/PerformanceTests/Speedometer/index.html",
"js-input/webkit/PerformanceTests/Speedometer3/index.html?startAutomatically=true",
"blueprint/sample.html",
"blueprint/forms.html",
"blueprint/grid.html",
@ -255,8 +256,8 @@ class AndroidProfileRun(TestingMixin, BaseScript, MozbaseMixin, AndroidMixin):
for page in PAGES:
driver.navigate("http://%s:%d/%s" % (IP, PORT, page))
timeout = 2
if "Speedometer/index.html" in page:
# The Speedometer test actually runs many tests internally in
if "Speedometer" in page:
# The Speedometer[23] test actually runs many tests internally in
# javascript, so it needs extra time to run through them. The
# emulator doesn't get very far through the whole suite, but
# this extra time at least lets some of them process.

34
third_party/webkit/PerformanceTests/Speedometer3/Development.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,34 @@
# Development Environment
## Prerequisite
Node is required for local development.
We recommend using a Node version manager ([nvm](https://github.com/nvm-sh/nvm) for example) to ensure a stable development environment is used.
Below are the required node / npm versions:
```sh
"node": ">18.13.0",
"npm": ">8.19.3"
```
## Installation
In your working directory, open terminal and paste the following commands:
```sh
git clone https://github.com/WebKit/Speedometer.git
cd Speedometer
npm install
```
## Run Development Server
1. In your terminal run:
```sh
npm run dev
```
2. Open your browser of choice and navigate to [http://127.0.0.1:7000](http://127.0.0.1:7000)
## Local Server
Speedometer uses [http-server](https://github.com/http-party/http-server), which is a static HTTP server. Meaning it does not provide hot-reloading. By default, the dev script disables caching and local changes can be viewed by simply refreshing your browser window. Additional options of the http-server can be found [here](https://github.com/http-party/http-server#available-options).

34
third_party/webkit/PerformanceTests/Speedometer3/Governance.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,34 @@
# Speedometer Governance Policy
Speedometer uses multistakeholder governance.
This will allow us to share work, and build a collaborative understanding of performance on the web
in order to drive resourcing towards appropriate areas.
This also provides a structure that can endure to provide maintenance and adapt to the future web.
An eligible “browser project” is a core end-to-end web browser engine with an integrated JavaScript engine
which distributes implementations widely. The project may delegate decision making within Speedometer
to multiple representatives (for example, to review code commits or to provide consensus for major changes).
The participating browser projects at this time are Blink/V8, Gecko/SpiderMonkey, and WebKit/JavaScriptCore.
The intent is that the working team should be able to move quickly for most changes,
with a higher level of process and consensus expected based on the impact of the change.
- **Trivial change** - This is a change that has no effect on the official benchmark and includes changes
to whitespaces, comments, documentation outside policies and governance model, and unofficial test cases.
A trivial change requires approval by a reviewer, who is not the author of the change,
from one of the participating browser projects.
The intent is to ensure basic code quality & license compatibility, not to reach agreement.
For example, one participating browser project might be both writing and reviewing a new benchmark in
a subfolder to test in their own CI, or reviewing code written by an external contributor.
- **Non-trivial change** - This is a change that has small impact on the official benchmark and includes
changes to official test cases, test runners, bug fixes, and the appearance of the benchmark.
A non-trivial change requires approval by at least two of the participating browser projects
(including either authoring or reviewing the change) and none other strongly opposed to the change
within 10 business days.
- **Major change** - This is a change that has major implications on the official benchmark such as
releasing of a new version of the benchmark or any revisions to governance policies and processes,
including changes to the participating browser projects.
A major change requires a consensus, meaning approvals by each of the participating browser projects.
This governance policy and associated code will be hosted inside the Speedometer repository within
the WebKit GitHub organization under the 2-clause BSD license.

Просмотреть файл

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=850" />
<title>Speedometer 3.0 Interactive Runner</title>
<script src="resources/interactive.mjs" type="module"></script>
<link rel="icon" href="resources/favicon.png" />
<style>
iframe {
border: 1px solid black;
}
ol {
list-style: none;
margin: 0;
padding: 0;
}
ol ol {
margin-left: 2em;
list-style-position: outside;
}
.running {
text-decoration: underline;
}
.ran {
color: grey;
}
nav {
position: absolute;
right: 10px;
height: 600px;
}
nav > ol {
height: 100%;
overflow-y: scroll;
}
</style>
</head>
<body></body>
</html>

23
third_party/webkit/PerformanceTests/Speedometer3/LICENSE поставляемый Normal file
Просмотреть файл

@ -0,0 +1,23 @@
Copyright (C) 2013-2022 Apple Inc. All rights reserved.
Copyright (C) 2017 Google Inc. All rights reserved.
Copyright (C) 2022-2023 Mozilla Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

19
third_party/webkit/PerformanceTests/Speedometer3/README.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,19 @@
| :warning: **Speedometer 3 is in active development and is unstable**. You can follow along with development in this repository, but see [Speedometer 2.1](https://browserbench.org/Speedometer2.1/) for the latest stable version. |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
# What is Speedometer?
Speedometer is a benchmark for web browsers that measures Web application responsiveness
by timing simulated user interactions on various workloads. Our primary goal is to make it
reflect the real-world Web as much as possible. When a browser improves its score on the
benchmark, actual users should benefit. In order to achieve this, it should:
- Test end-to-end user journeys instead of testing specific features in a tight loop. Each
test should exercise the full set of whats needed from the engine in order for a user to
accomplish a task.
- Evolve over time, adapting to the present Web on a regular basis. This should be informed
by current usage data, and by consensus about features which are important for engines to
optimize to provide a consistent experience for users and site authors.
- Be accessible to the public and useful to browser engineers. It should run in every modern
browser by visiting a normal web page. It should run relatively quickly, while providing
enough test coverage to be reflective of the real-world Web.

27
third_party/webkit/PerformanceTests/Speedometer3/Testing.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,27 @@
# Testing
Speedometer uses [Selenium](https://www.selenium.dev/) for testing of the application itself.
Tests are located in the `/tests` folder.
[Sinon](https://sinonjs.org/): Standalone test spies, stubs and mocks for JavaScript.
[Mocha](https://mochajs.org/): Testing framework.
## Local Testing
To run this locally you'll need the browsers installed along with the corresponding driver:
- [chromedriver](https://chromedriver.chromium.org/getting-started)
- [geckodriver](https://github.com/mozilla/geckodriver/releases)
- [safaridriver](https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari).
Once installed you can run the following scripts:
```bash
npm run test:chrome
npm run test:firefox
npm run test:safari
```
## Automated Testing
Currently Speedometer's tests run automatically, when pushing to the main branch or when opening a pr.

Просмотреть файл

@ -0,0 +1,10 @@
{
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}

181
third_party/webkit/PerformanceTests/Speedometer3/index.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,181 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=850" />
<title>Speedometer 3.0</title>
<link rel="stylesheet" href="resources/main.css" />
<link rel="icon" href="resources/favicon.png" />
<script src="resources/main.mjs" type="module"></script>
</head>
<body>
<main>
<section id="home">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="content">
<p>Speedometer is a browser benchmark that measures the responsiveness of Web applications. It uses demo web applications to simulate user actions such as adding to-do items.</p>
<p id="screen-size-warning">
<strong>
Your browser window is too small. For most accurate results, please make the view port size at least <span id="min-screen-width">850px</span> by <span id="min-screen-height">650px</span>.<br />
It's currently <span id="screen-size"></span>.
</strong>
</p>
</div>
<div class="buttons">
<div class="button-row">
<button class="start-tests-button">Start Test</button>
</div>
<div class="button-row">
<a href="#about">About Speedometer</a>
</div>
</div>
</section>
<section id="running">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div id="testContainer"></div>
<div id="progress">
<progress aria-label="Progress" id="progress-completed"></progress>
</div>
<div id="info">
<div id="info-label"></div>
<div id="info-progress"></div>
</div>
</section>
<section id="summary">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<h1>Score</h1>
<div class="gauge">
<div class="window"><div class="needle"></div></div>
</div>
<hr />
<div id="result-number"></div>
<div id="confidence-number"></div>
<div class="buttons">
<div class="button-row">
<button class="start-tests-button" title="Discard the current results and re-run all tests.">Test Again</button>
<a class="button" href="#details" id="show-details" title="Show detailed results data.">Details</a>
</div>
</div>
</section>
<section id="details">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="section-grid">
<h1 class="section-header">Detailed Results</h1>
<div class="section-content all-metric-results">
<div class="aggregated-metric-result">
<h2>Geomean</h2>
<div id="geomean-chart"></div>
<h2>Tests</h2>
<div id="tests-chart"></div>
</div>
<br />
<h2>Detailed Metrics</h2>
<div id="metrics-results"></div>
</div>
<div class="buttons section-footer">
<div class="button-row">
<button class="start-tests-button" title="Discard the current results and re-run all tests.">Test Again</button>
<a class="button" href="#summary" title="Go back to the simplified summary view.">Summary</a>
</div>
<div class="button-row export-buttons">
<button id="copy-full-json" title="Copy full result metrics as json string.">Copy JSON</button>
<a class="button" id="download-full-json" title="Download full result metrics as json string.">Download JSON</a>
<a class="button" id="download-classic-json" title="Download backwards-compatible result metrics as json string.">Download Classic JSON</a>
<a class="button" id="download-csv" title="Download all result metrics as CSV string.">Download CSV</a>
<button id="copy-csv" title="Copy all result metrics as CSV string.">Copy CSV</button>
</div>
</div>
</div>
</section>
<section id="about">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="section-grid">
<h1 class="section-header">About Speedometer 3</h1>
<div class="section-content">
<p>Speedometer 3 is a benchmark for web browsers that measures Web application responsiveness by timing simulated user interactions on various workloads.</p>
<p>
The following high level user journeys are implemented in the current version. Each of these journeys has one or more workloads which test important aspects of it - for example commonly used patterns, frameworks, or
technologies.
</p>
<ul>
<li>Working with a todo list</li>
<ul>
<li>Measures the time to add, complete, and remove 100 todo items in a basic list.</li>
<li>Each example implements the same todo application (TodoMVC) using different techniques and frameworks.</li>
<li>
Workloads: <a href="resources/todomvc/vanilla-examples/javascript-es5/readme.md" target="_blank">TodoMVC-JavaScript-ES5</a>,
<a href="resources/todomvc/vanilla-examples/javascript-web-components/readme.md" target="_blank">TodoMVC-WebComponents</a>,
<a href="resources/todomvc/architecture-examples/react-complex/readme.md" target="_blank">TodoMVC-React-Complex-DOM</a>,
<a href="resources/todomvc/architecture-examples/backbone/readme.md" target="_blank">TodoMVC-Backbone</a>, <a href="resources/todomvc/architecture-examples/angular/readme.md" target="_blank">TodoMVC-Angular</a>,
<a href="resources/todomvc/architecture-examples/vue/readme.md" target="_blank">TodoMVC-Vue</a>, <a href="resources/todomvc/architecture-examples/jquery/readme.md" target="_blank">TodoMVC-jQuery</a>,
<a href="resources/todomvc/architecture-examples/preact/readme.md" target="_blank">TodoMVC-Preact</a>, <a href="resources/todomvc/architecture-examples/svelte/readme.md" target="_blank">TodoMVC-Svelte</a>,
<a href="resources/todomvc/architecture-examples/lit/readme.md" target="_blank">TodoMVC-Lit</a>
</li>
</ul>
<li>Editing rich text</li>
<ul>
<li>Loading and styling text inside WYSIWYG and code editors.</li>
<li>Workloads: <a href="resources/editors/readme.md" target="_blank">Editor-CodeMirror</a>, <a href="resources/editors/readme.md" target="_blank">Editor-TipTap</a></li>
</ul>
<li>Rendering charts</li>
<ul>
<li>Loading and interacting with SVG and canvas charts.</li>
<li>
Workloads: <a href="resources/charts/readme.md" target="_blank">Charts-observable-plot</a>, <a href="resources/charts/readme.md" target="_blank">Charts-chartjs</a>,
<a href="resources/react-stockcharts/readme.md" target="_blank">React-Stockcharts-SVG</a>, <a href="resources/perf.webkit.org/readme.md" target="_blank">Perf-Dashboard</a>
</li>
</ul>
<li>Reading a news site</li>
<ul>
<li>Navigating across pages and interacting with a typical looking news site.</li>
<li>Workloads: <a href="resources/newssite/news-next/readme.md" target="_blank">NewsSite-Next</a>, <a href="resources/newssite/news-nuxt/readme.md" target="_blank">NewsSite-Nuxt</a></li>
</ul>
</ul>
<p class="note"><strong>Notes about methodology</strong></p>
<ul>
<li>Although user-driven actions like mouse movements and keyboard input cannot be fully emulated in JavaScript, Speedometer does its best to faithfully replay a typical workload within the demo applications.</li>
<li>To make the run time long enough to measure with the limited precision, we synchronously execute a large number of the operations, such as adding one hundred to-do items.</li>
<li>
Modern browser engines execute some work asynchronously as an optimization strategy to reduce the run time of synchronous operations. While returning control back to JavaScript execution as soon as possible is worth
pursuing, the run time cost of such an asynchronous work should still be taken into a holistic measurement of web application performance. In addition, some JavaScript frameworks call into DOM APIs asynchronously as an
optimization technique. Speedometer approximates the run time of this asynchronous work in the UI thread with a zero-second timer that is scheduled immediately after each execution of synchronous operations.
</li>
<li>Speedometer does not attempt to measure concurrent asynchronous work (e.g. in Web Workers).</li>
<li>Speedometer should not be used as a way to compare the performance of different JavaScript frameworks.</li>
<li>
The goal of all workloads is to represent a scenario that could be found on the Web. Although all workloads strive to use patterns that are commonly used, some implementation details are Speedometer specific and should
not be used as a guideline on how to implement and deploy a standalone app. For example, due to constraints within the test harness, workloads must not depend on a server infrastructure to function properly and are
built as static files ahead of time.
</li>
</ul>
</div>
<div class="buttons section-footer">
<div class="button-row">
<a class="button" href="#home" title="Show main section.">Home</a>
</div>
</div>
</div>
</section>
</main>
</body>
</html>

27
third_party/webkit/PerformanceTests/Speedometer3/moz.yaml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,27 @@
schema: 1
bugzilla:
product: "Firefox Build System"
component: "General"
origin:
name: speedometer3-benchmark
description: An open source repository for the Speedometer benchmark, v3
url: https://github.com/WebKit/Speedometer
release: 208e1abee2916fe4404c4ac561d80ee92dfee059 (2023-11-20T09:56:59Z).
revision: 208e1abee2916fe4404c4ac561d80ee92dfee059
license: BSD-2-Clause
vendoring:
url: https://github.com/WebKit/Speedometer
source-hosting: github
tracking: commit
exclude:
- '.*'
keep:
- LICENSE

11378
third_party/webkit/PerformanceTests/Speedometer3/package-lock.json сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

51
third_party/webkit/PerformanceTests/Speedometer3/package.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,51 @@
{
"name": "speedometer",
"version": "3.0.0-alpha",
"description": "An open source repository for the Speedometer benchmark.",
"engines": {
"node": ">=18.13.0",
"npm": ">=8.19.3"
},
"repository": {
"type": "git",
"url": "git+https://github.com/WebKit/Speedometer.git"
},
"bugs": {
"url": "https://github.com/WebKit/Speedometer/issues"
},
"license": "SEE LICENSE IN LICENSE",
"scripts": {
"dev": "http-server ./ -p 7000 -c-1 --cors",
"server": "http-server ./ -p 7000 --cors",
"lint:check": "eslint **/*.mjs",
"lint:fix": "eslint \"**/*.{js,mjs,jsx,ts}\" --fix",
"pretty:check": "prettier --check ./",
"pretty:fix": "prettier --write ./",
"format": "npm run pretty:fix ; npm run lint:fix",
"test:chrome": "BROWSER=chrome node tests/run.mjs",
"test:firefox": "BROWSER=firefox node tests/run.mjs",
"test:safari": "BROWSER=safari node tests/run.mjs"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@nuxt/eslint-config": "^0.1.1",
"@next/eslint-plugin-next": "^13.4.5",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^6.1.3",
"eslint": "^8.38.0",
"eslint-plugin-ember": "^11.4.8",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-vue": "^9.10.0",
"expect.js": "^0.3.1",
"http-server": "^14.1.1",
"mocha": "^10.2.0",
"prettier": "^2.8.3",
"selenium-webdriver": "^4.8.0",
"sinon": "^15.0.1",
"typescript": "^5.0.4"
}
}

Просмотреть файл

@ -0,0 +1,593 @@
import { Metric } from "./metric.mjs";
import { params } from "./params.mjs";
const performance = globalThis.performance;
export class BenchmarkTestStep {
constructor(testName, testFunction) {
this.name = testName;
this.run = testFunction;
}
}
function getParent(lookupStartNode, path) {
const parent = path.reduce((root, selector) => {
const node = root.querySelector(selector);
return node.shadowRoot ?? node;
}, lookupStartNode);
return parent;
}
class Page {
constructor(frame) {
this._frame = frame;
}
layout() {
const body = this._frame.contentDocument.body.getBoundingClientRect();
this.layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
async waitForElement(selector) {
return new Promise((resolve) => {
const resolveIfReady = () => {
const element = this.querySelector(selector);
let callback = resolveIfReady;
if (element)
callback = () => resolve(element);
window.requestAnimationFrame(callback);
};
resolveIfReady();
});
}
/**
* Returns the first element within the document that matches the specified selector, or group of selectors.
* If no matches are found, null is returned.
*
* An optional path param is added to be able to target elements within a shadow DOM or nested shadow DOMs.
*
* @example
* // DOM structure: <todo-app> -> #shadow-root -> <todo-list> -> #shadow-root -> <todo-item>
* // return PageElement(<todo-item>)
* querySelector("todo-item", ["todo-app", "todo-list"]);
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns PageElement | null
*/
querySelector(selector, path = []) {
const lookupStartNode = this._frame.contentDocument;
const element = getParent(lookupStartNode, path).querySelector(selector);
if (element === null)
return null;
return this._wrapElement(element);
}
/**
* Returns all elements within the document that matches the specified selector, or group of selectors.
* If no matches are found, null is returned.
*
* An optional path param is added to be able to target elements within a shadow DOM or nested shadow DOMs.
*
* @example
* // DOM structure: <todo-app> -> #shadow-root -> <todo-list> -> #shadow-root -> <todo-item>
* // return [PageElement(<todo-item>), PageElement(<todo-item>)]
* querySelectorAll("todo-item", ["todo-app", "todo-list"]);
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns array
*/
querySelectorAll(selector, path = []) {
const lookupStartNode = this._frame.contentDocument;
const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector));
for (let i = 0; i < elements.length; i++)
elements[i] = this._wrapElement(elements[i]);
return elements;
}
getElementById(id) {
const element = this._frame.contentDocument.getElementById(id);
if (element === null)
return null;
return this._wrapElement(element);
}
call(functionName) {
this._frame.contentWindow[functionName]();
return null;
}
callAsync(functionName) {
setTimeout(() => {
this._frame.contentWindow[functionName]();
}, 0);
}
callToGetElement(functionName) {
return this._wrapElement(this._frame.contentWindow[functionName]());
}
_wrapElement(element) {
return new PageElement(element);
}
}
const NATIVE_OPTIONS = {
bubbles: true,
cancellable: true,
};
class PageElement {
#node;
constructor(node) {
this.#node = node;
}
setValue(value) {
this.#node.value = value;
}
click() {
this.#node.click();
}
focus() {
this.#node.focus();
}
getElementByMethod(name) {
return new PageElement(this.#node[name]());
}
dispatchEvent(eventName, options = NATIVE_OPTIONS, eventType = Event) {
if (eventName === "submit")
// FIXME FireFox doesn't like `new Event('submit')
this._dispatchSubmitEvent();
else
this.#node.dispatchEvent(new eventType(eventName, options));
}
_dispatchSubmitEvent() {
const submitEvent = document.createEvent("Event");
submitEvent.initEvent("submit", true, true);
this.#node.dispatchEvent(submitEvent);
}
enter(type, options = undefined) {
const ENTER_KEY_CODE = 13;
return this.dispatchKeyEvent(type, ENTER_KEY_CODE, "Enter", options);
}
dispatchKeyEvent(type, keyCode, key, options) {
let eventOptions = { bubbles: true, cancelable: true, keyCode, which: keyCode, key };
if (options !== undefined)
eventOptions = Object.assign(eventOptions, options);
const event = new KeyboardEvent(type, eventOptions);
this.#node.dispatchEvent(event);
}
dispatchMouseEvent(type, offsetX, offsetY, options) {
const boundingRect = this.#node.getBoundingClientRect();
const clientX = offsetX + boundingRect.x;
const clientY = offsetY + boundingRect.y;
const contentWindow = this.#node.ownerDocument.defaultView;
const screenX = clientX + contentWindow.screenX;
const screenY = clientY + contentWindow.screenY;
let eventOptions = { bubbles: true, cancelable: true, clientX, clientY, screenX, screenY };
if (options !== undefined)
eventOptions = Object.assign(eventOptions, options);
const event = new contentWindow.MouseEvent(type, eventOptions);
this.#node.dispatchEvent(event);
}
/**
* Returns the first element found in a node of a PageElement that matches the specified selector, or group of selectors. If a shadow DOM is present in the node, the shadow DOM is used to query.
* If no matches are found, null is returned.
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns PageElement | null
*/
querySelectorInShadowRoot(selector, path = []) {
const lookupStartNode = this.#node.shadowRoot ?? this.#node;
const element = getParent(lookupStartNode, path).querySelector(selector);
if (element === null)
return null;
return new PageElement(element);
}
querySelector(selector) {
const element = this.#node.querySelector(selector);
if (element === null)
return null;
return new PageElement(element);
}
}
function geomeanToScore(geomean) {
return 1000 / geomean;
}
// The WarmupSuite is used to make sure all runner helper functions and
// classes are compiled, to avoid unnecessary pauses due to delayed
// compilation of runner methods in the middle of the measuring cycle.
const WarmupSuite = {
name: "Warmup",
url: "warmup/index.html",
async prepare(page) {
await page.waitForElement("#testItem");
},
tests: [
// Make sure to run ever page.method once at least
new BenchmarkTestStep("WarmingUpPageMethods", (page) => {
let results = [];
results.push(page.querySelector(".testItem"));
results.push(page.querySelectorAll(".item"));
results.push(page.getElementById("testItem"));
}),
new BenchmarkTestStep("WarmingUpPageElementMethods", (page) => {
const item = page.getElementById("testItem");
item.setValue("value");
item.click();
item.focus();
item.dispatchEvent("change");
item.enter("keypress");
item.dispatchEvent("input");
item.enter("keyup");
}),
new BenchmarkTestStep("WarmingUpPageElementMouseMethods", (page) => {
const item = page.getElementById("testItem");
const mouseEventOptions = { clientX: 100, clientY: 100, bubbles: true, cancelable: true };
const wheelEventOptions = {
clientX: 200,
clientY: 200,
deltaMode: 0,
delta: -10,
deltaY: -10,
bubbles: true,
cancelable: true,
};
item.dispatchEvent("mousedown", mouseEventOptions, MouseEvent);
item.dispatchEvent("mousemove", mouseEventOptions, MouseEvent);
item.dispatchEvent("mouseup", mouseEventOptions, MouseEvent);
item.dispatchEvent("wheel", wheelEventOptions, WheelEvent);
}),
],
};
class TestInvoker {
constructor(syncCallback, asyncCallback, reportCallback) {
this._syncCallback = syncCallback;
this._asyncCallback = asyncCallback;
this._reportCallback = reportCallback;
}
}
class TimerTestInvoker extends TestInvoker {
start() {
return new Promise((resolve) => {
setTimeout(() => {
this._syncCallback();
setTimeout(() => {
this._asyncCallback();
requestAnimationFrame(async () => {
await this._reportCallback();
resolve();
});
}, 0);
}, params.waitBeforeSync);
});
}
}
class RAFTestInvoker extends TestInvoker {
start() {
return new Promise((resolve) => {
if (params.waitBeforeSync)
setTimeout(() => this._scheduleCallbacks(resolve), params.waitBeforeSync);
else
this._scheduleCallbacks(resolve);
});
}
_scheduleCallbacks(resolve) {
requestAnimationFrame(() => this._syncCallback());
requestAnimationFrame(() => {
setTimeout(() => {
this._asyncCallback();
setTimeout(async () => {
await this._reportCallback();
resolve();
}, 0);
}, 0);
});
}
}
// https://stackoverflow.com/a/47593316
function seededHashRandomNumberGenerator(a) {
return function () {
var t = a += 0x6d2b79f5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return (t ^ (t >>> 14)) >>> 0;
};
}
export class BenchmarkRunner {
constructor(suites, client) {
this._suites = suites;
if (params.useWarmupSuite)
this._suites = [WarmupSuite, ...suites];
this._client = client;
this._page = null;
this._metrics = null;
this._iterationCount = params.iterationCount;
if (params.shuffleSeed !== "off")
this._suiteOrderRandomNumberGenerator = seededHashRandomNumberGenerator(params.shuffleSeed);
}
async runMultipleIterations(iterationCount) {
this._iterationCount = iterationCount;
if (this._client?.willStartFirstIteration)
await this._client.willStartFirstIteration(iterationCount);
const iterationStartLabel = "iteration-start";
const iterationEndLabel = "iteration-end";
for (let i = 0; i < iterationCount; i++) {
performance.mark(iterationStartLabel);
await this._runAllSuites();
performance.mark(iterationEndLabel);
performance.measure(`iteration-${i}`, iterationStartLabel, iterationEndLabel);
}
if (this._client?.didFinishLastIteration)
await this._client.didFinishLastIteration(this._metrics);
}
_removeFrame() {
if (this._frame) {
this._frame.parentNode.removeChild(this._frame);
this._frame = null;
}
}
async _appendFrame(src) {
const frame = document.createElement("iframe");
const style = frame.style;
style.width = `${params.viewport.width}px`;
style.height = `${params.viewport.height}px`;
style.border = "0px none";
style.position = "absolute";
frame.setAttribute("scrolling", "no");
frame.className = "test-runner";
style.left = "50%";
style.top = "50%";
style.transform = "translate(-50%, -50%)";
if (this._client?.willAddTestFrame)
await this._client.willAddTestFrame(frame);
document.body.insertBefore(frame, document.body.firstChild);
this._frame = frame;
return frame;
}
async _runAllSuites() {
this._measuredValues = { tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN };
const prepareStartLabel = "runner-prepare-start";
const prepareEndLabel = "runner-prepare-end";
performance.mark(prepareStartLabel);
this._removeFrame();
await this._appendFrame();
this._page = new Page(this._frame);
let suites = [...this._suites];
if (this._suiteOrderRandomNumberGenerator) {
// We just do a simple Fisher-Yates shuffle based on the repeated hash of the
// seed. This is not a high quality RNG, but it's plenty good enough.
for (let i = 0; i < suites.length - 1; i++) {
let j = i + (this._suiteOrderRandomNumberGenerator() % (suites.length - i));
let tmp = suites[i];
suites[i] = suites[j];
suites[j] = tmp;
}
}
performance.mark(prepareEndLabel);
performance.measure("runner-prepare", prepareStartLabel, prepareEndLabel);
for (const suite of suites) {
if (!suite.disabled)
await this._runSuite(suite);
}
const finalizeStartLabel = "runner-finalize-start";
const finalizeEndLabel = "runner-finalize-end";
performance.mark(finalizeStartLabel);
// Remove frame to clear the view for displaying the results.
this._removeFrame();
await this._finalize();
performance.mark(finalizeEndLabel);
performance.measure("runner-finalize", finalizeStartLabel, finalizeEndLabel);
}
async _runSuite(suite) {
const suitePrepareStartLabel = `suite-${suite.name}-prepare-start`;
const suitePrepareEndLabel = `suite-${suite.name}-prepare-end`;
const suiteStartLabel = `suite-${suite.name}-start`;
const suiteEndLabel = `suite-${suite.name}-end`;
performance.mark(suitePrepareStartLabel);
await this._prepareSuite(suite);
performance.mark(suitePrepareEndLabel);
performance.mark(suiteStartLabel);
for (const test of suite.tests)
await this._runTestAndRecordResults(suite, test);
performance.mark(suiteEndLabel);
performance.measure(`suite-${suite.name}-prepare`, suitePrepareStartLabel, suitePrepareEndLabel);
performance.measure(`suite-${suite.name}`, suiteStartLabel, suiteEndLabel);
}
async _prepareSuite(suite) {
return new Promise((resolve) => {
const frame = this._page._frame;
frame.onload = async () => {
await suite.prepare(this._page);
resolve();
};
frame.src = `resources/${suite.url}`;
});
}
async _runTestAndRecordResults(suite, test) {
/* eslint-disable-next-line no-async-promise-executor */
if (this._client?.willRunTest)
await this._client.willRunTest(suite, test);
// Prepare all mark labels outside the measuring loop.
const startLabel = `${suite.name}.${test.name}-start`;
const syncEndLabel = `${suite.name}.${test.name}-sync-end`;
const asyncStartLabel = `${suite.name}.${test.name}-async-start`;
const asyncEndLabel = `${suite.name}.${test.name}-async-end`;
let syncTime;
let asyncStartTime;
let asyncTime;
const runSync = () => {
if (params.warmupBeforeSync) {
performance.mark("warmup-start");
const startTime = performance.now();
// Infinite loop for the specified ms.
while (performance.now() - startTime < params.warmupBeforeSync)
continue;
performance.mark("warmup-end");
}
performance.mark(startLabel);
const syncStartTime = performance.now();
test.run(this._page);
const syncEndTime = performance.now();
performance.mark(syncEndLabel);
syncTime = syncEndTime - syncStartTime;
performance.mark(asyncStartLabel);
asyncStartTime = performance.now();
};
const measureAsync = () => {
// Some browsers don't immediately update the layout for paint.
// Force the layout here to ensure we're measuring the layout time.
const height = this._frame.contentDocument.body.getBoundingClientRect().height;
const asyncEndTime = performance.now();
asyncTime = asyncEndTime - asyncStartTime;
this._frame.contentWindow._unusedHeightValue = height; // Prevent dead code elimination.
performance.mark(asyncEndLabel);
if (params.warmupBeforeSync)
performance.measure("warmup", "warmup-start", "warmup-end");
performance.measure(`${suite.name}.${test.name}-sync`, startLabel, syncEndLabel);
performance.measure(`${suite.name}.${test.name}-async`, asyncStartLabel, asyncEndLabel);
};
const report = () => this._recordTestResults(suite, test, syncTime, asyncTime);
const invokerClass = params.measurementMethod === "raf" ? RAFTestInvoker : TimerTestInvoker;
const invoker = new invokerClass(runSync, measureAsync, report);
return invoker.start();
}
async _recordTestResults(suite, test, syncTime, asyncTime) {
// Skip reporting updates for the warmup suite.
if (suite === WarmupSuite)
return;
const suiteResults = this._measuredValues.tests[suite.name] || { tests: {}, total: 0 };
const total = syncTime + asyncTime;
this._measuredValues.tests[suite.name] = suiteResults;
suiteResults.tests[test.name] = { tests: { Sync: syncTime, Async: asyncTime }, total: total };
suiteResults.total += total;
if (this._client?.didRunTest)
await this._client.didRunTest(suite, test);
}
async _finalize() {
this._appendIterationMetrics();
if (this._client?.didRunSuites) {
let product = 1;
const values = [];
for (const suiteName in this._measuredValues.tests) {
const suiteTotal = this._measuredValues.tests[suiteName].total;
product *= suiteTotal;
values.push(suiteTotal);
}
values.sort((a, b) => a - b); // Avoid the loss of significance for the sum.
const total = values.reduce((a, b) => a + b);
const geomean = Math.pow(product, 1 / values.length);
this._measuredValues.total = total;
this._measuredValues.mean = total / values.length;
this._measuredValues.geomean = geomean;
this._measuredValues.score = geomeanToScore(geomean);
await this._client.didRunSuites(this._measuredValues);
}
}
_appendIterationMetrics() {
const getMetric = (name, unit = "ms") => this._metrics[name] || (this._metrics[name] = new Metric(name, unit));
const iterationTotalMetric = (i) => {
if (i >= params.iterationCount)
throw new Error(`Requested iteration=${i} does not exist.`);
return getMetric(`Iteration-${i}-Total`);
};
const collectSubMetrics = (prefix, items, parent) => {
for (let name in items) {
const results = items[name];
const metric = getMetric(prefix + name);
metric.add(results.total ?? results);
if (metric.parent !== parent)
parent.addChild(metric);
if (results.tests)
collectSubMetrics(`${metric.name}${Metric.separator}`, results.tests, metric);
}
};
const initializeMetrics = this._metrics === null;
if (initializeMetrics)
this._metrics = { __proto__: null };
const iterationResults = this._measuredValues.tests;
collectSubMetrics("", iterationResults);
if (initializeMetrics) {
// Prepare all iteration metrics so they are listed at the end of
// of the _metrics object, before "Total" and "Score".
for (let i = 0; i < this._iterationCount; i++)
iterationTotalMetric(i);
getMetric("Geomean");
getMetric("Score", "score");
}
const geomean = getMetric("Geomean");
const iterationTotal = iterationTotalMetric(geomean.length);
for (const results of Object.values(iterationResults))
iterationTotal.add(results.total);
iterationTotal.computeAggregatedMetrics();
geomean.add(iterationTotal.geomean);
getMetric("Score").add(geomeanToScore(iterationTotal.geomean));
for (const metric of Object.values(this._metrics))
metric.computeAggregatedMetrics();
}
}

Просмотреть файл

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chart.js</title>
<style type="text/css" media="all">
label {
user-select: none;
display: inline-flex;
align-items: center;
}
</style>
</head>
<body>
<div id="app">
<button id="run-all" type="button">Run all scenario (development only)</button>
<button id="prepare" type="button">Prepare data</button>
<button id="add-scatter-chart-button" type="button">Draw a scatter chart</button>
<button id="open-tooltip" type="button">Open tooltips</button>
<button id="reset" type="button">Reset</button>
<label><input type="checkbox" id="opaque-color" />Use opaque color for next draw</label>
</div>
<canvas id="chart"></canvas>
<script>
// This hack allows to capture the work normally happening in a rAF. We
// may be able to remove it if the runner improves.
window.requestAnimationFrame = (cb) => window.setTimeout(cb, 0);
window.cancelAnimationFrame = window.clearTimeout;
</script>
<script type="module" src="/chartjs.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,174 @@
import Chart from "chart.js/auto";
import { csvParse } from "d3-dsv";
// These 2 imports use some vite machinery to directly import these files as
// strings. Then they are constants and this reduces the load on the GC.
import airportsString from "./datasets/airports.csv?raw";
import flightsString from "./datasets/flights-airports.csv?raw";
/*
* From https://www.omnicalculator.com/other/latitude-longitude-distance:
* d = 2R × sin¹([sin²((θ₂ - θ₁)/2) + cosθ₁ × cosθ₂ × sin²((φ₂ - φ₁)/2)])
*
* where:
* θ₁, φ₁ First point latitude and longitude coordinates;
* θ₂, φ₂ Second point latitude and longitude coordinates;
* R Earth's radius (R = 6371 km); and
* d Distance between them along Earth's surface.
*/
const R = 6371;
function computeDistance(coords1, coords2) {
const long1 = toRadian(coords1.longitude);
const lat1 = toRadian(coords1.latitude);
const long2 = toRadian(coords2.longitude);
const lat2 = toRadian(coords2.latitude);
const longSquareSin = Math.sin((long2 - long1) / 2) ** 2;
const latSquareSin = Math.sin((lat2 - lat1) / 2) ** 2;
const d = 2 * R * Math.asin(Math.sqrt(latSquareSin + Math.cos(lat1) * Math.cos(lat2) * longSquareSin));
return d;
}
const RAD = Math.PI / 180;
function toRadian(deg) {
return deg * RAD;
}
let preparedData = null;
function prepare() {
/**
* AirportInformation: { state, iata, name, city, country, latitude, longitude }
* airports: Array<AirportInformation>
* flights: Array<{ origin, destination, count }>
*/
const airports = csvParse(airportsString);
const flights = csvParse(flightsString);
const airportMap = new Map(airports.map((airport) => [airport.iata, airport]));
for (const flight of flights) {
const origAirport = airportMap.get(flight.origin);
const destAirport = airportMap.get(flight.destination);
flight.distance = computeDistance(origAirport, destAirport);
}
preparedData = { flights, airportMap };
}
const ROOT = document.getElementById("chart");
const opaqueCheckBox = document.getElementById("opaque-color");
let currentChart = null;
function drawScattered(data) {
if (!preparedData)
throw new Error("Please prepare the data first.");
reset();
currentChart = new Chart(ROOT, {
type: "scatter",
data: {
datasets: [
{
data: preparedData.flights,
backgroundColor: opaqueCheckBox.checked ? "rgb(0, 125, 255)" : "rgba(0, 125, 255, .20)",
},
],
},
options: {
animation: false,
parsing: {
xAxisKey: "distance",
yAxisKey: "count",
},
plugins: {
legend: {
display: false,
},
title: {
display: true,
text: "Number of flights for a distance",
position: "bottom",
},
tooltip: {
displayColors: false,
callbacks: {
label: (item) => {
const orig = preparedData.airportMap.get(item.raw.origin);
const dest = preparedData.airportMap.get(item.raw.destination);
const result = `${orig.name} (${orig.iata}) → ${dest.name} (${dest.iata}): ${Math.round(item.raw.distance)} km`;
return result;
},
},
},
},
scales: {
x: {
title: {
text: "distance →",
align: "end",
display: true,
},
ticks: {
format: {
style: "unit",
unit: "kilometer",
},
},
},
y: {
type: "logarithmic",
title: {
text: "# of flights →",
align: "end",
display: true,
},
},
},
},
});
}
function openTooltip() {
if (!currentChart)
throw new Error("No chart is present, please draw a chart first");
const renderedDataset = currentChart.getDatasetMeta(0);
const node = currentChart.canvas;
const rect = node.getBoundingClientRect();
// Index 2426 is carefully chosen to display a lot of lines (which depends
// on the zoom level).
const point = renderedDataset.data[2426];
const event = new MouseEvent("mousemove", {
clientX: rect.left + point.x,
clientY: rect.top + point.y,
cancelable: true,
bubbles: true,
});
node.dispatchEvent(event);
}
function reset() {
if (currentChart) {
currentChart.destroy();
currentChart = null;
}
}
async function runAllTheThings() {
[
// Force prettier to use a multiline formatting
"prepare",
"add-scatter-chart-button",
"open-tooltip",
].forEach((id) => document.getElementById(id).click());
}
document.getElementById("prepare").addEventListener("click", prepare);
document.getElementById("add-scatter-chart-button").addEventListener("click", drawScattered);
document.getElementById("open-tooltip").addEventListener("click", openTooltip);
document.getElementById("reset").addEventListener("click", reset);
document.getElementById("run-all").addEventListener("click", runAllTheThings);
if (import.meta.env.DEV)
runAllTheThings();

Просмотреть файл

@ -0,0 +1,4 @@
Origin of the datasets:
airports.csv: https://github.com/d3/d3/blob/gh-pages-old/talk/20111116/airports.csv
flights-airports.csv: https://github.com/d3/d3/blob/gh-pages-old/talk/20111116/flights-airport.csv

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Observable Plot</strong> (<a href="./observable-plot.html">open in full page</a>)</summary>
<iframe src="./observable-plot.html"></iframe>
</details>
<details open>
<summary>
<strong>Chart.js (<a href="./chartjs.html">open in full page</a>)</strong>
</summary>
<iframe src="./chartjs.html"></iframe>
</details>
</div>
</body>
</html>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chart.js</title>
<style type="text/css" media="all">
label {
user-select: none;
display: inline-flex;
align-items: center;
}
</style>
<script type="module" crossorigin src="./assets/chartjs-7fd89fd7.js"></script>
<link rel="modulepreload" crossorigin href="./assets/flights-airports-9a9e6422.js">
</head>
<body>
<div id="app">
<button id="run-all" type="button">Run all scenario (development only)</button>
<button id="prepare" type="button">Prepare data</button>
<button id="add-scatter-chart-button" type="button">Draw a scatter chart</button>
<button id="open-tooltip" type="button">Open tooltips</button>
<button id="reset" type="button">Reset</button>
<label><input type="checkbox" id="opaque-color" />Use opaque color for next draw</label>
</div>
<canvas id="chart"></canvas>
<script>
// This hack allows to capture the work normally happening in a rAF. We
// may be able to remove it if the runner improves.
window.requestAnimationFrame = (cb) => window.setTimeout(cb, 0);
window.cancelAnimationFrame = window.clearTimeout;
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Observable Plot</strong> (<a href="./observable-plot.html">open in full page</a>)</summary>
<iframe src="./observable-plot.html"></iframe>
</details>
<details open>
<summary>
<strong>Chart.js (<a href="./chartjs.html">open in full page</a>)</strong>
</summary>
<iframe src="./chartjs.html"></iframe>
</details>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1" />
</head>
<body>
<h1>Charting workloads</h1>
<ul>
<li><a href="./observable-plot.html">Observable Plot</a></li>
<li><a href="./chartjs.html">Chart.js</a></li>
</ul>
<p>You can also find the <a href="./developer.html">developer page</a> that includes all workloads in iframes.</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Observable Plot</title>
<style>
label {
display: inline-flex;
align-content: center;
gap: 3px;
margin: 0 3px;
}
#airport-group-size {
min-width: 30px;
}
</style>
<script type="module" crossorigin src="./assets/plot-37d2a5fb.js"></script>
<link rel="modulepreload" crossorigin href="./assets/flights-airports-9a9e6422.js">
</head>
<body>
<div id="app">
<button id="run-all" type="button">Run all scenario (development only)</button>
<button id="prepare" type="button">Prepare data</button>
<button id="add-stacked-chart-button" type="button">Add a stacked chart</button>
<button id="add-dotted-chart-button" type="button">Add a dotted chart</button>
<button id="reset" type="button">Reset</button>
<label>
Number of airports to keep in each group:
<span id="airport-group-size"></span>
<input type="range" id="airport-group-size-input" min="0" max="20" value="6" />
</label>
</div>
<div id="chart"></div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1" />
</head>
<body>
<h1>Charting workloads</h1>
<ul>
<li><a href="./observable-plot.html">Observable Plot</a></li>
<li><a href="./chartjs.html">Chart.js</a></li>
</ul>
<p>You can also find the <a href="./developer.html">developer page</a> that includes all workloads in iframes.</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Observable Plot</title>
<style>
label {
display: inline-flex;
align-content: center;
gap: 3px;
margin: 0 3px;
}
#airport-group-size {
min-width: 30px;
}
</style>
</head>
<body>
<div id="app">
<button id="run-all" type="button">Run all scenario (development only)</button>
<button id="prepare" type="button">Prepare data</button>
<button id="add-stacked-chart-button" type="button">Add a stacked chart</button>
<button id="add-dotted-chart-button" type="button">Add a dotted chart</button>
<button id="reset" type="button">Reset</button>
<label>
Number of airports to keep in each group:
<span id="airport-group-size"></span>
<input type="range" id="airport-group-size-input" min="0" max="20" value="6" />
</label>
</div>
<div id="chart"></div>
<script type="module" src="/observable-plot.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,233 @@
import * as Plot from "@observablehq/plot";
import { csvParse } from "d3-dsv";
import * as d3Array from "d3-array";
import { format as d3Format } from "d3-format";
// These 2 imports use some vite machinery to directly import these files as
// strings. Then they are constants and this reduces the load on the GC.
import airportsString from "./datasets/airports.csv?raw";
import flightsString from "./datasets/flights-airports.csv?raw";
const ROOT = document.getElementById("chart");
const DEFAULT_OPTIONS = {
// This width is really a max-width: the plot adjusts using the available
// width as well.
width: 2000,
// The height is especially used for the ratio.
height: 1000,
};
let preparedData;
function prepare() {
/**
* AirportInformation: { state, iata, name, city, country, latitude, longitude }
* airports: Array<AirportInformation>
* flights: Array<{ origin, destination, count }>
*/
const { airports, flights } = parseAirportsInformation();
/**
* flightsByAirport: Map<iata: string, { origin: number, destination: number, total: number}>
*/
const flightsByAirport = groupFlightsByAirports(flights);
/**
* byAirport: Map<iata: string, AirportInformation>
*/
const byAirport = d3Array.index(airports, (d) => d.iata);
/* Array<[state, AirportInformation[]]> */
const airportsGroupedByStateArray = d3Array.groups(airports, (d) => d.state);
/* DescSortedArray<[{ state: string, total: number, mostUsedAirportsInState: AirportInformation[] }]> */
const stateInformationSortedArray = airportsGroupedByStateArray
.map(([state, airportsInState]) => {
const totalFlightsInState = d3Array.sum(airportsInState, ({ iata }) => flightsByAirport.get(iata)?.total);
const sorted = d3Array.sort(airportsInState, ({ iata }) => -flightsByAirport.get(iata)?.total);
const mostUsedAirportsInState = sorted.slice(0, airportCountPerGroup());
return {
state,
total: totalFlightsInState,
mostUsedAirports: mostUsedAirportsInState,
};
})
.sort((stateA, stateB) => stateB.total - stateA.total);
/* Array<state: string> */
const stateSortedArray = stateInformationSortedArray.map(({ state }) => state);
// Flatten the information in preparedData.stateInformationSortedArray, so that we
// have one item == one airport information.
/* Array<{state, iata, name, city, index, origin, destination, total}> */
const plotData = stateInformationSortedArray.flatMap(({ mostUsedAirports, total, state }) => {
const enrichedMostUsedAirports = mostUsedAirports.map(({ iata, name, city }, index) => ({
state,
iata,
name,
city,
index, // This will be used to have consistent colors.
...flightsByAirport.get(iata),
}));
const otherTotal = total - d3Array.sum(mostUsedAirports, ({ iata }) => flightsByAirport.get(iata)?.total);
if (otherTotal > 0) {
enrichedMostUsedAirports.push({
state,
iata: "Other",
total: otherTotal,
index: enrichedMostUsedAirports.length,
});
}
return enrichedMostUsedAirports;
});
preparedData = {
airports,
flights,
flightsByAirport,
byAirport,
stateInformationSortedArray,
stateSortedArray,
plotData,
};
}
function parseAirportsInformation() {
return {
airports: csvParse(airportsString),
flights: csvParse(flightsString),
};
}
function groupFlightsByAirports(flights) {
const flightsByAirport = new Map();
for (const { origin, destination, count } of flights) {
const infoForOriginAirport = flightsByAirport.get(origin) ?? { origin: 0, destination: 0, total: 0 };
const intCount = Number(count);
if (Number.isNaN(intCount)) {
console.error(`Couldn't convert ${count} to number.`);
continue;
}
infoForOriginAirport.origin += intCount;
infoForOriginAirport.total += intCount;
flightsByAirport.set(origin, infoForOriginAirport);
const infoForDestinationAirport = flightsByAirport.get(destination) ?? { origin: 0, destination: 0, total: 0 };
infoForDestinationAirport.destination += intCount;
infoForDestinationAirport.total += intCount;
flightsByAirport.set(destination, infoForDestinationAirport);
}
return flightsByAirport;
}
function isReady() {
return !!preparedData;
}
function addStackedBars() {
if (!isReady())
throw new Error("Please prepare the data first.");
const options = {
...DEFAULT_OPTIONS,
color: { type: "categorical" },
x: {
domain: preparedData.stateSortedArray,
},
y: { grid: true, tickFormat: "~s" },
marks: [
Plot.barY(preparedData.plotData, {
x: "state",
y: "total",
fill: "index",
title: (d) => `${d.iata === "Other" ? "Other" : `${d.name}, ${d.city} (${d.iata})`}\n${d3Format(",")(d.total)} flights`,
}),
Plot.text(preparedData.stateInformationSortedArray, { x: "state", y: "total", text: (d) => d3Format(".2~s")(d.total), dy: -10 }),
Plot.ruleY([0]),
],
};
ROOT.append(Plot.plot(options));
}
function addDottedBars() {
if (!isReady())
throw new Error("Please prepare the data first.");
const data = [...preparedData.flightsByAirport]
.flatMap(([iata, { origin, destination }]) => {
const airportInformation = preparedData.byAirport.get(iata);
return [
{ ...airportInformation, value: -origin },
{ ...airportInformation, value: destination },
];
})
.filter((d) => d.value);
const options = {
...DEFAULT_OPTIONS,
color: { type: "threshold", domain: [0] },
x: {
domain: preparedData.stateSortedArray,
},
y: {
grid: true,
label: "← outward Number of flights inward →",
labelAnchor: "center",
tickFormat: (v) => d3Format("~s")(Math.abs(v)),
type: "pow",
exponent: 0.2,
},
marks: [
Plot.dot(data, {
x: "state",
y: "value",
r: 4,
stroke: "value",
strokeWidth: 3,
title: (d) => `${d.iata === "Other" ? "Other" : `${d.name}, ${d.city} (${d.iata})`}\n${d3Format(",")(Math.abs(d.value))} ${d.value > 0 ? "inward" : "outward"} flights`,
}),
Plot.ruleY([0]),
],
};
ROOT.append(Plot.plot(options));
}
function reset() {
ROOT.textContent = "";
}
async function runAllTheThings() {
reset();
[
// Force prettier to use a multiline formatting
"prepare",
"add-stacked-chart-button",
"add-dotted-chart-button",
].forEach((id) => document.getElementById(id).click());
}
// This is the number of airports we keep for each state in the stacked bar
// graph. One additional group will be added, that will sum all airports that
// haven't been kept. It's retrieved from the input directly.
function airportCountPerGroup() {
return document.querySelector("#airport-group-size-input").value;
}
function onGroupSizeInputChange() {
document.querySelector("#airport-group-size").textContent = airportCountPerGroup();
if (import.meta.env.DEV) {
// In dev mode, redraw everything
runAllTheThings();
}
}
document.getElementById("prepare").addEventListener("click", prepare);
document.getElementById("add-stacked-chart-button").addEventListener("click", addStackedBars);
document.getElementById("add-dotted-chart-button").addEventListener("click", addDottedBars);
document.getElementById("reset").addEventListener("click", reset);
document.getElementById("run-all").addEventListener("click", runAllTheThings);
document.getElementById("airport-group-size-input").addEventListener("input", onGroupSizeInputChange);
onGroupSizeInputChange();
if (import.meta.env.DEV)
runAllTheThings();

1071
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/package-lock.json сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,19 @@
{
"name": "charts-benchmark",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.1.0"
},
"dependencies": {
"@observablehq/plot": "^0.6.4",
"chart.js": "^4.2.1",
"d3-dsv": "^3.0.1"
}
}

Просмотреть файл

@ -0,0 +1,78 @@
# Chart benchmarks
## How to run locally
This project uses `npm` to manage dependencies and run scripts. It has been
tested with node v18 but should work with other versions of node.
This uses [vite](https://vitejs.dev/) as builder but this should be fairly
transparent.
### Install dependencies
```
npm i
```
### Run the development server
```
npm run dev
```
### Build the app
```
npm run build
```
### Preview the production build
```
npm run preview
```
## Included benchmarks
All the benchmarks are included in iframes in the index page. But it may be more
convenient to open these benchmarks with their specific files.
### [Observable Plot](https://github.com/observablehq/plot)
You can load this benchmark with the `/observable-plot.html` page, for example
http://localhost:5173/observable-plot.html if you run it locally or
http://localhost:7000/resources/charts/dist/observable-plot.html in the
context of the speedometer.
Observable Plot is D3-based and outputs SVG.
When run in development mode, the page will automatically execute the 3
included graphs:
- a stacked bar graph
- a grouped bar graph
- a graph using dots
In production mode nothing executes by default, the user needs to push the
buttons to run any code. That's how the benchmark exercizes this code.
To build these graphs, we use datasets representing flight information in the
US. You can consult them in the [datasets directory](./datasets).
### [ChartJS](https://github.com/chartjs/Chart.js)
You can load this benchmark with the `/chartjs.html` page, for example
http://localhost:5173/chartjs.html if you run it locally or
http://localhost:7000/resources/charts/dist/chartjs.html in the
context of speedometer.
ChartJS is canvas-based.
When run in development mode, the page will automatically execute the scatter
graph.
In production mode nothing executes by default, the user needs to push the
buttons to run any code. That's how the benchmark exercizes this code.
To build these graphs, we use datasets representing flight information in the
US. You can consult them in the [datasets directory](./datasets).

Просмотреть файл

@ -0,0 +1,17 @@
import { resolve } from "path";
import { defineConfig } from "vite";
export default defineConfig({
base: "./", // Since this will be loaded from the project root
build: {
modulePreload: { polyfill: false },
minify: false,
rollupOptions: {
input: {
index: resolve(__dirname, "index.html"),
developer: resolve(__dirname, "developer.html"),
plot: resolve(__dirname, "observable-plot.html"),
chartjs: resolve(__dirname, "chartjs.html"),
},
},
},
});

Просмотреть файл

@ -0,0 +1,253 @@
import { Suites, Tags } from "./tests.mjs";
import { params, defaultParams } from "./params.mjs";
export function createDeveloperModeContainer() {
const container = document.createElement("div");
container.className = "developer-mode";
const details = document.createElement("details");
const summary = document.createElement("summary");
summary.textContent = "Developer Mode";
details.append(summary);
const content = document.createElement("div");
content.className = "developer-mode-content";
content.append(createUIForSuites());
content.append(document.createElement("hr"));
content.append(createUIForMeasurementMethod());
content.append(document.createElement("br"));
content.append(createUIForWarmupSuite());
content.append(document.createElement("br"));
content.append(createUIForIterationCount());
content.append(document.createElement("hr"));
content.append(createUIForRun());
details.append(content);
container.append(details);
return container;
}
export function createUIForMeasurementMethod() {
let check = document.createElement("input");
check.type = "checkbox";
check.id = "measurement-method";
check.checked = params.measurementMethod === "raf";
check.onchange = () => {
params.measurementMethod = check.checked ? "raf" : "timer";
updateURL();
};
let label = document.createElement("label");
label.append(check, " ", "rAF timing");
return label;
}
export function createUIForWarmupSuite() {
let check = document.createElement("input");
check.type = "checkbox";
check.id = "warmup-suite";
check.checked = !!params.useWarmupSuite;
check.onchange = () => {
params.useWarmupSuite = check.checked;
updateURL();
};
let label = document.createElement("label");
label.append(check, " ", "warmup suite");
return label;
}
export function createUIForIterationCount() {
let range = document.createElement("input");
range.type = "range";
range.id = "iteration-count";
range.min = 1;
range.max = 20;
range.value = params.iterationCount;
let label = document.createElement("label");
let countLabel = document.createElement("span");
countLabel.textContent = params.iterationCount;
label.append("iterations: ", countLabel, document.createElement("br"), range);
range.oninput = () => {
countLabel.textContent = range.value;
};
range.onchange = () => {
params.iterationCount = parseInt(range.value);
updateURL();
};
return label;
}
export function createUIForSuites() {
const control = document.createElement("nav");
const ol = document.createElement("ol");
const checkboxes = [];
const setSuiteEnabled = (suiteIndex, enabled) => {
Suites[suiteIndex].disabled = !enabled;
checkboxes[suiteIndex].checked = enabled;
};
for (const suite of Suites) {
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.id = suite.name;
checkbox.type = "checkbox";
checkbox.checked = !suite.disabled;
checkbox.onchange = () => {
suite.disabled = !checkbox.checked;
updateURL();
};
checkboxes.push(checkbox);
const label = document.createElement("label");
label.append(checkbox, " ", suite.name);
li.appendChild(label);
label.onclick = (event) => {
if (event?.ctrlKey || event?.metaKey) {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++) {
if (Suites[suiteIndex] !== suite)
setSuiteEnabled(suiteIndex, false);
else
setSuiteEnabled(suiteIndex, true);
}
}
};
ol.appendChild(li);
}
control.appendChild(ol);
let buttons = control.appendChild(document.createElement("div"));
buttons.className = "button-bar";
let button = document.createElement("button");
button.textContent = "Select all";
button.onclick = () => {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++)
setSuiteEnabled(suiteIndex, true);
updateURL();
};
buttons.appendChild(button);
button = document.createElement("button");
button.textContent = "Unselect all";
button.onclick = () => {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++)
setSuiteEnabled(suiteIndex, false);
updateURL();
};
buttons.appendChild(button);
let i = 0;
const kTagsPerLine = 3;
for (const tag of Tags) {
if (tag === "all")
continue;
if (!(i % kTagsPerLine)) {
buttons = control.appendChild(document.createElement("div"));
buttons.className = "button-bar";
}
i++;
button = document.createElement("button");
button.className = "tag";
button.textContent = `#${tag}`;
button.dataTag = tag;
button.onclick = (event) => {
const extendSelection = event?.shiftKey;
const invertSelection = event?.ctrlKey || event?.metaKey;
const selectedTag = event.target.dataTag;
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++) {
let enabled = Suites[suiteIndex].tags.includes(selectedTag);
if (invertSelection)
enabled = !enabled;
if (extendSelection && !enabled)
continue;
setSuiteEnabled(suiteIndex, enabled);
}
updateURL();
};
buttons.appendChild(button);
}
return control;
}
function createUIForRun() {
let button = document.createElement("button");
button.textContent = `Start Test`;
button.onclick = (event) => {
globalThis.benchmarkClient.start();
}
let buttons = document.createElement("div");
buttons.className = "button-bar";
buttons.appendChild(button);
return buttons
}
function updateURL() {
const url = new URL(window.location.href);
// If less than all suites are selected then change the URL "Suites" GET parameter
// to comma separate only the selected
const selectedSuites = Suites.filter((suite) => !suite.disabled);
if (!selectedSuites.length) {
url.searchParams.delete("tags");
url.searchParams.delete("suites");
url.searchParams.delete("suite");
} else {
url.searchParams.delete("tags");
url.searchParams.delete("suite");
// Try finding common tags that would result in the current suite selection.
let commonTags = new Set(selectedSuites[0].tags);
for (const suite of Suites) {
if (suite.disabled)
suite.tags.forEach((tag) => commonTags.delete(tag));
else
commonTags = new Set(suite.tags.filter((tag) => commonTags.has(tag)));
}
if (commonTags.size) {
const tags = [...commonTags][0];
if (tags === "default")
url.searchParams.delete("tags");
else
url.searchParams.set("tags", tags);
url.searchParams.delete("suites");
} else {
url.searchParams.delete("tags");
url.searchParams.set("suites", selectedSuites.map((suite) => suite.name).join(","));
}
}
if (params.measurementMethod !== "raf")
url.searchParams.set("measurementMethod", "timer");
else
url.searchParams.delete("measurementMethod");
if (params.iterationCount !== defaultParams.iterationCount)
url.searchParams.set("iterationCount", params.iterationCount);
else
url.searchParams.delete("iterationCount");
if (params.useWarmupSuite !== defaultParams.useWarmupSuite)
url.searchParams.set("useWarmupSuite", params.useWarmupSuite);
else
url.searchParams.delete("useWarmupSuite");
// Only push state if changed
url.search = decodeURIComponent(url.search);
if (url.href !== window.location.href)
window.history.pushState({}, "", url);
}

Просмотреть файл

@ -0,0 +1,26 @@
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

Просмотреть файл

@ -0,0 +1,2 @@
longtext.html is "Du côté de chez Swann" by Marcel Proust, downloaded from https://www.gutenberg.org/ebooks/2650
longscript.js has been downloaded from https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,206 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>CodeMirror Test</title>
<script>
window.requestIdleCallback = undefined;
window.cancelIdleCallback = undefined;
</script>
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="long">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Long text
</button>
<button id="short">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Short text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
<script>
// This hack allows to capture the work normally happening in a rAF. We
// may be able to remove it if the runner improves.
window.requestAnimationFrame = (cb) => window.setTimeout(cb, 0);
window.cancelAnimationFrame = window.clearTimeout;
</script>
<script type="module">
import { code as shorttext } from "./shorttext.js";
import { code as longtext } from "./longtext.js";
import editor from "./codemirror.js";
let editorContainer = document.querySelector("#editor");
let editorInstance = null;
let buttons = {
create: document.querySelector("#create"),
highlight: document.querySelector("#highlight"),
unhighlight: document.querySelector("#unhighlight"),
long: document.querySelector("#long"),
short: document.querySelector("#short"),
scroll: document.querySelector("#scroll"),
layout: document.querySelector("#layout"),
};
buttons.scroll.addEventListener("click", scroll);
buttons.highlight.addEventListener("click", highlight);
buttons.unhighlight.addEventListener("click", unhighlight);
buttons.long.addEventListener("click", long);
buttons.short.addEventListener("click", short);
buttons.layout.addEventListener("click", layout);
buttons.create.addEventListener("click", (e) => {
if (!editorInstance) {
editorInstance = editor(editorContainer);
editorInstance.ready.then(() => {
buttons.unhighlight.classList.add("active", "true");
buttons.create.setAttribute("disabled", "true");
});
}
});
function layout() {
// Todo - is this necessary with the runner?
const body = document.body.getBoundingClientRect();
layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
function highlight() {
buttons.unhighlight.classList.toggle("active", false);
buttons.highlight.classList.toggle("active", true);
editorInstance.format(true);
}
function unhighlight() {
buttons.unhighlight.classList.toggle("active", true);
buttons.highlight.classList.toggle("active", false);
editorInstance.format(false);
}
function long() {
buttons.short.classList.toggle("active", false);
buttons.long.classList.toggle("active", true);
editorInstance.setValue(longtext);
}
function short() {
buttons.short.classList.toggle("active", true);
buttons.long.classList.toggle("active", false);
editorInstance.setValue(shorttext);
}
function scroll() {
let isTop = editorInstance.getScrollTop() == 0;
editorInstance.setScrollTop(isTop ? editorInstance.getScrollHeight() : 0);
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,47 @@
// https://codemirror.net/examples/bundle/
import { EditorView, basicSetup } from "codemirror";
import { StateEffect } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript";
let lang = javascript();
let extensions = [basicSetup, EditorView.lineWrapping];
export default function (element, value) {
let view = new EditorView({
extensions,
parent: element,
doc: value,
wordWrapColumn: 80,
});
return {
editor: view,
// Anything before this promise resolves will happen before timing starts
ready: Promise.resolve(),
getScrollHeight() {
return element.scrollHeight;
},
getScrollTop() {
return element.scrollTop;
},
setScrollTop(value) {
element.scrollTop = value;
},
setValue: (value) =>
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: value },
}),
format(on) {
// https://codemirror.net/examples/config/
// https://discuss.codemirror.net/t/cm6-dynamically-switching-syntax-theme-w-reconfigure/2858/6
if (on && extensions.length === 2)
extensions.push(lang);
else if (!on && extensions.length === 3)
extensions.pop();
view.dispatch({
effects: StateEffect.reconfigure.of(extensions),
});
},
};
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,66 @@
:root {
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
line-height: 1.5;
font-weight: 400;
font-size: 13px;
color: #213547;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: none; /* Note that Firefox implements this with the webkit prefix. */
text-size-adjust: none;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #747bff;
}
body {
margin: 0;
display: flex;
min-width: 320px;
height: 100vh;
padding: 5px;
box-sizing: border-box;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
}
#editor {
flex: 1;
overflow: auto;
padding: 2px 5px;
}
#controls {
display: flex;
padding-bottom: 5px;
}
#controls button {
display: flex;
align-items: center;
margin: 0 2px;
padding: 2px;
}
#controls button.active {
color: blue;
}

Просмотреть файл

@ -0,0 +1,128 @@
const style = "";
const code$1 = `
function x() {
console.log("Hello world!");
}
`;
const text = `
<h1>MOBY-DICK;<br>or, THE WHALE.</h1><p>By Herman Melville</p><hr><h2>CHAPTER 1. Loomings.</h2><p>Call me Ishmael. Some years agonever mind how long preciselyhaving little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking peoples hats offthen, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.</p>
`;
var base = {
8: "Backspace",
9: "Tab",
10: "Enter",
12: "NumLock",
13: "Enter",
16: "Shift",
17: "Control",
18: "Alt",
20: "CapsLock",
27: "Escape",
32: " ",
33: "PageUp",
34: "PageDown",
35: "End",
36: "Home",
37: "ArrowLeft",
38: "ArrowUp",
39: "ArrowRight",
40: "ArrowDown",
44: "PrintScreen",
45: "Insert",
46: "Delete",
59: ";",
61: "=",
91: "Meta",
92: "Meta",
106: "*",
107: "+",
108: ",",
109: "-",
110: ".",
111: "/",
144: "NumLock",
145: "ScrollLock",
160: "Shift",
161: "Shift",
162: "Control",
163: "Control",
164: "Alt",
165: "Alt",
173: "-",
186: ";",
187: "=",
188: ",",
189: "-",
190: ".",
191: "/",
192: "`",
219: "[",
220: "\\",
221: "]",
222: "'"
};
var shift = {
48: ")",
49: "!",
50: "@",
51: "#",
52: "$",
53: "%",
54: "^",
55: "&",
56: "*",
57: "(",
59: ":",
61: "+",
173: "_",
186: ":",
187: "+",
188: "<",
189: "_",
190: ">",
191: "?",
192: "~",
219: "{",
220: "|",
221: "}",
222: '"'
};
var chrome = typeof navigator != "undefined" && /Chrome\/(\d+)/.exec(navigator.userAgent);
var mac = typeof navigator != "undefined" && /Mac/.test(navigator.platform);
var ie = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
var brokenModifierNames = mac || chrome && +chrome[1] < 57;
for (var i = 0; i < 10; i++)
base[48 + i] = base[96 + i] = String(i);
for (var i = 1; i <= 24; i++)
base[i + 111] = "F" + i;
for (var i = 65; i <= 90; i++) {
base[i] = String.fromCharCode(i + 32);
shift[i] = String.fromCharCode(i);
}
for (var code in base)
if (!shift.hasOwnProperty(code))
shift[code] = base[code];
function keyName(event) {
var ignoreKey = brokenModifierNames && (event.ctrlKey || event.altKey || event.metaKey) || ie && event.shiftKey && event.key && event.key.length == 1 || event.key == "Unidentified";
var name = !ignoreKey && event.key || (event.shiftKey ? shift : base)[event.keyCode] || event.key || "Unidentified";
if (name == "Esc")
name = "Escape";
if (name == "Del")
name = "Delete";
if (name == "Left")
name = "ArrowLeft";
if (name == "Up")
name = "ArrowUp";
if (name == "Right")
name = "ArrowRight";
if (name == "Down")
name = "ArrowDown";
return name;
}
export {
base as b,
code$1 as c,
keyName as k,
shift as s,
text as t
};

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CodeMirror Test</title>
<script>
window.requestIdleCallback = undefined;
window.cancelIdleCallback = undefined;
</script>
<script type="module" crossorigin src="./assets/codemirror-521de7ab.js"></script>
<link rel="modulepreload" crossorigin href="./assets/index.es-02a92ebc.js">
<link rel="stylesheet" href="./assets/index-2feebe42.css">
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="long">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Long text
</button>
<button id="short">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Short text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
<script>
// This hack allows to capture the work normally happening in a rAF. We
// may be able to remove it if the runner improves.
window.requestAnimationFrame = (cb) => window.setTimeout(cb, 0);
window.cancelAnimationFrame = window.clearTimeout;
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Code Editors</strong></summary>
<iframe src="./codemirror.html"></iframe>
</details>
<details open>
<summary><strong>WYSIWYG Editors</strong></summary>
<iframe src="./tiptap.html"></iframe>
</details>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TipTap Test</title>
<script type="module" crossorigin src="./assets/tiptap-95a40ba8.js"></script>
<link rel="modulepreload" crossorigin href="./assets/index.es-02a92ebc.js">
<link rel="stylesheet" href="./assets/index-2feebe42.css">
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="long">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Long text
</button>
<button id="short">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Short text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Просмотреть файл

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Code Editors</strong></summary>
<iframe src="./codemirror.html"></iframe>
</details>
<details open>
<summary><strong>WYSIWYG Editors</strong></summary>
<iframe src="./tiptap.html"></iframe>
</details>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,2 @@
export { default as code } from "./assets/longscript.js?raw";
export { default as text } from "./assets/longtext.html?raw";

2712
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/package-lock.json сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,18 @@
{
"name": "editors",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@codemirror/lang-javascript": "^6.1.4",
"@tiptap/core": "^2.0.0-beta.217",
"@tiptap/starter-kit": "^2.0.0-beta.217",
"codemirror": "^6.0.1",
"vite": "^4.1.0"
}
}

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Просмотреть файл

@ -0,0 +1,27 @@
## Description
Lots of people edit text content in the browser. Lots of that content, like WYSIWYG content or code, is too rich or complex to represent well with a `<textarea>`. Sites typically rely on advanced editor libraries for this, and we should make sure browsers perform well at common patterns used by them.
## Screenshot
![screenshot](./screenshot.jpg)
## What are we testing
- Basic DOM and editing
- Virtualization (DOM content changing during scroll)
- Basic flex layout with SVG icons
## How are we testing
The test simulates a real-world user flow by loading a number of popular editor libraries. After the initial load is complete, the following steps are timed:
- Setting to a fairly large value
- "Formatting" the text - in code editors this means turning on syntax highlighting, and in WYSISWYG this means bolding all of the contents
- Scrolling to the bottom of the editor
## Developer Documentation
The app was created with `npm create vite@latest editors`, and can be previewed with `npm run dev`. In order to update the files run in the harness you have to use `npm run build` which will recreate the `dist/` directory.
The built test can be loaded within the harness using i.e. http://localhost:7000/?suite=Editor-CodeMirror&startAutomatically=true, or the static versions at http://localhost:7000/resources/editors/dist/.

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/screenshot.jpg поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 37 KiB

Просмотреть файл

@ -0,0 +1,9 @@
export const code = `
function x() {
console.log("Hello world!");
}
`;
export const text = `
<h1>MOBY-DICK;<br>or, THE WHALE.</h1><p>By Herman Melville</p><hr><h2>CHAPTER 1. Loomings.</h2><p>Call me Ishmael. Some years agonever mind how long preciselyhaving little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking peoples hats offthen, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.</p>
`;

Просмотреть файл

@ -0,0 +1,66 @@
:root {
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
line-height: 1.5;
font-weight: 400;
font-size: 13px;
color: #213547;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: none; /* Note that Firefox implements this with the webkit prefix. */
text-size-adjust: none;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #747bff;
}
body {
margin: 0;
display: flex;
min-width: 320px;
height: 100vh;
padding: 5px;
box-sizing: border-box;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
}
#editor {
flex: 1;
overflow: auto;
padding: 2px 5px;
}
#controls {
display: flex;
padding-bottom: 5px;
}
#controls button {
display: flex;
align-items: center;
margin: 0 2px;
padding: 2px;
}
#controls button.active {
color: blue;
}

Просмотреть файл

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>TipTap Test</title>
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="long">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Long text
</button>
<button id="short">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Short text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
<script type="module">
import { text as shorttext } from "./shorttext.js";
import { text as longtext } from "./longtext.js";
import editor from "./tiptap.js";
let editorContainer = document.querySelector("#editor");
let editorInstance = null;
let buttons = {
create: document.querySelector("#create"),
highlight: document.querySelector("#highlight"),
unhighlight: document.querySelector("#unhighlight"),
long: document.querySelector("#long"),
short: document.querySelector("#short"),
scroll: document.querySelector("#scroll"),
layout: document.querySelector("#layout"),
};
buttons.scroll.addEventListener("click", scroll);
buttons.highlight.addEventListener("click", highlight);
buttons.unhighlight.addEventListener("click", unhighlight);
buttons.long.addEventListener("click", long);
buttons.short.addEventListener("click", short);
buttons.layout.addEventListener("click", layout);
buttons.create.addEventListener("click", (e) => {
if (!editorInstance) {
editorInstance = editor(editorContainer);
editorInstance.ready.then(() => {
buttons.unhighlight.classList.add("active", "true");
buttons.create.setAttribute("disabled", "true");
});
}
});
function layout() {
// Todo - is this necessary with the runner?
const body = document.body.getBoundingClientRect();
layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
function highlight() {
buttons.unhighlight.classList.toggle("active", false);
buttons.highlight.classList.toggle("active", true);
editorInstance.format(true);
}
function unhighlight() {
buttons.unhighlight.classList.toggle("active", true);
buttons.highlight.classList.toggle("active", false);
editorInstance.format(false);
}
function long() {
buttons.short.classList.toggle("active", false);
buttons.long.classList.toggle("active", true);
editorInstance.setValue(longtext);
}
function short() {
buttons.short.classList.toggle("active", true);
buttons.long.classList.toggle("active", false);
editorInstance.setValue(shorttext);
}
function scroll() {
let isTop = editorInstance.getScrollTop() == 0;
editorInstance.setScrollTop(isTop ? editorInstance.getScrollHeight() : 0);
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,42 @@
// https://tiptap.dev/examples/default
import { Editor } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
export default function (element, value) {
let editor = new Editor({
element,
extensions: [StarterKit],
content: value,
editorProps: {
attributes: {
spellcheck: "false",
},
},
});
return {
editor,
// Anything before this promise resolves will happen before timing starts
ready: Promise.resolve(),
getScrollHeight() {
return element.scrollHeight;
},
getScrollTop() {
return element.scrollTop;
},
setScrollTop(value) {
element.scrollTop = value;
},
setValue(value) {
// Recommendation is to call focus before most commands
// https://tiptap.dev/api/commands#chain-commands
editor.chain().focus().setContent(value).setTextSelection(0).run();
element.scrollTop = 0;
},
format(on) {
if (on)
editor.chain().focus().selectAll().setBold().setTextSelection(0).run();
else
editor.chain().focus().selectAll().unsetBold().setTextSelection(0).run();
},
};
}

Просмотреть файл

@ -0,0 +1,16 @@
import { resolve } from "path";
import { defineConfig } from "vite";
export default defineConfig({
base: "./", // Since this will be loaded from the project root
build: {
modulePreload: { polyfill: false },
minify: false,
rollupOptions: {
input: {
codemirror: resolve(__dirname, "codemirror.html"),
main: resolve(__dirname, "index.html"),
tiptap: resolve(__dirname, "tiptap.html"),
},
},
},
});

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/favicon.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/gauge.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/gauge@2x.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 33 KiB

Просмотреть файл

@ -0,0 +1,213 @@
import { BenchmarkRunner } from "./benchmark-runner.mjs";
import { params } from "./params.mjs";
import { Suites } from "./tests.mjs";
class InteractiveBenchmarkRunner extends BenchmarkRunner {
_stepPromise = undefined;
_stepPromiseResolve = undefined;
_isRunning = false;
_isStepping = false;
constructor(suites, iterationCount) {
super(suites);
this._client = this._createClient();
if (!Number.isInteger(iterationCount) || iterationCount <= 0)
throw Error("iterationCount must be a positive integer.");
this._iterationCount = iterationCount;
}
_createClient() {
return {
willStartFirstIteration: this._start.bind(this),
willRunTest: this._testStart.bind(this),
didRunTest: this._testDone.bind(this),
didRunSuites: this._iterationDone.bind(this),
didFinishLastIteration: this._done.bind(this),
};
}
_start() {
if (this._isRunning)
throw Error("Runner was not stopped before starting;");
this._isRunning = true;
if (this._isStepping)
this._stepPromise = this._newStepPromise();
}
_step() {
if (!this._stepPromise) {
// Allow switching to stepping mid-run.
this._stepPromise = this._newStepPromise();
} else {
const resolve = this._stepPromiseResolve;
this._stepPromise = this._newStepPromise();
resolve();
}
}
_newStepPromise() {
return new Promise((resolve) => {
this._stepPromiseResolve = resolve;
});
}
_testStart(suite, test) {
test.anchor.classList.add("running");
}
async _testDone(suite, test) {
const classList = test.anchor.classList;
classList.remove("running");
classList.add("ran");
if (this._isStepping)
await this._stepPromise;
}
_iterationDone(measuredValues) {
let results = "";
for (const suiteName in measuredValues.tests) {
let suiteResults = measuredValues.tests[suiteName];
for (const testName in suiteResults.tests) {
let testResults = suiteResults.tests[testName];
for (const subtestName in testResults.tests)
results += `${suiteName} : ${testName} : ${subtestName}: ${testResults.tests[subtestName]} ms\n`;
}
results += `${suiteName} : ${suiteResults.total} ms\n`;
}
results += `Arithmetic Mean : ${measuredValues.mean} ms\n`;
results += `Geometric Mean : ${measuredValues.geomean} ms\n`;
results += `Total : ${measuredValues.total} ms\n`;
results += `Score : ${measuredValues.score} rpm\n`;
if (!results)
return;
const pre = document.createElement("pre");
document.body.appendChild(pre);
pre.textContent = results;
}
_done() {
this.isRunning = false;
}
runStep() {
this._isStepping = true;
if (!this._isRunning)
this.runMultipleIterations(this._iterationCount);
else
this._step();
}
runSuites() {
if (this._isRunning) {
if (this._isStepping) {
// Switch to continuous running only if we've been stepping.
this._isStepping = false;
this._step();
}
} else {
this._isStepping = false;
this.runMultipleIterations(this._iterationCount);
}
}
}
// Expose Suites/BenchmarkRunner for backwards compatibility
globalThis.BenchmarkRunner = InteractiveBenchmarkRunner;
function formatTestName(suiteName, testName) {
return suiteName + (testName ? `/${testName}` : "");
}
function createUIForSuites(suites, onStep, onRunSuites) {
const control = document.createElement("nav");
const ol = document.createElement("ol");
const checkboxes = [];
for (let suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
const suite = suites[suiteIndex];
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.id = suite.name;
checkbox.type = "checkbox";
checkbox.checked = !suite.disabled;
checkbox.onchange = () => {
suite.disabled = !checkbox.checked;
};
checkbox.onchange();
checkboxes.push(checkbox);
li.appendChild(checkbox);
var label = document.createElement("label");
label.appendChild(document.createTextNode(formatTestName(suite.name)));
li.appendChild(label);
label.htmlFor = checkbox.id;
const testList = document.createElement("ol");
for (let testIndex = 0; testIndex < suite.tests.length; testIndex++) {
const testItem = document.createElement("li");
const test = suite.tests[testIndex];
const anchor = document.createElement("a");
anchor.id = `${suite.name}-${test.name}`;
test.anchor = anchor;
anchor.appendChild(document.createTextNode(formatTestName(suite.name, test.name)));
testItem.appendChild(anchor);
testList.appendChild(testItem);
}
li.appendChild(testList);
ol.appendChild(li);
}
control.appendChild(ol);
let button = document.createElement("button");
button.textContent = "Step";
button.onclick = onStep;
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Run";
button.id = "runSuites";
button.onclick = onRunSuites;
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Select all";
button.onclick = () => {
for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
suites[suiteIndex].disabled = false;
checkboxes[suiteIndex].checked = true;
}
};
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Unselect all";
button.onclick = () => {
for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
suites[suiteIndex].disabled = true;
checkboxes[suiteIndex].checked = false;
}
};
control.appendChild(button);
return control;
}
function startTest() {
if (params.suites.length > 0 || params.tags.length > 0)
Suites.enable(params.suites, params.tags);
const interactiveRunner = new window.BenchmarkRunner(Suites, params.iterationCount);
if (!(interactiveRunner instanceof InteractiveBenchmarkRunner))
throw Error("window.BenchmarkRunner must be a subclass of InteractiveBenchmarkRunner");
// Don't call step while step is already executing.
document.body.appendChild(createUIForSuites(Suites, interactiveRunner.runStep.bind(interactiveRunner), interactiveRunner.runSuites.bind(interactiveRunner)));
if (params.startAutomatically)
document.getElementById("runSuites").click();
}
window.addEventListener("load", startTest);

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/logo.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.8 KiB

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/logo@2x.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 19 KiB

Просмотреть файл

@ -0,0 +1,792 @@
:root {
--viewport-width: 800px;
--viewport-height: 600px;
--foreground: rgb(235, 235, 235);
--foreground-alpha: rgba(235, 235, 235, 0.2);
--inactive-color: rgb(128, 128, 128);
--background: rgb(46, 51, 55);
--running-background: #f5f5f5;
--highlight: rgb(232, 79, 79);
--text-width: 650px;
--metrics-line-height: 25px;
--scrollbar-width: 10px;
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
::selection {
color: var(--background);
background-color: var(--foreground);
}
::-webkit-scrollbar,
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
background-color: var(--background);
}
::-webkit-scrollbar,
::-webkit-scrollbar-track {
background-color: var(--inactive-color);
border-radius: 8px;
width: var(--scrollbar-width);
height: var(--scrollbar-width);
}
::-webkit-scrollbar-thumb {
background-color: var(--foreground);
border-radius: 8px;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--foreground);
}
thead {
vertical-align: bottom;
}
h1,
button,
.button {
font-family: "Futura-Medium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
.button {
text-decoration: none;
}
code {
font-family: Menlo, Monaco, monospace;
font-size: smaller;
}
hr {
border: 1px solid var(--foreground);
width: 50%;
margin: 40px auto;
}
img {
user-select: none;
-webkit-user-select: none;
-webkit-user-drag: none;
}
.no-select {
user-select: none;
}
main {
}
.logo {
position: absolute;
left: -70px;
top: 155px;
width: 75px;
height: 406px;
cursor: pointer;
text-decoration: none;
}
.version {
transform: rotate(-90deg);
font-size: 65px;
font-family: Futura-Medium, Futura, Roboto, "Helvetica Neue", Helvetica, Verdana, sans-serif;
font-style: italic;
letter-spacing: -0.05em;
position: absolute;
right: 0;
top: -105px;
}
h1 {
margin-top: 30px;
font-size: 40px;
font-weight: normal;
color: var(--foreground);
text-align: center;
}
h2 {
font-weight: normal;
text-align: center;
color: var(--foreground);
}
h3 {
margin: 10px 0 6px 0;
font-weight: normal;
text-align: center;
}
p {
font-size: 16px;
line-height: 21px;
}
a {
color: inherit;
}
li {
font-size: 16px;
line-height: 21px;
}
li + li {
margin-top: 21px;
}
li + ul > li {
margin-top: 0;
}
li + ul {
margin-bottom: 21px;
}
.buttons {
margin-top: 30px;
display: flex;
flex-direction: column;
}
section#home > .buttons {
margin-top: 80px;
}
.button-row {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
margin: 5px 0px;
}
button,
.button {
appearance: none;
border: none;
background-color: transparent;
cursor: pointer;
margin: 0px;
padding: 0px;
text-align: center;
}
.button {
box-sizing: border-box;
text-decoration: none;
}
.buttons button,
.buttons .button {
appearance: none;
border: 3px solid var(--foreground);
border-radius: 10px;
min-width: 200px;
padding: 5px 0px;
margin: 0 20px;
font-size: 25px;
color: var(--foreground);
background-color: transparent;
cursor: pointer;
user-select: none;
}
.buttons button:active {
background-color: var(--foreground);
color: var(--background);
border-color: var(--foreground) !important;
}
.buttons button:focus {
outline: none;
border-color: var(--highlight);
}
.developer-mode {
border-radius: 10px;
padding: 1rem;
background: #602525;
border: 3px solid rgba(255, 255, 255, 0.5);
position: fixed;
left: 10px;
top: 10px;
display: flex;
flex-direction: column;
}
.developer-mode summary {
user-select: none;
cursor: pointer;
padding: 1rem;
margin: -1rem;
}
.developer-mode-content {
flex: 1;
max-height: 80vh;
overflow: auto;
margin-right: calc(0px - var(--scrollbar-width));
padding-right: var(--scrollbar-width);
}
.developer-mode-content ol {
list-style: none;
padding: 0;
margin: 1em 0 0.5em 0;
}
.developer-mode-content .button-bar {
display: flex;
margin-top: 5px;
gap: 3px;
}
.developer-mode-content li + li {
margin-top: 0px;
}
.developer-mode-content button {
background: white;
flex: auto;
padding: 4px;
appearance: button;
border: 2px solid rgba(255, 255, 255, 0.5);
border-radius: 10px;
}
.developer-mode-content .tag {
border-radius: 100vh;
color: white;
background: rgba(255, 255, 255, 0.1);
}
.developer-mode-content label {
width: 100%;
display: inline-block;
}
.developer-mode-content hr {
width: initial;
margin: 10px 0;
}
section {
display: none;
--padding-width: 15px;
--border-width: 6px;
position: absolute;
width: var(--viewport-width);
height: var(--viewport-height);
top: 50%;
left: 50%;
margin-top: calc(var(--viewport-height) / -2 - var(--padding-width) - var(--border-width));
margin-left: calc(var(--viewport-width) / -2 - var(--padding-width) - var(--border-width));
padding: var(--padding-width);
border: var(--border-width) solid var(--foreground);
border-radius: 20px;
}
section:target {
display: block;
}
section > p {
margin: 10px 20px;
}
#testContainer {
position: absolute;
top: 15px;
left: 15px;
width: var(--viewport-width);
height: var(--viewport-height);
}
section#home p {
margin: 0 auto;
width: 70%;
text-align: center;
}
section#home .content {
margin-top: 160px;
text-align: center;
}
button.show-about {
margin-top: 100px;
font-size: 16px;
clear: both;
}
#screen-size-warning {
display: none;
}
#progress {
position: absolute;
bottom: -6px;
left: 60px;
right: 60px;
height: 6px;
border-left: 6px solid var(--background);
border-right: 6px solid var(--background);
}
#progress-completed {
position: absolute;
top: 0;
left: 0;
height: 6px;
width: 100%;
appearance: none;
border: none;
background-color: var(--inactive-color);
}
#progress-completed::-webkit-progress-value {
background-color: var(--foreground);
}
#progress-completed::-moz-progress-bar {
background-color: var(--foreground);
}
#progress .iteration-marker {
position: absolute;
width: 6px;
height: 100%;
background-color: var(--background);
}
#info {
position: absolute;
bottom: -25px;
left: 60px;
right: 60px;
height: 12px;
color: var(--inactive-color);
text-align: center;
font-size: 12px;
}
#info-label {
position: absolute;
left: 6px;
}
#info-progress {
position: absolute;
right: 6px;
text-align: right;
}
iframe.test-runner {
background: var(--running-background);
}
section#summary > #result-number,
section#summary > #confidence-number {
font-family: "Futura-CondensedMedium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
section#summary > #result-number {
text-align: center;
font-size: 145px;
line-height: 145px;
}
section#summary > #confidence-number {
text-align: center;
font-size: 36px;
line-height: 36px;
color: var(--inactive-color);
}
section#details {
--viewport-height: max(600px, 90vh);
--viewport-width: max(800px, 80vw);
}
section#details .results-table {
float: left;
width: 50%;
}
section#details .export-buttons {
justify-content: center;
margin-bottom: 0;
}
section#details .export-buttons button,
section#details .export-buttons .button {
padding: 5px 10px;
min-width: auto;
font-size: 14px;
margin: 0 3px;
}
section#details .arithmetic-mean {
clear: both;
padding-top: 32px;
text-align: center;
}
section#details .arithmetic-mean > label {
font-weight: bold;
margin-right: 10px;
}
section#details button.show-about {
margin-top: 30px;
}
section#details h1 {
margin-bottom: 10px;
}
section#about .section-content > * {
width: var(--text-width);
padding-left: calc((var(--viewport-width) - var(--text-width)) / 2);
}
section#details:target {
display: flex;
flex-direction: column;
}
section#details .all-metric-results {
flex: auto;
overflow-y: auto;
padding: 0px 10px;
}
section#details .arithmetic-mean {
clear: both;
padding-top: 32px;
text-align: center;
}
section#details .arithmetic-mean > label {
font-weight: bold;
margin-right: 10px;
}
section#details h1 {
margin-bottom: 10px;
}
section#details .metric {
margin: 0px 0 10px 0;
display: inline-block;
width: 100%;
}
section#details .metric dd {
margin-left: 0;
}
.submetrics h3 {
font-size: medium;
}
.metrics-table thead .prefix {
text-align: center;
}
.metrics-table thead th {
font-weight: normal;
text-align: left;
border-bottom: 1px var(--foreground) solid;
padding: 0 4px 3px 0px;
}
.metrics-table thead th + th {
padding: 0 4px 3px 4px;
}
.nowrap {
white-space: nowrap;
}
table .number {
text-align: right;
}
.chart-legend {
display: inline;
padding-left: 10px;
vertical-align: top;
margin-top: 0;
line-height: var(--metrics-line-height);
border-collapse: collapse;
}
.chart-legend td {
padding: 0 5px 0 0;
max-width: calc(var(--viewport-width) * 0.22);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
}
section#details h1 {
margin-bottom: 10px;
}
section#about h1 {
margin-top: 10px;
margin-bottom: 0px;
font-size: 25px;
}
section#about .note {
color: var(--inactive-color);
}
.section-grid {
display: grid;
width: 100%;
height: 100%;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header"
"content"
"footer";
}
.section-header {
grid-area: header;
}
.section-content {
grid-area: content;
overflow-y: auto;
}
.section-footer {
grid-area: footer;
}
.gauge {
position: relative;
width: 738px;
height: 78px;
background-image: -webkit-image-set(url("gauge@2x.png") 2x, url("gauge.png") 1x);
background-image: image-set(url("gauge@2x.png") 2x, url("gauge.png") 1x);
background-size: 100% 100%;
background-repeat: no-repeat;
margin: 0 auto;
}
.gauge > .window {
position: absolute;
left: 0;
top: 33px;
bottom: 0;
right: 0;
overflow: hidden;
}
.gauge > .window > .needle {
position: absolute;
left: 363px;
bottom: -88px;
width: 4px;
height: 400px;
background-color: rgb(247, 148, 29);
transform: rotate(-70deg);
transform-origin: 2px 400px;
}
.all-metric-results .submetrics {
display: none;
margin-left: 35px;
}
.all-metric-results .submetrics.visible {
display: block;
}
.metric-chart {
display: grid;
grid-template-columns: calc((var(--viewport-width) - 120px) / 2) 1fr;
}
.metric-chart-relative {
display: none;
}
.relative-charts .metric-chart-absolute {
display: none;
}
.relative-charts .metric-chart-relative {
display: block;
}
.details-toggle {
display: inline-block;
cursor: pointer;
user-select: none;
margin-left: -4px;
}
.details-toggle input {
display: inline-block;
appearance: none;
width: 1em;
height: 1em;
border: none;
background: none;
}
.details-toggle input:after {
content: "▶";
color: var(--foreground);
display: inline-block;
transform-origin: 50%;
position: relative;
transition: all 250ms ease;
}
.details-toggle input:checked:after {
transform: rotate(90deg);
}
/* Charts (lib/charts.js) */
.bar-chart text,
.scatter-plot text {
font-size: 11px;
fill: var(--foreground);
dominant-baseline: hanging;
}
.bar-chart .axis,
.scatter-plot .axis {
stroke: #aaa;
}
.bar-chart .minMax {
stroke: var(--foreground);
stroke-width: 1;
stroke-opacity: 0.5;
stroke-dasharray: 2;
}
.bar-chart .mean {
stroke: var(--foreground);
stroke-width: 2;
stroke-opacity: 0.5;
}
.bar-chart .mean:hover {
stroke-opacity: 1;
}
.bar-chart g.bar {
fill: #8ad;
}
.bar-chart g.bar.large text {
display: none;
}
.bar-chart g.bar:hover {
filter: brightness(1.1);
}
.bar-chart g.bar text {
fill: #ddd;
text-anchor: middle;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
.bar-chart g.bar:hover text {
display: block;
fill: #fff;
text-shadow: 0 0 5px #000;
}
.bar-chart .label {
text-anchor: end;
dominant-baseline: middle;
}
.scatter-plot .marker {
stroke-width: 0px;
opacity: 0.8;
fill: currentColor;
cursor: crosshair;
}
.scatter-plot .marker:hover {
opacity: 1;
filter: drop-shadow(0 0 2px currentColor);
}
.scatter-plot .percentile {
fill: currentColor;
fill-opacity: 0.3;
stroke-width: 0px;
}
.scatter-plot .percentile line {
stroke-width: 1px;
}
.scatter-plot .percentile:hover {
opacity: 1;
}
/* Chart colors */
.chart .blue {
color: #5b9aff;
stroke: #5b9aff;
}
.chart .blue-light {
color: #5bd6ff;
stroke: #5bd6ff;
}
.chart .green-light {
color: #73d147;
stroke: #73d147;
}
.chart .green {
color: #94bc4b;
stroke: #94bc4b;
}
.chart .yellow {
color: #ded300;
stroke: #ded300;
}
.chart .orange {
color: #fe8f06;
stroke: #fe8f06;
}
.chart .red {
color: #de4040;
stroke: #de4040;
}
.chart .magenta {
color: #cd1e90;
stroke: #cd1e90;
}
.chart .violet {
color: #a41ecd;
stroke: #a41ecd;
}
.chart .purple {
color: #7b3eff;
stroke: #7b3eff;
}
.chart .blue-dark {
color: #2d4ef7;
stroke: #2d4ef7;
}
.chart .green-dark {
color: #349a58;
stroke: #349a58;
}
.chart .ochre {
color: #cc9900;
stroke: #cc9900;
}
.chart .rust {
color: #cc5200;
stroke: #cc5200;
}
.chart .white {
color: #fff;
stroke: #fff;
}

Просмотреть файл

@ -0,0 +1,355 @@
import { BenchmarkRunner } from "./benchmark-runner.mjs";
import * as Statistics from "./statistics.mjs";
import { Suites } from "./tests.mjs";
import { renderMetricView } from "./metric-ui.mjs";
import { params } from "./params.mjs";
import { createDeveloperModeContainer } from "./developer-mode.mjs";
// FIXME(camillobruni): Add base class
class MainBenchmarkClient {
developerMode = false;
stepCount = null;
suitesCount = null;
_measuredValuesList = [];
_finishedTestCount = 0;
_progressCompleted = null;
_isRunning = false;
_hasResults = false;
_developerModeContainer = null;
_metrics = Object.create(null);
constructor() {
window.addEventListener("DOMContentLoaded", () => this.prepareUI());
this._showSection(window.location.hash);
}
start() {
if (this._startBenchmark())
this._showSection("#running");
}
_startBenchmark() {
if (this._isRunning)
return false;
if (Suites.every((suite) => suite.disabled)) {
const message = `No suites selected - "${params.suites}" does not exist.`;
alert(message);
console.error(
message,
params.suites,
"\nValid values:",
Suites.map((each) => each.name)
);
return false;
}
this._developerModeContainer?.remove();
this._progressCompleted = document.getElementById("progress-completed");
if (params.iterationCount < 50) {
const progressNode = document.getElementById("progress");
for (let i = 1; i < params.iterationCount; i++) {
const iterationMarker = progressNode.appendChild(document.createElement("div"));
iterationMarker.className = "iteration-marker";
iterationMarker.style.left = `${(i / params.iterationCount) * 100}%`;
}
}
this._metrics = Object.create(null);
this._isRunning = true;
const enabledSuites = Suites.filter((suite) => !suite.disabled);
const totalSubtestsCount = enabledSuites.reduce((testsCount, suite) => {
return testsCount + suite.tests.length;
}, 0);
this.stepCount = params.iterationCount * totalSubtestsCount;
this._progressCompleted.max = this.stepCount;
this.suitesCount = enabledSuites.length;
const runner = new BenchmarkRunner(Suites, this);
runner.runMultipleIterations(params.iterationCount);
return true;
}
get metrics() {
return this._metrics;
}
willAddTestFrame(frame) {
frame.style.left = "50%";
frame.style.top = "50%";
frame.style.transform = "translate(-50%, -50%)";
}
willRunTest(suite, test) {
document.getElementById("info-label").textContent = suite.name;
document.getElementById("info-progress").textContent = `${this._finishedTestCount} / ${this.stepCount}`;
}
didRunTest() {
this._finishedTestCount++;
this._progressCompleted.value = this._finishedTestCount;
}
didRunSuites(measuredValues) {
this._measuredValuesList.push(measuredValues);
}
willStartFirstIteration() {
this._measuredValuesList = [];
this._finishedTestCount = 0;
}
didFinishLastIteration(metrics) {
console.assert(this._isRunning);
this._isRunning = false;
this._hasResults = true;
this._metrics = metrics;
const scoreResults = this._computeResults(this._measuredValuesList, "score");
this._updateGaugeNeedle(scoreResults.mean);
document.getElementById("result-number").textContent = scoreResults.formattedMean;
if (scoreResults.formattedDelta)
document.getElementById("confidence-number").textContent = `\u00b1 ${scoreResults.formattedDelta}`;
this._populateDetailedResults(metrics);
if (params.developerMode)
this.showResultsDetails();
else
this.showResultsSummary();
}
_computeResults(measuredValuesList, displayUnit) {
function valueForUnit(measuredValues) {
if (displayUnit === "ms")
return measuredValues.geomean;
return measuredValues.score;
}
function sigFigFromPercentDelta(percentDelta) {
return Math.ceil(-Math.log(percentDelta) / Math.log(10)) + 3;
}
function toSigFigPrecision(number, sigFig) {
const nonDecimalDigitCount = number < 1 ? 0 : Math.floor(Math.log(number) / Math.log(10)) + 1;
return number.toPrecision(Math.max(nonDecimalDigitCount, Math.min(6, sigFig)));
}
const values = measuredValuesList.map(valueForUnit);
const sum = values.reduce((a, b) => {
return a + b;
}, 0);
const arithmeticMean = sum / values.length;
let meanSigFig = 4;
let formattedDelta;
let formattedPercentDelta;
const delta = Statistics.confidenceIntervalDelta(0.95, values.length, sum, Statistics.squareSum(values));
if (!isNaN(delta)) {
const percentDelta = (delta * 100) / arithmeticMean;
meanSigFig = sigFigFromPercentDelta(percentDelta);
formattedDelta = toSigFigPrecision(delta, 2);
formattedPercentDelta = `${toSigFigPrecision(percentDelta, 2)}%`;
}
const formattedMean = toSigFigPrecision(arithmeticMean, Math.max(meanSigFig, 3));
return {
formattedValues: values.map((value) => {
return `${toSigFigPrecision(value, 4)} ${displayUnit}`;
}),
mean: arithmeticMean,
formattedMean: formattedMean,
formattedDelta: formattedDelta,
formattedMeanAndDelta: formattedMean + (formattedDelta ? ` \xb1 ${formattedDelta} (${formattedPercentDelta})` : ""),
};
}
_addDetailedResultsRow(table, iterationNumber, value) {
const row = document.createElement("tr");
const th = document.createElement("th");
th.textContent = `Iteration ${iterationNumber + 1}`;
const td = document.createElement("td");
td.textContent = value;
row.appendChild(th);
row.appendChild(td);
table.appendChild(row);
}
_updateGaugeNeedle(score) {
const needleAngle = Math.max(0, Math.min(score, 140)) - 70;
const needleRotationValue = `rotate(${needleAngle}deg)`;
const gaugeNeedleElement = document.querySelector("#summary > .gauge .needle");
gaugeNeedleElement.style.setProperty("-webkit-transform", needleRotationValue);
gaugeNeedleElement.style.setProperty("-moz-transform", needleRotationValue);
gaugeNeedleElement.style.setProperty("-ms-transform", needleRotationValue);
gaugeNeedleElement.style.setProperty("transform", needleRotationValue);
}
_populateDetailedResults(metrics) {
const trackHeight = 24;
document.documentElement.style.setProperty("--metrics-line-height", `${trackHeight}px`);
const plotWidth = (params.viewport.width - 120) / 2;
document.getElementById("geomean-chart").innerHTML = renderMetricView({
metrics: [metrics.Geomean],
width: plotWidth,
trackHeight,
renderChildren: false,
colors: ["white"],
});
const toplevelMetrics = Object.values(metrics).filter((each) => !each.parent && each.children.length > 0);
document.getElementById("tests-chart").innerHTML = renderMetricView({
metrics: toplevelMetrics,
width: plotWidth,
trackHeight,
renderChildren: false,
});
let html = "";
for (const metric of toplevelMetrics) {
html += renderMetricView({
metrics: metric.children,
width: plotWidth,
trackHeight,
title: metric.name,
});
}
document.getElementById("metrics-results").innerHTML = html;
const filePrefix = `speedometer-3-${new Date().toISOString()}`;
let jsonData = this._formattedJSONResult({ modern: false });
let jsonLink = document.getElementById("download-classic-json");
jsonLink.href = URL.createObjectURL(new Blob([jsonData], { type: "application/json" }));
jsonLink.setAttribute("download", `${filePrefix}.json`);
jsonLink = document.getElementById("download-full-json");
jsonData = this._formattedJSONResult({ modern: true });
jsonLink.href = URL.createObjectURL(new Blob([jsonData], { type: "application/json" }));
jsonLink.setAttribute("download", `${filePrefix}.json`);
const csvData = this._formattedCSVResult();
const csvLink = document.getElementById("download-csv");
csvLink.href = URL.createObjectURL(new Blob([csvData], { type: "text/csv" }));
csvLink.setAttribute("download", `${filePrefix}.csv`);
}
prepareUI() {
window.addEventListener("hashchange", this._hashChangeHandler.bind(this));
window.addEventListener("resize", this._resizeScreeHandler.bind(this));
this._resizeScreeHandler();
document.querySelectorAll("logo").forEach((button) => {
button.onclick = this._logoClickHandler.bind(this);
});
document.getElementById("copy-full-json").onclick = this.copyJsonResults.bind(this);
document.getElementById("copy-csv").onclick = this.copyCSVResults.bind(this);
document.querySelectorAll(".start-tests-button").forEach((button) => {
button.onclick = this._startBenchmarkHandler.bind(this);
});
if (params.suites.length > 0 || params.tags.length > 0)
Suites.enable(params.suites, params.tags);
if (params.developerMode) {
this._developerModeContainer = createDeveloperModeContainer(Suites);
document.body.append(this._developerModeContainer);
}
if (params.startAutomatically)
this.start();
}
_hashChangeHandler() {
this._showSection(window.location.hash);
}
_resizeScreeHandler() {
// FIXME: Detect when the window size changes during the test.
const mainSize = document.querySelector("main").getBoundingClientRect();
const screenIsTooSmall = window.innerWidth < mainSize.width || window.innerHeight < mainSize.height;
document.getElementById("min-screen-width").textContent = `${params.viewport.width + 50}px`;
document.getElementById("min-screen-height").textContent = `${params.viewport.height + 50}px`;
document.getElementById("screen-size").textContent = `${window.innerWidth}px by ${window.innerHeight}px`;
document.getElementById("screen-size-warning").style.display = screenIsTooSmall ? null : "none";
}
_startBenchmarkHandler() {
this.start();
}
_logoClickHandler(event) {
// Prevent any accidental UI changes during benchmark runs.
if (!this._isRunning)
this._showSection("#home");
event.preventDefault();
return false;
}
showResultsSummary() {
this._showSection("#summary");
}
showResultsDetails() {
this._showSection("#details");
}
_formattedJSONResult({ modern = false }) {
const indent = " ";
if (modern)
return JSON.stringify(this._metrics, undefined, indent);
return JSON.stringify(this._measuredValuesList, undefined, indent);
}
_formattedCSVResult() {
// The CSV format is similar to the details view table. Each measurement is a row with
// the name and N columns with the measurement for each iteration:
// ```
// Measurement,#1,...,#N
// TodoMVC-JavaScript-ES5/Total,num,...,num
// TodoMVC-JavaScript-ES5/Adding100Items,num,...,num
// ...
const labels = ["Name"];
for (let i = 0; i < params.iterationCount; i++)
labels.push(`#${i + 1}`);
labels.push("Mean");
const metrics = Array.from(Object.values(this._metrics)).filter((metric) => !metric.name.startsWith("Iteration-"));
const metricsValues = metrics.map((metric) => [metric.name, ...metric.values, metric.mean].join(","));
const csv = [labels.join(","), ...metricsValues];
return csv.join("\n");
}
copyJsonResults() {
navigator.clipboard.writeText(this._formattedJSONResult({ modern: true }));
}
copyCSVResults() {
navigator.clipboard.writeText(this._formattedCSVResult());
}
_showSection(hash) {
if (this._isRunning) {
window.location.hash = "#running";
return;
} else if (this._hasResults) {
if (hash !== "#summary" && hash !== "#details") {
window.location.hash = "#summary";
return;
}
} else {
if (hash !== "#home" && hash !== "#about") {
window.location.hash = "#home";
return;
}
}
window.location.hash = hash || "#home";
}
}
const rootStyle = document.documentElement.style;
rootStyle.setProperty("--viewport-width", `${params.viewport.width}px`);
rootStyle.setProperty("--viewport-height", `${params.viewport.height}px`);
globalThis.benchmarkClient = new MainBenchmarkClient();

Просмотреть файл

@ -0,0 +1,323 @@
import { Metric } from "./metric.mjs";
export const COLORS = Object.freeze(["blue", "blue-light", "green-light", "green", "yellow", "orange", "red", "magenta", "violet", "purple", "blue-dark", "green-dark", "ochre", "rust"]);
export function renderMetricView(viewParams) {
let { metrics, width = 500, trackHeight = 20, subMetricMargin = 35, title = "", colors = COLORS } = viewParams;
// Make sure subMetricMargin is set for use in renderSubMetrics.
viewParams.subMetricMargin = subMetricMargin;
const scatterPlotParams = { width, trackHeight, colors };
scatterPlotParams.xAxisPositiveOnly = false;
scatterPlotParams.xAxisShowZero = true;
scatterPlotParams.values = prepareScatterPlotValues(metrics, true);
scatterPlotParams.unit = "%";
scatterPlotParams.xAxisLabel = "Spread Normalized";
const normalizedScatterPlot = renderScatterPlot(scatterPlotParams);
scatterPlotParams.xAxisPositiveOnly = true;
scatterPlotParams.xAxisShowZero = false;
scatterPlotParams.values = prepareScatterPlotValues(metrics, false);
scatterPlotParams.unit = metrics[0].unit;
scatterPlotParams.xAxisLabel = metrics[0].unit;
const absoluteScatterPlot = renderScatterPlot(scatterPlotParams);
const legend = metrics
.map(
(metric, i) => `
<tr >
<td class="${colors[i % colors.length]} no-select" ></td>
<td class="label">${metric.shortName}</td>
<td class="number">${metric.mean.toFixed(2)}</td>
<td>±</td>
<td>${metric.deltaString}</td>
<td>${metric.unit}</td>
</tr>`
)
.join("");
return `
<dl class="metric">
<dt><h3>${title}<h3></dt>
<dd>
<div class="metric-chart"">
<div onclick="document.body.classList.toggle('relative-charts')">
<div class="metric-chart-absolute">
${absoluteScatterPlot}
</div>
<div class="metric-chart-relative">
${normalizedScatterPlot}
</div>
</div>
<table class="chart chart-legend">${legend}</table>
</div>
${renderSubMetrics(viewParams)}
</dd>
</dl>
`;
}
function renderSubMetrics(viewParams) {
const { metrics, width, subMetricMargin, colors = COLORS, renderChildren = true } = viewParams;
const valuesTable = `
<label class="details-toggle">
<input type="checkbox"
onclick="this.parentNode.nextElementSibling.classList.toggle('visible')" />
Table
</label>
<div class="submetrics">
${renderMetricsTable(metrics)}
</div>`;
const hasChildMetric = metrics.length > 0 && metrics[0].children.length > 0;
if (!hasChildMetric || !renderChildren)
return valuesTable;
const subMetricWidth = width - subMetricMargin;
const childColors = [...colors];
const subMetrics = metrics
.map((metric) => {
// Rotate colors to get different colors for sub-plots.
for (let i = 0; i < metric.children.length; i++) {
const color = childColors.pop();
childColors.unshift(color);
}
const subMetricParams = {
...viewParams,
parentMetric: metric,
metrics: metric.children,
title: metric.name,
width: subMetricWidth,
colors: childColors,
};
return renderMetricView(subMetricParams);
})
.join("");
return `${valuesTable}
<label class="details-toggle">
<input type="checkbox"
onclick="this.parentNode.nextElementSibling.classList.toggle('visible')" />
Submetrics
</label>
<div class="submetrics">
${subMetrics}
</div>
`;
}
function renderMetricsTable(metrics, min, max) {
let numRows = 0;
let columnHeaders = "";
let commonPrefixes = metrics[0].name.split(Metric.separator);
for (const metric of metrics) {
const prefixes = metric.name.split(Metric.separator);
for (let i = commonPrefixes.length - 1; i >= 0; i--) {
if (commonPrefixes[i] !== prefixes[i])
commonPrefixes.pop();
}
}
const commonPrefix = commonPrefixes.join(Metric.separator);
let commonPrefixHeader = "";
if (commonPrefix) {
commonPrefixHeader = `
<tr>
<td></td>
<td colspan="${metrics.length}" class="prefix">${commonPrefix}</td>
</tr>`;
}
for (const metric of metrics) {
const name = metric.name.substring(commonPrefix.length);
columnHeaders += `<th>${name} [${metric.unit}]</th>`;
numRows = Math.max(metric.values.length, numRows);
}
let body = "";
for (let row = 0; row < numRows; row++) {
let columns = "";
for (const metric of metrics) {
const value = metric.values[row];
if (value === undefined)
continue;
const delta = metric.max - metric.min;
const percent = Math.max(Math.min((value - metric.min) / delta, 1), 0) * 100;
const percentGradient = `background: linear-gradient(90deg, var(--foreground-alpha) ${percent}%, rgba(0,0,0,0) ${percent}%);`;
columns += `<td style="${percentGradient}">${value.toFixed(2)}</td>`;
}
body += `<tr>
<td>${row}</td>
${columns}
</tr>`;
}
return `<table class="metrics-table" >
<thead onclick="this.classList.toggle('nowrap')" >
${commonPrefixHeader}
<tr>
<th>Iteration</th>
${columnHeaders}
</tr>
</thead>
<tbody>
${body}
<tbody>
</table>`;
}
function prepareScatterPlotValues(metrics, normalize = true) {
let points = [];
// Arrange child-metrics values in a single coordinate system:
// - metric 1: x values are in range [0, 1]
// - metric 2: y values are in range [1, 2]
// - ...
// This way each metric data point is on a separate track in the scatter
// plot.
// If normalize == true:
// All x values are normalized by the mean of each metric and
// centered on 0.
// Example: [90ms, 100ms, 110ms] => [-10%, 0%, +10%]
const toPercent = 100;
let unit;
for (let metricIndex = 0; metricIndex < metrics.length; metricIndex++) {
const metric = metrics[metricIndex];
// If the mean is 0 we can't normalize values properly.
const mean = metric.mean || 1;
if (!unit)
unit = metric.unit;
else if (unit !== metric.unit)
throw new Error("All metrics must have the same unit.");
let width = metric.delta || 1;
let center = mean;
if (normalize) {
width = (metric.delta / mean) * toPercent;
center = 0;
}
const left = center - width / 2;
const y = metricIndex;
const label = `Mean: ${metric.valueString}\n` + `Min: ${metric.min.toFixed(2)}${unit}\n` + `Max: ${metric.max.toFixed(2)}${unit}`;
const rect = [left, y, label, width];
// Add data for individual points:
points.push(rect);
const values = metric.values;
const length = values.length;
for (let i = 0; i < length; i++) {
const value = values[i];
let x = value;
let normalized = (value / mean - 1) * toPercent;
if (normalize)
x = normalized;
const sign = normalized < 0 ? "-" : "+";
normalized = Math.abs(normalized);
// Each value is mapped to a y-coordinate in the range of [metricIndex, metricIndex + 1]
const valueOffsetY = length === 1 ? 0.5 : i / length;
const y = metricIndex + valueOffsetY;
let label = `Iteration ${i}: ${value.toFixed(3)}${unit}\n` + `Normalized: ${metric.mean.toFixed(3)}${unit} ${sign} ${normalized.toFixed(2)}%`;
const point = [x, y, label];
points.push(point);
}
}
return points;
}
function renderScatterPlot({ values, width = 500, height, trackHeight, xAxisPositiveOnly = false, xAxisShowZero = false, xAxisLabel, unit = "", colors = COLORS }) {
if (!height && !trackHeight)
throw new Error("Either height or trackHeight must be specified");
let xMin = Infinity;
let xMax = 0;
let yMin = Infinity;
let yMax = 0;
for (let value of values) {
let [x, y] = value;
xMin = Math.min(xMin, x);
xMax = Math.max(xMax, x);
yMin = Math.min(yMin, y);
yMax = Math.max(yMax, y);
}
if (xAxisPositiveOnly)
xMin = Math.max(xMin, 0);
// Max delta of values across each axis:
const trackCount = Math.ceil(yMax - yMin) || 1;
const spreadX = xMax - xMin;
// Axis + labels height:
const axisHeight = 18;
const axisMarginY = 4;
const markerSize = 5;
const trackMargin = 2;
// Recalculate height:
if (height)
trackHeight = (height - axisHeight - axisMarginY) / trackCount;
else
height = trackCount * trackHeight + axisHeight + axisMarginY;
// Horizontal axis position:
const axisY = height - axisHeight + axisMarginY;
const unitToPosX = width / spreadX;
const unitToPosY = trackHeight - trackMargin - markerSize / 2;
const points = values.map(renderValue).join("");
let xAxisZeroLine = "";
if (xAxisShowZero) {
const xZeroPos = (0 - xMin) * unitToPosX;
xAxisZeroLine = `<line x1="${xZeroPos}" x2="${xZeroPos}" y1="${0}" y2="${axisY}" class="axis"/>`;
}
return `
<svg class="scatter-plot chart"
width="${width}" height="${height}"
viewBox="${`0 0 ${width} ${height}`}">
<g class="horizontal-axis no-select">
<line
x1="${0}" x2="${width}"
y1="${axisY - axisMarginY}" y2="${axisY - axisMarginY}"
class="axis" />
<text y="${axisY}" x="0" text-anchor="start">${xMin.toFixed(2)}${unit}</text>
<text y="${axisY}" x="${width / 2}" text-anchor="middle">${xAxisLabel}</text>
<text y="${axisY}" x="${width}" text-anchor="end">${xMax.toFixed(2)}${unit}</text>
</g>
<defs>
<g id="marker">
<circle r="${markerSize / 2}" />
</g>
</defs>
<g class="values">
${xAxisZeroLine}
${points}
</g>
</svg>
`;
function renderValue(value) {
const [rawX, rawY, label, rawWidth = 0] = value;
const trackIndex = rawY | 0;
const y = (rawY - yMin) * unitToPosY + markerSize * trackIndex;
const cssClass = colors[trackIndex % colors.length];
if (value.length <= 3) {
// Render a simple marker:
const x = (rawX - xMin) * unitToPosX;
const adjustedY = y + markerSize / 2;
return `
<use href="#marker" x="${x}" y="${adjustedY}" class="marker ${cssClass}">
<title>${label}</title>
</use>
`;
} else {
// Render a rect with 4 input values:
const x = (rawX - xMin) * unitToPosX + rawWidth / 2;
const w = rawWidth * unitToPosX;
const centerX = x + w / 2;
const top = y;
const height = trackHeight - trackMargin;
const bottom = top + height;
return `
<g class="percentile ${cssClass}">
<rect x="${x}" y="${top}" width="${w}" height="${height}">
<title>${label}</title>
</rect>
<line x1="${x}" x2="${x}" y1="${top}" y2="${bottom}" />
<line
x1="${centerX}" x2="${centerX}"
y1="${top}" y2="${bottom}"
stroke-dasharray="${height / 3}" />
<line x1="${x + w}" x2="${x + w}" y1="${top}" y2="${bottom}" />
</g>
`;
}
}
}

Просмотреть файл

@ -0,0 +1,86 @@
import * as Statistics from "./statistics.mjs";
export const MILLISECONDS_PER_MINUTE = 60 * 1000;
export class Metric {
static separator = "/";
constructor(name, unit = "ms") {
if (typeof name !== "string")
throw new Error(`Invalid metric.name=${name}, expected string.`);
this.name = name;
this.unit = unit;
this.mean = 0.0;
this.geomean = 0.0;
this.delta = 0.0;
this.percentDelta = 0.0;
this.sum = 0.0;
this.min = 0.0;
this.max = 0.0;
this.values = [];
this.children = [];
// Mark properties which refer to other Metric objects as
// non-enumerable to avoid issue with JSON.stringify due to circular
// references.
Object.defineProperties(this, {
parent: {
writable: true,
value: undefined,
},
});
}
get shortName() {
return this.parent ? this.name.replace(`${this.parent.name}-`, "") : this.name;
}
get valueString() {
const mean = this.mean.toFixed(2);
if (!this.percentDelta || !this.delta)
return `${mean} ${this.unit}`;
return `${mean} ± ${this.deltaString} ${this.unit}`;
}
get deltaString() {
if (!this.percentDelta || !this.delta)
return "";
return `${this.delta.toFixed(2)} (${this.percentDelta.toFixed(1)}%)`;
}
get length() {
return this.values.length;
}
addChild(metric) {
if (metric.parent)
throw new Error("Cannot re-add sub metric");
metric.parent = this;
this.children.push(metric);
}
add(value) {
if (typeof value !== "number")
throw new Error(`Adding invalid value=${value} to metric=${this.name}`);
this.values.push(value);
}
computeAggregatedMetrics() {
// Avoid the loss of significance for the sum.
const sortedValues = this.values.concat().sort((a, b) => a - b);
this.sum = Statistics.sum(sortedValues);
this.min = sortedValues[0];
this.max = sortedValues[sortedValues.length - 1];
this.mean = this.sum / this.values.length;
const product = Statistics.product(sortedValues);
this.geomean = Math.pow(product, 1 / this.values.length);
if (this.values.length > 1) {
const squareSum = Statistics.squareSum(sortedValues);
this.delta = Statistics.confidenceIntervalDelta(0.95, this.values.length, this.sum, squareSum);
this.percentDelta = isNaN(this.delta) ? undefined : (this.delta * 100) / this.mean;
}
}
}

Просмотреть файл

@ -0,0 +1,3 @@
{
"extends": "plugin:@next/next/recommended"
}

Просмотреть файл

@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
/dist/cache
/dist/server
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

Просмотреть файл

@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/pages/_error-54de1933a164a1ff.js" defer=""></script><script src="./_next/static/4r_MNg5TRvBbFl4DDRyla/_buildManifest.js" defer=""></script><script src="./_next/static/4r_MNg5TRvBbFl4DDRyla/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"4r_MNg5TRvBbFl4DDRyla","assetPrefix":".","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>

Просмотреть файл

@ -0,0 +1 @@
self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/743-fd706aeabb7828e3.js","static/css/2cf5163b53bb0adb.css","static/chunks/pages/index-a6d76b1b73bbfbe0.js"],"/_error":["static/chunks/pages/_error-54de1933a164a1ff.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

Просмотреть файл

@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB();

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1118:function(n,e,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return i(8375)}])},8375:function(n,e,i){"use strict";i.r(e);var t=i(5893);i(6026),i(858),i(6669),i(3454),i(9917);var u=i(7294),o=i(4298),a=i.n(o);e.default=function(n){let{Component:e,pageProps:i}=n,[o,r]=(0,u.useState)(!1);return(0,u.useEffect)(()=>r(!0),[]),o?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(a(),{id:"raf-mock",children:"// This hack allows to capture the work normally happening in a rAF. We\n// may be able to remove it if the runner improves.\nwindow.requestAnimationFrame = (cb) => window.setTimeout(cb, 0);\nwindow.cancelAnimationFrame = window.clearTimeout;\n// Disable requestIdleCallback until WebKit / Safari supports it.\nwindow.requestIdleCallback = undefined;\nwindow.cancelIdleCallback = undefined;"}),(0,t.jsx)(e,{...i})]}):null}},6669:function(){},858:function(){},3454:function(){},9917:function(){},6026:function(){},4298:function(n,e,i){n.exports=i(5442)}},function(n){var e=function(e){return n(n.s=e)};n.O(0,[774,179],function(){return e(1118),e(6885)}),_N_E=n.O()}]);

Просмотреть файл

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(3499)}])}},function(n){n.O(0,[774,888,179],function(){return n(n.s=1981)}),_N_E=n.O()}]);

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1 @@
!function(){"use strict";var e,n,r,t,o={},u={};function i(e){var n=u[e];if(void 0!==n)return n.exports;var r=u[e]={exports:{}},t=!0;try{o[e](r,r.exports,i),t=!1}finally{t&&delete u[e]}return r.exports}i.m=o,e=[],i.O=function(n,r,t,o){if(r){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[r,t,o];return}for(var f=1/0,u=0;u<e.length;u++){for(var r=e[u][0],t=e[u][1],o=e[u][2],l=!0,c=0;c<r.length;c++)f>=o&&Object.keys(i.O).every(function(e){return i.O[e](r[c])})?r.splice(c--,1):(l=!1,o<f&&(f=o));if(l){e.splice(u--,1);var a=t();void 0!==a&&(n=a)}}return n},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,{a:n}),n},i.d=function(e,n){for(var r in n)i.o(n,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.p=".//_next/",n={272:0},i.O.j=function(e){return 0===n[e]},r=function(e,r){var t,o,u=r[0],f=r[1],l=r[2],c=0;if(u.some(function(e){return 0!==n[e]})){for(t in f)i.o(f,t)&&(i.m[t]=f[t]);if(l)var a=l(i)}for(e&&e(r);c<u.length;c++)o=u[c],i.o(n,o)&&n[o]&&n[o][0](),n[o]=0;return i.O(a)},(t=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))}();

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/dist/favicon.ico поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Просмотреть файл

@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><link rel="preload" href="./_next/static/css/2cf5163b53bb0adb.css" as="style"/><link rel="stylesheet" href="./_next/static/css/2cf5163b53bb0adb.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/743-fd706aeabb7828e3.js" defer=""></script><script src="./_next/static/chunks/pages/index-a6d76b1b73bbfbe0.js" defer=""></script><script src="./_next/static/4r_MNg5TRvBbFl4DDRyla/_buildManifest.js" defer=""></script><script src="./_next/static/4r_MNg5TRvBbFl4DDRyla/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"4r_MNg5TRvBbFl4DDRyla","assetPrefix":".","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 35 KiB

Просмотреть файл

@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: "export",
distDir: "dist",
assetPrefix: "./",
images: {
unoptimized: true,
},
};
module.exports = nextConfig;

6553
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/package-lock.json сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,25 @@
{
"name": "news-next",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"serve": "http-server ./dist -p 7002 -c-1 --cors"
},
"dependencies": {
"classnames": "^2.3.2",
"eslint": "8.39.0",
"eslint-config-next": "13.3.4",
"http-server": "^14.1.1",
"news-site-css": "file:../news-site-css",
"next": "13.3.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-router-dom": "^6.11.1",
"react-router-hash-link": "^2.4.3",
"uuid": "^9.0.0"
}
}

Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/public/favicon.ico поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 35 KiB

Просмотреть файл

@ -0,0 +1,38 @@
# The Daily Broadcast
> **_NOTE:_** This is not a typical use-case for Next.js and we encourage developers to follow the [official documentation](https://vercel.com/docs) for recommended usage of the framework.
This app is a news-site built with [Next.js](https://nextjs.org/). It utilizes the [News Site Template](https://github.com/flashdesignory/news-site-template) as the basis for styling and functionality.
Since Speedometer expects static files for all apps included, this project's build step uses [static html export](https://nextjs.org/docs/pages/building-your-application/deploying/static-exports).
<br>With this implementation, some features of Next.js are not available and therefore omitted to ensure compatibility with Speedometer.
## Local Development
Start the local dev server:
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Deployment of changes
To ensure Next.js lint rules are followed, run:
```bash
npm run lint
```
To create new build files, run:
```bash
npm run build
```
Add, commit and push changes to the working branch.
## Test steps
The Speedometer test consists of navigating between the different pages of the news site.
It includes interactions with the navigation drop-down menu to ensure state changes happen in between the page navigations.

Просмотреть файл

@ -0,0 +1,13 @@
import React from "react";
export default function A11yIcon() {
return (
<svg clipRule="evenodd" fillRule="evenodd" strokeLinejoin="round" strokeMiterlimit="2" viewBox="0 0 24 24">
<title>Accessibility Icon</title>
<path
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm4.044 5.607c-.235 0-1.892.576-4.044.576-2.166 0-3.791-.576-4.044-.576-.379 0-.687.308-.687.687 0 .318.225.599.531.669.613.16 1.261.293 1.756.542.459.231.781.566.781 1.14 0 2.027-1.326 3.92-1.86 4.817 0 0 0 0-.001.001-.06.105-.092.224-.092.344 0 .379.308.687.688.687.183 0 .357-.072.488-.204.447-.449 1.333-1.784 1.738-2.429.201-.319.396-.621.706-.622.302.001.498.303.698.622.405.645 1.291 1.98 1.738 2.429.13.132.304.204.489.204.379 0 .687-.308.687-.687 0-.119-.031-.237-.098-.353 0-.001-.001-.001-.001-.002-.547-.919-1.854-2.778-1.854-4.807 0-.609.369-.956.851-1.186.519-.247 1.167-.362 1.682-.495.31-.071.536-.352.536-.67 0-.379-.309-.687-.688-.687zm-4.03-3.113c-.875 0-1.587.713-1.587 1.593 0 .879.712 1.592 1.587 1.592.876 0 1.586-.713 1.586-1.592 0-.88-.71-1.593-1.586-1.593z"
fillRule="nonzero"
/>
</svg>
);
}

Просмотреть файл

@ -0,0 +1,10 @@
import React from "react";
export default function FacebookIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24">
<title>Facebook Icon</title>
<path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z" />
</svg>
);
}

Просмотреть файл

@ -0,0 +1,10 @@
import React from "react";
export default function FireIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fillRule="evenodd" clipRule="evenodd">
<title>Fire Icon</title>
<path d="M8.625 0c.61 7.189-5.625 9.664-5.625 15.996 0 4.301 3.069 7.972 9 8.004 5.931.032 9-4.414 9-8.956 0-4.141-2.062-8.046-5.952-10.474.924 2.607-.306 4.988-1.501 5.808.07-3.337-1.125-8.289-4.922-10.378zm4.711 13c3.755 3.989 1.449 9-1.567 9-1.835 0-2.779-1.265-2.769-2.577.019-2.433 2.737-2.435 4.336-6.423z" />
</svg>
);
}

Просмотреть файл

@ -0,0 +1,10 @@
import React from "react";
export default function InstagramIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24">
<title>Instagram Icon</title>
<path d="M11.984 16.815c2.596 0 4.706-2.111 4.706-4.707 0-1.409-.623-2.674-1.606-3.538-.346-.303-.735-.556-1.158-.748-.593-.27-1.249-.421-1.941-.421s-1.349.151-1.941.421c-.424.194-.814.447-1.158.749-.985.864-1.608 2.129-1.608 3.538 0 2.595 2.112 4.706 4.706 4.706zm.016-8.184c1.921 0 3.479 1.557 3.479 3.478 0 1.921-1.558 3.479-3.479 3.479s-3.479-1.557-3.479-3.479c0-1.921 1.558-3.478 3.479-3.478zm5.223.369h6.777v10.278c0 2.608-2.114 4.722-4.722 4.722h-14.493c-2.608 0-4.785-2.114-4.785-4.722v-10.278h6.747c-.544.913-.872 1.969-.872 3.109 0 3.374 2.735 6.109 6.109 6.109s6.109-2.735 6.109-6.109c.001-1.14-.327-2.196-.87-3.109zm2.055-9h-12.278v5h-1v-5h-1v5h-1v-4.923c-.346.057-.682.143-1 .27v4.653h-1v-4.102c-1.202.857-2 2.246-2 3.824v3.278h7.473c1.167-1.282 2.798-2 4.511-2 1.722 0 3.351.725 4.511 2h7.505v-3.278c0-2.608-2.114-4.722-4.722-4.722zm2.722 5.265c0 .406-.333.735-.745.735h-2.511c-.411 0-.744-.329-.744-.735v-2.53c0-.406.333-.735.744-.735h2.511c.412 0 .745.329.745.735v2.53z" />
</svg>
);
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше