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",