From ac2413b302cf8a6853b7f358a7c6a32e701e1e9b Mon Sep 17 00:00:00 2001 From: Daniel Smith <56164590+DanielRyanSmith@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:20:46 -0700 Subject: [PATCH] Validate Chromium inputs for OT creation (UI) (#3790) * Add chromium file checks for OT creation * reorganize functions * change critical trial logic --- .../elements/chromedash-ot-creation-page.js | 150 +++++++++++++++++- client-src/elements/form-field-specs.js | 15 +- package-lock.json | 5 +- package.json | 1 + 4 files changed, 163 insertions(+), 8 deletions(-) diff --git a/client-src/elements/chromedash-ot-creation-page.js b/client-src/elements/chromedash-ot-creation-page.js index 24d34d0ff9be..bdbc70a84ce5 100644 --- a/client-src/elements/chromedash-ot-creation-page.js +++ b/client-src/elements/chromedash-ot-creation-page.js @@ -11,6 +11,12 @@ import {ORIGIN_TRIAL_CREATION_FIELDS} from './form-definition.js'; import {SHARED_STYLES} from '../css/shared-css.js'; import {FORM_STYLES} from '../css/forms-css.js'; import {ALL_FIELDS} from './form-field-specs.js'; +import json5 from 'json5'; + + +const WEBFEATURE_FILE_URL = 'https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom?format=TEXT'; +const ENABLED_FEATURES_FILE_URL = 'https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5?format=TEXT'; +const GRACE_PERIOD_FILE = 'https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc?format=TEXT'; export class ChromedashOTCreationPage extends LitElement { @@ -32,6 +38,11 @@ export class ChromedashOTCreationPage extends LitElement { appTitle: {type: String}, fieldValues: {type: Array}, showApprovalsFields: {type: Boolean}, + + // Chromium file contents for validating inputs. + webfeatureFile: {type: String}, + enabledFeaturesJson: {type: Object}, + gracePeriodFile: {type: String}, }; } @@ -44,6 +55,9 @@ export class ChromedashOTCreationPage extends LitElement { this.appTitle = ''; this.fieldValues = []; this.showApprovalsFields = false; + this.webfeatureFile = ''; + this.enabledFeaturesJson = undefined; + this.gracePeriodFile = ''; } connectedCallback() { @@ -178,7 +192,132 @@ export class ChromedashOTCreationPage extends LitElement { setupScrollToHash(this); } - handleFormSubmit(e) { + async getChromiumFile(url) { + const resp = await fetch(url); + const respJson = await resp.text(); + return atob(respJson); + } + + // Check that the code has landed that is used to monitor feature usage. + async checkWebfeatureUseCounter(field) { + if (!this.webfeatureFile) { + this.webfeatureFile = await this.getChromiumFile(WEBFEATURE_FILE_URL); + } + const webfeatureCounterExists = this.webfeatureFile.includes(`${field.value} =`); + if (!webfeatureCounterExists) { + field.checkMessage = html` + + Error: UseCounter name not found in file. + `; + return true; + } else { + field.checkMessage = nothing; + } + return false; + } + + // Check that code has landed that is required for the origin trial feature. + async checkChromiumTrialName(field) { + if (!this.enabledFeaturesJson) { + const enabledFeaturesFileText = await this.getChromiumFile(ENABLED_FEATURES_FILE_URL); + this.enabledFeaturesJson = json5.parse(enabledFeaturesFileText); + } + if (!this.enabledFeaturesJson.data.some( + feature => feature.origin_trial_feature_name === field.value)) { + field.checkMessage = html` + + Error: Name not found in file. + `; + return true; + } else { + field.checkMessage = nothing; + } + return false; + } + + // Check that code has landed that is required for third party support. + async checkThirdPartySupport(field) { + if (!field.value) { + field.checkMessage = nothing; + return false; + } + const chromiumTrialName = this.fieldValues.find( + field => field.name === 'ot_chromium_trial_name').value; + if (!this.enabledFeaturesJson) { + const enabledFeaturesFileText = await this.getChromiumFile(ENABLED_FEATURES_FILE_URL); + this.enabledFeaturesJson = json5.parse(enabledFeaturesFileText); + } + + const thirdPartySupportEnabled = this.enabledFeaturesJson.data.every( + feature => { + return (feature.origin_trial_feature_name !== chromiumTrialName || + feature.origin_trial_allows_third_party); + }); + if (!thirdPartySupportEnabled) { + field.checkMessage = html` +
+ + Error: Property not set in file. + `; + return true; + } else { + field.checkMessage = nothing; + } + return false; + } + + // Check that code has landed that is required for critical trials. + async checkCriticalTrial(field) { + if (!field.value) { + field.checkMessage = nothing; + return false; + } + const chromiumTrialName = this.fieldValues.find( + field => field.name === 'ot_chromium_trial_name').value; + if (!this.gracePeriodFile) { + this.gracePeriodFile = await this.getChromiumFile(GRACE_PERIOD_FILE); + } + const includedInGracePeriodArray = this.gracePeriodFile.includes( + `blink::mojom::OriginTrialFeature::k${chromiumTrialName}`); + if (!includedInGracePeriodArray) { + field.checkMessage = html` +
+ + Error: Trial name not found in file. + `; + return true; + } else { + field.checkMessage = nothing; + } + return false; + } + + /** + * Check that given args related to Chromium are valid. + * @returns Whether any inputs cannot be found in Chromium files. + */ + async handleChromiumChecks() { + // Clear saved file info in order to fetch the most recent version. + this.webfeatureFile = ''; + this.enabledFeaturesJson = undefined; + this.gracePeriodFile = ''; + let hasErrors = false; + + for (const field of this.fieldValues) { + if (field.name === 'ot_webfeature_use_counter') { + hasErrors = hasErrors || await this.checkWebfeatureUseCounter(field); + } else if (field.name === 'ot_chromium_trial_name') { + hasErrors = hasErrors || await this.checkChromiumTrialName(field); + } else if (field.name === 'ot_has_third_party_support') { + hasErrors = hasErrors || await this.checkThirdPartySupport(field); + } else if (field.name === 'ot_is_critical_trial') { + hasErrors = hasErrors || await this.checkCriticalTrial(field); + } + } + return hasErrors; + } + + async handleFormSubmit(e) { e.preventDefault(); // If registration approvals is not enabled, ignore all fields related to that setting. if (!this.showApprovalsFields) { @@ -189,6 +328,14 @@ export class ChromedashOTCreationPage extends LitElement { }); } + const hasErrors = await this.handleChromiumChecks(); + this.requestUpdate(); + if (hasErrors) { + showToastMessage( + 'Some issues were found with the given inputs. Check input errors and try again.'); + return; + } + const featureSubmitBody = formatFeatureChanges(this.fieldValues, this.featureId); // We only need the single stage changes. const stageSubmitBody = featureSubmitBody.stages[0]; @@ -254,6 +401,7 @@ export class ChromedashOTCreationPage extends LitElement { name=${fieldInfo.name} index=${i} value=${fieldInfo.value} + .checkMessage=${fieldInfo.checkMessage} .fieldValues=${this.fieldValues} .shouldFadeIn=${shouldFadeIn} @form-field-update="${this.handleFormFieldUpdate}"> diff --git a/client-src/elements/form-field-specs.js b/client-src/elements/form-field-specs.js index fb600456f044..95f185e10fcb 100644 --- a/client-src/elements/form-field-specs.js +++ b/client-src/elements/form-field-specs.js @@ -1242,7 +1242,10 @@ export const ALL_FIELDS = { help_text: html` Whether this trial supports third party origins. See this article - for more information.`, + for more information. The feature should have "origin_trial_allows_third_party" + set to "true" in runtime_enabled_features.json5`, }, 'ot_is_critical_trial': { @@ -1252,7 +1255,10 @@ export const ALL_FIELDS = { help_text: html` See go/running-an-origin-trial - for criteria and additional process requirements.`, + for criteria and additional process requirements. + The feature name must be added to the "kHasExpiryGracePeriod" array in manual_completion_origin_trial_features.cc`, }, 'ot_is_deprecation_trial': { @@ -1283,8 +1289,9 @@ export const ALL_FIELDS = { label: 'WebFeature UseCounter name', help_text: html` For measuring usage, this must be a single named value from the - WebFeature enum, e.g. kWorkerStart. See - web_feature.mojom.`, }, diff --git a/package-lock.json b/package-lock.json index 4cef2f497fc9..5852ff2e842e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@polymer/iron-collapse": "^3.0.1", "@polymer/iron-icon": "^3.0.1", "@polymer/iron-iconset-svg": "^3.0.1", + "json5": "^2.2.3", "lit": "^3", "node-fetch": ">=3.3.2", "page": "^1.11.6", @@ -9127,7 +9128,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -19943,8 +19943,7 @@ "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", diff --git a/package.json b/package.json index d25549aa06e6..146560150f5a 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "@polymer/iron-collapse": "^3.0.1", "@polymer/iron-icon": "^3.0.1", "@polymer/iron-iconset-svg": "^3.0.1", + "json5": "^2.2.3", "lit": "^3", "node-fetch": ">=3.3.2", "page": "^1.11.6",