├── .circleci └── config.yml ├── .gitignore ├── .yamllint ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── archive ├── cfr-archived.yaml ├── cfr-fxa-archived.yaml ├── cfr-heartbeat-archived.yaml ├── messaging-experiments-archived.yaml ├── moments-archived.yaml └── whats-new-panel-archived.yaml ├── messages ├── cfr-heartbeat.yaml ├── cfr.yaml ├── message-groups.yaml ├── messaging-experiments.yaml ├── moments.yaml └── whats-new-panel.yaml ├── outgoing ├── InflightAssetsMessageProvider.sys.mjs ├── cfr-heartbeat.json ├── cfr.json ├── message-groups.json ├── messaging-experiments.json ├── moments.json └── whats-new-panel.json ├── requirements.txt ├── schema ├── MessagingExperiment.schema.json ├── NimbusExperiment.schema.json ├── SpecialMessageActionSchemas.json └── message-groups.schema.json └── scripts ├── export-all.py ├── fetch-schemas.py ├── rscat.sh ├── spot.sh └── validate.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | python: circleci/python@0.2.1 5 | 6 | jobs: 7 | build-and-test: 8 | docker: 9 | - image: cimg/python:3.8 10 | steps: 11 | - checkout 12 | - python/install-deps 13 | # Do NOT want `python/save-cache` here as it won't cache yamllint 14 | # - python/save-cache 15 | - run: 16 | name: Ensure schemas are up-to-date 17 | command: | 18 | ./scripts/fetch-schemas.py 19 | if [[ ! -z "$(git diff)" ]]; then 20 | echo "Schemas do not match schemas in mozilla-central!" 21 | exit 1 22 | fi 23 | - run: 24 | command: | 25 | touch *.json && make 26 | name: Build 27 | - run: 28 | command: make check 29 | name: Validate 30 | 31 | workflows: 32 | main: 33 | jobs: 34 | - build-and-test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | outgoing/InflightAssetsMessageProvider.jsm 2 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | line-length: disable 7 | document-start: disable 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | 375 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON = python3 2 | FLAGS = -c 3 | CMD = 'import sys, yaml, json; json.dump(yaml.safe_load(sys.stdin), sys.stdout, indent=2)' 4 | 5 | all: outgoing/cfr-heartbeat.json outgoing/cfr.json \ 6 | outgoing/message-groups.json outgoing/messaging-experiments.json \ 7 | outgoing/moments.json outgoing/whats-new-panel.json 8 | 9 | outgoing/%.json: messages/%.yaml pre-build 10 | $(PYTHON) $(FLAGS) $(CMD) < $< > $@ 11 | 12 | .PHONY: clean check 13 | 14 | pre-build: 15 | yamllint . 16 | 17 | clean: 18 | rm *.json 19 | 20 | check: 21 | scripts/validate.py message outgoing/cfr.json 22 | scripts/validate.py message outgoing/cfr-heartbeat.json 23 | scripts/validate.py message outgoing/moments.json 24 | scripts/validate.py message outgoing/whats-new-panel.json 25 | scripts/validate.py experiment outgoing/messaging-experiments.json 26 | scripts/validate.py message-groups outgoing/message-groups.json 27 | 28 | export: all 29 | scripts/export-all.py 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Mozilla](https://circleci.com/gh/mozilla/messaging-system-inflight-assets.svg?style=svg)](https://circleci.com/gh/mozilla/messaging-system-inflight-assets) 2 | 3 | This repo hosts various in-flight remote assets of Firefox Messaging System. 4 | 5 | Currently, it consists of CFR, CFR-FXA, and What's New Pannel. 6 | 7 | ### Usage 8 | 9 | To add/modify/delete assets, please edit the YAML files other than the JSON ones, because the former allows us to use comments in the document. Once you complete the editing, you can sync your changes to the JSON file(s), and copy them over to Remote Settings for publishing. 10 | 11 | When deleting asset(s), make sure copy the deleted content in the YAML files to 12 | the corresponding archive file located in the `archive` directory. For instance, 13 | when you're deleting messages in `cfr.yaml`, please copy the deletions to the 14 | `archive/cfr-archived.yaml`. 15 | 16 | To sync from YAML to JSON, just run 17 | 18 | ```sh 19 | $ make 20 | ``` 21 | 22 | To validate the JSON against the schema 23 | 24 | ```sh 25 | $ make check 26 | ``` 27 | 28 | It requires Python 3 and various libraries for the schema validation and file generation. 29 | 30 | ```sh 31 | # if you don't have Python 3 installed 32 | $ brew install python3 33 | $ pip3 install -r requirements.txt 34 | $ python -m pip install requests 35 | $ python3 -m pip install pyjexl 36 | $ python3 -m pip install jsonschema 37 | $ python3 -m pip install pyyaml 38 | ``` 39 | 40 | To sync schema changes with Mozilla Central run 41 | 42 | ```sh 43 | python scripts/fetch-schemas.py 44 | ``` 45 | 46 | Note: make sure you commit all the changes (YAML&JSON) to the repo. 47 | -------------------------------------------------------------------------------- /archive/cfr-fxa-archived.yaml: -------------------------------------------------------------------------------- 1 | - id: FXA_BOOKMARK_PANEL_1 2 | content: 3 | background_color_1: '#7d31ae' 4 | background_color_2: '#5033be' 5 | close_button: 6 | tooltiptext: 7 | string_id: cfr-doorhanger-bookmark-fxa-close-btn-tooltip 8 | color: white 9 | cta: 10 | string_id: cfr-doorhanger-bookmark-fxa-link-text 11 | info_icon: 12 | tooltiptext: 13 | string_id: cfr-doorhanger-bookmark-fxa-info-icon-tooltip 14 | text: 15 | string_id: cfr-doorhanger-bookmark-fxa-body 16 | title: 17 | string_id: cfr-doorhanger-bookmark-fxa-header 18 | frequency: 19 | lifetime: 3 20 | targeting: locale != 'ru' && !usesFirefoxSync && isFxAEnabled == true && totalBookmarksCount 21 | >= 10 && firefoxVersion == 69 22 | template: fxa_bookmark_panel 23 | trigger: 24 | id: bookmark-panel 25 | weight: 100 26 | - id: FXA_BOOKMARK_PANEL_68 27 | content: 28 | background_color_1: '#7d31ae' 29 | background_color_2: '#5033be' 30 | close_button: 31 | tooltiptext: cfr-doorhanger-bookmark-fxa-close-btn-tooltip 32 | color: white 33 | cta: cfr-doorhanger-bookmark-fxa-link-text 34 | info_icon: 35 | tooltiptext: cfr-doorhanger-bookmark-fxa-info-icon-tooltip 36 | text: cfr-doorhanger-bookmark-fxa-body 37 | title: cfr-doorhanger-bookmark-fxa-header 38 | frequency: 39 | lifetime: 3 40 | targeting: locale != 'ru' && !usesFirefoxSync && isFxAEnabled == true && totalBookmarksCount 41 | >= 10 && firefoxVersion == 68 42 | template: fxa_bookmark_panel 43 | trigger: 44 | id: bookmark-panel 45 | weight: 100 46 | - id: FXA_BOOKMARK_PANEL_71_plus 47 | groups: 48 | - cfr 49 | content: 50 | background_color_1: '#7d31ae' 51 | background_color_2: '#5033be' 52 | bucket_id: FXA_BOOKMARK_PANEL_71_plus 53 | close_button: 54 | tooltiptext: 55 | string_id: cfr-doorhanger-bookmark-fxa-close-btn-tooltip 56 | color: white 57 | cta: 58 | string_id: cfr-doorhanger-bookmark-fxa-link-text 59 | info_icon: 60 | tooltiptext: 61 | string_id: cfr-doorhanger-bookmark-fxa-info-icon-tooltip 62 | text: 63 | string_id: cfr-doorhanger-bookmark-fxa-body 64 | title: 65 | string_id: cfr-doorhanger-bookmark-fxa-header 66 | frequency: 67 | lifetime: 3 68 | targeting: | 69 | !usesFirefoxSync && isFxAEnabled == true && totalBookmarksCount >= 10 70 | && firefoxVersion >= 70 && userPrefs.cfrFeatures && firefoxVersion < 89 71 | template: fxa_bookmark_panel 72 | trigger: 73 | id: bookmark-panel 74 | weight: 100 75 | -------------------------------------------------------------------------------- /archive/cfr-heartbeat-archived.yaml: -------------------------------------------------------------------------------- 1 | - id: HEARTBEAT_TACTIC_2_75 2 | template: cfr_urlbar_chiclet 3 | content: 4 | layout: chiclet_open_url 5 | category: cfrHeartbeat 6 | bucket_id: HEARTBEAT_TACTIC_2_75, 7 | notification_text: Help improve Firefox 8 | active_color: '#595e91' 9 | action: 10 | url: https://qsurvey.mozilla.com/s3/goals-survey 11 | where: tabshifted 12 | targeting: localeLanguageCode == "en" && 13 | browserSettings.update.channel == "release" && firefoxVersion >= 75 && 14 | [userId, "HEARTBEAT_TACTIC_2_75"]|stableSample(0.01) 15 | frequency: 16 | lifetime: 3 17 | trigger: 18 | id: "openURL" 19 | patterns: 20 | - '*://*/*' 21 | -------------------------------------------------------------------------------- /archive/moments-archived.yaml: -------------------------------------------------------------------------------- 1 | - id: WNP_MOMENTS_8 2 | groups: [moments-pages] 3 | content: 4 | action: 5 | data: 6 | # Valid until August 3rd EOD 7 | expire: 1596499200000 8 | url: https://www.mozilla.org/firefox/welcome/8 9 | id: moments-wnp 10 | bucket_id: WNP_MOMENTS_8 11 | # Fx 70+, profile age > 30 days all en-* and selected locales 12 | targeting: firefoxVersion >= 70 && 13 | ( 14 | localeLanguageCode == "en" || 15 | locale in ["cy", "de", "dsb", "es-AR", "fr", "fy-NL", "hsb", "hu", "ia", "it", "ka", "kab", "nb-NO", "nl", "nn-NO", "pl", "pt-BR", "ru", "sl", "sv-SE", "tr", "uk", "vi", "zh-TW"] 16 | ) && ((currentDate|date - profileAgeCreated) / 86400000) > 30 17 | template: update_action 18 | trigger: 19 | id: momentsUpdate 20 | - id: WNP_MOMENTS_7 21 | groups: [moments-pages] 22 | content: 23 | action: 24 | data: 25 | expireDelta: 604800000 26 | url: https://www.mozilla.org/firefox/welcome/7/ 27 | id: moments-wnp 28 | bucket_id: WNP_MOMENTS_7 29 | # Fx 69+, profile age > 30 days all en-* and selected locales 30 | targeting: firefoxVersion >= 69 && 31 | ( 32 | localeLanguageCode == "en" || 33 | locale in ["be", "cy", "da", "de", "dsb", "es-AR", "es-CL", "es-ES", "es-MX", "fr", "fy-NL", "gn", "hr", "hsb", "hu", "hy-AM", "ia", "id", "it", "ka", "kab", "nl", "nn-NO", "pl", "pt-BR", "pt-PT", "ro", "ru", "sk", "sl", "sq", "sr", "sv-SE", "tr", "uk", "vi", "zh-TW"] 34 | ) && ((currentDate|date - profileAgeCreated) / 86400000) > 30 35 | && xpinstallEnabled && 36 | (['@contain-facebook','{bb1b80be-e6b3-40a1-9b6e-9d4073343f0b}', '{a50d61ca-d27b-437a-8b52-5fd801a0a88b}'] intersect addonsInfo.addons|keys)|length == 0 37 | template: update_action 38 | trigger: 39 | id: momentsUpdate 40 | - id: WNP_MOMENTS_2 41 | groups: [moments-pages] 42 | content: 43 | action: 44 | data: 45 | expireDelta: 604800000 46 | url: https://www.mozilla.org/firefox/welcome/2/ 47 | id: moments-wnp 48 | bucket_id: WNP_MOMENTS_2 49 | targeting: firefoxVersion >= 69 && localeLanguageCode in ["en","fr","de"] && ((currentDate|date 50 | - profileAgeCreated) / 86400000) >= 17 && ((currentDate|date - profileAgeCreated) 51 | / 86400000) < 30 && firefoxVersion < 89 52 | template: update_action 53 | trigger: 54 | id: momentsUpdate 55 | - id: WNP_MOMENTS_1 56 | groups: [moments-pages] 57 | content: 58 | action: 59 | data: 60 | expireDelta: 604800000 61 | url: https://www.mozilla.org/firefox/welcome/1/ 62 | id: moments-wnp 63 | bucket_id: WNP_MOMENTS_1 64 | targeting: firefoxVersion >= 69 && localeLanguageCode in ["en","fr","de"] && ((currentDate|date 65 | - profileAgeCreated) / 86400000) >= 4 && ((currentDate|date - profileAgeCreated) 66 | / 86400000) < 15 && firefoxVersion < 89 67 | template: update_action 68 | trigger: 69 | id: momentsUpdate 70 | - id: WNP_MOMENTS_4 71 | groups: [moments-pages] 72 | content: 73 | action: 74 | data: 75 | expireDelta: 604800000 76 | url: https://www.mozilla.org/firefox/welcome/4/ 77 | id: moments-wnp 78 | bucket_id: WNP_MOMENTS_4 79 | targeting: firefoxVersion >= 69 && localeLanguageCode in ["en", "fr", "de"] && 80 | ((currentDate|date - profileAgeCreated) / 86400000) > 30 && firefoxVersion < 89 81 | template: update_action 82 | trigger: 83 | id: momentsUpdate 84 | - id: WNP_MOMENTS_9 85 | groups: [moments-pages] 86 | content: 87 | action: 88 | data: 89 | expireDelta: 604800000 90 | url: https://www.mozilla.org/%LOCALE%/firefox/welcome/9/ 91 | id: moments-wnp 92 | bucket_id: WNP_MOMENTS_9 93 | targeting: firefoxVersion >= 86 && localeLanguageCode in ["fr", "de"] && region in ["FR", "DE", "US"] && firefoxVersion < 89 && ((currentDate|date - profileAgeCreated) / 86400000) > 30 94 | template: update_action 95 | trigger: 96 | id: momentsUpdate 97 | - id: WNP_MOMENTS_9_89 98 | groups: [moments-pages] 99 | content: 100 | action: 101 | data: 102 | # run the campaign until June 25th 103 | # https://searchfox.org/mozilla-central/rev/da5d08750e504f3710f7ea051327d9c311c39902/browser/components/BrowserContentHandler.jsm#686 104 | expire: 1624579200000 105 | url: https://www.mozilla.org/%LOCALE%/firefox/welcome/9/ 106 | id: moments-wnp 107 | bucket_id: WNP_MOMENTS_9_89 108 | # e6eb0d1e856335fc is the identifier for Mozilla VPN 109 | targeting: firefoxVersion >= 89 && firefoxVersion < 90 && localeLanguageCode in ["fr", "de"] && region in ["FR", "DE", "US"] && ((currentDate|date - profileAgeCreated) / 86400000) > 30 && usesFirefoxSync && !("e6eb0d1e856335fc" in attachedFxAOAuthClients|mapToProperty("id")) 110 | template: update_action 111 | trigger: 112 | id: momentsUpdate 113 | - id: WNP_MOMENTS_10 114 | groups: [moments-pages] 115 | content: 116 | action: 117 | data: 118 | # run the campaign for 3 weeks until July 13th 119 | # https://searchfox.org/mozilla-central/rev/da5d08750e504f3710f7ea051327d9c311c39902/browser/components/BrowserContentHandler.jsm#686 120 | expire: 1627603200000 121 | url: https://www.mozilla.org/firefox/welcome/10/ 122 | id: moments-wnp 123 | bucket_id: WNP_MOMENTS_10 124 | targeting: localeLanguageCode == 'en' && region in ['US', 'CA', 'GB', 'NZ', 'SG', 'MY'] && userPrefs.cfrFeatures && firefoxVersion < 90 && firefoxVersion >= 86 125 | template: update_action 126 | trigger: 127 | id: momentsUpdate 128 | - id: WNP_MOMENTS_9 129 | groups: [moments-pages] 130 | content: 131 | action: 132 | data: 133 | # Valid until November 29th EOD 134 | expire: 1638144000000 135 | # URL will redirect to correct locale based on browser settings 136 | url: https://www.mozilla.org/firefox/welcome/9 137 | id: moments-wnp 138 | bucket_id: WNP_MOMENTS_9 139 | targeting: localeLanguageCode in ["fr", "de"] && 140 | region in ["DE", "AT", "BE", "CA", "FR", "IE", "IT", "MY", "NL", "NZ", "SG", "ES", "CH", "US", "GB"] && 141 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 142 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 143 | template: update_action 144 | trigger: 145 | id: momentsUpdate 146 | - id: WNP_MOMENTS_10 147 | groups: [moments-pages] 148 | content: 149 | action: 150 | data: 151 | # Valid until November 29th EOD 152 | expire: 1638144000000 153 | # URL will redirect to correct locale based on browser settings 154 | url: https://www.mozilla.org/firefox/welcome/10 155 | id: moments-wnp 156 | bucket_id: WNP_MOMENTS_10 157 | targeting: (localeLanguageCode in ["en", "nl", "it"] || locale == "es-ES") && 158 | region in ["DE", "AT", "BE", "CA", "FR", "IE", "IT", "MY", "NL", "NZ", "SG", "ES", "CH", "US", "GB"] && 159 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 160 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 161 | template: update_action 162 | trigger: 163 | id: momentsUpdate 164 | - id: WNP_MOMENTS_11 165 | groups: [moments-pages] 166 | content: 167 | action: 168 | data: 169 | # Valid until December 15th EOD 170 | expire: 1639526400000 171 | # URL will redirect to correct locale based on browser settings 172 | url: https://www.mozilla.org/firefox/welcome/11 173 | id: moments-wnp 174 | bucket_id: WNP_MOMENTS_11 175 | targeting: localeLanguageCode in ["en", "de", "fr", "nl", "it"] && 176 | region in ["DE", "AT", "BE", "CA", "FR", "IE", "IT", "MY", "NL", "NZ", "SG", "CH", "US", "GB", "ES"] && 177 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 178 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 179 | template: update_action 180 | trigger: 181 | id: momentsUpdate 182 | - id: WNP_MOMENTS_12 183 | groups: [moments-pages] 184 | content: 185 | action: 186 | data: 187 | # Valid until December 31th EOD 188 | expire: 1640908800000 189 | # URL will redirect to correct locale based on browser settings 190 | url: https://www.mozilla.org/firefox/welcome/12 191 | id: moments-wnp 192 | bucket_id: WNP_MOMENTS_12 193 | # multi-account container EN only 194 | targeting: localeLanguageCode == "en" && 195 | region in ["DE", "AT", "BE", "CA", "FR", "IE", "IT", "MY", "NL", "NZ", "SG", "CH", "US", "GB", "ES"] && 196 | (addonsInfo.addons|keys intersect ["@testpilot-containers"])|length == 1 && 197 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 198 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 199 | template: update_action 200 | trigger: 201 | id: momentsUpdate 202 | - id: WNP_MOMENTS_13 203 | groups: [moments-pages] 204 | content: 205 | action: 206 | data: 207 | # Valid until December 31th EOD 208 | expire: 1640908800000 209 | # URL will redirect to correct locale based on browser settings 210 | url: https://www.mozilla.org/firefox/welcome/13 211 | id: moments-wnp 212 | bucket_id: WNP_MOMENTS_13 213 | targeting: (localeLanguageCode in ["en", "de", "fr", "nl", "it", "ms"] || locale == "es-ES") && 214 | region in ["DE", "AT", "BE", "CA", "FR", "IE", "IT", "MY", "NL", "NZ", "SG", "CH", "US", "GB", "ES"] && 215 | (addonsInfo.addons|keys intersect ["@testpilot-containers"])|length == 0 && 216 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 217 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 218 | template: update_action 219 | trigger: 220 | id: momentsUpdate 221 | - id: WNP_MOMENTS_14 222 | groups: [moments-pages] 223 | content: 224 | action: 225 | data: 226 | # Valid until 2022 November 14 EOD (coupon expiry) 227 | expire: 1668470400000 228 | # URL will redirect to correct locale based on browser settings 229 | url: https://www.mozilla.org/firefox/welcome/14 230 | id: moments-wnp 231 | bucket_id: WNP_MOMENTS_14 232 | targeting: localeLanguageCode in ["en", "de", "fr"] && 233 | region in ["AT", "BE", "CA", "CH", "DE", "ES", "FI", "FR", "GB", "IE", "IT", "MY", "NL", "NZ", "SE", "SG", "US"] && 234 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && 235 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons'|preferenceValue 236 | template: update_action 237 | trigger: 238 | id: momentsUpdate 239 | -------------------------------------------------------------------------------- /messages/cfr-heartbeat.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/messaging-system-inflight-assets/070049e3df8deb7c16b391fcba2cdbec702eb4f5/messages/cfr-heartbeat.yaml -------------------------------------------------------------------------------- /messages/cfr.yaml: -------------------------------------------------------------------------------- 1 | - id: MILESTONE_MESSAGE_87 2 | groups: 3 | - cfr 4 | content: 5 | anchor_id: tracking-protection-icon-container 6 | bucket_id: CFR_MILESTONE_MESSAGE 7 | buttons: 8 | primary: 9 | action: 10 | type: OPEN_PROTECTION_REPORT 11 | event: PROTECTION 12 | label: 13 | string_id: cfr-doorhanger-milestone-ok-button 14 | secondary: 15 | - label: 16 | string_id: "cfr-doorhanger-milestone-close-button" 17 | action: 18 | type: "CANCEL" 19 | event: "DISMISS" 20 | category: cfrFeatures 21 | heading_text: 22 | string_id: cfr-doorhanger-milestone-heading2 23 | layout: short_message 24 | notification_text: '' 25 | skip_address_bar_notifier: true 26 | text: '' 27 | frequency: 28 | lifetime: 7 29 | targeting: pageLoad >= 4 && firefoxVersion >= 115 && firefoxVersion < 121 && userPrefs.cfrFeatures 30 | template: milestone_message 31 | trigger: 32 | id: contentBlocking 33 | params: 34 | - ContentBlockingMilestone 35 | - id: DOH_ROLLOUT_CONFIRMATION_89 36 | groups: 37 | - cfr 38 | # Don't show to profiles newer than Oct 31st 2019 39 | targeting: >- 40 | profileAgeCreated < 1572480000000 && 41 | ( 42 | 'doh-rollout.enabled'|preferenceValue || 43 | 'doh-rollout.self-enabled'|preferenceValue || 44 | 'doh-rollout.ru.enabled'|preferenceValue || 45 | 'doh-rollout.ua.enabled'|preferenceValue 46 | ) && 47 | !( 48 | 'doh-rollout.disable-heuristics'|preferenceValue || 49 | 'doh-rollout.skipHeuristicsCheck'|preferenceValue || 50 | 'doh-rollout.doorhanger-decision'|preferenceValue 51 | ) && 52 | firefoxVersion >= 89 53 | template: infobar 54 | content: 55 | priority: 3 56 | type: global 57 | text: 58 | string_id: cfr-doorhanger-doh-body 59 | buttons: 60 | - label: 61 | string_id: cfr-doorhanger-doh-primary-button-2 62 | action: 63 | type: ACCEPT_DOH 64 | primary: true 65 | - label: 66 | string_id: cfr-doorhanger-doh-secondary-button 67 | action: 68 | type: DISABLE_DOH 69 | - label: 70 | string_id: notification-learnmore-default-label 71 | supportPage: dns-over-https 72 | # Use no callback/action so telemetry doesn't mix link and secondary 73 | callback: null 74 | action: 75 | type: CANCEL 76 | bucket_id: DOH_ROLLOUT_CONFIRMATION_89 77 | category: cfrFeatures 78 | frequency: 79 | lifetime: 3 80 | trigger: 81 | id: openURL 82 | patterns: 83 | - "*://*/*" 84 | - id: INFOBAR_DEFAULT_AND_PIN_87 85 | groups: 86 | - cfr 87 | content: 88 | category: cfrFeatures 89 | bucket_id: INFOBAR_DEFAULT_AND_PIN_87 90 | text: 91 | string_id: default-browser-notification-message 92 | type: global 93 | buttons: 94 | - label: 95 | string_id: default-browser-notification-button 96 | action: 97 | type: PIN_AND_DEFAULT 98 | primary: true 99 | accessKey: P 100 | trigger: 101 | id: defaultBrowserCheck 102 | template: infobar 103 | frequency: 104 | lifetime: 2 105 | custom: 106 | - period: 3024000000 # 86400000 * 35 (35 days to ms) 107 | cap: 1 108 | # Not default browser and user dismissed the default browser prompt 109 | # For Firefox version 87-88 the message is triggered only on the newtab page. 110 | # For versions 89 - 138 we trigger the message at startup and want to ignore the 111 | # newtab trigger. 112 | targeting: 113 | "((firefoxVersion >= 87 && firefoxVersion < 89) || (firefoxVersion >= 89 && 114 | firefoxVersion < 138 && source == 'startup')) && !isDefaultBrowser && 115 | !'browser.shell.checkDefaultBrowser'|preferenceValue && isMajorUpgrade != 116 | true && platformName != 'linux' && ((currentDate|date - profileAgeCreated) / 117 | 604800000) >= 5 && !activeNotifications && 118 | 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue 119 | && ((currentDate|date - profileAgeCreated) / 604800000) < 15" 120 | - id: CFR_FULL_VIDEO_SUPPORT_EN 121 | groups: 122 | - cfr 123 | targeting: 124 | firefoxVersion < 88 && firefoxVersion != 78 && localeLanguageCode in ['en', 'fr', 'de', 'ru', 'zh', 'es', 'it', 'pl'] 125 | template: cfr_doorhanger 126 | content: 127 | skip_address_bar_notifier: true 128 | persistent_doorhanger: true 129 | anchor_id: PanelUI-menu-button 130 | layout: icon_and_message 131 | text: 132 | string_id: cfr-doorhanger-video-support-body 133 | buttons: 134 | secondary: 135 | - label: 136 | string_id: cfr-doorhanger-extension-cancel-button 137 | action: 138 | type: CANCEL 139 | primary: 140 | label: 141 | string_id: cfr-doorhanger-video-support-primary-button 142 | action: 143 | type: OPEN_URL 144 | data: 145 | args: https://support.mozilla.org/kb/update-firefox-latest-release 146 | where: tabshifted 147 | bucket_id: CFR_FULL_VIDEO_SUPPORT_EN 148 | heading_text: 149 | string_id: cfr-doorhanger-video-support-header 150 | info_icon: 151 | label: 152 | string_id: cfr-doorhanger-extension-sumo-link 153 | sumo_path: extensionrecommendations 154 | notification_text: Message from Firefox 155 | category: cfrFeatures 156 | frequency: 157 | lifetime: 3 158 | trigger: 159 | id: openURL 160 | patterns: 161 | - https://*/Amazon-Video/* 162 | - https://*/Prime-Video/* 163 | params: 164 | - www.hulu.com 165 | - hulu.com 166 | - www.netflix.com 167 | - netflix.com 168 | - www.disneyplus.com 169 | - disneyplus.com 170 | - www.hbomax.com 171 | - hbomax.com 172 | - www.sho.com 173 | - sho.com 174 | - www.directv.com 175 | - directv.com 176 | - www.starzplay.com 177 | - starzplay.com 178 | - www.sling.com 179 | - sling.com 180 | - www.facebook.com 181 | - facebook.com 182 | -------------------------------------------------------------------------------- /messages/message-groups.yaml: -------------------------------------------------------------------------------- 1 | - id: cfr 2 | enabled: true 3 | type: remote-settings 4 | frequency: 5 | custom: 6 | - {period: 86400000, cap: 1} # 1 day 7 | userPreferences: 8 | - browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features 9 | - id: cfr-experiments 10 | enabled: true 11 | type: remote-settings 12 | frequency: 13 | custom: 14 | - {period: 86400000, cap: 1} # 1 day 15 | - id: moments-pages 16 | enabled: true 17 | type: remote-settings 18 | userPreferences: 19 | - browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features 20 | - id: eco 21 | enabled: true 22 | type: remote-settings 23 | frequency: 24 | custom: 25 | - {period: 2419200000, cap: 1} # 4 weeks 26 | userPreferences: 27 | - browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features 28 | - id: import-spotlights 29 | enabled: true 30 | type: remote-settings 31 | frequency: 32 | custom: 33 | - {period: 4838400000, cap: 1} # 56 days 34 | lifetime: 4 35 | userPreferences: 36 | - browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features 37 | - id: micro-surveys 38 | enabled: true 39 | type: remote-settings 40 | frequency: 41 | custom: 42 | - {period: 15778476000, cap: 1} # 6 months 43 | userPreferences: 44 | - messaging-system.askForFeedback 45 | -------------------------------------------------------------------------------- /messages/messaging-experiments.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/messaging-system-inflight-assets/070049e3df8deb7c16b391fcba2cdbec702eb4f5/messages/messaging-experiments.yaml -------------------------------------------------------------------------------- /messages/moments.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/messaging-system-inflight-assets/070049e3df8deb7c16b391fcba2cdbec702eb4f5/messages/moments.yaml -------------------------------------------------------------------------------- /messages/whats-new-panel.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/messaging-system-inflight-assets/070049e3df8deb7c16b391fcba2cdbec702eb4f5/messages/whats-new-panel.yaml -------------------------------------------------------------------------------- /outgoing/InflightAssetsMessageProvider.sys.mjs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | // This file is generated by: 7 | // https://github.com/mozilla/messaging-system-inflight-assets/tree/master/scripts/export-all.py 8 | 9 | export const InflightAssetsMessageProvider = { 10 | getMessages() { 11 | return [ 12 | { 13 | "id": "MILESTONE_MESSAGE_87", 14 | "groups": [ 15 | "cfr" 16 | ], 17 | "content": { 18 | "anchor_id": "tracking-protection-icon-container", 19 | "bucket_id": "CFR_MILESTONE_MESSAGE", 20 | "buttons": { 21 | "primary": { 22 | "action": { 23 | "type": "OPEN_PROTECTION_REPORT" 24 | }, 25 | "event": "PROTECTION", 26 | "label": { 27 | "string_id": "cfr-doorhanger-milestone-ok-button" 28 | } 29 | }, 30 | "secondary": [ 31 | { 32 | "label": { 33 | "string_id": "cfr-doorhanger-milestone-close-button" 34 | }, 35 | "action": { 36 | "type": "CANCEL" 37 | }, 38 | "event": "DISMISS" 39 | } 40 | ] 41 | }, 42 | "category": "cfrFeatures", 43 | "heading_text": { 44 | "string_id": "cfr-doorhanger-milestone-heading2" 45 | }, 46 | "layout": "short_message", 47 | "notification_text": "", 48 | "skip_address_bar_notifier": true, 49 | "text": "" 50 | }, 51 | "frequency": { 52 | "lifetime": 7 53 | }, 54 | "targeting": "pageLoad >= 4 && firefoxVersion >= 115 && firefoxVersion < 121 && userPrefs.cfrFeatures", 55 | "template": "milestone_message", 56 | "trigger": { 57 | "id": "contentBlocking", 58 | "params": [ 59 | "ContentBlockingMilestone" 60 | ] 61 | } 62 | }, 63 | { 64 | "id": "DOH_ROLLOUT_CONFIRMATION_89", 65 | "groups": [ 66 | "cfr" 67 | ], 68 | "targeting": "profileAgeCreated < 1572480000000 && ( 'doh-rollout.enabled'|preferenceValue || 'doh-rollout.self-enabled'|preferenceValue || 'doh-rollout.ru.enabled'|preferenceValue || 'doh-rollout.ua.enabled'|preferenceValue ) && !( 'doh-rollout.disable-heuristics'|preferenceValue || 'doh-rollout.skipHeuristicsCheck'|preferenceValue || 'doh-rollout.doorhanger-decision'|preferenceValue ) && firefoxVersion >= 89", 69 | "template": "infobar", 70 | "content": { 71 | "priority": 3, 72 | "type": "global", 73 | "text": { 74 | "string_id": "cfr-doorhanger-doh-body" 75 | }, 76 | "buttons": [ 77 | { 78 | "label": { 79 | "string_id": "cfr-doorhanger-doh-primary-button-2" 80 | }, 81 | "action": { 82 | "type": "ACCEPT_DOH" 83 | }, 84 | "primary": true 85 | }, 86 | { 87 | "label": { 88 | "string_id": "cfr-doorhanger-doh-secondary-button" 89 | }, 90 | "action": { 91 | "type": "DISABLE_DOH" 92 | } 93 | }, 94 | { 95 | "label": { 96 | "string_id": "notification-learnmore-default-label" 97 | }, 98 | "supportPage": "dns-over-https", 99 | "callback": null, 100 | "action": { 101 | "type": "CANCEL" 102 | } 103 | } 104 | ], 105 | "bucket_id": "DOH_ROLLOUT_CONFIRMATION_89", 106 | "category": "cfrFeatures" 107 | }, 108 | "frequency": { 109 | "lifetime": 3 110 | }, 111 | "trigger": { 112 | "id": "openURL", 113 | "patterns": [ 114 | "*://*/*" 115 | ] 116 | } 117 | }, 118 | { 119 | "id": "INFOBAR_DEFAULT_AND_PIN_87", 120 | "groups": [ 121 | "cfr" 122 | ], 123 | "content": { 124 | "category": "cfrFeatures", 125 | "bucket_id": "INFOBAR_DEFAULT_AND_PIN_87", 126 | "text": { 127 | "string_id": "default-browser-notification-message" 128 | }, 129 | "type": "global", 130 | "buttons": [ 131 | { 132 | "label": { 133 | "string_id": "default-browser-notification-button" 134 | }, 135 | "action": { 136 | "type": "PIN_AND_DEFAULT" 137 | }, 138 | "primary": true, 139 | "accessKey": "P" 140 | } 141 | ] 142 | }, 143 | "trigger": { 144 | "id": "defaultBrowserCheck" 145 | }, 146 | "template": "infobar", 147 | "frequency": { 148 | "lifetime": 2, 149 | "custom": [ 150 | { 151 | "period": 3024000000, 152 | "cap": 1 153 | } 154 | ] 155 | }, 156 | "targeting": "((firefoxVersion >= 87 && firefoxVersion < 89) || (firefoxVersion >= 89 && firefoxVersion < 138 && source == 'startup')) && !isDefaultBrowser && !'browser.shell.checkDefaultBrowser'|preferenceValue && isMajorUpgrade != true && platformName != 'linux' && ((currentDate|date - profileAgeCreated) / 604800000) >= 5 && !activeNotifications && 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && ((currentDate|date - profileAgeCreated) / 604800000) < 15" 157 | }, 158 | { 159 | "id": "CFR_FULL_VIDEO_SUPPORT_EN", 160 | "groups": [ 161 | "cfr" 162 | ], 163 | "targeting": "firefoxVersion < 88 && firefoxVersion != 78 && localeLanguageCode in ['en', 'fr', 'de', 'ru', 'zh', 'es', 'it', 'pl']", 164 | "template": "cfr_doorhanger", 165 | "content": { 166 | "skip_address_bar_notifier": true, 167 | "persistent_doorhanger": true, 168 | "anchor_id": "PanelUI-menu-button", 169 | "layout": "icon_and_message", 170 | "text": { 171 | "string_id": "cfr-doorhanger-video-support-body" 172 | }, 173 | "buttons": { 174 | "secondary": [ 175 | { 176 | "label": { 177 | "string_id": "cfr-doorhanger-extension-cancel-button" 178 | }, 179 | "action": { 180 | "type": "CANCEL" 181 | } 182 | } 183 | ], 184 | "primary": { 185 | "label": { 186 | "string_id": "cfr-doorhanger-video-support-primary-button" 187 | }, 188 | "action": { 189 | "type": "OPEN_URL", 190 | "data": { 191 | "args": "https://support.mozilla.org/kb/update-firefox-latest-release", 192 | "where": "tabshifted" 193 | } 194 | } 195 | } 196 | }, 197 | "bucket_id": "CFR_FULL_VIDEO_SUPPORT_EN", 198 | "heading_text": { 199 | "string_id": "cfr-doorhanger-video-support-header" 200 | }, 201 | "info_icon": { 202 | "label": { 203 | "string_id": "cfr-doorhanger-extension-sumo-link" 204 | }, 205 | "sumo_path": "extensionrecommendations" 206 | }, 207 | "notification_text": "Message from Firefox", 208 | "category": "cfrFeatures" 209 | }, 210 | "frequency": { 211 | "lifetime": 3 212 | }, 213 | "trigger": { 214 | "id": "openURL", 215 | "patterns": [ 216 | "https://*/Amazon-Video/*", 217 | "https://*/Prime-Video/*" 218 | ], 219 | "params": [ 220 | "www.hulu.com", 221 | "hulu.com", 222 | "www.netflix.com", 223 | "netflix.com", 224 | "www.disneyplus.com", 225 | "disneyplus.com", 226 | "www.hbomax.com", 227 | "hbomax.com", 228 | "www.sho.com", 229 | "sho.com", 230 | "www.directv.com", 231 | "directv.com", 232 | "www.starzplay.com", 233 | "starzplay.com", 234 | "www.sling.com", 235 | "sling.com", 236 | "www.facebook.com", 237 | "facebook.com" 238 | ] 239 | } 240 | } 241 | ]; 242 | } 243 | }; 244 | -------------------------------------------------------------------------------- /outgoing/cfr-heartbeat.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /outgoing/cfr.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "MILESTONE_MESSAGE_87", 4 | "groups": [ 5 | "cfr" 6 | ], 7 | "content": { 8 | "anchor_id": "tracking-protection-icon-container", 9 | "bucket_id": "CFR_MILESTONE_MESSAGE", 10 | "buttons": { 11 | "primary": { 12 | "action": { 13 | "type": "OPEN_PROTECTION_REPORT" 14 | }, 15 | "event": "PROTECTION", 16 | "label": { 17 | "string_id": "cfr-doorhanger-milestone-ok-button" 18 | } 19 | }, 20 | "secondary": [ 21 | { 22 | "label": { 23 | "string_id": "cfr-doorhanger-milestone-close-button" 24 | }, 25 | "action": { 26 | "type": "CANCEL" 27 | }, 28 | "event": "DISMISS" 29 | } 30 | ] 31 | }, 32 | "category": "cfrFeatures", 33 | "heading_text": { 34 | "string_id": "cfr-doorhanger-milestone-heading2" 35 | }, 36 | "layout": "short_message", 37 | "notification_text": "", 38 | "skip_address_bar_notifier": true, 39 | "text": "" 40 | }, 41 | "frequency": { 42 | "lifetime": 7 43 | }, 44 | "targeting": "pageLoad >= 4 && firefoxVersion >= 115 && firefoxVersion < 121 && userPrefs.cfrFeatures", 45 | "template": "milestone_message", 46 | "trigger": { 47 | "id": "contentBlocking", 48 | "params": [ 49 | "ContentBlockingMilestone" 50 | ] 51 | } 52 | }, 53 | { 54 | "id": "DOH_ROLLOUT_CONFIRMATION_89", 55 | "groups": [ 56 | "cfr" 57 | ], 58 | "targeting": "profileAgeCreated < 1572480000000 && ( 'doh-rollout.enabled'|preferenceValue || 'doh-rollout.self-enabled'|preferenceValue || 'doh-rollout.ru.enabled'|preferenceValue || 'doh-rollout.ua.enabled'|preferenceValue ) && !( 'doh-rollout.disable-heuristics'|preferenceValue || 'doh-rollout.skipHeuristicsCheck'|preferenceValue || 'doh-rollout.doorhanger-decision'|preferenceValue ) && firefoxVersion >= 89", 59 | "template": "infobar", 60 | "content": { 61 | "priority": 3, 62 | "type": "global", 63 | "text": { 64 | "string_id": "cfr-doorhanger-doh-body" 65 | }, 66 | "buttons": [ 67 | { 68 | "label": { 69 | "string_id": "cfr-doorhanger-doh-primary-button-2" 70 | }, 71 | "action": { 72 | "type": "ACCEPT_DOH" 73 | }, 74 | "primary": true 75 | }, 76 | { 77 | "label": { 78 | "string_id": "cfr-doorhanger-doh-secondary-button" 79 | }, 80 | "action": { 81 | "type": "DISABLE_DOH" 82 | } 83 | }, 84 | { 85 | "label": { 86 | "string_id": "notification-learnmore-default-label" 87 | }, 88 | "supportPage": "dns-over-https", 89 | "callback": null, 90 | "action": { 91 | "type": "CANCEL" 92 | } 93 | } 94 | ], 95 | "bucket_id": "DOH_ROLLOUT_CONFIRMATION_89", 96 | "category": "cfrFeatures" 97 | }, 98 | "frequency": { 99 | "lifetime": 3 100 | }, 101 | "trigger": { 102 | "id": "openURL", 103 | "patterns": [ 104 | "*://*/*" 105 | ] 106 | } 107 | }, 108 | { 109 | "id": "INFOBAR_DEFAULT_AND_PIN_87", 110 | "groups": [ 111 | "cfr" 112 | ], 113 | "content": { 114 | "category": "cfrFeatures", 115 | "bucket_id": "INFOBAR_DEFAULT_AND_PIN_87", 116 | "text": { 117 | "string_id": "default-browser-notification-message" 118 | }, 119 | "type": "global", 120 | "buttons": [ 121 | { 122 | "label": { 123 | "string_id": "default-browser-notification-button" 124 | }, 125 | "action": { 126 | "type": "PIN_AND_DEFAULT" 127 | }, 128 | "primary": true, 129 | "accessKey": "P" 130 | } 131 | ] 132 | }, 133 | "trigger": { 134 | "id": "defaultBrowserCheck" 135 | }, 136 | "template": "infobar", 137 | "frequency": { 138 | "lifetime": 2, 139 | "custom": [ 140 | { 141 | "period": 3024000000, 142 | "cap": 1 143 | } 144 | ] 145 | }, 146 | "targeting": "((firefoxVersion >= 87 && firefoxVersion < 89) || (firefoxVersion >= 89 && firefoxVersion < 138 && source == 'startup')) && !isDefaultBrowser && !'browser.shell.checkDefaultBrowser'|preferenceValue && isMajorUpgrade != true && platformName != 'linux' && ((currentDate|date - profileAgeCreated) / 604800000) >= 5 && !activeNotifications && 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && ((currentDate|date - profileAgeCreated) / 604800000) < 15" 147 | }, 148 | { 149 | "id": "CFR_FULL_VIDEO_SUPPORT_EN", 150 | "groups": [ 151 | "cfr" 152 | ], 153 | "targeting": "firefoxVersion < 88 && firefoxVersion != 78 && localeLanguageCode in ['en', 'fr', 'de', 'ru', 'zh', 'es', 'it', 'pl']", 154 | "template": "cfr_doorhanger", 155 | "content": { 156 | "skip_address_bar_notifier": true, 157 | "persistent_doorhanger": true, 158 | "anchor_id": "PanelUI-menu-button", 159 | "layout": "icon_and_message", 160 | "text": { 161 | "string_id": "cfr-doorhanger-video-support-body" 162 | }, 163 | "buttons": { 164 | "secondary": [ 165 | { 166 | "label": { 167 | "string_id": "cfr-doorhanger-extension-cancel-button" 168 | }, 169 | "action": { 170 | "type": "CANCEL" 171 | } 172 | } 173 | ], 174 | "primary": { 175 | "label": { 176 | "string_id": "cfr-doorhanger-video-support-primary-button" 177 | }, 178 | "action": { 179 | "type": "OPEN_URL", 180 | "data": { 181 | "args": "https://support.mozilla.org/kb/update-firefox-latest-release", 182 | "where": "tabshifted" 183 | } 184 | } 185 | } 186 | }, 187 | "bucket_id": "CFR_FULL_VIDEO_SUPPORT_EN", 188 | "heading_text": { 189 | "string_id": "cfr-doorhanger-video-support-header" 190 | }, 191 | "info_icon": { 192 | "label": { 193 | "string_id": "cfr-doorhanger-extension-sumo-link" 194 | }, 195 | "sumo_path": "extensionrecommendations" 196 | }, 197 | "notification_text": "Message from Firefox", 198 | "category": "cfrFeatures" 199 | }, 200 | "frequency": { 201 | "lifetime": 3 202 | }, 203 | "trigger": { 204 | "id": "openURL", 205 | "patterns": [ 206 | "https://*/Amazon-Video/*", 207 | "https://*/Prime-Video/*" 208 | ], 209 | "params": [ 210 | "www.hulu.com", 211 | "hulu.com", 212 | "www.netflix.com", 213 | "netflix.com", 214 | "www.disneyplus.com", 215 | "disneyplus.com", 216 | "www.hbomax.com", 217 | "hbomax.com", 218 | "www.sho.com", 219 | "sho.com", 220 | "www.directv.com", 221 | "directv.com", 222 | "www.starzplay.com", 223 | "starzplay.com", 224 | "www.sling.com", 225 | "sling.com", 226 | "www.facebook.com", 227 | "facebook.com" 228 | ] 229 | } 230 | } 231 | ] -------------------------------------------------------------------------------- /outgoing/message-groups.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "cfr", 4 | "enabled": true, 5 | "type": "remote-settings", 6 | "frequency": { 7 | "custom": [ 8 | { 9 | "period": 86400000, 10 | "cap": 1 11 | } 12 | ] 13 | }, 14 | "userPreferences": [ 15 | "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" 16 | ] 17 | }, 18 | { 19 | "id": "cfr-experiments", 20 | "enabled": true, 21 | "type": "remote-settings", 22 | "frequency": { 23 | "custom": [ 24 | { 25 | "period": 86400000, 26 | "cap": 1 27 | } 28 | ] 29 | } 30 | }, 31 | { 32 | "id": "moments-pages", 33 | "enabled": true, 34 | "type": "remote-settings", 35 | "userPreferences": [ 36 | "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" 37 | ] 38 | }, 39 | { 40 | "id": "eco", 41 | "enabled": true, 42 | "type": "remote-settings", 43 | "frequency": { 44 | "custom": [ 45 | { 46 | "period": 2419200000, 47 | "cap": 1 48 | } 49 | ] 50 | }, 51 | "userPreferences": [ 52 | "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" 53 | ] 54 | }, 55 | { 56 | "id": "import-spotlights", 57 | "enabled": true, 58 | "type": "remote-settings", 59 | "frequency": { 60 | "custom": [ 61 | { 62 | "period": 4838400000, 63 | "cap": 1 64 | } 65 | ], 66 | "lifetime": 4 67 | }, 68 | "userPreferences": [ 69 | "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" 70 | ] 71 | }, 72 | { 73 | "id": "micro-surveys", 74 | "enabled": true, 75 | "type": "remote-settings", 76 | "frequency": { 77 | "custom": [ 78 | { 79 | "period": 15778476000, 80 | "cap": 1 81 | } 82 | ] 83 | }, 84 | "userPreferences": [ 85 | "messaging-system.askForFeedback" 86 | ] 87 | } 88 | ] -------------------------------------------------------------------------------- /outgoing/messaging-experiments.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /outgoing/moments.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /outgoing/whats-new-panel.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jsonschema==3.2.0 2 | PyYAML==5.4 3 | yamllint==1.21.0 4 | pyjexl==0.3.0 5 | requests>=2.27.1,<2.28.0 6 | -------------------------------------------------------------------------------- /schema/MessagingExperiment.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "$id": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", 4 | "title": "Messaging Experiment", 5 | "description": "A Firefox Messaging System message.", 6 | "if": { 7 | "type": "object", 8 | "properties": { 9 | "template": { 10 | "const": "multi" 11 | } 12 | }, 13 | "required": [ 14 | "template" 15 | ] 16 | }, 17 | "then": { 18 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/MultiMessage" 19 | }, 20 | "else": { 21 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/TemplatedMessage" 22 | }, 23 | "$defs": { 24 | "BookmarksBarButton": { 25 | "$schema": "https://json-schema.org/draft/2019-09/schema", 26 | "$id": "file:///BookmarksBarButton.schema.json", 27 | "title": "BookmarksBarButton", 28 | "description": "A template with a label and a special message action (currently only supports OPEN_URL)", 29 | "allOf": [ 30 | { 31 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 32 | } 33 | ], 34 | "type": "object", 35 | "properties": { 36 | "template": { 37 | "type": "string", 38 | "const": "bookmarks_bar_button" 39 | }, 40 | "content": { 41 | "type": "object", 42 | "properties": { 43 | "action": { 44 | "type": "object", 45 | "properties": { 46 | "type": { 47 | "type": "string", 48 | "description": "Action dispatched by the button." 49 | }, 50 | "data": { 51 | "type": "object" 52 | } 53 | }, 54 | "required": [ 55 | "type" 56 | ], 57 | "additionalProperties": true 58 | } 59 | }, 60 | "additionalProperties": true 61 | } 62 | }, 63 | "additionalProperties": true, 64 | "required": [ 65 | "targeting" 66 | ] 67 | }, 68 | "CFRUrlbarChiclet": { 69 | "$schema": "https://json-schema.org/draft/2019-09/schema", 70 | "$id": "file:///CFRUrlbarChiclet.schema.json", 71 | "title": "CFRUrlbarChiclet", 72 | "description": "A template with a chiclet button with text.", 73 | "allOf": [ 74 | { 75 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 76 | } 77 | ], 78 | "type": "object", 79 | "properties": { 80 | "content": { 81 | "type": "object", 82 | "properties": { 83 | "category": { 84 | "type": "string", 85 | "description": "Attribute used for different groups of messages from the same provider" 86 | }, 87 | "layout": { 88 | "type": "string", 89 | "description": "Describes how content should be displayed.", 90 | "enum": [ 91 | "chiclet_open_url" 92 | ] 93 | }, 94 | "bucket_id": { 95 | "type": "string", 96 | "description": "A bucket identifier for the addon. This is used in order to anonymize telemetry for history-sensitive targeting." 97 | }, 98 | "notification_text": { 99 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 100 | "description": "The text in the small blue chicklet that appears in the URL bar. This can be a reference to a localized string in Firefox or just a plain string." 101 | }, 102 | "active_color": { 103 | "type": "string", 104 | "description": "Background color of the button" 105 | }, 106 | "action": { 107 | "type": "object", 108 | "properties": { 109 | "url": { 110 | "description": "The page to open when the button is clicked.", 111 | "type": "string", 112 | "format": "moz-url-format" 113 | }, 114 | "where": { 115 | "description": "Should it open in a new tab or the current tab", 116 | "type": "string", 117 | "enum": [ 118 | "current", 119 | "tabshifted" 120 | ] 121 | } 122 | }, 123 | "additionalProperties": true, 124 | "required": [ 125 | "url", 126 | "where" 127 | ] 128 | } 129 | }, 130 | "additionalProperties": true, 131 | "required": [ 132 | "layout", 133 | "category", 134 | "bucket_id", 135 | "notification_text", 136 | "action" 137 | ] 138 | }, 139 | "template": { 140 | "type": "string", 141 | "const": "cfr_urlbar_chiclet" 142 | } 143 | }, 144 | "required": [ 145 | "targeting", 146 | "trigger" 147 | ] 148 | }, 149 | "ExtensionDoorhanger": { 150 | "$schema": "https://json-schema.org/draft/2019-09/schema", 151 | "$id": "file:///ExtensionDoorhanger.schema.json", 152 | "title": "ExtensionDoorhanger", 153 | "description": "A template with a heading, addon icon, title and description. No markup allowed.", 154 | "allOf": [ 155 | { 156 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 157 | } 158 | ], 159 | "type": "object", 160 | "properties": { 161 | "content": { 162 | "type": "object", 163 | "properties": { 164 | "category": { 165 | "type": "string", 166 | "description": "Attribute used for different groups of messages from the same provider" 167 | }, 168 | "layout": { 169 | "type": "string", 170 | "description": "Attribute used for different groups of messages from the same provider", 171 | "enum": [ 172 | "short_message", 173 | "icon_and_message", 174 | "addon_recommendation" 175 | ] 176 | }, 177 | "anchor_id": { 178 | "type": "string", 179 | "description": "A DOM element ID that the pop-over will be anchored." 180 | }, 181 | "alt_anchor_id": { 182 | "type": "string", 183 | "description": "An alternate DOM element ID that the pop-over will be anchored." 184 | }, 185 | "bucket_id": { 186 | "type": "string", 187 | "description": "A bucket identifier for the addon. This is used in order to anonymize telemetry for history-sensitive targeting." 188 | }, 189 | "skip_address_bar_notifier": { 190 | "type": "boolean", 191 | "description": "Skip the 'Recommend' notifier and show directly." 192 | }, 193 | "persistent_doorhanger": { 194 | "type": "boolean", 195 | "description": "Prevent the doorhanger from being dismissed if user interacts with the page or switches between applications." 196 | }, 197 | "show_in_private_browsing": { 198 | "type": "boolean", 199 | "description": "Whether to allow the message to be shown in private browsing mode. Defaults to false." 200 | }, 201 | "notification_text": { 202 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 203 | "description": "The text in the small blue chicklet that appears in the URL bar. This can be a reference to a localized string in Firefox or just a plain string." 204 | }, 205 | "info_icon": { 206 | "type": "object", 207 | "description": "The small icon displayed in the top right corner of the pop-over. Should be 19x19px, svg or png. Defaults to a small question mark.", 208 | "properties": { 209 | "label": { 210 | "oneOf": [ 211 | { 212 | "type": "object", 213 | "properties": { 214 | "attributes": { 215 | "type": "object", 216 | "properties": { 217 | "tooltiptext": { 218 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 219 | "description": "Text for button tooltip used to provide information about the doorhanger." 220 | } 221 | }, 222 | "required": [ 223 | "tooltiptext" 224 | ] 225 | } 226 | }, 227 | "required": [ 228 | "attributes" 229 | ] 230 | }, 231 | { 232 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizedText" 233 | } 234 | ] 235 | }, 236 | "sumo_path": { 237 | "type": "string", 238 | "description": "Last part of the path in the URL to the support page with the information about the doorhanger.", 239 | "examples": [ 240 | "extensionpromotions", 241 | "extensionrecommendations" 242 | ] 243 | } 244 | } 245 | }, 246 | "learn_more": { 247 | "type": "string", 248 | "description": "Last part of the path in the SUMO URL to the support page with the information about the doorhanger.", 249 | "examples": [ 250 | "extensionpromotions", 251 | "extensionrecommendations" 252 | ] 253 | }, 254 | "heading_text": { 255 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 256 | "description": "The larger heading text displayed in the pop-over. This can be a reference to a localized string in Firefox or just a plain string." 257 | }, 258 | "icon": { 259 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/linkUrl", 260 | "description": "The icon displayed in the pop-over. Should be 32x32px or 64x64px and png/svg." 261 | }, 262 | "icon_dark_theme": { 263 | "type": "string", 264 | "description": "Pop-over icon, dark theme variant. Should be 32x32px or 64x64px and png/svg." 265 | }, 266 | "icon_class": { 267 | "type": "string", 268 | "description": "CSS class of the pop-over icon." 269 | }, 270 | "addon": { 271 | "description": "Addon information including AMO URL.", 272 | "type": "object", 273 | "properties": { 274 | "id": { 275 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText", 276 | "description": "Unique addon ID" 277 | }, 278 | "title": { 279 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText", 280 | "description": "Addon name" 281 | }, 282 | "author": { 283 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText", 284 | "description": "Addon author" 285 | }, 286 | "icon": { 287 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/linkUrl", 288 | "description": "The icon displayed in the pop-over. Should be 64x64px and png/svg." 289 | }, 290 | "rating": { 291 | "type": "string", 292 | "description": "Star rating" 293 | }, 294 | "users": { 295 | "type": "string", 296 | "description": "Installed users" 297 | }, 298 | "amo_url": { 299 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/linkUrl", 300 | "description": "Link that offers more information related to the addon." 301 | } 302 | }, 303 | "required": [ 304 | "title", 305 | "author", 306 | "icon", 307 | "amo_url" 308 | ] 309 | }, 310 | "text": { 311 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 312 | "description": "The body text displayed in the pop-over. This can be a reference to a localized string in Firefox or just a plain string." 313 | }, 314 | "descriptionDetails": { 315 | "description": "Additional information and steps on how to use", 316 | "type": "object", 317 | "properties": { 318 | "steps": { 319 | "description": "Array of string_ids", 320 | "type": "array", 321 | "items": { 322 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizedText", 323 | "description": "Id of string to localized addon description" 324 | } 325 | } 326 | }, 327 | "required": [ 328 | "steps" 329 | ] 330 | }, 331 | "buttons": { 332 | "description": "The label and functionality for the buttons in the pop-over.", 333 | "type": "object", 334 | "properties": { 335 | "primary": { 336 | "type": "object", 337 | "properties": { 338 | "label": { 339 | "type": "object", 340 | "oneOf": [ 341 | { 342 | "properties": { 343 | "value": { 344 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText", 345 | "description": "Button label override used when a localized version is not available." 346 | }, 347 | "attributes": { 348 | "type": "object", 349 | "properties": { 350 | "accesskey": { 351 | "type": "string", 352 | "description": "A single character to be used as a shortcut key for the secondary button. This should be one of the characters that appears in the button label." 353 | } 354 | }, 355 | "required": [ 356 | "accesskey" 357 | ], 358 | "description": "Button attributes." 359 | } 360 | }, 361 | "required": [ 362 | "value", 363 | "attributes" 364 | ] 365 | }, 366 | { 367 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizedText" 368 | } 369 | ], 370 | "description": "Id of localized string or message override." 371 | }, 372 | "action": { 373 | "type": "object", 374 | "properties": { 375 | "type": { 376 | "type": "string", 377 | "description": "Action dispatched by the button." 378 | }, 379 | "data": { 380 | "properties": { 381 | "url": { 382 | "type": "string", 383 | "$comment": "This is dynamically generated from the addon.id. See CFRPageActions.sys.mjs", 384 | "description": "URL used in combination with the primary action dispatched." 385 | } 386 | } 387 | } 388 | } 389 | } 390 | } 391 | }, 392 | "secondary": { 393 | "type": "array", 394 | "items": { 395 | "type": "object", 396 | "properties": { 397 | "label": { 398 | "type": "object", 399 | "oneOf": [ 400 | { 401 | "properties": { 402 | "value": { 403 | "allOf": [ 404 | { 405 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText" 406 | }, 407 | { 408 | "description": "Button label override used when a localized version is not available." 409 | } 410 | ] 411 | }, 412 | "attributes": { 413 | "type": "object", 414 | "properties": { 415 | "accesskey": { 416 | "type": "string", 417 | "description": "A single character to be used as a shortcut key for the secondary button. This should be one of the characters that appears in the button label." 418 | } 419 | }, 420 | "required": [ 421 | "accesskey" 422 | ], 423 | "description": "Button attributes." 424 | } 425 | }, 426 | "required": [ 427 | "value", 428 | "attributes" 429 | ] 430 | }, 431 | { 432 | "properties": { 433 | "string_id": { 434 | "allOf": [ 435 | { 436 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/plainText" 437 | }, 438 | { 439 | "description": "Id of localized string for button" 440 | } 441 | ] 442 | } 443 | }, 444 | "required": [ 445 | "string_id" 446 | ] 447 | } 448 | ], 449 | "description": "Id of localized string or message override." 450 | }, 451 | "action": { 452 | "type": "object", 453 | "properties": { 454 | "type": { 455 | "type": "string", 456 | "description": "Action dispatched by the button." 457 | }, 458 | "data": { 459 | "properties": { 460 | "url": { 461 | "allOf": [ 462 | { 463 | "$ref": "file:///ExtensionDoorhanger.schema.json#/$defs/linkUrl" 464 | }, 465 | { 466 | "description": "URL used in combination with the primary action dispatched." 467 | } 468 | ] 469 | } 470 | } 471 | } 472 | } 473 | } 474 | } 475 | } 476 | } 477 | } 478 | } 479 | }, 480 | "additionalProperties": true, 481 | "required": [ 482 | "layout", 483 | "bucket_id", 484 | "heading_text", 485 | "text", 486 | "buttons" 487 | ], 488 | "if": { 489 | "properties": { 490 | "skip_address_bar_notifier": { 491 | "anyOf": [ 492 | { 493 | "const": "false" 494 | }, 495 | { 496 | "const": null 497 | } 498 | ] 499 | } 500 | } 501 | }, 502 | "then": { 503 | "required": [ 504 | "category", 505 | "notification_text" 506 | ] 507 | } 508 | }, 509 | "template": { 510 | "type": "string", 511 | "enum": [ 512 | "cfr_doorhanger", 513 | "milestone_message" 514 | ] 515 | } 516 | }, 517 | "additionalProperties": true, 518 | "required": [ 519 | "targeting", 520 | "trigger" 521 | ], 522 | "$defs": { 523 | "plainText": { 524 | "description": "Plain text (no HTML allowed)", 525 | "type": "string" 526 | }, 527 | "linkUrl": { 528 | "description": "Target for links or buttons", 529 | "type": "string", 530 | "format": "uri" 531 | } 532 | } 533 | }, 534 | "InfoBar": { 535 | "$schema": "https://json-schema.org/draft/2019-09/schema", 536 | "$id": "file:///InfoBar.schema.json", 537 | "title": "InfoBar", 538 | "description": "A template with an image, test and buttons.", 539 | "allOf": [ 540 | { 541 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 542 | } 543 | ], 544 | "type": "object", 545 | "properties": { 546 | "content": { 547 | "type": "object", 548 | "properties": { 549 | "type": { 550 | "type": "string", 551 | "description": "Should the message be global (persisted across tabs) or local (disappear when switching to a different tab).", 552 | "enum": [ 553 | "global", 554 | "tab" 555 | ] 556 | }, 557 | "text": { 558 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 559 | "description": "The text show in the notification box." 560 | }, 561 | "priority": { 562 | "description": "Infobar priority level https://searchfox.org/mozilla-central/rev/3aef835f6cb12e607154d56d68726767172571e4/toolkit/content/widgets/notificationbox.js#387", 563 | "type": "number", 564 | "minumum": 0, 565 | "exclusiveMaximum": 10 566 | }, 567 | "buttons": { 568 | "type": "array", 569 | "items": { 570 | "type": "object", 571 | "properties": { 572 | "label": { 573 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 574 | "description": "The text label of the button." 575 | }, 576 | "primary": { 577 | "type": "boolean", 578 | "description": "Is this the primary button?" 579 | }, 580 | "accessKey": { 581 | "type": "string", 582 | "description": "Keyboard shortcut letter." 583 | }, 584 | "action": { 585 | "type": "object", 586 | "properties": { 587 | "type": { 588 | "type": "string", 589 | "description": "Action dispatched by the button." 590 | }, 591 | "data": { 592 | "type": "object" 593 | } 594 | }, 595 | "required": [ 596 | "type" 597 | ], 598 | "additionalProperties": true 599 | }, 600 | "supportPage": { 601 | "type": "string", 602 | "description": "A page title on SUMO to link to" 603 | } 604 | }, 605 | "required": [ 606 | "label", 607 | "action" 608 | ], 609 | "additionalProperties": true 610 | } 611 | } 612 | }, 613 | "additionalProperties": true, 614 | "required": [ 615 | "text", 616 | "buttons" 617 | ] 618 | }, 619 | "template": { 620 | "type": "string", 621 | "const": "infobar" 622 | } 623 | }, 624 | "additionalProperties": true, 625 | "required": [ 626 | "targeting", 627 | "trigger" 628 | ], 629 | "$defs": { 630 | "plainText": { 631 | "description": "Plain text (no HTML allowed)", 632 | "type": "string" 633 | }, 634 | "linkUrl": { 635 | "description": "Target for links or buttons", 636 | "type": "string", 637 | "format": "uri" 638 | } 639 | } 640 | }, 641 | "MenuMessage": { 642 | "$schema": "https://json-schema.org/draft/2019-09/schema", 643 | "$id": "file:///MenuMessage.schema.json", 644 | "title": "MenuMessage", 645 | "description": "A template for messages that appear within our menus.", 646 | "allOf": [ 647 | { 648 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 649 | } 650 | ], 651 | "type": "object", 652 | "properties": { 653 | "content": { 654 | "type": "object", 655 | "properties": { 656 | "messageType": { 657 | "type": "string", 658 | "description": "The subtype of the message.", 659 | "enum": [ 660 | "fxa_cta" 661 | ] 662 | }, 663 | "primaryText": { 664 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 665 | "description": "The primary text for the message, which offers the value proposition to the user." 666 | }, 667 | "secondaryText": { 668 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 669 | "description": "The second text for the message, which offers more detail on the value proposition to the user." 670 | }, 671 | "closeAction": { 672 | "type": "object", 673 | "properties": { 674 | "type": { 675 | "type": "string", 676 | "description": "Action dispatched by the button." 677 | }, 678 | "data": { 679 | "type": "object" 680 | } 681 | }, 682 | "required": [ 683 | "type" 684 | ], 685 | "additionalProperties": true, 686 | "description": "The action to take upon clicking the close button." 687 | }, 688 | "primaryAction": { 689 | "type": "object", 690 | "properties": { 691 | "type": { 692 | "type": "string", 693 | "description": "Action dispatched by the button." 694 | }, 695 | "data": { 696 | "type": "object" 697 | } 698 | }, 699 | "required": [ 700 | "type" 701 | ], 702 | "additionalProperties": true, 703 | "description": "The action to take upon clicking the primary action button." 704 | }, 705 | "primaryActionText": { 706 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 707 | "description": "The label for the primary action." 708 | }, 709 | "imageURL": { 710 | "type": "string", 711 | "description": "URL for image to use with the content." 712 | }, 713 | "imageVerticalOffset": { 714 | "type": "number", 715 | "description": "The margin-block-start value to apply to the image in pixels." 716 | } 717 | } 718 | }, 719 | "template": { 720 | "type": "string", 721 | "const": "menu_message" 722 | }, 723 | "testingTriggerContext": { 724 | "type": "string", 725 | "enum": [ 726 | "app_menu", 727 | "pxi_menu" 728 | ] 729 | } 730 | }, 731 | "additionalProperties": true 732 | }, 733 | "NewtabPromoMessage": { 734 | "$schema": "https://json-schema.org/draft/2019-09/schema", 735 | "$id": "file:///NewtabPromoMessage.schema.json", 736 | "title": "PBNewtabPromoMessage", 737 | "description": "Message shown on the private browsing newtab page.", 738 | "allOf": [ 739 | { 740 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 741 | } 742 | ], 743 | "type": "object", 744 | "properties": { 745 | "content": { 746 | "type": "object", 747 | "properties": { 748 | "hideDefault": { 749 | "type": "boolean", 750 | "description": "Should we hide the default promo after the experiment promo is dismissed." 751 | }, 752 | "infoEnabled": { 753 | "type": "boolean", 754 | "description": "Should we show the info section." 755 | }, 756 | "infoIcon": { 757 | "type": "string", 758 | "description": "Icon shown in the left side of the info section. Default is the private browsing icon." 759 | }, 760 | "infoTitle": { 761 | "type": "string", 762 | "description": "Is the title in the info section enabled." 763 | }, 764 | "infoTitleEnabled": { 765 | "type": "boolean", 766 | "description": "Is the title in the info section enabled." 767 | }, 768 | "infoBody": { 769 | "type": "string", 770 | "description": "Text content in the info section." 771 | }, 772 | "infoLinkText": { 773 | "type": "string", 774 | "description": "Text for the link in the info section." 775 | }, 776 | "infoLinkUrl": { 777 | "type": "string", 778 | "description": "URL for the info section link.", 779 | "format": "moz-url-format" 780 | }, 781 | "promoEnabled": { 782 | "type": "boolean", 783 | "description": "Should we show the promo section." 784 | }, 785 | "promoType": { 786 | "type": "string", 787 | "description": "Promo type used to determine if promo should show to a given user", 788 | "enum": [ 789 | "FOCUS", 790 | "VPN", 791 | "PIN", 792 | "COOKIE_BANNERS", 793 | "OTHER" 794 | ] 795 | }, 796 | "promoSectionStyle": { 797 | "type": "string", 798 | "description": "Sets the position of the promo section. Possible values are: top, below-search, bottom. Default bottom.", 799 | "enum": [ 800 | "top", 801 | "below-search", 802 | "bottom" 803 | ] 804 | }, 805 | "promoTitle": { 806 | "type": "string", 807 | "description": "The text content of the promo section." 808 | }, 809 | "promoTitleEnabled": { 810 | "type": "boolean", 811 | "description": "Should we show text content in the promo section." 812 | }, 813 | "promoLinkText": { 814 | "type": "string", 815 | "description": "The text of the link in the promo box." 816 | }, 817 | "promoHeader": { 818 | "type": "string", 819 | "description": "The title of the promo section." 820 | }, 821 | "promoButton": { 822 | "type": "object", 823 | "properties": { 824 | "action": { 825 | "type": "object", 826 | "properties": { 827 | "type": { 828 | "type": "string", 829 | "description": "Action dispatched by the button." 830 | }, 831 | "data": { 832 | "type": "object" 833 | } 834 | }, 835 | "required": [ 836 | "type" 837 | ], 838 | "additionalProperties": true 839 | } 840 | }, 841 | "required": [ 842 | "action" 843 | ] 844 | }, 845 | "promoLinkType": { 846 | "type": "string", 847 | "description": "Type of promo link type. Possible values: link, button. Default is link.", 848 | "enum": [ 849 | "link", 850 | "button" 851 | ] 852 | }, 853 | "promoImageLarge": { 854 | "type": "string", 855 | "description": "URL for image used on the left side of the promo box, larger, showcases some feature. Default off.", 856 | "format": "uri" 857 | }, 858 | "promoImageSmall": { 859 | "type": "string", 860 | "description": "URL for image used on the right side of the promo box, smaller, usually a logo. Default off.", 861 | "format": "uri" 862 | } 863 | }, 864 | "additionalProperties": true, 865 | "allOf": [ 866 | { 867 | "if": { 868 | "properties": { 869 | "promoEnabled": { 870 | "const": true 871 | } 872 | }, 873 | "required": [ 874 | "promoEnabled" 875 | ] 876 | }, 877 | "then": { 878 | "required": [ 879 | "promoButton" 880 | ] 881 | } 882 | }, 883 | { 884 | "if": { 885 | "properties": { 886 | "infoEnabled": { 887 | "const": true 888 | } 889 | }, 890 | "required": [ 891 | "infoEnabled" 892 | ] 893 | }, 894 | "then": { 895 | "required": [ 896 | "infoLinkText" 897 | ], 898 | "if": { 899 | "properties": { 900 | "infoTitleEnabled": { 901 | "const": true 902 | } 903 | }, 904 | "required": [ 905 | "infoTitleEnabled" 906 | ] 907 | }, 908 | "then": { 909 | "required": [ 910 | "infoTitle" 911 | ] 912 | } 913 | } 914 | } 915 | ] 916 | }, 917 | "template": { 918 | "type": "string", 919 | "const": "pb_newtab" 920 | } 921 | }, 922 | "additionalProperties": true, 923 | "required": [ 924 | "targeting" 925 | ] 926 | }, 927 | "NewtabMessage": { 928 | "$schema": "https://json-schema.org/draft/2019-09/schema", 929 | "$id": "file:///NewtabMessage.schema.json", 930 | "title": "NewtabMessage", 931 | "description": "A template for messages that are rendered within newtab", 932 | "allOf": [ 933 | { 934 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 935 | } 936 | ], 937 | "type": "object", 938 | "properties": { 939 | "content": { 940 | "type": "object", 941 | "properties": { 942 | "messageType": { 943 | "type": "string", 944 | "description": "The subtype of the message." 945 | } 946 | }, 947 | "additionalProperties": true, 948 | "required": [ 949 | "messageType" 950 | ] 951 | }, 952 | "template": { 953 | "type": "string", 954 | "const": "newtab_message" 955 | } 956 | }, 957 | "additionalProperties": true, 958 | "required": [ 959 | "targeting" 960 | ] 961 | }, 962 | "Spotlight": { 963 | "$schema": "https://json-schema.org/draft/2019-09/schema", 964 | "$id": "file:///Spotlight.schema.json", 965 | "title": "Spotlight", 966 | "description": "A template with an image, title, content and two buttons.", 967 | "allOf": [ 968 | { 969 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 970 | } 971 | ], 972 | "type": "object", 973 | "properties": { 974 | "content": { 975 | "type": "object", 976 | "properties": { 977 | "template": { 978 | "type": "string", 979 | "description": "Specify the layout template for the Spotlight", 980 | "const": "multistage" 981 | }, 982 | "backdrop": { 983 | "type": "string", 984 | "description": "Background css behind modal content" 985 | }, 986 | "logo": { 987 | "type": "object", 988 | "properties": { 989 | "imageURL": { 990 | "type": "string", 991 | "description": "URL for image to use with the content" 992 | }, 993 | "imageId": { 994 | "type": "string", 995 | "description": "The ID for a remotely hosted image" 996 | }, 997 | "size": { 998 | "type": "string", 999 | "description": "The logo size." 1000 | } 1001 | }, 1002 | "additionalProperties": true 1003 | }, 1004 | "screens": { 1005 | "type": "array", 1006 | "description": "Collection of individual screen content" 1007 | }, 1008 | "transitions": { 1009 | "type": "boolean", 1010 | "description": "Show transitions within and between screens" 1011 | }, 1012 | "disableEscClose": { 1013 | "type": "boolean", 1014 | "description": "Don't allow the message to be dismissed using the ESC key - for limited use in Spotlight modals only when the message content clearly informs the user that a decision is required to proceed" 1015 | }, 1016 | "disableHistoryUpdates": { 1017 | "type": "boolean", 1018 | "description": "Don't alter the browser session's history stack - used with messaging surfaces like Feature Callouts" 1019 | }, 1020 | "startScreen": { 1021 | "type": "integer", 1022 | "description": "Index of first screen to show from message, defaulting to 0" 1023 | }, 1024 | "no-rdm": { 1025 | "type": "boolean", 1026 | "description": "If true, prevents the spotlight from entering responsive design mode at widths less than 800px" 1027 | } 1028 | }, 1029 | "additionalProperties": true 1030 | }, 1031 | "template": { 1032 | "type": "string", 1033 | "description": "Specify whether the surface is shown as a Spotlight modal or an in-surface Feature Callout dialog", 1034 | "enum": [ 1035 | "spotlight", 1036 | "feature_callout" 1037 | ] 1038 | } 1039 | }, 1040 | "additionalProperties": true, 1041 | "required": [ 1042 | "targeting" 1043 | ] 1044 | }, 1045 | "ToastNotification": { 1046 | "$schema": "https://json-schema.org/draft/2019-09/schema", 1047 | "$id": "file:///ToastNotification.schema.json", 1048 | "title": "ToastNotification", 1049 | "description": "A template for toast notifications displayed by the Alert service.", 1050 | "allOf": [ 1051 | { 1052 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 1053 | } 1054 | ], 1055 | "type": "object", 1056 | "properties": { 1057 | "content": { 1058 | "type": "object", 1059 | "properties": { 1060 | "title": { 1061 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 1062 | "description": "Id of localized string or message override of toast notification title" 1063 | }, 1064 | "body": { 1065 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 1066 | "description": "Id of localized string or message override of toast notification body" 1067 | }, 1068 | "icon_url": { 1069 | "description": "The URL of the image used as an icon of the toast notification.", 1070 | "type": "string", 1071 | "format": "moz-url-format" 1072 | }, 1073 | "image_url": { 1074 | "description": "The URL of an image to be displayed as part of the notification.", 1075 | "type": "string", 1076 | "format": "moz-url-format" 1077 | }, 1078 | "launch_url": { 1079 | "description": "The URL to launch when the notification or an action button is clicked.", 1080 | "type": "string", 1081 | "format": "moz-url-format" 1082 | }, 1083 | "launch_action": { 1084 | "type": "object", 1085 | "properties": { 1086 | "type": { 1087 | "type": "string", 1088 | "description": "The launch action to be performed when Firefox is launched." 1089 | }, 1090 | "data": { 1091 | "type": "object" 1092 | } 1093 | }, 1094 | "required": [ 1095 | "type" 1096 | ], 1097 | "additionalProperties": true 1098 | }, 1099 | "requireInteraction": { 1100 | "type": "boolean", 1101 | "description": "Whether the toast notification should remain active until the user clicks or dismisses it, rather than closing automatically." 1102 | }, 1103 | "tag": { 1104 | "type": "string", 1105 | "description": "An identifying tag for the toast notification." 1106 | }, 1107 | "data": { 1108 | "type": "object", 1109 | "description": "Arbitrary data associated with the toast notification." 1110 | }, 1111 | "actions": { 1112 | "type": "array", 1113 | "items": { 1114 | "type": "object", 1115 | "properties": { 1116 | "title": { 1117 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizableText", 1118 | "description": "The action text to be shown to the user." 1119 | }, 1120 | "action": { 1121 | "type": "string", 1122 | "description": "Opaque identifer that identifies action." 1123 | }, 1124 | "iconURL": { 1125 | "type": "string", 1126 | "format": "uri", 1127 | "description": "URL of an icon to display with the action." 1128 | }, 1129 | "windowsSystemActivationType": { 1130 | "type": "boolean", 1131 | "description": "Whether to have Windows process the given `action`." 1132 | }, 1133 | "launch_action": { 1134 | "type": "object", 1135 | "properties": { 1136 | "type": { 1137 | "type": "string", 1138 | "description": "The launch action to be performed when Firefox is launched." 1139 | }, 1140 | "data": { 1141 | "type": "object" 1142 | } 1143 | }, 1144 | "required": [ 1145 | "type" 1146 | ], 1147 | "additionalProperties": true 1148 | } 1149 | }, 1150 | "required": [ 1151 | "action", 1152 | "title" 1153 | ], 1154 | "additionalProperties": true 1155 | } 1156 | } 1157 | }, 1158 | "additionalProperties": true, 1159 | "required": [ 1160 | "title", 1161 | "body" 1162 | ] 1163 | }, 1164 | "template": { 1165 | "type": "string", 1166 | "const": "toast_notification" 1167 | } 1168 | }, 1169 | "required": [ 1170 | "content", 1171 | "targeting", 1172 | "template", 1173 | "trigger" 1174 | ], 1175 | "additionalProperties": true 1176 | }, 1177 | "ToolbarBadgeMessage": { 1178 | "$schema": "https://json-schema.org/draft/2019-09/schema", 1179 | "$id": "file:///ToolbarBadgeMessage.schema.json", 1180 | "title": "ToolbarBadgeMessage", 1181 | "description": "A template that specifies to which element in the browser toolbar to add a notification.", 1182 | "allOf": [ 1183 | { 1184 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 1185 | } 1186 | ], 1187 | "type": "object", 1188 | "properties": { 1189 | "content": { 1190 | "type": "object", 1191 | "properties": { 1192 | "target": { 1193 | "type": "string" 1194 | }, 1195 | "action": { 1196 | "type": "object", 1197 | "properties": { 1198 | "id": { 1199 | "type": "string" 1200 | } 1201 | }, 1202 | "additionalProperties": true, 1203 | "required": [ 1204 | "id" 1205 | ], 1206 | "description": "Optional action to take in addition to showing the notification" 1207 | }, 1208 | "delay": { 1209 | "type": "number", 1210 | "description": "Optional delay in ms after which to show the notification" 1211 | }, 1212 | "badgeDescription": { 1213 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizedText", 1214 | "description": "This is used in combination with the badged button to offer a text based alternative to the visual badging. Example 'New Feature: What's New'" 1215 | } 1216 | }, 1217 | "additionalProperties": true, 1218 | "required": [ 1219 | "target" 1220 | ] 1221 | }, 1222 | "template": { 1223 | "type": "string", 1224 | "const": "toolbar_badge" 1225 | } 1226 | }, 1227 | "additionalProperties": true, 1228 | "required": [ 1229 | "targeting" 1230 | ] 1231 | }, 1232 | "UpdateAction": { 1233 | "$schema": "https://json-schema.org/draft/2019-09/schema", 1234 | "$id": "file:///UpdateAction.schema.json", 1235 | "title": "UpdateActionMessage", 1236 | "description": "A template for messages that execute predetermined actions.", 1237 | "allOf": [ 1238 | { 1239 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 1240 | } 1241 | ], 1242 | "type": "object", 1243 | "properties": { 1244 | "content": { 1245 | "type": "object", 1246 | "properties": { 1247 | "action": { 1248 | "type": "object", 1249 | "properties": { 1250 | "id": { 1251 | "type": "string" 1252 | }, 1253 | "data": { 1254 | "type": "object", 1255 | "description": "Additional data provided as argument when executing the action", 1256 | "properties": { 1257 | "url": { 1258 | "type": "string", 1259 | "description": "URL data to be used as argument to the action" 1260 | }, 1261 | "expireDelta": { 1262 | "type": "number", 1263 | "description": "Expiration timestamp to be used as argument to the action" 1264 | } 1265 | } 1266 | } 1267 | }, 1268 | "additionalProperties": true, 1269 | "description": "Optional action to take in addition to showing the notification", 1270 | "required": [ 1271 | "id", 1272 | "data" 1273 | ] 1274 | } 1275 | }, 1276 | "additionalProperties": true, 1277 | "required": [ 1278 | "action" 1279 | ] 1280 | }, 1281 | "template": { 1282 | "type": "string", 1283 | "const": "update_action" 1284 | } 1285 | }, 1286 | "required": [ 1287 | "targeting" 1288 | ] 1289 | }, 1290 | "Message": { 1291 | "type": "object", 1292 | "properties": { 1293 | "id": { 1294 | "type": "string", 1295 | "description": "The message identifier" 1296 | }, 1297 | "groups": { 1298 | "description": "Array of preferences used to control `enabled` status of the group. If any is `false` the group is disabled.", 1299 | "type": "array", 1300 | "items": { 1301 | "type": "string", 1302 | "description": "Preference name" 1303 | } 1304 | }, 1305 | "template": { 1306 | "type": "string", 1307 | "description": "Which messaging template this message is using.", 1308 | "enum": [ 1309 | "bookmarks_bar_button", 1310 | "cfr_urlbar_chiclet", 1311 | "cfr_doorhanger", 1312 | "milestone_message", 1313 | "infobar", 1314 | "menu_message", 1315 | "pb_newtab", 1316 | "newtab_message", 1317 | "spotlight", 1318 | "feature_callout", 1319 | "toast_notification", 1320 | "toolbar_badge", 1321 | "update_action" 1322 | ] 1323 | }, 1324 | "frequency": { 1325 | "type": "object", 1326 | "description": "An object containing frequency cap information for a message.", 1327 | "properties": { 1328 | "lifetime": { 1329 | "type": "integer", 1330 | "description": "The maximum lifetime impressions for a message.", 1331 | "minimum": 1, 1332 | "maximum": 100 1333 | }, 1334 | "custom": { 1335 | "type": "array", 1336 | "description": "An array of custom frequency cap definitions.", 1337 | "items": { 1338 | "description": "A frequency cap definition containing time and max impression information", 1339 | "type": "object", 1340 | "properties": { 1341 | "period": { 1342 | "type": "integer", 1343 | "description": "Period of time in milliseconds (e.g. 86400000 for one day)" 1344 | }, 1345 | "cap": { 1346 | "type": "integer", 1347 | "description": "The maximum impressions for the message within the defined period.", 1348 | "minimum": 1, 1349 | "maximum": 100 1350 | } 1351 | }, 1352 | "required": [ 1353 | "period", 1354 | "cap" 1355 | ] 1356 | } 1357 | } 1358 | } 1359 | }, 1360 | "priority": { 1361 | "description": "The priority of the message. If there are two competing messages to show, the one with the highest priority will be shown", 1362 | "type": "integer" 1363 | }, 1364 | "order": { 1365 | "description": "The order in which messages should be shown. Messages will be shown in increasing order.", 1366 | "type": "integer" 1367 | }, 1368 | "targeting": { 1369 | "description": "A JEXL expression representing targeting information", 1370 | "type": "string" 1371 | }, 1372 | "trigger": { 1373 | "description": "An action to trigger potentially showing the message", 1374 | "type": "object", 1375 | "properties": { 1376 | "id": { 1377 | "type": "string", 1378 | "description": "A string identifying the trigger action" 1379 | }, 1380 | "params": { 1381 | "type": "array", 1382 | "description": "An optional array of string parameters for the trigger action", 1383 | "items": { 1384 | "anyOf": [ 1385 | { 1386 | "type": "integer" 1387 | }, 1388 | { 1389 | "type": "string" 1390 | } 1391 | ] 1392 | } 1393 | } 1394 | }, 1395 | "required": [ 1396 | "id" 1397 | ] 1398 | }, 1399 | "provider": { 1400 | "description": "An identifier for the provider of this message, such as \"cfr\" or \"preview\".", 1401 | "type": "string" 1402 | } 1403 | }, 1404 | "additionalProperties": true, 1405 | "dependentRequired": { 1406 | "content": [ 1407 | "id", 1408 | "template" 1409 | ], 1410 | "template": [ 1411 | "id", 1412 | "content" 1413 | ] 1414 | } 1415 | }, 1416 | "localizedText": { 1417 | "type": "object", 1418 | "properties": { 1419 | "string_id": { 1420 | "description": "Id of localized string to be rendered.", 1421 | "type": "string" 1422 | } 1423 | }, 1424 | "required": [ 1425 | "string_id" 1426 | ] 1427 | }, 1428 | "localizableText": { 1429 | "description": "Either a raw string or an object containing the string_id of the localized text", 1430 | "oneOf": [ 1431 | { 1432 | "type": "string", 1433 | "description": "The string to be rendered." 1434 | }, 1435 | { 1436 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/localizedText" 1437 | } 1438 | ] 1439 | }, 1440 | "TemplatedMessage": { 1441 | "description": "An FxMS message of one of a variety of types.", 1442 | "type": "object", 1443 | "allOf": [ 1444 | { 1445 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message" 1446 | }, 1447 | { 1448 | "if": { 1449 | "type": "object", 1450 | "properties": { 1451 | "template": { 1452 | "type": "string", 1453 | "enum": [ 1454 | "bookmarks_bar_button" 1455 | ] 1456 | } 1457 | }, 1458 | "required": [ 1459 | "template" 1460 | ] 1461 | }, 1462 | "then": { 1463 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/BookmarksBarButton" 1464 | } 1465 | }, 1466 | { 1467 | "if": { 1468 | "type": "object", 1469 | "properties": { 1470 | "template": { 1471 | "type": "string", 1472 | "enum": [ 1473 | "cfr_urlbar_chiclet" 1474 | ] 1475 | } 1476 | }, 1477 | "required": [ 1478 | "template" 1479 | ] 1480 | }, 1481 | "then": { 1482 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/CFRUrlbarChiclet" 1483 | } 1484 | }, 1485 | { 1486 | "if": { 1487 | "type": "object", 1488 | "properties": { 1489 | "template": { 1490 | "type": "string", 1491 | "enum": [ 1492 | "cfr_doorhanger", 1493 | "milestone_message" 1494 | ] 1495 | } 1496 | }, 1497 | "required": [ 1498 | "template" 1499 | ] 1500 | }, 1501 | "then": { 1502 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/ExtensionDoorhanger" 1503 | } 1504 | }, 1505 | { 1506 | "if": { 1507 | "type": "object", 1508 | "properties": { 1509 | "template": { 1510 | "type": "string", 1511 | "enum": [ 1512 | "infobar" 1513 | ] 1514 | } 1515 | }, 1516 | "required": [ 1517 | "template" 1518 | ] 1519 | }, 1520 | "then": { 1521 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/InfoBar" 1522 | } 1523 | }, 1524 | { 1525 | "if": { 1526 | "type": "object", 1527 | "properties": { 1528 | "template": { 1529 | "type": "string", 1530 | "enum": [ 1531 | "menu_message" 1532 | ] 1533 | } 1534 | }, 1535 | "required": [ 1536 | "template" 1537 | ] 1538 | }, 1539 | "then": { 1540 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/MenuMessage" 1541 | } 1542 | }, 1543 | { 1544 | "if": { 1545 | "type": "object", 1546 | "properties": { 1547 | "template": { 1548 | "type": "string", 1549 | "enum": [ 1550 | "pb_newtab" 1551 | ] 1552 | } 1553 | }, 1554 | "required": [ 1555 | "template" 1556 | ] 1557 | }, 1558 | "then": { 1559 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/NewtabPromoMessage" 1560 | } 1561 | }, 1562 | { 1563 | "if": { 1564 | "type": "object", 1565 | "properties": { 1566 | "template": { 1567 | "type": "string", 1568 | "enum": [ 1569 | "newtab_message" 1570 | ] 1571 | } 1572 | }, 1573 | "required": [ 1574 | "template" 1575 | ] 1576 | }, 1577 | "then": { 1578 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/NewtabMessage" 1579 | } 1580 | }, 1581 | { 1582 | "if": { 1583 | "type": "object", 1584 | "properties": { 1585 | "template": { 1586 | "type": "string", 1587 | "enum": [ 1588 | "spotlight", 1589 | "feature_callout" 1590 | ] 1591 | } 1592 | }, 1593 | "required": [ 1594 | "template" 1595 | ] 1596 | }, 1597 | "then": { 1598 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Spotlight" 1599 | } 1600 | }, 1601 | { 1602 | "if": { 1603 | "type": "object", 1604 | "properties": { 1605 | "template": { 1606 | "type": "string", 1607 | "enum": [ 1608 | "toast_notification" 1609 | ] 1610 | } 1611 | }, 1612 | "required": [ 1613 | "template" 1614 | ] 1615 | }, 1616 | "then": { 1617 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/ToastNotification" 1618 | } 1619 | }, 1620 | { 1621 | "if": { 1622 | "type": "object", 1623 | "properties": { 1624 | "template": { 1625 | "type": "string", 1626 | "enum": [ 1627 | "toolbar_badge" 1628 | ] 1629 | } 1630 | }, 1631 | "required": [ 1632 | "template" 1633 | ] 1634 | }, 1635 | "then": { 1636 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/ToolbarBadgeMessage" 1637 | } 1638 | }, 1639 | { 1640 | "if": { 1641 | "type": "object", 1642 | "properties": { 1643 | "template": { 1644 | "type": "string", 1645 | "enum": [ 1646 | "update_action" 1647 | ] 1648 | } 1649 | }, 1650 | "required": [ 1651 | "template" 1652 | ] 1653 | }, 1654 | "then": { 1655 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/UpdateAction" 1656 | } 1657 | } 1658 | ] 1659 | }, 1660 | "MultiMessage": { 1661 | "description": "An object containing an array of messages.", 1662 | "type": "object", 1663 | "properties": { 1664 | "template": { 1665 | "type": "string", 1666 | "const": "multi" 1667 | }, 1668 | "messages": { 1669 | "type": "array", 1670 | "description": "An array of messages.", 1671 | "items": { 1672 | "$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/TemplatedMessage" 1673 | } 1674 | } 1675 | }, 1676 | "required": [ 1677 | "template", 1678 | "messages" 1679 | ] 1680 | } 1681 | } 1682 | } 1683 | -------------------------------------------------------------------------------- /schema/NimbusExperiment.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "title": "DesktopNimbusExperiment", 4 | "description": "A Nimbus experiment for Firefox Desktop. This schema is less strict than DesktopAllVersionsNimbusExperiment and is intended for use in Firefox Desktop.", 5 | "type": "object", 6 | "properties": { 7 | "schemaVersion": { 8 | "description": "Version of the NimbusExperiment schema this experiment refers to", 9 | "type": "string" 10 | }, 11 | "slug": { 12 | "description": "Unique identifier for the experiment", 13 | "type": "string" 14 | }, 15 | "id": { 16 | "description": "Unique identifier for the experiiment. This is a duplicate of slug, but is required field for all Remote Settings records.", 17 | "type": "string" 18 | }, 19 | "appName": { 20 | "description": "A slug identifying the targeted product of this experiment. It should be a lowercased_with_underscores name that is short and unambiguous and it should match the app_name found in https://probeinfo.telemetry.mozilla.org/glean/repositories. Examples are \"fenix\" and \"firefox_desktop\".", 21 | "type": "string" 22 | }, 23 | "appId": { 24 | "description": "The platform identifier for the targeted app. This should match app's identifier exactly as it appears in the relevant app store listing (for relevant platforms) or the app's Glean initialization (for other platforms). Examples are \"org.mozilla.firefox_beta\" and \"firefox-desktop\".", 25 | "type": "string" 26 | }, 27 | "channel": { 28 | "description": "A specific channel of an application such as \"nightly\", \"beta\", or \"release\".", 29 | "type": "string" 30 | }, 31 | "userFacingName": { 32 | "description": "Public name of the experiment that will be displayed on \"about:studies\".", 33 | "type": "string" 34 | }, 35 | "userFacingDescription": { 36 | "description": "Short public description of the experiment that will be displayed on \"about:studies\".", 37 | "type": "string" 38 | }, 39 | "isEnrollmentPaused": { 40 | "description": "When this property is set to true, the SDK should not enroll new users into the experiment that have not already been enrolled.", 41 | "type": "boolean" 42 | }, 43 | "isRollout": { 44 | "description": "When this property is set to true, treat this experiment as a rollout. Rollouts are currently handled as single-branch experiments separated from the bucketing namespace for normal experiments. See-also: https://mozilla-hub.atlassian.net/browse/SDK-405", 45 | "type": "boolean" 46 | }, 47 | "bucketConfig": { 48 | "$ref": "#/$defs/ExperimentBucketConfig", 49 | "description": "Bucketing configuration." 50 | }, 51 | "outcomes": { 52 | "description": "A list of outcomes relevant to the experiment analysis.", 53 | "items": { 54 | "$ref": "#/$defs/ExperimentOutcome" 55 | }, 56 | "type": "array" 57 | }, 58 | "featureIds": { 59 | "description": "A list of featureIds the experiment contains configurations for.", 60 | "items": { 61 | "type": "string" 62 | }, 63 | "type": "array" 64 | }, 65 | "targeting": { 66 | "anyOf": [ 67 | { 68 | "type": "string" 69 | }, 70 | { 71 | "type": "null" 72 | } 73 | ], 74 | "description": "A JEXL targeting expression used to filter out experiments." 75 | }, 76 | "startDate": { 77 | "anyOf": [ 78 | { 79 | "format": "date", 80 | "type": "string" 81 | }, 82 | { 83 | "type": "null" 84 | } 85 | ], 86 | "description": "Actual publish date of the experiment. Note that this value is expected to be null in Remote Settings." 87 | }, 88 | "enrollmentEndDate": { 89 | "anyOf": [ 90 | { 91 | "format": "date", 92 | "type": "string" 93 | }, 94 | { 95 | "type": "null" 96 | } 97 | ], 98 | "description": "Actual enrollment end date of the experiment. Note that this value is expected to be null in Remote Settings." 99 | }, 100 | "endDate": { 101 | "anyOf": [ 102 | { 103 | "format": "date", 104 | "type": "string" 105 | }, 106 | { 107 | "type": "null" 108 | } 109 | ], 110 | "description": "Actual end date of this experiment. Note that this field is expected to be null in Remote Settings." 111 | }, 112 | "proposedDuration": { 113 | "description": "Duration of the experiment from the start date in days. Note that this property is only used during the analysis phase (i.e., not by the SDK).", 114 | "type": "integer" 115 | }, 116 | "proposedEnrollment": { 117 | "description": "This represents the number of days that we expect to enroll new users. Note that this property is only used during the analysis phase (i.e., not by the SDK).", 118 | "type": "integer" 119 | }, 120 | "referenceBranch": { 121 | "anyOf": [ 122 | { 123 | "type": "string" 124 | }, 125 | { 126 | "type": "null" 127 | } 128 | ], 129 | "description": "The slug of the reference branch (i.e., the branch we consider \"control\")." 130 | }, 131 | "locales": { 132 | "anyOf": [ 133 | { 134 | "items": { 135 | "type": "string" 136 | }, 137 | "type": "array" 138 | }, 139 | { 140 | "type": "null" 141 | } 142 | ], 143 | "description": "The list of locale codes (e.g., \"en-US\" or \"fr\") that this experiment is targeting. If null, all locales are targeted." 144 | }, 145 | "publishedDate": { 146 | "anyOf": [ 147 | { 148 | "format": "date-time", 149 | "type": "string" 150 | }, 151 | { 152 | "type": "null" 153 | } 154 | ], 155 | "description": "The date that this experiment was first published to Remote Settings. If null, it has not yet been published." 156 | }, 157 | "branches": { 158 | "description": "Branch configuration for the experiment.", 159 | "items": { 160 | "$ref": "#/$defs/DesktopExperimentBranch" 161 | }, 162 | "type": "array" 163 | }, 164 | "isFirefoxLabsOptIn": { 165 | "description": "When this property is set to true, treat this experiment as aFirefox Labs experiment", 166 | "type": "boolean" 167 | }, 168 | "firefoxLabsGroup": { 169 | "anyOf": [ 170 | { 171 | "type": "string" 172 | }, 173 | { 174 | "type": "null" 175 | } 176 | ], 177 | "description": "The group this should appear under in Firefox Labs" 178 | }, 179 | "firefoxLabsTitle": { 180 | "anyOf": [ 181 | { 182 | "type": "string" 183 | }, 184 | { 185 | "type": "null" 186 | } 187 | ], 188 | "description": "The title shown in Firefox Labs (Fluent ID)" 189 | }, 190 | "firefoxLabsDescription": { 191 | "anyOf": [ 192 | { 193 | "type": "string" 194 | }, 195 | { 196 | "type": "null" 197 | } 198 | ], 199 | "description": "The description shown in Firefox Labs (Fluent ID)" 200 | }, 201 | "firefoxLabsDescriptionLinks": { 202 | "anyOf": [ 203 | { 204 | "additionalProperties": { 205 | "format": "uri", 206 | "maxLength": 2083, 207 | "minLength": 1, 208 | "type": "string" 209 | }, 210 | "type": "object" 211 | }, 212 | { 213 | "type": "null" 214 | } 215 | ], 216 | "description": "Links that will be used with the firefoxLabsDescription Fluent ID. May be null for Firefox Labs Opt-In recipes that do not use links." 217 | }, 218 | "featureValidationOptOut": { 219 | "description": "Opt out of feature schema validation.", 220 | "type": "boolean" 221 | }, 222 | "requiresRestart": { 223 | "description": "Does the experiment require a restart to take effect? Only used by Firefox Labs Opt-Ins.", 224 | "type": "boolean" 225 | }, 226 | "localizations": { 227 | "anyOf": [ 228 | { 229 | "$ref": "#/$defs/ExperimentLocalizations" 230 | }, 231 | { 232 | "type": "null" 233 | } 234 | ] 235 | } 236 | }, 237 | "required": [ 238 | "schemaVersion", 239 | "slug", 240 | "id", 241 | "appName", 242 | "appId", 243 | "channel", 244 | "userFacingName", 245 | "userFacingDescription", 246 | "isEnrollmentPaused", 247 | "bucketConfig", 248 | "startDate", 249 | "endDate", 250 | "proposedEnrollment", 251 | "referenceBranch", 252 | "branches" 253 | ], 254 | "dependentSchemas": { 255 | "isFirefoxLabsOptIn": { 256 | "if": { 257 | "properties": { 258 | "isFirefoxLabsOptIn": { 259 | "const": true 260 | } 261 | } 262 | }, 263 | "then": { 264 | "if": { 265 | "properties": { 266 | "isRollout": { 267 | "const": false 268 | } 269 | }, 270 | "required": [ 271 | "isRollout" 272 | ] 273 | }, 274 | "required": [ 275 | "firefoxLabsDescription", 276 | "firefoxLabsDescriptionLinks", 277 | "firefoxLabsGroup", 278 | "firefoxLabsTitle" 279 | ], 280 | "then": { 281 | "properties": { 282 | "branches": { 283 | "items": { 284 | "required": [ 285 | "firefoxLabsTitle" 286 | ] 287 | } 288 | } 289 | } 290 | } 291 | } 292 | } 293 | }, 294 | "$defs": { 295 | "DesktopExperimentBranch": { 296 | "description": "The branch definition supported on Firefox Desktop 95+.", 297 | "properties": { 298 | "slug": { 299 | "description": "Identifier for the branch.", 300 | "type": "string" 301 | }, 302 | "ratio": { 303 | "description": "Relative ratio of population for the branch. e.g., if branch A=1 and branch B=3, then branch A would get 25% of the population.", 304 | "type": "integer" 305 | }, 306 | "features": { 307 | "description": "An array of feature configurations.", 308 | "items": { 309 | "$ref": "#/$defs/ExperimentFeatureConfig" 310 | }, 311 | "type": "array" 312 | }, 313 | "firefoxLabsTitle": { 314 | "anyOf": [ 315 | { 316 | "type": "string" 317 | }, 318 | { 319 | "type": "null" 320 | } 321 | ], 322 | "description": "The branch title shown in Firefox Labs (Fluent ID)" 323 | } 324 | }, 325 | "required": [ 326 | "slug", 327 | "ratio", 328 | "features" 329 | ], 330 | "type": "object" 331 | }, 332 | "ExperimentBucketConfig": { 333 | "properties": { 334 | "randomizationUnit": { 335 | "$ref": "#/$defs/RandomizationUnit" 336 | }, 337 | "namespace": { 338 | "description": "Additional inputs to the hashing function.", 339 | "type": "string" 340 | }, 341 | "start": { 342 | "description": "Index of the starting bucket of the range.", 343 | "type": "integer" 344 | }, 345 | "count": { 346 | "description": "Number of buckets in the range.", 347 | "type": "integer" 348 | }, 349 | "total": { 350 | "description": "The total number of buckets. You can assume this will always be 10000", 351 | "type": "integer" 352 | } 353 | }, 354 | "required": [ 355 | "randomizationUnit", 356 | "namespace", 357 | "start", 358 | "count", 359 | "total" 360 | ], 361 | "type": "object" 362 | }, 363 | "ExperimentFeatureConfig": { 364 | "properties": { 365 | "featureId": { 366 | "description": "The identifier for the feature flag.", 367 | "type": "string" 368 | }, 369 | "value": { 370 | "description": "The values that define the feature configuration. This should be validated against a schema.", 371 | "type": "object" 372 | } 373 | }, 374 | "required": [ 375 | "featureId", 376 | "value" 377 | ], 378 | "type": "object" 379 | }, 380 | "ExperimentLocalizations": { 381 | "additionalProperties": { 382 | "additionalProperties": { 383 | "type": "string" 384 | }, 385 | "type": "object" 386 | }, 387 | "description": "Per-locale localization substitutions. The top level key is the locale (e.g., \"en-US\" or \"fr\"). Each entry is a mapping of string IDs to their localized equivalents.", 388 | "type": "object" 389 | }, 390 | "ExperimentOutcome": { 391 | "properties": { 392 | "slug": { 393 | "description": "Identifier for the outcome.", 394 | "type": "string" 395 | }, 396 | "priority": { 397 | "description": "e.g., \"primary\" or \"secondary\".", 398 | "type": "string" 399 | } 400 | }, 401 | "required": [ 402 | "slug", 403 | "priority" 404 | ], 405 | "type": "object" 406 | }, 407 | "RandomizationUnit": { 408 | "description": "A unique, stable indentifier for the user used as an input to bucket hashing.", 409 | "enum": [ 410 | "normandy_id", 411 | "nimbus_id", 412 | "user_id", 413 | "group_id" 414 | ], 415 | "type": "string" 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /schema/SpecialMessageActionSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$ref": "#/definitions/SpecialMessageActionSchemas", 4 | "definitions": { 5 | "SpecialMessageActionSchemas": { 6 | "anyOf": [ 7 | { 8 | "type": "object", 9 | "properties": { 10 | "type": { 11 | "type": "string", 12 | "enum": ["DISABLE_STP_DOORHANGERS"] 13 | } 14 | }, 15 | "required": ["type"], 16 | "additionalProperties": false, 17 | "description": "Disables all STP doorhangers." 18 | }, 19 | { 20 | "type": "object", 21 | "properties": { 22 | "data": { 23 | "type": "object", 24 | "properties": { 25 | "args": { 26 | "type": "string", 27 | "description": "The element to highlight" 28 | } 29 | }, 30 | "required": ["args"], 31 | "additionalProperties": false 32 | }, 33 | "type": { 34 | "type": "string", 35 | "enum": ["HIGHLIGHT_FEATURE"] 36 | } 37 | }, 38 | "required": ["data", "type"], 39 | "additionalProperties": false, 40 | "description": "Highlights an element, such as a menu item" 41 | }, 42 | { 43 | "type": "object", 44 | "properties": { 45 | "data": { 46 | "type": "object", 47 | "properties": { 48 | "telemetrySource": { 49 | "type": "string" 50 | }, 51 | "url": { 52 | "type": "string" 53 | } 54 | }, 55 | "required": ["telemetrySource", "url"], 56 | "additionalProperties": false 57 | }, 58 | "type": { 59 | "type": "string", 60 | "enum": ["INSTALL_ADDON_FROM_URL"] 61 | } 62 | }, 63 | "required": ["data", "type"], 64 | "additionalProperties": false, 65 | "description": "Install an add-on from AMO" 66 | }, 67 | { 68 | "type": "object", 69 | "properties": { 70 | "data": { 71 | "type": "object", 72 | "properties": { 73 | "args": { 74 | "type": "string", 75 | "description": "The about page. E.g. \"welcome\" for about:welcome'" 76 | }, 77 | "where": { 78 | "type": "string", 79 | "enum": ["current", "save", "tab", "tabshifted", "window"], 80 | "description": "Where the URL is opened", 81 | "default": "tab" 82 | }, 83 | "entrypoint": { 84 | "type": "string", 85 | "description": "Any optional entrypoint value that will be added to the search. E.g. \"foo=bar\" would result in about:welcome?foo=bar'" 86 | } 87 | }, 88 | "required": ["args", "where", "entrypoint"], 89 | "additionalProperties": false 90 | }, 91 | "type": { 92 | "type": "string", 93 | "enum": ["OPEN_ABOUT_PAGE"] 94 | } 95 | }, 96 | "required": ["data", "type"], 97 | "additionalProperties": false, 98 | "description": "Opens an about: page in Firefox" 99 | }, 100 | { 101 | "type": "object", 102 | "properties": { 103 | "type": { 104 | "type": "string", 105 | "enum": ["OPEN_FIREFOX_VIEW"] 106 | } 107 | }, 108 | "required": ["type"], 109 | "additionalProperties": false, 110 | "description": "Opens the Firefox View pseudo-pinned-tab" 111 | }, 112 | { 113 | "type": "object", 114 | "properties": { 115 | "data": { 116 | "type": "object", 117 | "properties": { 118 | "args": { 119 | "type": "string", 120 | "description": "The menu name, e.g. \"appMenu\"" 121 | } 122 | }, 123 | "required": ["args"], 124 | "additionalProperties": false 125 | }, 126 | "type": { 127 | "type": "string", 128 | "enum": ["OPEN_APPLICATIONS_MENU"] 129 | } 130 | }, 131 | "required": ["data", "type"], 132 | "additionalProperties": false, 133 | "description": "Opens an application menu" 134 | }, 135 | { 136 | "type": "object", 137 | "properties": { 138 | "type": { 139 | "type": "string", 140 | "enum": ["OPEN_AWESOME_BAR"] 141 | } 142 | }, 143 | "required": ["type"], 144 | "additionalProperties": false, 145 | "description": "Focuses and expands the awesome bar" 146 | }, 147 | { 148 | "type": "object", 149 | "properties": { 150 | "data": { 151 | "type": "object", 152 | "properties": { 153 | "category": { 154 | "type": "string", 155 | "description": "Section of about:preferences, e.g. \"privacy-reports\"" 156 | }, 157 | "entrypoint": { 158 | "type": "string", 159 | "description": "Add a queryparam for metrics" 160 | } 161 | }, 162 | "required": ["category"], 163 | "additionalProperties": false 164 | }, 165 | "type": { 166 | "type": "string", 167 | "enum": ["OPEN_PREFERENCES_PAGE"] 168 | } 169 | }, 170 | "required": ["data", "type"], 171 | "additionalProperties": false, 172 | "description": "Opens a preference page" 173 | }, 174 | { 175 | "type": "object", 176 | "properties": { 177 | "type": { 178 | "type": "string", 179 | "enum": ["OPEN_PRIVATE_BROWSER_WINDOW"] 180 | } 181 | }, 182 | "required": ["type"], 183 | "additionalProperties": false, 184 | "description": "Opens a private browsing window." 185 | }, 186 | { 187 | "type": "object", 188 | "properties": { 189 | "type": { 190 | "type": "string", 191 | "enum": ["OPEN_PROTECTION_PANEL"] 192 | } 193 | }, 194 | "required": ["type"], 195 | "additionalProperties": false, 196 | "description": "Opens the protections panel" 197 | }, 198 | { 199 | "type": "object", 200 | "properties": { 201 | "type": { 202 | "type": "string", 203 | "enum": ["OPEN_PROTECTION_REPORT"] 204 | } 205 | }, 206 | "required": ["type"], 207 | "additionalProperties": false, 208 | "description": "Opens the protections panel report" 209 | }, 210 | { 211 | "type": "object", 212 | "properties": { 213 | "data": { 214 | "type": "string", 215 | "description": "Id of the pane to open, e.g., viewHistorySidebar" 216 | }, 217 | "type": { 218 | "type": "string", 219 | "enum": ["OPEN_SIDEBAR"] 220 | } 221 | }, 222 | "required": ["data", "type"], 223 | "additionalProperties": false, 224 | "description": "Opens a sidebar pane" 225 | }, 226 | { 227 | "type": "object", 228 | "properties": { 229 | "data": { 230 | "type": "object", 231 | "properties": { 232 | "args": { 233 | "type": "string", 234 | "description": "URL to open" 235 | }, 236 | "where": { 237 | "type": "string", 238 | "enum": ["current", "save", "tab", "tabshifted", "window"], 239 | "description": "Where the URL is opened", 240 | "default": "tab" 241 | } 242 | }, 243 | "required": ["args", "where"], 244 | "additionalProperties": false 245 | }, 246 | "type": { 247 | "type": "string", 248 | "enum": ["OPEN_URL"] 249 | } 250 | }, 251 | "required": ["data", "type"], 252 | "additionalProperties": false, 253 | "description": "Opens given URL" 254 | }, 255 | { 256 | "type": "object", 257 | "properties": { 258 | "type": { 259 | "type": "string", 260 | "enum": ["PIN_CURRENT_TAB"] 261 | } 262 | }, 263 | "required": ["type"], 264 | "additionalProperties": false, 265 | "description": "Pin the current tab" 266 | }, 267 | { 268 | "type": "object", 269 | "properties": { 270 | "type": { 271 | "type": "string", 272 | "enum": ["SHOW_FIREFOX_ACCOUNTS"] 273 | }, 274 | "data": { 275 | "type": "object", 276 | "properties": { 277 | "entrypoint": { 278 | "type": "string", 279 | "description": "Adds entrypoint={your value} to the FXA URL" 280 | }, 281 | "extraParams": { 282 | "type": "object", 283 | "description": "Any extra parameter that will be added to the FXA URL. E.g. {foo: bar} would result in ?foo=bar'" 284 | } 285 | }, 286 | "required": ["entrypoint"], 287 | "additionalProperties": false 288 | } 289 | }, 290 | "required": ["type", "data"], 291 | "additionalProperties": false, 292 | "description": "Show Firefox Accounts" 293 | }, 294 | { 295 | "type": "object", 296 | "properties": { 297 | "type": { 298 | "type": "string", 299 | "enum": ["SHOW_MIGRATION_WIZARD"] 300 | }, 301 | "data": { 302 | "type": "object", 303 | "properties": { 304 | "source": { 305 | "type": "string", 306 | "description": "Identitifer of the browser that should be pre-selected in the import migration wizard popup (e.g. 'chrome'), See https://searchfox.org/mozilla-central/rev/8dae1cc76a6b45e05198bc0d5d4edb7bf1003265/browser/components/migration/MigrationUtils.jsm#917" 307 | } 308 | }, 309 | "additionalProperties": false 310 | } 311 | }, 312 | "required": ["type"], 313 | "additionalProperties": false, 314 | "description": "Shows the Migration Wizard to import data from another Browser. See https://support.mozilla.org/en-US/kb/import-data-another-browser\"" 315 | }, 316 | { 317 | "type": "object", 318 | "properties": { 319 | "type": { 320 | "type": "string", 321 | "enum": ["CANCEL"] 322 | } 323 | }, 324 | "required": ["type"], 325 | "additionalProperties": false, 326 | "description": "Minimize the CFR doorhanger back into the URLbar" 327 | }, 328 | { 329 | "type": "object", 330 | "properties": { 331 | "type": { 332 | "type": "string", 333 | "enum": ["ACCEPT_DOH"] 334 | } 335 | }, 336 | "required": ["type"], 337 | "additionalProperties": false, 338 | "description": "Accept DOH doorhanger notification" 339 | }, 340 | { 341 | "type": "object", 342 | "properties": { 343 | "type": { 344 | "type": "string", 345 | "enum": ["DISABLE_DOH"] 346 | } 347 | }, 348 | "required": ["type"], 349 | "additionalProperties": false, 350 | "description": "Dismiss DOH doorhanger notification" 351 | }, 352 | { 353 | "type": "object", 354 | "properties": { 355 | "type": { 356 | "type": "string", 357 | "enum": ["PIN_FIREFOX_TO_TASKBAR"] 358 | }, 359 | "data": { 360 | "type": "object", 361 | "properties": { 362 | "privatePin": { 363 | "type": "boolean", 364 | "description": "Whether or not to pin private browsing mode" 365 | } 366 | }, 367 | "additionalProperties": false 368 | } 369 | }, 370 | "required": ["type"], 371 | "additionalProperties": false, 372 | "description": "Pin the app to taskbar" 373 | }, 374 | { 375 | "type": "object", 376 | "properties": { 377 | "type": { 378 | "type": "string", 379 | "enum": ["PIN_FIREFOX_TO_START_MENU"] 380 | } 381 | }, 382 | "required": ["type"], 383 | "additionalProperties": false, 384 | "description": "Pin the app to Windows Start Menu" 385 | }, 386 | { 387 | "type": "object", 388 | "properties": { 389 | "type": { 390 | "type": "string", 391 | "enum": ["SET_DEFAULT_BROWSER"] 392 | } 393 | }, 394 | "required": ["type"], 395 | "additionalProperties": false, 396 | "description": "Message action to set Firefox as default browser" 397 | }, 398 | { 399 | "type": "object", 400 | "properties": { 401 | "type": { 402 | "type": "string", 403 | "enum": ["SET_DEFAULT_PDF_HANDLER"] 404 | }, 405 | "data": { 406 | "type": "object", 407 | "properties": { 408 | "onlyIfKnownBrowser": { 409 | "type": "boolean", 410 | "description": "Only set Firefox as the default PDF handler if the current PDF handler is a known browser." 411 | } 412 | }, 413 | "additionalProperties": false 414 | } 415 | }, 416 | "required": ["type"], 417 | "additionalProperties": false, 418 | "description": "Message action to set Firefox as the default PDF handler" 419 | }, 420 | { 421 | "type": "object", 422 | "properties": { 423 | "type": { 424 | "type": "string", 425 | "enum": ["DECLINE_DEFAULT_PDF_HANDLER"] 426 | } 427 | }, 428 | "required": ["type"], 429 | "additionalProperties": false, 430 | "description": "Message action to decline setting Firefox as the default PDF handler" 431 | }, 432 | { 433 | "type": "object", 434 | "properties": { 435 | "data": { 436 | "type": "object", 437 | "properties": { 438 | "homePage": { 439 | "type": "string", 440 | "description": "Should reset homepage pref", 441 | "enum": ["default"] 442 | }, 443 | "newtab": { 444 | "type": "string", 445 | "enum": ["default"], 446 | "description": "Should reset newtab pref" 447 | }, 448 | "layout": { 449 | "type": "object", 450 | "description": "Section name and boolean value that specifies if the section should be on or off.", 451 | "properties": { 452 | "search": { 453 | "type": "boolean" 454 | }, 455 | "topsites": { 456 | "type": "boolean" 457 | }, 458 | "highlights": { 459 | "type": "boolean" 460 | }, 461 | "topstories": { 462 | "type": "boolean" 463 | } 464 | }, 465 | "required": [ 466 | "search", 467 | "topsites", 468 | "highlights", 469 | "topstories" 470 | ], 471 | "additionalProperties": false 472 | } 473 | }, 474 | "additionalProperties": false 475 | }, 476 | "type": { 477 | "type": "string", 478 | "enum": ["CONFIGURE_HOMEPAGE"] 479 | } 480 | }, 481 | "required": ["data", "type"], 482 | "additionalProperties": false, 483 | "description": "Resets homepage pref and sections layout" 484 | }, 485 | { 486 | "type": "object", 487 | "properties": { 488 | "data": { 489 | "type": "object", 490 | "properties": { 491 | "content": { 492 | "type": "object", 493 | "description": "Object containing content rendered inside spotlight dialog" 494 | } 495 | }, 496 | "required": ["content"], 497 | "additionalProperties": false 498 | }, 499 | "type": { 500 | "type": "string", 501 | "enum": ["SHOW_SPOTLIGHT"] 502 | } 503 | }, 504 | "required": ["data", "type"], 505 | "additionalProperties": false, 506 | "description": "Opens a spotlight dialog" 507 | }, 508 | { 509 | "type": "object", 510 | "properties": { 511 | "data": { 512 | "type": "object", 513 | "properties": { 514 | "id": { 515 | "type": "string", 516 | "description": "Message id to block" 517 | } 518 | }, 519 | "required": ["id"], 520 | "additionalProperties": false 521 | }, 522 | "type": { 523 | "type": "string", 524 | "enum": ["BLOCK_MESSAGE"] 525 | } 526 | }, 527 | "required": ["data", "type"], 528 | "additionalProperties": false, 529 | "description": "Add message to an indexedDb list of blocked messages" 530 | }, 531 | { 532 | "type": "object", 533 | "properties": { 534 | "data": { 535 | "type": "object", 536 | "properties": { 537 | "pref": { 538 | "type": "object", 539 | "properties": { 540 | "name": { 541 | "type": "string" 542 | }, 543 | "value": { 544 | "type": ["boolean", "string", "number", "null"] 545 | } 546 | }, 547 | "description": "An object representing a pref containing a name and a value." 548 | } 549 | }, 550 | "required": ["pref"], 551 | "additionalProperties": false 552 | }, 553 | "type": { 554 | "type": "string", 555 | "enum": ["SET_PREF"] 556 | } 557 | }, 558 | "required": ["data", "type"], 559 | "additionalProperties": false, 560 | "description": "Sets prefs from special message actions" 561 | }, 562 | { 563 | "type": "object", 564 | "properties": { 565 | "data": { 566 | "type": "object", 567 | "properties": { 568 | "orderedExecution": { 569 | "type": "boolean", 570 | "description": "Optional - if true, actions will execute sequentially" 571 | }, 572 | "actions": { 573 | "type": "array", 574 | "items": { 575 | "type": "object", 576 | "description": "A special message action definition" 577 | } 578 | } 579 | }, 580 | "required": ["actions"], 581 | "additionalProperties": false 582 | }, 583 | "type": { 584 | "type": "string", 585 | "enum": ["MULTI_ACTION"] 586 | } 587 | }, 588 | "required": ["data", "type"], 589 | "additionalProperties": false, 590 | "description": "Runs multiple actions" 591 | }, 592 | { 593 | "type": "object", 594 | "properties": { 595 | "data": { 596 | "selector": { 597 | "type": "string", 598 | "description": "A CSS selector for the HTML element to be clicked" 599 | } 600 | }, 601 | "type": { 602 | "type": "string", 603 | "enum": ["CLICK_ELEMENT"] 604 | } 605 | }, 606 | "required": ["data", "type"], 607 | "additionalProperties": false, 608 | "description": "Selects an element in the current Window's document and triggers a click action" 609 | }, 610 | { 611 | "type": "object", 612 | "properties": { 613 | "type": { 614 | "type": "string", 615 | "enum": ["RELOAD_BROWSER"] 616 | } 617 | }, 618 | "required": ["type"], 619 | "additionalProperties": false, 620 | "description": "Message action that reloads the current browser" 621 | }, 622 | { 623 | "type": "object", 624 | "properties": { 625 | "type": { 626 | "type": "string", 627 | "enum": ["FOCUS_URLBAR"] 628 | } 629 | }, 630 | "required": ["type"], 631 | "additionalProperties": false, 632 | "description": "Focuses the urlbar in the window the message was displayed in" 633 | }, 634 | { 635 | "type": "object", 636 | "properties": { 637 | "data": { 638 | "type": "object", 639 | "properties": { 640 | "shouldHideDialog": { 641 | "type": "boolean", 642 | "description": "Whether or not the bookmarks dialog should be visible" 643 | }, 644 | "shouldHideConfirmationHint": { 645 | "type": "boolean", 646 | "description": "Whether or not the bookmarks confirmation hint should be visible" 647 | } 648 | } 649 | }, 650 | "type": { 651 | "type": "string", 652 | "enum": ["BOOKMARK_CURRENT_TAB"] 653 | } 654 | }, 655 | "required": ["type"], 656 | "additionalProperties": false, 657 | "description": "Bookmarks the tab that was selected when the message was displayed" 658 | }, 659 | { 660 | "type": "object", 661 | "properties": { 662 | "data": { 663 | "visibility": { 664 | "type": "string", 665 | "description": "The visibility of the bookmarks toolbar", 666 | "enum": ["always", "never", "newtab"] 667 | } 668 | }, 669 | "type": { 670 | "type": "string", 671 | "enum": ["SET_BOOKMARKS_TOOLBAR_VISIBILITY"] 672 | } 673 | }, 674 | "required": ["data", "type"], 675 | "additionalProperties": false, 676 | "description": "Sets the visibility of the bookmarks toolbar" 677 | }, 678 | { 679 | "type": "object", 680 | "properties": { 681 | "type": { 682 | "type": "string", 683 | "enum": ["DATAREPORTING_NOTIFY_DATA_POLICY_INTERACTED"] 684 | } 685 | }, 686 | "required": ["type"], 687 | "additionalProperties": false, 688 | "description": "Notify Firefox that the notification policy was interacted with." 689 | }, 690 | { 691 | "type": "object", 692 | "properties": { 693 | "type": { 694 | "type": "string", 695 | "enum": ["CREATE_NEW_SELECTABLE_PROFILE"] 696 | } 697 | }, 698 | "required": ["type"], 699 | "additionalProperties": false, 700 | "description": "Creates a new user profile using SelectableProfileService and launches it in a new instance" 701 | }, 702 | { 703 | "type": "object", 704 | "properties": { 705 | "type": { 706 | "type": "string", 707 | "enum": ["SUBMIT_ONBOARDING_OPT_OUT_PING"] 708 | } 709 | }, 710 | "required": ["type"], 711 | "additionalProperties": false, 712 | "description": "Submit an `onboarding-opt-out` Glean ping. Should only be used during preonboarding." 713 | } 714 | ] 715 | } 716 | } 717 | } 718 | -------------------------------------------------------------------------------- /schema/message-groups.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "MessageGroup", 3 | "description": "Configuration object for groups of Messaging System messages", 4 | "type": "object", 5 | "version": "1.0.0", 6 | "properties": { 7 | "id": { 8 | "type": "string", 9 | "description": "A unique identifier for the message that should not conflict with any other previous message." 10 | }, 11 | "enabled": { 12 | "type": "boolean", 13 | "description": "Enables or disables all messages associated with this group." 14 | }, 15 | "userPreferences": { 16 | "type": "array", 17 | "description": "Collection of preferences that control if the group is enabled.", 18 | "items": { 19 | "type": "string", 20 | "description": "Preference name" 21 | } 22 | }, 23 | "frequency": { 24 | "type": "object", 25 | "description": "An object containing frequency cap information for a message.", 26 | "properties": { 27 | "lifetime": { 28 | "type": "integer", 29 | "description": "The maximum lifetime impressions for a message.", 30 | "minimum": 1, 31 | "maximum": 100 32 | }, 33 | "custom": { 34 | "type": "array", 35 | "description": "An array of custom frequency cap definitions.", 36 | "items": { 37 | "description": "A frequency cap definition containing time and max impression information", 38 | "type": "object", 39 | "properties": { 40 | "period": { 41 | "type": "integer", 42 | "description": "Period of time in milliseconds (e.g. 86400000 for one day)" 43 | }, 44 | "cap": { 45 | "type": "integer", 46 | "description": "The maximum impressions for the message within the defined period.", 47 | "minimum": 1, 48 | "maximum": 100 49 | } 50 | }, 51 | "required": ["period", "cap"] 52 | } 53 | } 54 | } 55 | }, 56 | "type": { 57 | "type": "string", 58 | "description": "Local auto-generated group or remote group configuration from RS.", 59 | "enum": ["remote-settings", "local", "default"] 60 | } 61 | }, 62 | "required": ["id", "enabled", "type"], 63 | "additionalProperties": true 64 | } 65 | -------------------------------------------------------------------------------- /scripts/export-all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | """Export all messages as a message provider JSM. 6 | 7 | This script will create the file `outgoing/InflightAssetsMessageProvider`, which 8 | contains all messages in this repository. This file is intended to be landed in 9 | mozilla-central at browser/components/newtab/test/ so we can be sure that they 10 | validate with the JSON schemas in mozilla-central. 11 | 12 | (See https://bugzilla.mozilla.org/show_bug.cgi?id=1775849 for the initial import.) 13 | 14 | Usage: 15 | make export 16 | """ 17 | 18 | import itertools 19 | import json 20 | from pathlib import Path 21 | 22 | FORMATS = [ 23 | "cfr", 24 | "cfr-heartbeat", 25 | "moments", 26 | "whats-new-panel", 27 | "messaging-experiments", 28 | ] 29 | 30 | JSM_CONTENTS = """\ 31 | /* This Source Code Form is subject to the terms of the Mozilla Public 32 | * License, v. 2.0. If a copy of the MPL was not distributed with this 33 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 34 | */ 35 | 36 | // This file is generated by: 37 | // https://github.com/mozilla/messaging-system-inflight-assets/tree/master/scripts/export-all.py 38 | 39 | export const InflightAssetsMessageProvider = {{ 40 | getMessages() {{ 41 | return {messages_json}; 42 | }} 43 | }}; 44 | """ 45 | 46 | 47 | def main(): 48 | all_messages = [] 49 | for format in FORMATS: 50 | path = Path("outgoing", f"{format}.json") 51 | with path.open() as f: 52 | messages = json.load(f) 53 | 54 | if messages is not None: 55 | all_messages.extend(messages) 56 | 57 | # Indent all but the first line by 4 spaces. 58 | json_lines = json.dumps(all_messages, indent=2).split("\n") 59 | messages_json = "\n".join( 60 | itertools.chain( 61 | [json_lines[0]], 62 | (f" {line}" for line in json_lines[1:]), 63 | ) 64 | ) 65 | 66 | with Path("outgoing", "InflightAssetsMessageProvider.sys.mjs").open("wb") as f: 67 | f.write(JSM_CONTENTS.format(messages_json=messages_json).encode("utf-8")) 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /scripts/fetch-schemas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | from pathlib import Path 7 | 8 | import requests 9 | 10 | URL_BASE = "https://hg.mozilla.org/mozilla-central/raw-file/tip/" 11 | 12 | SCHEMAS = { 13 | # The combined FxMS Mega schema 14 | "MessagingExperiment.schema.json": ( 15 | "browser/components/asrouter/content-src/schemas/" 16 | "MessagingExperiment.schema.json" 17 | ), 18 | # The Nimbus recipe schema 19 | "NimbusExperiment.schema.json": ( 20 | "toolkit/components/nimbus/schemas/NimbusExperiment.schema.json" 21 | ), 22 | # Non-message schemas 23 | "SpecialMessageActionSchemas.json": ( 24 | "toolkit/components/messaging-system/schemas/" 25 | "SpecialMessageActionSchemas/SpecialMessageActionSchemas.json" 26 | ), 27 | "message-groups.schema.json": ( 28 | "browser/components/asrouter/content-src/schemas/" 29 | "message-group.schema.json" 30 | ), 31 | } 32 | 33 | 34 | def main(): 35 | session = requests.session() 36 | 37 | for schema, fragment in SCHEMAS.items(): 38 | path = Path("schema") / schema 39 | url = URL_BASE + fragment 40 | 41 | print(f"Fetching {schema} ...") 42 | 43 | with path.open("w") as f, session.get(url) as req: 44 | f.write(req.text) 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /scripts/rscat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | USAGE="Usage: 4 | 5 | # list all collections 6 | $0 7 | 8 | # list all record IDs in a collection 9 | $0 -l collection-name 10 | 11 | # list all records in a collection 12 | $0 collection-name 13 | 14 | # list a specific record in a collection 15 | $0 -r record-name collection-name 16 | " 17 | 18 | RS_URL=https://firefox.settings.services.mozilla.com/v1/buckets/main/collections 19 | 20 | POSTIONAL=() 21 | while [[ $# -gt 0 ]]; do 22 | key="$1" 23 | case $key in 24 | -l|--list) 25 | list=YES 26 | shift 27 | ;; 28 | -r|--record) 29 | record="$2" 30 | shift 31 | shift 32 | ;; 33 | -h|--help) 34 | echo "${USAGE}" 35 | exit 0 36 | ;; 37 | *) 38 | POSTIONAL+=("$1") 39 | shift 40 | ;; 41 | esac 42 | done 43 | 44 | if [[ ${#POSTIONAL[@]} -eq 0 ]]; then 45 | curl -SsL ${RS_URL} | jq -r '.data | .[].id' 46 | exit 0 47 | fi 48 | 49 | collection=${POSTIONAL[0]} 50 | 51 | RS_FULL_URL=${RS_URL}/${collection}/records 52 | 53 | if [[ -z ${record} ]]; then 54 | if [[ "${list}" == YES ]]; then 55 | curl -SsL ${RS_FULL_URL} | jq -r '.data | .[].id' 56 | else 57 | curl -SsL ${RS_FULL_URL} | jq -r '.data | .[]' 58 | fi 59 | else 60 | curl -SsL ${RS_FULL_URL} | jq -r --arg r "${record}" '.data | .[] | select(.id == $r)' 61 | fi 62 | -------------------------------------------------------------------------------- /scripts/spot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | USAGE="Usage: 4 | 5 | # list all experiment slugs 6 | $0 7 | 8 | # copy [experiment-slug] arguments to yaml 9 | $0 [experiment-slug] 10 | " 11 | 12 | PYTHON=python3 13 | EXPERIMENTER_URL=https://experimenter.services.mozilla.com/api/v1/experiments 14 | RECIPE_URL=${EXPERIMENTER_URL}/$1/recipe/ 15 | 16 | if [[ $1 == "-h" ]] || [[ $1 = "--help" ]]; then 17 | echo "${USAGE}" 18 | elif [[ -z $1 ]]; then 19 | curl -SsL ${EXPERIMENTER_URL}/ | jq -r '.[].slug' 20 | echo "Choose from one of the available slugs: $0 [slug_name]" 21 | else 22 | json_input=$( 23 | curl -SsL ${RECIPE_URL} |\ 24 | jq -r '{id: .experimenter_slug, enabled: true, arguments: .arguments, filter_expression: "", targeting: ""}' 25 | ) 26 | ${PYTHON} -c 'import sys, yaml, json; yaml.dump(json.load(sys.stdin), sys.stdout) 27 | '<<< ${json_input} > $1.yaml 28 | fi 29 | -------------------------------------------------------------------------------- /scripts/validate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | import json 7 | import sys 8 | from pathlib import Path 9 | 10 | import jsonschema 11 | from jsonschema.exceptions import best_match, ValidationError 12 | from pyjexl.jexl import JEXL 13 | 14 | 15 | class NestedRefResolver(jsonschema.RefResolver): 16 | """A custom ref resolver that handles bundled schema. 17 | 18 | This is the resolver used by Experimenter. 19 | """ 20 | 21 | def __init__(self, schema): 22 | super().__init__(base_uri=None, referrer=None) 23 | 24 | if "$id" in schema: 25 | self.store[schema["$id"]] = schema 26 | 27 | if "$defs" in schema: 28 | for dfn in schema["$defs"].values(): 29 | if "$id" in dfn: 30 | self.store[dfn["$id"]] = dfn 31 | 32 | 33 | # Create a jexl evaluator 34 | EVALUATOR = JEXL() 35 | EVALUATOR.add_binary_operator("intersect", 40, lambda x, y: set(x).intersection(y)) 36 | EVALUATOR.add_transform("bucketSample", lambda x, y, z, q: False) 37 | EVALUATOR.add_transform("date", lambda x: 0) 38 | EVALUATOR.add_transform("keys", lambda x: []) 39 | EVALUATOR.add_transform("length", lambda x: 0) 40 | EVALUATOR.add_transform("mapToProperty", lambda x, y: []) 41 | EVALUATOR.add_transform("preferenceExists", lambda x: False) 42 | EVALUATOR.add_transform("preferenceIsUserSet", lambda x: False) 43 | EVALUATOR.add_transform("preferenceValue", lambda x: False) 44 | EVALUATOR.add_transform("regExpMatch", lambda x: False) 45 | EVALUATOR.add_transform("stableSample", lambda x, y: False) 46 | EVALUATOR.add_transform("versionCompare", lambda x: 0) 47 | 48 | # cache all the known schemas to validate experiments 49 | ALL_SCHEMAS = dict() 50 | 51 | SCHEMA_MAP = { 52 | "message": Path("schema", "MessagingExperiment.schema.json"), 53 | "experiment": Path("schema", "NimbusExperiment.schema.json"), 54 | "action": Path("schema", "SpecialMessageActionSchemas.json"), 55 | "message-groups": Path("schema", "message-groups.schema.json"), 56 | } 57 | 58 | USAGE = """ 59 | Usage: 60 | validate.py TYPE PATH 61 | 62 | Where TYPE should be one of "message", "experiment", or "message-groups". 63 | 64 | Exmaple: 65 | validate.py message ./outgoing/cfr.json 66 | """ 67 | 68 | 69 | def load_schema(name): 70 | """Load a schema and cache it.""" 71 | if name in ALL_SCHEMAS: 72 | return ALL_SCHEMAS[name] 73 | 74 | with SCHEMA_MAP[name].open("r") as f: 75 | schema = json.load(f) 76 | 77 | if name == "experiment": 78 | # The new schema has a title "DesktopNimbusExperiment" instead of a self-ref. 79 | if "DesktopNimbusExperiment" in schema: 80 | schema = schema["DesktopNimbusExperiment"] 81 | 82 | ALL_SCHEMAS[name] = schema 83 | 84 | return ALL_SCHEMAS[name] 85 | 86 | 87 | def validate_item_targeting(item, for_exp=False): 88 | if "targeting" not in item and "filter_expression" not in item: 89 | return 90 | indentation = "\t" if for_exp else "" 91 | print("{}Validate targeting {}".format(indentation, item["id"])) 92 | for key in ["targeting", "filter_expression"]: 93 | jexl_expression = item.get(key) 94 | if jexl_expression is None: 95 | continue 96 | try: 97 | result = list(EVALUATOR.validate(jexl_expression)) 98 | if len(result) > 0: 99 | raise SyntaxError(result[0]) 100 | except SyntaxError as e: 101 | print(e) 102 | sys.exit(1) 103 | 104 | 105 | def validate_action(action, for_exp): 106 | """Validate a special message action""" 107 | indentation = "\t" if for_exp else "" 108 | schema = load_schema("action") 109 | 110 | print("{}Validate message action {}".format(indentation, action["type"])) 111 | 112 | try: 113 | jsonschema.validate(instance=action, schema=schema) 114 | except ValidationError as err: 115 | match = best_match([err]) 116 | print("{}Validation error: {}".format(indentation, match.message)) 117 | sys.exit(1) 118 | 119 | 120 | def extract_actions(message): 121 | """Extract all of the special message actions from the given message.""" 122 | message_type = message["template"] 123 | 124 | def _extract_cfr_doorhanger(): 125 | buttons_prop = message["content"].get("buttons", {}) 126 | if type(buttons_prop) is list: 127 | for button in buttons_prop: 128 | yield button["action"] 129 | else: 130 | for name, button in buttons_prop.items(): 131 | if name == "primary": 132 | yield button["action"] 133 | else: 134 | for sub_button in button: 135 | if "action" in sub_button: 136 | yield sub_button["action"] 137 | 138 | def _extract_cfr_urlbar_chiclet(): 139 | yield message["content"]["action"] 140 | 141 | def _extract_infobar(): 142 | for button in message["buttons"]: 143 | yield button["action"] 144 | 145 | def _extract_spotlight(): 146 | template = message["content"].get("template", "logo-and-content") 147 | 148 | if template == "logo-and-content": 149 | yield message["content"]["body"]["primary"]["action"] 150 | yield message["content"]["body"]["secondary"]["action"] 151 | elif template == "multistage": 152 | for screen in message["content"]["screens"]: 153 | for button_name in ["primary_button", "secondary_button"]: 154 | button = screen["content"].get(button_name) 155 | if button and button["action"].get("type"): 156 | yield button["action"] 157 | 158 | def _extract_toolbar_badge(): 159 | if "action" in message["content"]: 160 | yield message["content"]["action"] 161 | 162 | def _extract_pb_newtab(): 163 | if "promoButton" in message["content"]: 164 | yield message["content"]["promoButton"]["action"] 165 | 166 | extractors = { 167 | "cfr_doorhanger": _extract_cfr_doorhanger, 168 | "cfr_urlbar_chiclet": _extract_cfr_urlbar_chiclet, 169 | "infobar": _extract_infobar, 170 | "spotlight": _extract_spotlight, 171 | "toolbar_badge": _extract_toolbar_badge, 172 | "pb_newtab": _extract_pb_newtab, 173 | } 174 | 175 | try: 176 | yield from extractors[message_type]() 177 | except KeyError: 178 | return [] 179 | 180 | 181 | def validate_all_actions(message, for_exp=False): 182 | """Validate all actions in the given message.""" 183 | for action in extract_actions(message): 184 | validate_action(action, for_exp) 185 | 186 | 187 | def get_branch_message(branch): 188 | """Return the message from an experiment branch.""" 189 | # TODO: This does not support multi-feature experiments. 190 | feature = branch["feature"] 191 | feature_id = feature["featureId"] 192 | if feature_id == "cfr": 193 | value = feature["value"] 194 | if value is not None and "id" in value: 195 | return "cfr", feature["value"] 196 | return "cfr", None 197 | elif feature_id == "aboutwelcome": 198 | value = feature["value"] 199 | if value is None: 200 | return "onboarding-multistage", None 201 | elif "cards" in value: 202 | return "onboarding", value["cards"] 203 | elif "screens" in value: 204 | return "onboarding-multistage", value 205 | else: 206 | return "onboarding", None 207 | elif feature_id == "moments-page": 208 | if "id" in feature["value"]: 209 | return "moments-page", feature["value"] 210 | return "moments-page", None 211 | else: 212 | return None, None 213 | 214 | 215 | def validate_experiment_message_id(exp_slug, branch): 216 | """This validation enforces certain naming convention for some message 217 | types such as CFR in order to support the automated analysis feature of 218 | Jetstream. 219 | """ 220 | message_type, branch_message = get_branch_message(branch) 221 | if branch_message is None: 222 | return 223 | 224 | if message_type == "cfr": 225 | print(f"\tValidate experiment message ID for branch {branch['slug']}") 226 | 227 | assert branch_message["id"] == f"{exp_slug}:{branch['slug']}", ( 228 | f"Invalid CFR message ID {branch_message['id']}, " 229 | f"it should be named as {{experiment-slug}}:{{branch-slug}}" 230 | ) 231 | assert ( 232 | branch_message["content"]["bucket_id"] == f"{exp_slug}:{branch['slug']}" 233 | ), ( 234 | f"Invalid CFR bucket_id {branch_message['content']['bucket_id']}, " 235 | f"it should be named as {{experiment-slug}}:{{branch-slug}}" 236 | ) 237 | 238 | 239 | def validate_experiment(item): 240 | for branch in item.get("branches"): 241 | message_type, branch_message = get_branch_message(branch) 242 | if branch_message is None: 243 | print("\tSkip branch {} because it's empty".format(branch.get("slug"))) 244 | continue 245 | 246 | schema = load_schema(message_type) 247 | try: 248 | branch_message_list = ( 249 | branch_message if isinstance(branch_message, list) else [branch_message] 250 | ) 251 | for message in branch_message_list: 252 | jsonschema.validate(instance=message, schema=schema) 253 | print( 254 | "\tValidate {} with schema {}".format(branch.get("slug"), message_type) 255 | ) 256 | except ValidationError as err: 257 | match = best_match([err]) 258 | print("\tValidation error: {}".format(match.message)) 259 | sys.exit(1) 260 | 261 | # Validate the targeting JEXL if any 262 | validate_item_targeting(branch_message, True) 263 | 264 | # Validate all the message actions 265 | validate_all_actions(branch_message, True) 266 | 267 | # Validate the message_id naming 268 | validate_experiment_message_id(item["slug"], branch) 269 | 270 | 271 | def validate_message(message): 272 | """Validate the components of a message""" 273 | validate_item_targeting(message) 274 | validate_all_actions(message) 275 | 276 | 277 | def validate(schema_name, src_path): 278 | schema = load_schema(schema_name) 279 | resolver = NestedRefResolver(schema) 280 | 281 | print(f"Validating {src_path} with {schema_name} schema...") 282 | with open(src_path, "r") as f: 283 | items = json.load(f) 284 | 285 | if items is not None: 286 | for item in items: 287 | print(f"Valiating schema for {item['id']}") 288 | 289 | try: 290 | jsonschema.validate( 291 | instance=item, 292 | schema=schema, 293 | resolver=resolver, 294 | ) 295 | except ValidationError as err: 296 | match = best_match([err]) 297 | print(f"Validation error: {match.message}") 298 | sys.exit(1) 299 | 300 | if schema_name == "message": 301 | validate_message(item) 302 | elif schema_name == "experiment": 303 | validate_experiment(item) 304 | elif schema_name == "message-group": 305 | pass 306 | elif schema_name == "action": 307 | pass 308 | 309 | print("PASS") 310 | 311 | 312 | if __name__ == "__main__": 313 | if len(sys.argv) < 3: 314 | print(USAGE) 315 | sys.exit(1) 316 | 317 | validate(sys.argv[1], sys.argv[2]) 318 | --------------------------------------------------------------------------------