Skip to content

Commit 06449bd

Browse files
Enable Speedometer to use an external config.json file (#515)
1 parent 87f9ed8 commit 06449bd

File tree

14 files changed

+1335
-1218
lines changed

14 files changed

+1335
-1218
lines changed

resources/benchmark-configurator.mjs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// example url for local testing:
2+
// http://localhost:8080/?developerMode=true&config=http://localhost:8080/resources/config.json
3+
// since the json doesn't contain a default suite, dismiss warning popups and select from the developerMenu
4+
import { defaultSuites } from "./default-tests.mjs";
5+
import { params } from "./shared/params.mjs";
6+
7+
const DEFAULT_TAGS = ["all", "default", "experimental"];
8+
const DISALLOWED_DOMAINS = ["browserbench.org"];
9+
export class BenchmarkConfigurator {
10+
#tags = new Set(DEFAULT_TAGS);
11+
#suites = [];
12+
13+
get tags() {
14+
return this.#tags;
15+
}
16+
17+
get suites() {
18+
return this.#suites;
19+
}
20+
21+
/**
22+
* Checks if a given string is a valid URL, supporting both absolute and relative paths.
23+
*
24+
* This function attempts to construct a URL object. For relative paths, it uses
25+
* a dummy base URL to allow the URL constructor to parse them successfully.
26+
*
27+
* @param {string} url The URL string to validate.
28+
* @returns {boolean} True if the URL is valid (absolute or relative), false otherwise.
29+
*/
30+
_isValidUrl(url) {
31+
if (typeof url !== "string" || url.length === 0)
32+
return false;
33+
34+
try {
35+
new URL(url, "http://www.example.com");
36+
return true;
37+
} catch (error) {
38+
return false;
39+
}
40+
}
41+
42+
/**
43+
* Reports an error by showing an alert and logging a message to the console.
44+
* @private
45+
* @param {string} message The error message to display.
46+
* @param {object} [debugInfo] An optional object containing additional debug information.
47+
*/
48+
_reportError(message, debugInfo) {
49+
alert(message);
50+
console.error(message, debugInfo);
51+
}
52+
53+
_freezeSuites() {
54+
Object.freeze(this.#suites);
55+
this.#suites.forEach((suite) => {
56+
if (!suite.tags)
57+
suite.tags = [];
58+
if (suite.url.startsWith("experimental/"))
59+
suite.tags.unshift("all", "experimental");
60+
else
61+
suite.tags.unshift("all");
62+
suite.enabled = suite.tags.includes("default");
63+
Object.freeze(suite.tags);
64+
Object.freeze(suite.steps);
65+
});
66+
}
67+
68+
_freezeTags() {
69+
Object.freeze(this.#tags);
70+
}
71+
72+
async init() {
73+
if (params.config) {
74+
try {
75+
const benchmarkUrl = new URL(window.location);
76+
if (DISALLOWED_DOMAINS.some((domain) => benchmarkUrl.hostname.endsWith(domain))) {
77+
console.warn("Configuration fetch not allowed. Loading default suites.");
78+
this._loadDefaultSuites();
79+
return;
80+
}
81+
82+
const response = await fetch(params.config);
83+
84+
if (!response.ok)
85+
throw new Error(`Could not fetch config: ${response.status}`);
86+
87+
const config = await response.json();
88+
89+
if (!config || !Array.isArray(config.suites))
90+
throw new Error("Could not find a valid config structure!");
91+
92+
config.suites.flatMap((suite) => suite.tags || []).forEach((tag) => this.#tags.add(tag));
93+
config.suites.forEach((suite) => {
94+
if (suite && suite.url && this._isValidUrl(suite.url))
95+
this.#suites.push(suite);
96+
else
97+
throw new Error("Invalid suite data");
98+
});
99+
} catch (error) {
100+
console.warn(`Error loading custom configuration: ${error.message}. Loading default suites.`);
101+
this._loadDefaultSuites();
102+
}
103+
} else {
104+
this._loadDefaultSuites();
105+
}
106+
107+
this._freezeTags();
108+
this._freezeSuites();
109+
}
110+
111+
_loadDefaultSuites() {
112+
defaultSuites.flatMap((suite) => suite.tags).forEach((tag) => this.#tags.add(tag));
113+
defaultSuites.forEach((suite) => this.#suites.push(suite));
114+
}
115+
116+
enableSuites(names, tags) {
117+
if (names?.length) {
118+
const lowerCaseNames = names.map((each) => each.toLowerCase());
119+
this.#suites.forEach((suite) => {
120+
suite.enabled = lowerCaseNames.includes(suite.name.toLowerCase());
121+
});
122+
} else if (tags?.length) {
123+
tags.forEach((tag) => {
124+
if (!this.#tags.has(tag))
125+
console.error(`Unknown Suites tag: "${tag}"`);
126+
});
127+
const tagsSet = new Set(tags);
128+
this.#suites.forEach((suite) => {
129+
suite.enabled = suite.tags.some((tag) => tagsSet.has(tag));
130+
});
131+
} else {
132+
console.warn("Neither names nor tags provided. Enabling all default suites.");
133+
this.#suites.forEach((suite) => {
134+
suite.enabled = suite.tags.includes("default");
135+
});
136+
}
137+
if (this.#suites.some((suite) => suite.enabled))
138+
return;
139+
140+
if (names?.length) {
141+
this._reportError(`Suites "${names}" does not match any Suite. No tests to run.`, {
142+
providedNames: names,
143+
validNames: this.#suites.map((each) => each.name),
144+
});
145+
} else if (tags?.length) {
146+
this._reportError(`Tags "${tags}" does not match any Suite. No tests to run.`, {
147+
providedTags: tags,
148+
validTags: Array.from(this.#tags),
149+
});
150+
}
151+
}
152+
}
153+
154+
const benchmarkConfigurator = new BenchmarkConfigurator();
155+
await benchmarkConfigurator.init();
156+
157+
export { benchmarkConfigurator };

resources/config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"suites": [
3+
{
4+
"name": "NewsSite-PostMessage",
5+
"url": "resources/newssite/news-next/dist/index.html",
6+
"tags": ["newssite", "language"],
7+
"type": "remote",
8+
"config": {
9+
"name": "default"
10+
}
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)