Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion resources/shared/benchmark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class BenchmarkConnector {
async onMessage(event) {
const message = event.data;
if (message.appId !== this.appId || message.key !== "benchmark-connector") {
console.warning("Invalid message", message);
console.warn("Invalid message", message);
return;
}

Expand Down
14 changes: 10 additions & 4 deletions resources/shared/step-runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ export class StepRunner {
#params;
#suite;
#step;
#type;

constructor(frame, page, params, suite, step, type) {
constructor(frame, page, params, suite, step) {
this.#suite = suite;
this.#step = step;
this.#params = params;
this.#page = page;
this.#frame = frame;
this.#type = type;
}

get page() {
Expand All @@ -26,6 +24,10 @@ export class StepRunner {
return this.#step;
}

get isAsync() {
return false;
}

_runSyncStep(step, page) {
step.run(page);
}
Expand Down Expand Up @@ -54,7 +56,7 @@ export class StepRunner {
performance.mark(syncStartLabel);
const syncStartTime = performance.now();

if (this.#type === "async")
if (this.isAsync)
await this._runSyncStep(this.step, this.page);
else
this._runSyncStep(this.step, this.page);
Expand Down Expand Up @@ -96,6 +98,10 @@ export class StepRunner {
}

export class AsyncStepRunner extends StepRunner {
get isAsync() {
return true;
}

async _runSyncStep(step, page) {
await step.run(page);
}
Expand Down
2 changes: 1 addition & 1 deletion resources/suite-runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class SuiteRunner {

const stepRunnerType = this.#suite.type ?? this.params.useAsyncSteps ? "async" : "default";
const stepRunnerClass = STEP_RUNNER_LOOKUP[stepRunnerType];
const stepRunner = new stepRunnerClass(this.#frame, this.#page, this.#params, this.#suite, step, stepRunnerType);
const stepRunner = new stepRunnerClass(this.#frame, this.#page, this.#params, this.#suite, step);
let { syncTime, asyncTime } = await stepRunner.runStep();
this._recordTestResults(step, syncTime, asyncTime);
}
Expand Down
2 changes: 2 additions & 0 deletions suites-experimental/pdf-viewer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
public
103 changes: 103 additions & 0 deletions suites-experimental/pdf-viewer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# PDFViewer-PDFjs

## Description

A document viewer built on [pdf.js](https://mozilla.github.io/pdf.js/), the way sites
embed it for inline previews. It exercises work no other suite covers: canvas 2D drawing,
rasterising glyphs from embedded fonts, and JPEG decoding.

The viewer uses the pdf.js core API directly rather than `pdf_viewer.mjs`, whose render
queue and scroll handling schedule work with timers we don't want inside a measured step.
It has a toolbar and a thumbnail sidebar similar to Firefox's built-in viewer, and lays
pages out in a wrapping grid. The toolbar buttons are inert.

### Benchmark steps

`RenderPages` renders `6 * complexity` pages, each into its main canvas and its sidebar
thumbnail, and waits for all of them. The pages render concurrently, since awaiting them
one by one would leave the step idle waiting on the worker.

Worker startup, fetching the document, `getDocument` and `getPage` all happen before the
app signals `app-ready`, so they are not measured. Nothing is rendered ahead of the step:
pdf.js caches a page's operator list, so rendering early would leave the step with a warm
cache.

The suite is `remote`. The steps await pdf.js promises, so they run inside the workload's
frame through `BenchmarkConnector`.

### The requestAnimationFrame shim

pdf.js splits a render into 15ms chunks and continues each one on the next animation
frame. That ties the step's duration to the display's refresh rate, so for the duration
of the step `requestAnimationFrame` is replaced with a zero-delay timer. The shim is
scoped to the step because the harness's step scheduler also uses animation frames.

## The document

`document.pdf` is a 24-page report generated at build time by `scripts/generate-pdf.mjs`
with [PDFKit](https://pdfkit.org/), so what it contains is reviewable as code. The output
is byte-identical between builds. Each page is one of four layouts: text, photos, charts
and a mixed page with a table. Between them they cover three embedded subset fonts, four
JPEG photos, an image soft mask, axial and radial shadings, a tiling pattern, clipping,
dash patterns, blend modes, link annotations, an outline and a tagged structure tree.

Everything stays inside pdf.js' free tier: no CMaps, no standard fonts and no wasm image
decoders. `npm run analyze:pdf` checks that and exits non-zero if it stops being true.

## Params

- `complexity` scales the number of pages rendered.
- `pdfPixelRatio` sets canvas backing-store pixels per CSS pixel. It is pinned to `2`
instead of following `devicePixelRatio`, so every machine rasterises the same number of
pixels. The harness only forwards params it knows, so to override it add it to the
suite's `url` in `suites-experimental/suites.mjs`.

## Build

```sh
npm install
npm run build
```

`prebuild` regenerates `public/document.pdf`; `public/` is gitignored and `dist/` is
committed. Format sources before building so the committed source map stays in sync.

## Requirements

```
* Node (min version: 24.0.0)
```

## Local preview

Run the dev server from the repo root and load the suite through the harness:

```
http://127.0.0.1:8080/?developerMode&iterationCount=1&suites=PDFViewer-PDFjs
```

`npm run dev` inside this directory serves the viewer on its own, which is useful for
layout work but does not run the steps.

## Dev tools

```sh
npm run generate:pdf # rebuild public/document.pdf
npm run analyze:pdf # report what the document contains
```

## Third-party assets

The photos are NASA and National Park Service works in the public domain; see
[`assets/README.md`](assets/README.md) for sources.

The toolbar and sidebar icons come from `pdfjs-dist/web/images/` (Apache-2.0) and are
inlined into the built stylesheet.

The fonts are read from `node_modules` at build time and embedded as subsets:

| Role | Font | License |
| -------- | ----------------------------------------------------------------- | ----------- |
| Body | [Source Serif 4](https://github.com/adobe-fonts/source-serif) | SIL OFL 1.1 |
| Headings | [Inter](https://github.com/rsms/inter) | SIL OFL 1.1 |
| Code | [Source Code Pro](https://github.com/adobe-fonts/source-code-pro) | SIL OFL 1.1 |
29 changes: 29 additions & 0 deletions suites-experimental/pdf-viewer/assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Photo provenance

Every photo here is a work of the U.S. federal government (NASA or the National Park
Service) and is therefore in the public domain in the United States. None of them
carries an attribution requirement; the credits below are recorded for traceability,
not because the license demands them.

Each file was downloaded from Wikimedia Commons at full resolution, then resized and
re-encoded once with ImageMagick and committed in that form:

```sh
magick <original> -auto-orient -resize <box> -strip -sampling-factor 4:2:0 \
-quality 82 -interlace none -colorspace sRGB <output>
```

They are committed pre-sized so `scripts/generate-pdf.mjs` can embed the JPEG bytes
verbatim as `DCTDecode` streams without re-encoding, which keeps the generated PDF
byte-deterministic.

| File | Box | Source | Credit |
| ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `earth-apollo17.jpg` | 1400x1400 | [The Earth seen from Apollo 17](https://commons.wikimedia.org/wiki/File:The_Earth_seen_from_Apollo_17.jpg) | NASA, Apollo 17 crew, 1972 |
| `earthrise-apollo8.jpg` | 1200x1200 | [NASA-Apollo8-Dec24-Earthrise](https://commons.wikimedia.org/wiki/File:NASA-Apollo8-Dec24-Earthrise.jpg) | NASA / Bill Anders, Apollo 8, 1968 |
| `apollo11-launch.jpg` | 1120x1400 | [Apollo 11 Launch - GPN-2000-000630](https://commons.wikimedia.org/wiki/File:Apollo_11_Launch_-_GPN-2000-000630.jpg) | NASA, 1969 |
| `grand-prismatic-spring.jpg` | 1400x1400 | [Aerial view of Excelsior Geyser and Grand Prismatic Spring](<https://commons.wikimedia.org/wiki/File:Aerial_view_of_Excelsior_Geyser_and_Grand_Prismatic_Spring_(23320428202).jpg>) | National Park Service / Yellowstone National Park, 2006 |

The four subjects were picked to spread the JPEG decode work: a bright subject on a
large black field, a small low-contrast frame, a grainy daylight scan with a smooth sky
gradient, and a saturated high-frequency aerial.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions suites-experimental/pdf-viewer/dist/assets/index.css

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions suites-experimental/pdf-viewer/dist/assets/index.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions suites-experimental/pdf-viewer/dist/assets/index.js.map

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions suites-experimental/pdf-viewer/dist/assets/pdf.worker.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Binary file added suites-experimental/pdf-viewer/dist/document.pdf
Binary file not shown.
19 changes: 19 additions & 0 deletions suites-experimental/pdf-viewer/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PDF Viewer</title>
<script type="module" crossorigin src="./assets/index.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index.css">
</head>
<body>
<div class="pdf-app">
<div class="pdf-toolbar" id="toolbar"></div>
<div class="pdf-body">
<div class="pdf-sidebar" id="sidebar"></div>
<div class="pdf-viewer" id="viewer"></div>
</div>
</div>
</body>
</html>
7 changes: 7 additions & 0 deletions suites-experimental/pdf-viewer/dist/resources.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
assets/index.css
assets/index.js
assets/index.js.map
assets/pdf.worker.js
assets/pdf.worker.js.map
document.pdf
index.html
19 changes: 19 additions & 0 deletions suites-experimental/pdf-viewer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PDF Viewer</title>
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div class="pdf-app">
<div class="pdf-toolbar" id="toolbar"></div>
<div class="pdf-body">
<div class="pdf-sidebar" id="sidebar"></div>
<div class="pdf-viewer" id="viewer"></div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Loading
Loading