| 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()); |
| |
| const settings = document.createElement("div"); |
| settings.className = "settings"; |
| settings.append(createUIForIterationCount()); |
| settings.append(createUIForMeasurementMethod()); |
| settings.append(createUIForWarmupSuite()); |
| settings.append(createUIForWarmupBeforeSync()); |
| settings.append(createUIForSyncStepDelay()); |
| settings.append(createUIForMeasurePrepare()); |
| settings.append(createUIForDomainPerIteration()); |
| settings.append(createUIForForceGC()); |
| settings.append(createUIForLeakyIframes()); |
| |
| content.append(settings); |
| |
| content.append(document.createElement("hr")); |
| content.append(createUIForRun()); |
| |
| details.append(content); |
| container.append(details); |
| return container; |
| } |
| |
| function span(text) { |
| const span = document.createElement("span"); |
| span.textContent = text; |
| return span; |
| } |
| |
| 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, " ", span("rAF timing")); |
| |
| return label; |
| } |
| |
| function createUIForLeakyIframes() { |
| let check = document.createElement("input"); |
| check.type = "checkbox"; |
| check.id = "leaky-iframes-checkbox"; |
| check.checked = !!params.leakyIframes; |
| |
| check.onchange = () => { |
| params.leakyIframes = check.checked; |
| updateURL(); |
| }; |
| |
| let label = document.createElement("label"); |
| label.append(check, " ", span("Leak IFrames")); |
| |
| return label; |
| } |
| |
| 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, " ", span("Use Warmup Suite")); |
| |
| return label; |
| } |
| |
| function createUIForMeasurePrepare() { |
| let check = document.createElement("input"); |
| check.type = "checkbox"; |
| check.id = "measure-prepare"; |
| check.checked = params.measurePrepare; |
| |
| check.onchange = () => { |
| params.measurePrepare = check.checked; |
| updateURL(); |
| }; |
| |
| let label = document.createElement("label"); |
| label.append(check, " ", "Measure Prepare"); |
| |
| return label; |
| } |
| |
| function createUIForDomainPerIteration() { |
| let check = document.createElement("input"); |
| check.type = "checkbox"; |
| check.id = "domain-per-iteration"; |
| check.checked = params.domainPerIteration; |
| |
| check.onchange = () => { |
| params.domainPerIteration = check.checked; |
| updateURL(); |
| }; |
| |
| let label = document.createElement("label"); |
| label.append(check, " ", "Use Subdomain-Runner"); |
| |
| return label; |
| } |
| |
| function createUIForForceGC() { |
| let check = document.createElement("input"); |
| check.type = "checkbox"; |
| check.id = "suite-force-gc"; |
| check.checked = params.suiteForceGC; |
| |
| check.onchange = () => { |
| params.suiteForceGC = check.checked; |
| updateURL(); |
| }; |
| |
| let label = document.createElement("label"); |
| label.append(check, " ", "Force GC before every Suite"); |
| |
| return label; |
| } |
| |
| function createUIForIterationCount() { |
| const { range, label } = createTimeRangeUI("Iterations: ", params.iterationCount, "#", 1, 200); |
| range.onchange = () => { |
| params.iterationCount = parseInt(range.value); |
| updateURL(); |
| }; |
| return label; |
| } |
| |
| function createUIForWarmupBeforeSync() { |
| const { range, label } = createTimeRangeUI("Warmup time: ", params.warmupBeforeSync); |
| range.onchange = () => { |
| params.warmupBeforeSync = parseInt(range.value); |
| updateURL(); |
| }; |
| return label; |
| } |
| |
| function createUIForSyncStepDelay() { |
| const { range, label } = createTimeRangeUI("Sync step delay: ", params.waitBeforeSync); |
| range.onchange = () => { |
| params.waitBeforeSync = parseInt(range.value); |
| updateURL(); |
| }; |
| return label; |
| } |
| |
| function createTimeRangeUI(labelText, initialValue, unit = "ms", min = 0, max = 1000) { |
| const range = document.createElement("input"); |
| range.type = "range"; |
| range.min = min; |
| range.max = max; |
| range.value = initialValue; |
| |
| const rangeValueAndUnit = document.createElement("span"); |
| rangeValueAndUnit.className = "range-label-data"; |
| |
| const rangeValue = document.createElement("span"); |
| rangeValue.textContent = initialValue; |
| rangeValueAndUnit.append(rangeValue, " ", unit); |
| |
| const label = document.createElement("label"); |
| label.append(span(labelText), range, rangeValueAndUnit); |
| |
| range.oninput = () => { |
| rangeValue.textContent = range.value; |
| }; |
| |
| return { range, label }; |
| } |
| |
| function createUIForSuites() { |
| const control = document.createElement("nav"); |
| control.className = "suites"; |
| 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"); |
| |
| const boolParamKeys = ["iterationCount", "useWarmupSuite", "warmupBeforeSync", "waitBeforeSync", "domainPerIteration", "suiteForceGC", "leakyIframes"]; |
| for (const paramKey of boolParamKeys) { |
| if (params[paramKey] !== defaultParams[paramKey]) |
| url.searchParams.set(paramKey, params[paramKey]); |
| else |
| url.searchParams.delete(paramKey); |
| } |
| |
| if (params.measurePrepare !== defaultParams.measurePrepare) |
| url.searchParams.set("measurePrepare", params.measurePrepare); |
| else |
| url.searchParams.delete("measurePrepare"); |
| |
| // Only push state if changed |
| url.search = decodeURIComponent(url.search); |
| if (url.href !== window.location.href) |
| window.history.pushState({}, "", url); |
| } |