diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in index 88ebcafdc3990..5dfd02e06a62e 100644 --- a/.cargo/config.toml.in +++ b/.cargo/config.toml.in @@ -75,9 +75,9 @@ git = "https://github.com/jfkthame/mapped_hyph.git" rev = "eff105f6ad7ec9b79816cfc1985a28e5340ad14b" replace-with = "vendored-sources" -[source."git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56"] +[source."git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8"] git = "https://github.com/mozilla/application-services" -rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" +rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" replace-with = "vendored-sources" [source."git+https://github.com/mozilla/audioipc?rev=c8d3f03cb5f889e4cab18cc1360ad0daa074f17a"] diff --git a/Cargo.lock b/Cargo.lock index 4920d73538d3f..aa2a8008c983b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,7 +978,7 @@ dependencies = [ [[package]] name = "context_id" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "chrono", "error-support", @@ -1940,14 +1940,13 @@ dependencies = [ [[package]] name = "error-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "env_logger", "error-support-macros", "lazy_static", "log", "parking_lot", - "tracing", "tracing-support", "uniffi", ] @@ -1955,7 +1954,7 @@ dependencies = [ [[package]] name = "error-support-macros" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "proc-macro2", "quote", @@ -2050,7 +2049,7 @@ dependencies = [ [[package]] name = "filter_adult" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "base64 0.22.1", "error-support", @@ -2086,7 +2085,7 @@ dependencies = [ [[package]] name = "firefox-versioning" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "serde_json", "thiserror 2.0.12", @@ -3461,7 +3460,7 @@ dependencies = [ [[package]] name = "init_rust_components" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "nss", "uniffi", @@ -3470,7 +3469,7 @@ dependencies = [ [[package]] name = "interrupt-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "lazy_static", "parking_lot", @@ -3629,7 +3628,7 @@ dependencies = [ [[package]] name = "jwcrypto" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "base64 0.21.999", "error-support", @@ -3997,7 +3996,7 @@ checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "logins" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "async-trait", @@ -5035,7 +5034,7 @@ dependencies = [ [[package]] name = "nss" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "base64 0.21.999", "error-support", @@ -5064,12 +5063,12 @@ dependencies = [ [[package]] name = "nss_build_common" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" [[package]] name = "nss_sys" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "libsqlite3-sys", "nss_build_common", @@ -5417,7 +5416,7 @@ checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "payload-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "serde", "serde_derive", @@ -5982,7 +5981,7 @@ dependencies = [ [[package]] name = "rc_crypto" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "base64 0.21.999", "error-support", @@ -6052,7 +6051,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relevancy" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "base64 0.21.999", @@ -6076,7 +6075,7 @@ dependencies = [ [[package]] name = "remote_settings" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "camino", @@ -6347,7 +6346,7 @@ dependencies = [ [[package]] name = "search" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "error-support", "firefox-versioning", @@ -6665,7 +6664,7 @@ dependencies = [ [[package]] name = "sql-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "error-support", "interrupt-support", @@ -6856,7 +6855,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "suggest" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "chrono", @@ -6909,7 +6908,7 @@ dependencies = [ [[package]] name = "sync-guid" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "base64 0.21.999", "rand", @@ -6920,7 +6919,7 @@ dependencies = [ [[package]] name = "sync15" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "base16", @@ -6964,7 +6963,7 @@ dependencies = [ [[package]] name = "tabs" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "error-support", @@ -7313,7 +7312,7 @@ dependencies = [ [[package]] name = "tracing-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "parking_lot", "serde_json", @@ -7393,7 +7392,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "types" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "rusqlite 0.37.0", "serde", @@ -7774,7 +7773,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "viaduct" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "async-trait", "error-support", @@ -7949,7 +7948,7 @@ dependencies = [ [[package]] name = "webext-storage" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=900d0b03aeb82245c28a11a9adc1cbbead31ce56#900d0b03aeb82245c28a11a9adc1cbbead31ce56" +source = "git+https://github.com/mozilla/application-services?rev=42c175c2ecb5aa39346dcd9c7b45ff02325562d8#42c175c2ecb5aa39346dcd9c7b45ff02325562d8" dependencies = [ "anyhow", "error-support", diff --git a/Cargo.toml b/Cargo.toml index 5ade903144c78..d663c8193c398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -270,21 +270,21 @@ objc = { git = "https://github.com/glandium/rust-objc", rev = "4de89f5aa9851ceca allocator-api2 = { git = "https://github.com/glandium/allocator-api2", rev = "ad5f3d56a5a4519eff52af4ff85293431466ef5c" } # application-services overrides to make updating them all simpler. -context_id = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -error-support = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -filter_adult = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -relevancy = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -search = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -sql-support = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -suggest = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -sync15 = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -tabs = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -tracing-support = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -viaduct = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -webext-storage = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -logins = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } -init_rust_components = { git = "https://github.com/mozilla/application-services", rev = "900d0b03aeb82245c28a11a9adc1cbbead31ce56" } +context_id = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +error-support = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +filter_adult = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +relevancy = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +search = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +sql-support = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +suggest = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +sync15 = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +tabs = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +tracing-support = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +viaduct = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +webext-storage = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +logins = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } +init_rust_components = { git = "https://github.com/mozilla/application-services", rev = "42c175c2ecb5aa39346dcd9c7b45ff02325562d8" } # Patched version of zip 2.4.2 to allow for reading omnijars. zip = { path = "third_party/rust/zip" } diff --git a/browser/components/aboutwelcome/.eslintrc.mjs b/browser/components/aboutwelcome/.eslintrc.mjs index 13027c4b06d8d..260445d68f908 100644 --- a/browser/components/aboutwelcome/.eslintrc.mjs +++ b/browser/components/aboutwelcome/.eslintrc.mjs @@ -72,7 +72,7 @@ export default [ "guard-for-in": "error", "max-nested-callbacks": ["error", 4], "max-params": ["error", 6], - "max-statements": ["error", 50], + "max-statements": "off", "new-cap": ["error", { newIsCap: true, capIsNew: false }], "no-alert": "error", "no-div-regex": "error", diff --git a/browser/components/aboutwelcome/content-src/aboutwelcome.jsx b/browser/components/aboutwelcome/content-src/aboutwelcome.jsx index 1798a867bf46c..b20b3a23bab32 100644 --- a/browser/components/aboutwelcome/content-src/aboutwelcome.jsx +++ b/browser/components/aboutwelcome/content-src/aboutwelcome.jsx @@ -35,6 +35,7 @@ class AboutWelcome extends React.PureComponent { mountStart: performance.getEntriesByName("mount").pop().startTime, domState, source: this.props.UTMTerm, + writeInMicrosurvey: this.props.write_in_microsurvey, }); }; if (document.readyState === "complete") { @@ -65,6 +66,7 @@ class AboutWelcome extends React.PureComponent { addonIconURL={props.iconURL} themeScreenshots={props.screenshots} message_id={props.messageId} + writeInMicrosurvey={props.write_in_microsurvey} defaultScreens={props.screens} updateHistory={!props.disableHistoryUpdates} metricsFlowUri={this.state.metricsFlowUri} diff --git a/browser/components/aboutwelcome/content-src/aboutwelcome.scss b/browser/components/aboutwelcome/content-src/aboutwelcome.scss index c3e3783c2298f..eee837474b89e 100644 --- a/browser/components/aboutwelcome/content-src/aboutwelcome.scss +++ b/browser/components/aboutwelcome/content-src/aboutwelcome.scss @@ -2142,6 +2142,31 @@ html { } } + .textarea-container { + display: flex; + flex-flow: column nowrap; + + .textarea-char-counter { + font-size: 13px; + color: var(--text-color-deemphasized); + text-align: end; + margin-block: -10px 4px; + + &.invalid { + color: var(--text-color-error) + } + } + + .textarea-input { + resize: none; + + &.invalid { + border-color: var(--outline-color-error); + outline-color: var(--outline-color-error); + } + } + } + .confirmation-checklist-section { display: flex; flex-direction: column; diff --git a/browser/components/aboutwelcome/content-src/components/ActionChecklist.jsx b/browser/components/aboutwelcome/content-src/components/ActionChecklist.jsx index dc97404dcc9eb..65a83ad244937 100644 --- a/browser/components/aboutwelcome/content-src/components/ActionChecklist.jsx +++ b/browser/components/aboutwelcome/content-src/components/ActionChecklist.jsx @@ -80,7 +80,11 @@ export const ActionChecklistProgressBar = ({ progress }) => { ); }; -export const ActionChecklist = ({ content, message_id }) => { +export const ActionChecklist = ({ + content, + message_id, + writeInMicrosurvey, +}) => { const tiles = content.tiles.data; const [progressValue, setProgressValue] = useState(0); const [numberOfCompletedActions, setNumberOfCompletedActions] = useState(0); @@ -119,7 +123,12 @@ export const ActionChecklist = ({ content, message_id }) => { setNumberOfCompletedActions(numberOfCompletedActions + 1); AboutWelcomeUtils.handleUserAction({ type, data }); - AboutWelcomeUtils.sendActionTelemetry(message_id, source_id); + AboutWelcomeUtils.sendActionTelemetry( + message_id, + source_id, + "CLICK_BUTTON", + { writeInMicrosurvey } + ); } return ( diff --git a/browser/components/aboutwelcome/content-src/components/AdditionalCTA.jsx b/browser/components/aboutwelcome/content-src/components/AdditionalCTA.jsx index 99f3336c0c806..eb9884303f973 100644 --- a/browser/components/aboutwelcome/content-src/components/AdditionalCTA.jsx +++ b/browser/components/aboutwelcome/content-src/components/AdditionalCTA.jsx @@ -6,7 +6,12 @@ import React from "react"; import { Localized } from "./MSLocalized"; import { SubmenuButton } from "./SubmenuButton"; -export const AdditionalCTA = ({ content, handleAction }) => { +export const AdditionalCTA = ({ + content, + handleAction, + activeMultiSelect, + textInputs, +}) => { let buttonStyle = ""; const isSplitButton = content.submenu_button?.attached_to === "additional_button"; @@ -24,6 +29,36 @@ export const AdditionalCTA = ({ content, handleAction }) => { : content.additional_button?.style; } + const computeDisabled = React.useCallback( + disabledValue => { + if (disabledValue === "hasActiveMultiSelect") { + if (!activeMultiSelect) { + return true; + } + + for (const key in activeMultiSelect) { + if (activeMultiSelect[key]?.length > 0) { + return false; + } + } + + return true; + } + if (disabledValue === "hasTextInput") { + // For text input, we check if the user has entered any text in the + // textarea(s) present on the screen. + if (!textInputs) { + return true; + } + return Object.values(textInputs).every( + input => !input.isValid || input.value.trim().length === 0 + ); + } + return disabledValue; + }, + [activeMultiSelect, textInputs] + ); + return (
@@ -32,7 +67,7 @@ export const AdditionalCTA = ({ content, handleAction }) => { className={`${buttonStyle} additional-cta`} onClick={handleAction} value="additional_button" - disabled={content.additional_button?.disabled === true} + disabled={computeDisabled(content.additional_button?.disabled)} /> {isSplitButton ? ( diff --git a/browser/components/aboutwelcome/content-src/components/AddonsPicker.jsx b/browser/components/aboutwelcome/content-src/components/AddonsPicker.jsx index 1f16529cf8b50..df550ce23dc6a 100644 --- a/browser/components/aboutwelcome/content-src/components/AddonsPicker.jsx +++ b/browser/components/aboutwelcome/content-src/components/AddonsPicker.jsx @@ -15,7 +15,7 @@ export const AddonsPicker = props => { } function handleAction(event) { - const { message_id } = props; + const { message_id, writeInMicrosurvey } = props; let { action, source_id } = content.tiles.data[event.currentTarget.value]; let { type, data } = action; @@ -26,7 +26,12 @@ export const AddonsPicker = props => { } AboutWelcomeUtils.handleUserAction({ type, data }); - AboutWelcomeUtils.sendActionTelemetry(message_id, source_id); + AboutWelcomeUtils.sendActionTelemetry( + message_id, + source_id, + "CLICK_BUTTON", + { writeInMicrosurvey } + ); } function handleAuthorClick(event, authorId) { diff --git a/browser/components/aboutwelcome/content-src/components/ContentTiles.jsx b/browser/components/aboutwelcome/content-src/components/ContentTiles.jsx index 244873afb819c..3eb15d27545c1 100644 --- a/browser/components/aboutwelcome/content-src/components/ContentTiles.jsx +++ b/browser/components/aboutwelcome/content-src/components/ContentTiles.jsx @@ -8,6 +8,7 @@ import { AddonsPicker } from "./AddonsPicker"; import { SingleSelect } from "./SingleSelect"; import { MobileDownloads } from "./MobileDownloads"; import { MultiSelect } from "./MultiSelect"; +import { TextAreaTile } from "./TextAreaTile"; import { EmbeddedMigrationWizard } from "./EmbeddedMigrationWizard"; import { EmbeddedFxBackupOptIn } from "./EmbeddedFxBackupOptIn"; import { ActionChecklist } from "./ActionChecklist"; @@ -170,8 +171,12 @@ export const ContentTiles = props => { const toggleTile = (index, tile) => { const tileId = `${tile.type}${tile.id ? "_" : ""}${tile.id ?? ""}_header`; - AboutWelcomeUtils.sendActionTelemetry(props.messageId, tileId); - + AboutWelcomeUtils.sendActionTelemetry( + props.messageId, + tileId, + "CLICK_BUTTON", + { writeInMicrosurvey: props.writeInMicrosurvey } + ); if (tile.type === "link" && tile.action) { props.handleAction( { @@ -190,7 +195,9 @@ export const ContentTiles = props => { setTilesHeaderExpanded(prev => !prev); AboutWelcomeUtils.sendActionTelemetry( props.messageId, - "content_tiles_header" + "content_tiles_header", + "CLICK_BUTTON", + { writeInMicrosurvey: props.writeInMicrosurvey } ); }; @@ -273,6 +280,7 @@ export const ContentTiles = props => { message_id={props.messageId} handleAction={props.handleAction} layout={content.position} + writeInMicrosurvey={props.writeInMicrosurvey} /> )} {["theme", "single-select"].includes(tile.type) && tile.data && ( @@ -311,6 +319,14 @@ export const ContentTiles = props => { multiSelectId={`tile-${index}`} /> )} + {tile.type === "textarea" && tile.data && ( + + )} {tile.type === "migration-wizard" && ( { /> )} {tile.type === "action_checklist" && tile.data && ( - + )} {tile.type === "embedded_browser" && tile.data?.url && ( diff --git a/browser/components/aboutwelcome/content-src/components/LanguageSwitcher.jsx b/browser/components/aboutwelcome/content-src/components/LanguageSwitcher.jsx index 6db47daec4d35..78d454ec16574 100644 --- a/browser/components/aboutwelcome/content-src/components/LanguageSwitcher.jsx +++ b/browser/components/aboutwelcome/content-src/components/LanguageSwitcher.jsx @@ -168,6 +168,7 @@ export function LanguageSwitcher(props) { negotiatedLanguage, langPackInstallPhase, messageId, + writeInMicrosurvey, } = props; const [isAwaitingLangpack, setIsAwaitingLangpack] = useState(false); @@ -271,7 +272,9 @@ export function LanguageSwitcher(props) { onClick={() => { AboutWelcomeUtils.sendActionTelemetry( messageId, - "download_langpack" + "download_langpack", + "CLICK_BUTTON", + { writeInMicrosurvey } ); setIsAwaitingLangpack(true); }} diff --git a/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx b/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx index e848a4db8c6a2..349f2d14ae8fb 100644 --- a/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx +++ b/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx @@ -75,7 +75,9 @@ export const MultiStageAboutWelcome = props => { .AWGetUnhandledCampaignAction?.() .then(action => { if (typeof action === "string") { - AboutWelcomeUtils.handleCampaignAction(action, props.message_id); + AboutWelcomeUtils.handleCampaignAction(action, props.message_id, { + writeInMicrosurvey: props.writeInMicrosurvey, + }); } }) .catch(error => { @@ -96,6 +98,7 @@ export const MultiStageAboutWelcome = props => { screen_index: order, screen_id: screen.id, screen_initials: screenInitials, + writeInMicrosurvey: props.writeInMicrosurvey, }); window.AWAddScreenImpression?.(screen); @@ -215,6 +218,10 @@ export const MultiStageAboutWelcome = props => { const [activeSingleSelectSelections, setActiveSingleSelectSelections] = useState({}); + // Save textarea inputs for each screen as an object keyed by screen id. It's + // structured like this: { screenId: { textareaId: { value, isValid } } } + const [textInputs, setTextInputs] = useState({}); + // Get the active theme so the rendering code can make it selected // by default. const [activeTheme, setActiveTheme] = useState(null); @@ -319,6 +326,20 @@ export const MultiStageAboutWelcome = props => { }); }; + const setTextInput = (value, inputId) => { + setTextInputs(prevState => { + const currentScreenInputs = prevState[currentScreen.id] || {}; + + return { + ...prevState, + [currentScreen.id]: { + ...currentScreenInputs, + [inputId]: value, + }, + }; + }); + }; + return index === order ? ( { previousOrder={previousOrder} content={currentScreen.content} navigate={handleTransition} + autoAdvance={currentScreen.auto_advance} messageId={`${props.message_id}_${order}_${currentScreen.id}`} + writeInMicrosurvey={props.writeInMicrosurvey} UTMTerm={props.utm_term} flowParams={flowParams} activeTheme={activeTheme} @@ -342,11 +365,12 @@ export const MultiStageAboutWelcome = props => { setScreenMultiSelects={setScreenMultiSelects} activeMultiSelect={activeMultiSelects[currentScreen.id]} setActiveMultiSelect={setActiveMultiSelect} - autoAdvance={currentScreen.auto_advance} activeSingleSelectSelections={ activeSingleSelectSelections[currentScreen.id] } setActiveSingleSelectSelection={setActiveSingleSelectSelection} + textInputs={textInputs[currentScreen.id]} + setTextInput={setTextInput} negotiatedLanguage={negotiatedLanguage} langPackInstallPhase={langPackInstallPhase} forceHideStepsIndicator={currentScreen.force_hide_steps_indicator} @@ -378,6 +402,7 @@ const renderSingleSecondaryCTAButton = ({ position, handleAction, activeMultiSelect, + textInputs, isArrayItem, index = null, }) => { @@ -413,6 +438,16 @@ const renderSingleSecondaryCTAButton = ({ return true; } + if (disabledValue === "hasTextInput") { + // For text input, we check if the user has entered any text in the + // textarea(s) present on the screen. + if (!textInputs) { + return true; + } + return Object.values(textInputs).every( + input => !input.isValid || input.value.trim().length === 0 + ); + } return disabledValue; }; @@ -516,6 +551,7 @@ export const SecondaryCTA = props => { position, handleAction: props.handleAction, activeMultiSelect: props.activeMultiSelect, + textInputs: props.textInputs, isArrayItem: true, index, }) @@ -531,6 +567,7 @@ export const SecondaryCTA = props => { position, handleAction: props.handleAction, activeMultiSelect: props.activeMultiSelect, + textInputs: props.textInputs, isArrayItem: false, }); }; @@ -603,13 +640,17 @@ export class WelcomeScreen extends React.PureComponent { } logTelemetry({ value, event, source, props }) { - AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name); + AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name, { + writeInMicrosurvey: props.writeInMicrosurvey, + }); // Send additional telemetry if a messaging surface like feature callout is // dismissed via the dismiss button. Other causes of dismissal will be // handled separately by the messaging surface's own code. if (value === "dismiss_button" && !event.name) { - AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source); + AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source, { + writeInMicrosurvey: props.writeInMicrosurvey, + }); } } @@ -620,7 +661,12 @@ export class WelcomeScreen extends React.PureComponent { if (hasMigrate(action)) { await window.AWWaitForMigrationClose(); - AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close"); + AboutWelcomeUtils.sendActionTelemetry( + props.messageId, + "migrate_close", + "CLICK_BUTTON", + { writeInMicrosurvey: props.writeInMicrosurvey } + ); } } @@ -709,6 +755,10 @@ export class WelcomeScreen extends React.PureComponent { this.setMultiSelectActions(action); } + if (action.collectTextInput && Object.values(props.textInputs).length) { + this.setTextInputActions(action); + } + let actionResult; if (["OPEN_URL", "SHOW_FIREFOX_ACCOUNTS"].includes(action.type)) { this.handleOpenURL(action, props.flowParams, props.UTMTerm); @@ -721,7 +771,8 @@ export class WelcomeScreen extends React.PureComponent { AboutWelcomeUtils.sendActionTelemetry( props.messageId, actionResult ? "sign_in" : "sign_in_cancel", - "FXA_SIGNIN_FLOW" + "FXA_SIGNIN_FLOW", + { writeInMicrosurvey: props.writeInMicrosurvey } ); } @@ -858,9 +909,80 @@ export class WelcomeScreen extends React.PureComponent { AboutWelcomeUtils.sendActionTelemetry( props.messageId, value.flat(), - "SELECT_CHECKBOX" + "SELECT_CHECKBOX", + { writeInMicrosurvey: props.writeInMicrosurvey } + ); + } + } + + setTextInputActions(action) { + let { props } = this; + + if (action.type !== "MULTI_ACTION") { + console.error( + "collectTextInput is only supported for MULTI_ACTION type actions" ); + action.type = "MULTI_ACTION"; } + if (!Array.isArray(action.data?.actions)) { + console.error( + "collectTextInput is only supported for MULTI_ACTION type actions with an array of actions" + ); + action.data = { actions: [] }; + } + + const collectedActions = []; + + // If there is no character_limit, we still need to limit the size of the + // input to avoid sending huge payloads. We'll go with 8KB. + const truncateToByteSize = (str, maxBytes) => { + const encoder = new TextEncoder(); + const encoded = encoder.encode(str); + if (encoded.length <= maxBytes) { + return str; + } + let end = maxBytes; + // Step back until we find a valid UTF-8 start byte + while (end > 0 && (encoded[end] & 0b11000000) === 0b10000000) { + end--; // this is a continuation byte + } + return new TextDecoder().decode(encoded.subarray(0, end)); + }; + + const processTile = (tile, tileIndex) => { + if (tile?.type !== "textarea" || !tile.data) { + return; + } + + const inputId = tile.data.id || `tile-${tileIndex}`; + const inputData = props.textInputs[inputId]; + if (inputData?.isValid && inputData.value.trim().length) { + if (tile.data.action) { + collectedActions.push(tile.data.action); + } + AboutWelcomeUtils.sendActionTelemetry( + props.messageId, + inputId, + "TEXT_INPUT", + { + value: truncateToByteSize(inputData.value, 8192), + writeInMicrosurvey: props.writeInMicrosurvey, + } + ); + } + }; + + if (props.content?.tiles) { + if (Array.isArray(props.content.tiles)) { + for (const [index, tile] of props.content.tiles.entries()) { + processTile(tile, index); + } + } else { + processTile(props.content.tiles, 0); + } + } + + action.data.actions.unshift(...collectedActions); } render() { @@ -880,12 +1002,15 @@ export class WelcomeScreen extends React.PureComponent { setActiveSingleSelectSelection={ this.props.setActiveSingleSelectSelection } + textInputs={this.props.textInputs} + setTextInput={this.props.setTextInput} totalNumberOfScreens={this.props.totalNumberOfScreens} appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo} negotiatedLanguage={this.props.negotiatedLanguage} langPackInstallPhase={this.props.langPackInstallPhase} handleAction={this.handleAction} messageId={this.props.messageId} + writeInMicrosurvey={this.props.writeInMicrosurvey} isFirstScreen={this.props.isFirstScreen} isLastScreen={this.props.isLastScreen} isSingleScreen={this.props.isSingleScreen} diff --git a/browser/components/aboutwelcome/content-src/components/MultiStageProtonScreen.jsx b/browser/components/aboutwelcome/content-src/components/MultiStageProtonScreen.jsx index 800ce64dede42..dcacf3d0d35ec 100644 --- a/browser/components/aboutwelcome/content-src/components/MultiStageProtonScreen.jsx +++ b/browser/components/aboutwelcome/content-src/components/MultiStageProtonScreen.jsx @@ -86,6 +86,8 @@ export const MultiStageProtonScreen = props => { setActiveMultiSelect={props.setActiveMultiSelect} activeSingleSelectSelections={props.activeSingleSelectSelections} setActiveSingleSelectSelection={props.setActiveSingleSelectSelection} + textInputs={props.textInputs} + setTextInput={props.setTextInput} totalNumberOfScreens={props.totalNumberOfScreens} handleAction={props.handleAction} isFirstScreen={props.isFirstScreen} @@ -101,6 +103,7 @@ export const MultiStageProtonScreen = props => { addonIconURL={props.addonIconURL} themeScreenshots={props.themeScreenshots} messageId={props.messageId} + writeInMicrosurvey={props.writeInMicrosurvey} negotiatedLanguage={props.negotiatedLanguage} langPackInstallPhase={props.langPackInstallPhase} forceHideStepsIndicator={props.forceHideStepsIndicator} @@ -119,6 +122,7 @@ export const ProtonScreenActionButtons = props => { addonType, addonName, activeMultiSelect, + textInputs, installedAddons, } = props; const defaultValue = content.checkbox?.defaultValue; @@ -150,8 +154,8 @@ export const ProtonScreenActionButtons = props => { // If we have a multi-select screen, we want to disable the primary button // until the user has selected at least one item. - const isPrimaryDisabled = primaryDisabledValue => { - if (primaryDisabledValue === "hasActiveMultiSelect") { + const isPrimaryDisabled = disabledValue => { + if (disabledValue === "hasActiveMultiSelect") { if (!activeMultiSelect) { return true; } @@ -164,7 +168,17 @@ export const ProtonScreenActionButtons = props => { } return true; } - return primaryDisabledValue; + if (disabledValue === "hasTextInput") { + // For text input, we check if the user has entered any text in the + // textarea(s) present on the screen. + if (!textInputs) { + return true; + } + return Object.values(textInputs).every( + input => !input.isValid || input.value.trim().length === 0 + ); + } + return disabledValue; }; return ( @@ -212,7 +226,12 @@ export const ProtonScreenActionButtons = props => { )} {content.additional_button ? ( - + ) : null} {content.checkbox ? (
@@ -234,6 +253,7 @@ export const ProtonScreenActionButtons = props => { content={content} handleAction={props.handleAction} activeMultiSelect={activeMultiSelect} + textInputs={textInputs} /> ) : null}
@@ -372,6 +392,7 @@ export class ProtonScreen extends React.PureComponent { negotiatedLanguage={this.props.negotiatedLanguage} langPackInstallPhase={this.props.langPackInstallPhase} messageId={this.props.messageId} + writeInMicrosurvey={this.props.writeInMicrosurvey} /> ) : null; } @@ -615,6 +636,7 @@ export class ProtonScreen extends React.PureComponent { addonType={this.props.addonType} handleAction={this.props.handleAction} activeMultiSelect={this.props.activeMultiSelect} + textInputs={this.props.textInputs} /> ) : null; } diff --git a/browser/components/aboutwelcome/content-src/components/TextAreaTile.jsx b/browser/components/aboutwelcome/content-src/components/TextAreaTile.jsx new file mode 100644 index 0000000000000..a0258b9bddfd4 --- /dev/null +++ b/browser/components/aboutwelcome/content-src/components/TextAreaTile.jsx @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React, { useEffect, useCallback, useMemo, useState } from "react"; +import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils.mjs"; + +const CONFIGURABLE_STYLES = [ + "color", + "display", + "fontSize", + "fontWeight", + "letterSpacing", + "lineHeight", + "marginBlock", + "marginInline", + "paddingBlock", + "paddingInline", + "textAlign", + "whiteSpace", + "width", + "border", + "borderRadius", + "minHeight", + "minWidth", +]; + +export const TextAreaTile = ({ + content, + textInputs, + setTextInput, + tileIndex, +}) => { + const { data } = content.tiles; + const id = data.id || `tile-${tileIndex}`; + + const [isValid, setIsValid] = useState(true); + const [charCounter, setCharCounter] = useState(data.character_limit || 0); + + const textInput = useMemo(() => { + if (textInputs) { + return textInputs?.[id]; + } + return null; + }, [textInputs, id]); + + const handleChange = useCallback( + event => { + let valid = isValid; + if (data.character_limit) { + setCharCounter(data.character_limit - event.target.value.length); + valid = event.target.value.length <= data.character_limit; + } + setIsValid(valid); + setTextInput({ value: event.target.value, isValid: valid }, id); + }, + [isValid, data.character_limit, id, setTextInput] + ); + + useEffect(() => { + if (!textInput) { + setTextInput({ value: "", isValid: true }, id); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + return ( +
+ {data.character_limit && ( +
+ {charCounter} +
+ )} +