Skip to content

Commit ba41f91

Browse files
authored
Customize Force Layout (#510)
Currently we have two different modes in which we force layout - getBoundingClientRect used by default in the benchmark runner and tests - elementFromPoint used in the remote workloads This PR adds a new layoutMode param to switch between the two modes.
1 parent 25eb32e commit ba41f91

File tree

5 files changed

+57
-20
lines changed

5 files changed

+57
-20
lines changed

resources/benchmark-runner.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Metric } from "./metric.mjs";
22
import { params } from "./shared/params.mjs";
3+
import { forceLayout } from "./shared/helpers.mjs";
34
import { SUITE_RUNNER_LOOKUP } from "./suite-runner.mjs";
45

56
const performance = globalThis.performance;
@@ -30,8 +31,9 @@ class Page {
3031
}
3132

3233
layout() {
33-
const body = this._frame.contentDocument.body.getBoundingClientRect();
34-
this.layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
34+
const body = this._frame ? this._frame.contentDocument.body : document.body;
35+
const value = forceLayout(body, params.layoutMode);
36+
body._leakedLayoutValue = value; // Prevent dead code elimination.
3537
}
3638

3739
async waitForElement(selector) {

resources/developer-mode.mjs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Suites, Tags } from "./tests.mjs";
2-
import { params } from "./shared/params.mjs";
2+
import { params, LAYOUT_MODES } from "./shared/params.mjs";
33

44
export function createDeveloperModeContainer() {
55
const container = document.createElement("div");
@@ -23,6 +23,7 @@ export function createDeveloperModeContainer() {
2323
settings.append(createUIForWarmupBeforeSync());
2424
settings.append(createUIForSyncStepDelay());
2525
settings.append(createUIForAsyncSteps());
26+
settings.append(createUIForLayoutMode());
2627

2728
content.append(document.createElement("hr"));
2829
content.append(settings);
@@ -114,6 +115,31 @@ function createTimeRangeUI(labelText, paramKey, unit = "ms", min = 0, max = 1000
114115
return label;
115116
}
116117

118+
function createUIForLayoutMode() {
119+
return createSelectUI("Force layout mode", params.layoutMode, LAYOUT_MODES, (value) => {
120+
params.layoutMode = value;
121+
});
122+
}
123+
124+
function createSelectUI(labelValue, initialValue, choices, paramsUpdateCallback) {
125+
const select = document.createElement("select");
126+
select.onchange = () => {
127+
paramsUpdateCallback(select.value);
128+
updateURL();
129+
};
130+
131+
choices.forEach((choice) => {
132+
const option = new Option(choice, choice);
133+
select.add(option);
134+
});
135+
select.value = initialValue;
136+
137+
const label = document.createElement("label");
138+
label.append(span(labelValue), select);
139+
140+
return label;
141+
}
142+
117143
function createUIForSuites() {
118144
const control = document.createElement("nav");
119145
control.className = "suites";

resources/shared/helpers.mjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@ export function getAllElements(selector, path = [], lookupStartNode = document)
2323
return elements;
2424
}
2525

26-
export function forceLayout() {
27-
const rect = document.body.getBoundingClientRect();
28-
const e = document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0);
29-
return e;
26+
export function forceLayout(body, layoutMode = "getBoundingRectAndElementFromPoint") {
27+
body ??= document.body;
28+
const rect = body.getBoundingClientRect();
29+
switch (layoutMode) {
30+
case "getBoundingRectAndElementFromPoint":
31+
return document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0);
32+
case "getBoundingClientRect":
33+
return rect.height;
34+
default:
35+
throw Error(`Invalid layoutMode: ${layoutMode}`);
36+
}
3037
}

resources/shared/params.mjs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export const LAYOUT_MODES = Object.freeze(["getBoundingClientRect", "getBoundingRectAndElementFromPoint"]);
2+
13
export class Params {
24
viewport = {
35
width: 800,
@@ -27,6 +29,8 @@ export class Params {
2729
// "generate": generate a random seed
2830
// <integer>: use the provided integer as a seed
2931
shuffleSeed = "off";
32+
// Choices: "getBoundingClientRect" or "getBoundingRectAndElementFromPoint"
33+
layoutMode = LAYOUT_MODES[0];
3034
// Measure more workload prepare time.
3135
measurePrepare = false;
3236

@@ -57,8 +61,9 @@ export class Params {
5761
this.useAsyncSteps = this._parseBooleanParam(searchParams, "useAsyncSteps");
5862
this.waitBeforeSync = this._parseIntParam(searchParams, "waitBeforeSync", 0);
5963
this.warmupBeforeSync = this._parseIntParam(searchParams, "warmupBeforeSync", 0);
60-
this.measurementMethod = this._parseMeasurementMethod(searchParams);
64+
this.measurementMethod = this._parseEnumParam(searchParams, "measurementMethod", ["raf"]);
6165
this.shuffleSeed = this._parseShuffleSeed(searchParams);
66+
this.layoutMode = this._parseEnumParam(searchParams, "layoutMode", LAYOUT_MODES);
6267
this.measurePrepare = this._parseBooleanParam(searchParams, "measurePrepare");
6368

6469
const unused = Array.from(searchParams.keys());
@@ -124,14 +129,14 @@ export class Params {
124129
return tags;
125130
}
126131

127-
_parseMeasurementMethod(searchParams) {
128-
if (!searchParams.has("measurementMethod"))
129-
return defaultParams.measurementMethod;
130-
const measurementMethod = searchParams.get("measurementMethod");
131-
if (measurementMethod !== "raf")
132-
throw new Error(`Invalid measurement method: '${measurementMethod}', must be 'raf'.`);
133-
searchParams.delete("measurementMethod");
134-
return measurementMethod;
132+
_parseEnumParam(searchParams, paramKey, enumArray) {
133+
if (!searchParams.has(paramKey))
134+
return defaultParams[paramKey];
135+
const value = searchParams.get(paramKey);
136+
if (!enumArray.includes(value))
137+
throw new Error(`Got invalid ${paramKey}: '${value}', choices are ${enumArray}`);
138+
searchParams.delete(paramKey);
139+
return value;
135140
}
136141

137142
_parseShuffleSeed(searchParams) {

resources/shared/test-runner.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@ export class TestRunner {
6767
asyncStartTime = syncEndTime;
6868
};
6969
const measureAsync = () => {
70-
const bodyReference = this.#frame ? this.#frame.contentDocument.body : document.body;
71-
const windowReference = this.#frame ? this.#frame.contentWindow : window;
7270
// Some browsers don't immediately update the layout for paint.
7371
// Force the layout here to ensure we're measuring the layout time.
74-
const height = bodyReference.getBoundingClientRect().height;
75-
windowReference._unusedHeightValue = height; // Prevent dead code elimination.
72+
this.page.layout();
7673

7774
const asyncEndTime = performance.now();
7875
performance.mark(asyncEndLabel);

0 commit comments

Comments
 (0)