resources/perf.webkit.org/public/v3/components/custom-analysis-task-configurator.js (684 lines of code) (raw):

class CustomAnalysisTaskConfigurator extends ComponentBase { constructor() { super('custom-analysis-task-configurator'); this._selectedTests = []; this._triggerablePlatforms = []; this._selectedPlatform = null; this._showComparison = false; this._commitSetMap = {}; this._specifiedRevisions = {'Baseline': new Map, 'Comparison': new Map}; this._patchUploaders = {'Baseline': new Map, 'Comparison': new Map}; this._customRootUploaders = {'Baseline': null, 'Comparison': null}; this._fetchedCommits = {'Baseline': new Map, 'Comparison': new Map}; this._repositoryGroupByConfiguration = {'Baseline': null, 'Comparison': null}; this._invalidRevisionsByConfiguration = {'Baseline': new Map, 'Comparison': new Map}; this._updateTriggerableLazily = new LazilyEvaluatedFunction(this._updateTriggerable.bind(this)); this._renderTriggerableTestsLazily = new LazilyEvaluatedFunction(this._renderTriggerableTests.bind(this)); this._renderTriggerablePlatformsLazily = new LazilyEvaluatedFunction(this._renderTriggerablePlatforms.bind(this)); this._renderRepositoryPanesLazily = new LazilyEvaluatedFunction(this._renderRepositoryPanes.bind(this)); } tests() { return this._selectedTests; } platform() { return this._selectedPlatform; } commitSets() { const map = this._commitSetMap; if (!map['Baseline'] || !map['Comparison']) return null; return [map['Baseline'], map['Comparison']]; } selectTests(selectedTests) { this._selectedTests = selectedTests; this._triggerablePlatforms = Triggerable.triggerablePlatformsForTests(this._selectedTests); if (this._selectedTests.length && !this._triggerablePlatforms.includes(this._selectedPlatform)) this._selectedPlatform = null; this._didUpdateSelectedPlatforms(); } selectPlatform(selectedPlatform) { this._selectedPlatform = selectedPlatform; const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform); this._updateRepositoryGroups(triggerable); this._didUpdateSelectedPlatforms(); } _didUpdateSelectedPlatforms() { for (const configuration of ['Baseline', 'Comparison']) { this._updateMapFromSpecifiedRevisionsForConfiguration(this._fetchedCommits, configuration); this._updateMapFromSpecifiedRevisionsForConfiguration(this._invalidRevisionsByConfiguration, configuration); } this._updateCommitSetMap(); this.dispatchAction('testConfigChange'); this.enqueueToRender(); } _updateMapFromSpecifiedRevisionsForConfiguration(map, configuration) { const referenceMap = this._specifiedRevisions[configuration]; const newValue = new Map; for (const [key, value] of map[configuration].entries()) { if (!referenceMap.has(key)) continue; newValue.set(key, value); } if (newValue.size !== map[configuration].size) map[configuration] = newValue; } setCommitSets(baselineCommitSet, comparisonCommitSet) { const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform); if (!triggerable) return; const baselineRepositoryGroup = triggerable.repositoryGroups().find((repositoryGroup) => repositoryGroup.accepts(baselineCommitSet)); if (baselineRepositoryGroup) { this._repositoryGroupByConfiguration['Baseline'] = baselineRepositoryGroup; this._setUploadedFilesToUploader(this._customRootUploaders['Baseline'], baselineCommitSet.customRoots()); this._specifiedRevisions['Baseline'] = this._revisionMapFromCommitSet(baselineCommitSet); this._setPatchFiles('Baseline', baselineCommitSet); } const comparisonRepositoryGroup = triggerable.repositoryGroups().find((repositoryGroup) => repositoryGroup.accepts(baselineCommitSet)); if (comparisonRepositoryGroup) { this._repositoryGroupByConfiguration['Comparison'] = comparisonRepositoryGroup; this._setUploadedFilesToUploader(this._customRootUploaders['Comparison'], comparisonCommitSet.customRoots()); this._specifiedRevisions['Comparison'] = this._revisionMapFromCommitSet(comparisonCommitSet); this._setPatchFiles('Comparison', comparisonCommitSet); } this._showComparison = true; this._updateCommitSetMap(); } _setUploadedFilesToUploader(uploader, files) { if (!uploader || uploader.hasFileToUpload() || uploader.uploadedFiles().length) return; uploader.clearUploads(); for (const uploadedFile of files) uploader.addUploadedFile(uploadedFile); } _setPatchFiles(configurationName, commitSet) { for (const repository of commitSet.repositories()) { const patch = commitSet.patchForRepository(repository); if (patch) this._setUploadedFilesToUploader(this._ensurePatchUploader(configurationName, repository), [patch]); } } _revisionMapFromCommitSet(commitSet) { const revisionMap = new Map; for (const repository of commitSet.repositories()) revisionMap.set(repository, commitSet.revisionForRepository(repository)); return revisionMap; } didConstructShadowTree() { this.content('specify-comparison-button').onclick = this.createEventHandler(() => this._configureComparison()); const createRootUploader = () => { const uploader = new InstantFileUploader; uploader.allowMultipleFiles(); uploader.element().textContent = 'Add a new root'; uploader.listenToAction('removedFile', () => this._updateCommitSetMap()); return uploader; } const baselineRootsUploader = createRootUploader(); baselineRootsUploader.listenToAction('uploadedFile', (uploadedFile) => this._updateCommitSetMap()); this._customRootUploaders['Baseline'] = baselineRootsUploader; const comparisonRootsUploader = createRootUploader(); comparisonRootsUploader.listenToAction('uploadedFile', () => this._updateCommitSetMap()); this._customRootUploaders['Comparison'] = comparisonRootsUploader; } _ensurePatchUploader(configurationName, repository) { const uploaderMap = this._patchUploaders[configurationName]; let uploader = uploaderMap.get(repository); if (uploader) return uploader; uploader = new InstantFileUploader; uploader.element().textContent = 'Apply a patch'; uploader.listenToAction('uploadedFile', () => this._updateCommitSetMap()); uploader.listenToAction('removedFile', () => this._updateCommitSetMap()); uploaderMap.set(repository, uploader); return uploader; } _configureComparison() { this._showComparison = true; this._repositoryGroupByConfiguration['Comparison'] = this._repositoryGroupByConfiguration['Baseline']; const specifiedBaselineRevisions = this._specifiedRevisions['Baseline']; const specifiedComparisonRevisions = new Map; for (let key of specifiedBaselineRevisions.keys()) specifiedComparisonRevisions.set(key, specifiedBaselineRevisions.get(key)); this._specifiedRevisions['Comparison'] = specifiedComparisonRevisions; for (const [repository, patchUploader] of this._patchUploaders['Baseline']) { const files = patchUploader.uploadedFiles(); if (!files.length) continue; const comparisonPatchUploader = this._ensurePatchUploader('Comparison', repository); for (const uploadedFile of files) comparisonPatchUploader.addUploadedFile(uploadedFile); } const comparisonRootUploader = this._customRootUploaders['Comparison']; for (const uploadedFile of this._customRootUploaders['Baseline'].uploadedFiles()) comparisonRootUploader.addUploadedFile(uploadedFile); this.enqueueToRender(); } render() { super.render(); const updateSelectedTestsLazily = this._renderTriggerableTestsLazily.evaluate(); updateSelectedTestsLazily.evaluate(...this._selectedTests); const updateSelectedPlatformsLazily = this._renderTriggerablePlatformsLazily.evaluate(this._selectedTests, this._triggerablePlatforms); if (updateSelectedPlatformsLazily) updateSelectedPlatformsLazily.evaluate(this._selectedPlatform); const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform); this._renderRepositoryPanesLazily.evaluate(triggerable, error, this._selectedPlatform, this._repositoryGroupByConfiguration, this._showComparison); this.renderReplace(this.content('baseline-testability'), this._buildTestabilityList(this._commitSetMap['Baseline'], 'Baseline', this._invalidRevisionsByConfiguration['Baseline'])); this.renderReplace(this.content('comparison-testability'), !this._showComparison ? null : this._buildTestabilityList(this._commitSetMap['Comparison'], 'Comparison', this._invalidRevisionsByConfiguration['Comparison'])); } _renderTriggerableTests() { const enabledTriggerables = Triggerable.all().filter((triggerable) => !triggerable.isDisabled()); const acceptedTests = new Set; for (const triggerable of enabledTriggerables) { for (const test of triggerable.acceptedTests()) acceptedTests.add(test); } const tests = [...acceptedTests].sort((testA, testB) => { if (testA.fullName() == testB.fullName()) return 0; return testA.fullName() < testB.fullName() ? -1 : 1; }); return this._renderRadioButtonList(this.content('test-list'), 'test', tests, this.selectTests.bind(this), (test) => test.fullName()); } _renderTriggerablePlatforms(selectedTests, triggerablePlatforms) { if (!selectedTests.length) { this.content('platform-pane').style.display = 'none'; return null; } this.content('platform-pane').style.display = null; return this._renderRadioButtonList(this.content('platform-list'), 'platform', triggerablePlatforms, (selectedPlatforms) => { this.selectPlatform(selectedPlatforms.length ? selectedPlatforms[0] : null); }); } _renderRadioButtonList(listContainer, name, objects, callback, labelForObject = (object) => object.label()) { const listItems = []; let selectedListItems = []; const checkSelectedRadioButtons = (newSelectedListItems) => { selectedListItems.forEach((item) => { item.label.classList.remove('selected'); item.radioButton.checked = false; }); selectedListItems = newSelectedListItems; selectedListItems.forEach((item) => { item.label.classList.add('selected'); item.radioButton.checked = true; }); } const element = ComponentBase.createElement; this.renderReplace(listContainer, objects.map((object) => { const radioButton = element('input', {type: 'radio', name: name, onchange: () => { checkSelectedRadioButtons(listItems.filter((item) => item.radioButton.checked)); callback(selectedListItems.map((item) => item.object)); this.enqueueToRender(); }}); const label = element('label', [radioButton, labelForObject(object)]); listItems.push({radioButton, label, object}); return element('li', label); })); return new LazilyEvaluatedFunction((...selectedObjects) => { const objects = new Set(selectedObjects); checkSelectedRadioButtons(listItems.filter((item) => objects.has(item.object))); }); } _updateTriggerable(tests, platform) { let triggerable = null; let error = null; if (tests.length && platform) { triggerable = Triggerable.findByTestConfiguration(tests[0], platform); let matchingTests = new Set; let mismatchingTests = new Set; for (let test of tests) { if (Triggerable.findByTestConfiguration(test, platform) == triggerable) matchingTests.add(test); else mismatchingTests.add(test); } if (matchingTests.size < tests.length) { const matchingTestNames = [...matchingTests].map((test) => test.fullName()).sort().join('", "'); const mismathingTestNames = [...mismatchingTests].map((test) => test.fullName()).sort().join('", "'); error = `Tests "${matchingTestNames}" and "${mismathingTestNames}" cannot be scheduled simultenosuly on "${platform.label()}". Please select one of them at a time.`; } } return [triggerable, error]; } _updateRepositoryGroups(triggerable) { const repositoryGroups = triggerable ? TriggerableRepositoryGroup.sortByNamePreferringSmallerRepositories(triggerable.repositoryGroups()) : []; for (let name in this._repositoryGroupByConfiguration) { const currentGroup = this._repositoryGroupByConfiguration[name]; let matchingGroup = null; if (currentGroup) { if (repositoryGroups.includes(currentGroup)) matchingGroup = currentGroup; else matchingGroup = repositoryGroups.find((group) => group.name() == currentGroup.name()); } if (!matchingGroup && repositoryGroups.length) matchingGroup = repositoryGroups[0]; this._repositoryGroupByConfiguration[name] = matchingGroup; } } _updateCommitSetMap() { const newBaseline = this._computeCommitSet('Baseline'); let newComparison = this._computeCommitSet('Comparison'); if (newBaseline && newComparison && newBaseline.equals(newComparison)) newComparison = null; const currentBaseline = this._commitSetMap['Baseline']; const currentComparison = this._commitSetMap['Comparison']; const areCommitSetsEqual = (commitSetA, commitSetB) => commitSetA == commitSetB || (commitSetA && commitSetB && commitSetA.equals(commitSetB)); const sameBaselineConfig = areCommitSetsEqual(currentBaseline, newBaseline); const sameComparisionConfig = areCommitSetsEqual(currentComparison, newComparison); if (sameBaselineConfig && sameComparisionConfig) return; this._commitSetMap = {'Baseline': newBaseline, 'Comparison': newComparison}; this.dispatchAction('testConfigChange'); this.enqueueToRender(); } _computeCommitSet(configurationName) { const repositoryGroup = this._repositoryGroupByConfiguration[configurationName]; if (!repositoryGroup) return null; const fileUploader = this._customRootUploaders[configurationName]; if (!fileUploader || fileUploader.hasFileToUpload()) return null; const commitSet = new CustomCommitSet; for (let repository of repositoryGroup.repositories()) { let revision = this._specifiedRevisions[configurationName].get(repository); const commit = this._fetchedCommits[configurationName].get(repository); if (commit) { const commitLabel = commit.label(); if (!revision || commit.revision().startsWith(revision) || commitLabel.startsWith(revision) || revision.startsWith(commitLabel)) revision = commit.revision(); } if (!revision) return null; let patch = null; if (repositoryGroup.acceptsPatchForRepository(repository)) { const uploaderMap = this._patchUploaders[configurationName]; const uploader = uploaderMap.get(repository); if (uploader) { const files = uploader.uploadedFiles(); console.assert(files.length <= 1); if (files.length) patch = files[0]; } } commitSet.setRevisionForRepository(repository, revision, patch); } for (let uploadedFile of fileUploader.uploadedFiles()) commitSet.addCustomRoot(uploadedFile); return commitSet; } async _fetchCommitsForConfiguration(configurationName) { const commitSet = this._commitSetMap[configurationName]; if (!commitSet) return; const specifiedRevisions = this._specifiedRevisions[configurationName]; const fetchedCommits = this._fetchedCommits[configurationName]; const invalidRevisionForRepository = this._invalidRevisionsByConfiguration[configurationName]; await Promise.all(Array.from(commitSet.repositories()).map((repository) => { const revision = commitSet.revisionForRepository(repository); return this._resolveRevision(repository, revision, specifiedRevisions, invalidRevisionForRepository, fetchedCommits); })); const latestCommitSet = this._commitSetMap[configurationName]; if (commitSet != latestCommitSet) return; this.enqueueToRender(); } async _resolveRevision(repository, revision, specifiedRevisions, invalidRevisionForRepository, fetchedCommits) { const fetchedCommit = fetchedCommits.get(repository); const specifiedRevision = specifiedRevisions.get(repository); if (fetchedCommit && fetchedCommit.revision() == revision && (!specifiedRevision || specifiedRevision == revision)) return; fetchedCommits.delete(repository); let commits = []; const revisionToFetch = specifiedRevision || revision; try { commits = await CommitLog.fetchForSingleRevision(repository, revisionToFetch, true); } catch (error) { console.assert(error == 'UnknownCommit' || error == 'AmbiguousRevisionPrefix'); if (revisionToFetch != specifiedRevisions.get(repository)) return; invalidRevisionForRepository.set(repository, `"${revisionToFetch}": ${error == 'UnknownCommit' ? 'Invalid revision' : 'Ambiguous revision prefix'}`); return; } console.assert(commits.length, 1); if (revisionToFetch != specifiedRevisions.get(repository)) return; invalidRevisionForRepository.delete(repository); fetchedCommits.set(repository, commits[0]); if (revisionToFetch != commits[0].revision()) this._updateCommitSetMap(); } _renderRepositoryPanes(triggerable, error, platform, repositoryGroupByConfiguration, showComparison) { this.content('repository-configuration-error-pane').style.display = error ? null : 'none'; this.content('error').textContent = error; this.content('baseline-configuration-pane').style.display = triggerable ? null : 'none'; this.content('specify-comparison-pane').style.display = triggerable && !showComparison ? null : 'none'; this.content('comparison-configuration-pane').style.display = triggerable && showComparison ? null : 'none'; if (!triggerable) return; const visibleRepositoryGroups = triggerable.repositoryGroups().filter((group) => !group.isHidden()); const repositoryGroups = TriggerableRepositoryGroup.sortByNamePreferringSmallerRepositories(visibleRepositoryGroups); const repositorySet = new Set; for (let group of repositoryGroups) { for (let repository of group.repositories()) repositorySet.add(repository); } const repositories = Repository.sortByNamePreferringOnesWithURL([...repositorySet]); const requiredRepositories = repositories.filter((repository) => { return repositoryGroups.every((group) => group.repositories().includes(repository)); }); const alwaysAcceptsCustomRoots = repositoryGroups.every((group) => group.acceptsCustomRoots()); this._renderBaselineRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots); if (showComparison) this._renderComparisonRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots); } _renderBaselineRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots) { let currentGroup = repositoryGroupByConfiguration['Baseline']; const optionalRepositoryList = this._optionalRepositoryList(currentGroup, requiredRepositories); this.renderReplace(this.content('baseline-revision-table'), this._buildRevisionTable('Baseline', repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots)); } _renderComparisonRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots) { let currentGroup = repositoryGroupByConfiguration['Comparison']; const optionalRepositoryList = this._optionalRepositoryList(currentGroup, requiredRepositories); this.renderReplace(this.content('comparison-revision-table'), this._buildRevisionTable('Comparison', repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots)); } _optionalRepositoryList(currentGroup, requiredRepositories) { if (!currentGroup) return []; return Repository.sortByNamePreferringOnesWithURL(currentGroup.repositories().filter((repository) => !requiredRepositories.includes(repository))); } _buildRevisionTable(configurationName, repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots) { const element = ComponentBase.createElement; const customRootsTBody = element('tbody', [ element('tr', [ element('th', 'Roots'), element('td', this._customRootUploaders[configurationName]), ]), ]); return [ element('tbody', requiredRepositories.map((repository) => { return element('tr', [ element('th', repository.name()), element('td', this._buildRevisionInput(configurationName, repository, platform)) ]); })), alwaysAcceptsCustomRoots ? customRootsTBody : [], element('tbody', [ element('tr', {'class': 'group-row'}, element('td', {colspan: 2}, this._buildRepositoryGroupList(repositoryGroups, currentGroup, configurationName))), ]), !alwaysAcceptsCustomRoots && currentGroup && currentGroup.acceptsCustomRoots() ? customRootsTBody : [], element('tbody', optionalRepositoryList.map((repository) => { let uploader = currentGroup.acceptsPatchForRepository(repository) ? this._ensurePatchUploader(configurationName, repository) : null; return element('tr',[ element('th', repository.name()), element('td', [ this._buildRevisionInput(configurationName, repository, platform), uploader || [], ]) ]); }) )]; } _buildTestabilityList(commitSet, configurationName, invalidRevisionForRepository) { const element = ComponentBase.createElement; const entries = []; if (!commitSet || !commitSet.repositories().length) return []; for (const repository of commitSet.repositories()) { const commit = this._fetchedCommits[configurationName].get(repository); if (commit && commit.testability() && !invalidRevisionForRepository.has(repository)) entries.push(element('li', `${commit.repository().name()} - "${commit.label()}": ${commit.testability()}`)); if (invalidRevisionForRepository.has(repository)) entries.push(element('li', `${repository.name()} - ${invalidRevisionForRepository.get(repository)}`)); } return entries; } _buildRepositoryGroupList(repositoryGroups, currentGroup, configurationName) { const element = ComponentBase.createElement; return repositoryGroups.map((group) => { const input = element('input', { type: 'radio', name: 'repositoryGroup-for-' + configurationName.toLowerCase(), checked: currentGroup == group, onchange: () => this._selectRepositoryGroup(configurationName, group) }); return [element('label', [input, group.description()])]; }); } _selectRepositoryGroup(configurationName, group) { const source = this._repositoryGroupByConfiguration; const clone = {}; for (let key in source) clone[key] = source[key]; clone[configurationName] = group; this._repositoryGroupByConfiguration = clone; this._updateCommitSetMap(); this._fetchCommitsForConfiguration(configurationName); this.enqueueToRender(); } _buildRevisionInput(configurationName, repository, platform) { const revision = this._specifiedRevisions[configurationName].get(repository) || ''; const element = ComponentBase.createElement; let scheduledUpdate = null; const input = element('input', {value: revision, oninput: () => { unmodifiedInput = null; const revisionToFetch = input.value; this._specifiedRevisions[configurationName].set(repository, revisionToFetch); this._updateCommitSetMap(); if (scheduledUpdate) clearTimeout(scheduledUpdate); scheduledUpdate = setTimeout(() => { if (revisionToFetch == input.value) this._fetchCommitsForConfiguration(configurationName); scheduledUpdate = null; }, CustomAnalysisTaskConfigurator.commitFetchInterval); }}); let unmodifiedInput = input; if (!revision) { CommitLog.fetchLatestCommitForPlatform(repository, platform).then((commit) => { if (commit && unmodifiedInput) { unmodifiedInput.value = commit.revision(); this._fetchedCommits[configurationName].set(repository, commit); this._updateCommitSetMap(); } }); } return input; } static htmlTemplate() { return ` <section id="test-pane" class="pane"> <h2>1. Select a Test</h2> <ul id="test-list" class="config-list"></ul> </section> <section id="platform-pane" class="pane"> <h2>2. Select a Platform</h2> <ul id="platform-list" class="config-list"></ul> </section> <section id="repository-configuration-error-pane" class="pane"> <h2>Incompatible tests</h2> <p id="error"></p> </section> <section id="baseline-configuration-pane" class="pane"> <h2>3. Configure Baseline</h2> <table id="baseline-revision-table" class="revision-table"></table> <ul id="baseline-testability"></ul> </section> <section id="specify-comparison-pane" class="pane"> <button id="specify-comparison-button">Configure to Compare</button> </section> <section id="comparison-configuration-pane" class="pane"> <h2>4. Configure Comparison</h2> <table id="comparison-revision-table" class="revision-table"></table> <ul id="comparison-testability"></ul> </section>`; } static cssTemplate() { return ` :host { display: flex !important; flex-direction: row !important; } .pane { margin-right: 1rem; padding: 0; } .pane h2 { padding: 0; margin: 0; margin-bottom: 0.5rem; font-size: 1.2rem; font-weight: inherit; text-align: center; white-space: nowrap; } .config-list { height: 20rem; overflow: scroll; display: block; margin: 0; padding: 0; list-style: none; font-size: inherit; font-weight: inherit; border: none; border-top: solid 1px #ddd; border-bottom: solid 1px #ddd; white-space: nowrap; } #platform-list:empty:before { content: "No matching platform"; display: block; margin: 1rem 0.5rem; text-align: center; } .config-list label { display: block; padding: 0.1rem 0.2rem; } .config-list label:hover, .config-list a:hover { background: rgba(204, 153, 51, 0.1); } .config-list label.selected, .config-list a.selected { background: #eee; } .config-list a { display: block; padding: 0.1rem 0.2rem; text-decoration: none; color: inherit; } #repository-configuration-pane { position: relative; } #repository-configuration-pane > button { margin-left: 19.5rem; } .revision-table { border: none; border-collapse: collapse; font-size: 1rem; } .revision-table thead { font-size: 1.2rem; } .revision-table tbody:empty { display: none; } .revision-table tbody td, .revision-table tbody th { border-top: solid 1px #ddd; padding-top: 0.5rem; padding-bottom: 0.5rem; } .revision-table td, .revision-table th { width: 15rem; height: 1.8rem; padding: 0 0.2rem; border: none; font-weight: inherit; } .revision-table thead th { text-align: center; } .revision-table th close-button { vertical-align: bottom; } .revision-table td:first-child, .revision-table th:first-child { width: 6rem; } .revision-table tr.group-row td { padding-left: 5rem; } label { white-space: nowrap; display: block; } input:not([type=radio]) { width: calc(100% - 0.6rem); padding: 0.1rem 0.2rem; font-size: 0.9rem; font-weight: inherit; } #specify-comparison-pane button { margin-top: 1.5rem; font-size: 1.1rem; font-weight: inherit; } #start-pane button { margin-top: 1.5rem; font-size: 1.2rem; font-weight: inherit; } #baseline-testability li, #comparison-testability li { color: #c33; width: 20rem; } `; } } CustomAnalysisTaskConfigurator.commitFetchInterval = 100; ComponentBase.defineElement('custom-analysis-task-configurator', CustomAnalysisTaskConfigurator);