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
|
@ -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.
|
||||
|
|
|
@ -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).
|
|
@ -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>
|
|
@ -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.
|
|
@ -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 what’s 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.
|
|
@ -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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -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>
|
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
593
third_party/webkit/PerformanceTests/Speedometer3/resources/benchmark-runner.mjs
поставляемый
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
33
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/chartjs.html
поставляемый
Normal file
|
@ -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>
|
174
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/chartjs.js
поставляемый
Normal file
|
@ -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();
|
4
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/datasets/README
поставляемый
Normal file
|
@ -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
|
3377
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/datasets/airports.csv
поставляемый
Normal file
5367
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/datasets/flights-airports.csv
поставляемый
Normal file
33
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/developer.html
поставляемый
Normal file
|
@ -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>
|
14247
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/assets/chartjs-7fd89fd7.js
поставляемый
Normal file
3516
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/assets/flights-airports-9a9e6422.js
поставляемый
Normal file
12315
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/assets/plot-37d2a5fb.js
поставляемый
Normal file
35
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/chartjs.html
поставляемый
Normal file
|
@ -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>
|
33
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/developer.html
поставляемый
Normal file
|
@ -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>
|
15
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/index.html
поставляемый
Normal file
|
@ -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>
|
37
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/dist/observable-plot.html
поставляемый
Normal file
|
@ -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>
|
15
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/index.html
поставляемый
Normal file
|
@ -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>
|
35
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/observable-plot.html
поставляемый
Normal file
|
@ -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>
|
233
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/observable-plot.js
поставляемый
Normal file
|
@ -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
19
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/package.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"
|
||||
}
|
||||
}
|
78
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/readme.md
поставляемый
Normal file
|
@ -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).
|
17
third_party/webkit/PerformanceTests/Speedometer3/resources/charts/vite.config.js
поставляемый
Normal file
|
@ -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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
253
third_party/webkit/PerformanceTests/Speedometer3/resources/developer-mode.mjs
поставляемый
Normal file
|
@ -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);
|
||||
}
|
26
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/.gitignore
поставляемый
Normal file
|
@ -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?
|
2
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/assets/README.md
поставляемый
Normal file
|
@ -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
|
29869
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/assets/longscript.js
поставляемый
Normal file
1560
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/assets/longtext.html
поставляемый
Normal file
206
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/codemirror.html
поставляемый
Normal file
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
47
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/codemirror.js
поставляемый
Normal file
|
@ -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),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
21907
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/assets/codemirror-521de7ab.js
поставляемый
Normal file
66
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/assets/index-2feebe42.css
поставляемый
Normal file
|
@ -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;
|
||||
}
|
128
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/assets/index.es-02a92ebc.js
поставляемый
Normal file
|
@ -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 ago—never mind how long precisely—having 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 people’s hats off—then, 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
|
||||
};
|
16357
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/assets/tiptap-95a40ba8.js
поставляемый
Normal file
144
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/codemirror.html
поставляемый
Normal file
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
32
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/index.html
поставляемый
Normal file
|
@ -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>
|
134
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/tiptap.html
поставляемый
Normal file
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
1
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/dist/vite.svg
поставляемый
Normal file
|
@ -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 |
32
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/index.html
поставляемый
Normal file
|
@ -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>
|
2
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/longtext.js
поставляемый
Normal file
|
@ -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
18
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/package.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"
|
||||
}
|
||||
}
|
1
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/public/vite.svg
поставляемый
Normal file
|
@ -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 |
27
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/readme.md
поставляемый
Normal file
|
@ -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 |
9
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/shorttext.js
поставляемый
Normal file
|
@ -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 ago—never mind how long precisely—having 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 people’s hats off—then, 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>
|
||||
`;
|
66
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/style.css
поставляемый
Normal file
|
@ -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;
|
||||
}
|
196
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/tiptap.html
поставляемый
Normal file
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
42
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/tiptap.js
поставляемый
Normal file
|
@ -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();
|
||||
},
|
||||
};
|
||||
}
|
16
third_party/webkit/PerformanceTests/Speedometer3/resources/editors/vite.config.js
поставляемый
Normal file
|
@ -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 |
213
third_party/webkit/PerformanceTests/Speedometer3/resources/interactive.mjs
поставляемый
Normal file
|
@ -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();
|
323
third_party/webkit/PerformanceTests/Speedometer3/resources/metric-ui.mjs
поставляемый
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
3
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/.eslintrc.json
поставляемый
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "plugin:@next/next/recommended"
|
||||
}
|
38
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/.gitignore
поставляемый
Normal file
|
@ -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
|
1
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/dist/404.html
поставляемый
Normal file
|
@ -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 |
1
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/dist/index.html
поставляемый
Normal file
|
@ -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>
|
Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/dist/placeholder_light.jpg
поставляемый
Normal file
После Ширина: | Высота: | Размер: 35 KiB |
7
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/jsconfig.json
поставляемый
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
12
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/next.config.js
поставляемый
Normal file
|
@ -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
25
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/package.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 |
Двоичные данные
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/public/placeholder_light.jpg
поставляемый
Normal file
После Ширина: | Высота: | Размер: 35 KiB |
38
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/readme.md
поставляемый
Normal file
|
@ -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.
|
13
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/src/assets/a11y-icon.jsx
поставляемый
Normal file
|
@ -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>
|
||||
);
|
||||
}
|
10
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/src/assets/facebook-icon.jsx
поставляемый
Normal file
|
@ -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>
|
||||
);
|
||||
}
|
10
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/src/assets/fire-icon.jsx
поставляемый
Normal file
|
@ -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>
|
||||
);
|
||||
}
|
10
third_party/webkit/PerformanceTests/Speedometer3/resources/newssite/news-next/src/assets/instagram-icon.jsx
поставляемый
Normal file
|
@ -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>
|
||||
);
|
||||
}
|