├── .gitattributes ├── .github ├── CODEOWNERS ├── ci │ └── griffin-study-utils.py ├── matchers │ └── actionlint-matcher.json ├── renovate.json └── workflows │ ├── delete-test-seed.yml │ ├── deploy-to-production.yml │ ├── generate-test-seed.yml │ ├── lint-github-actions.yml │ ├── scripts │ └── comment.js │ ├── test-src.yml │ └── upsert-study.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .semgrepignore ├── LICENSE ├── README.md ├── crypto ├── .gitignore ├── crypto_util.go └── publickey ├── package-lock.json ├── package.json ├── src ├── .eslintrc.js ├── .gitignore ├── README.md ├── base │ ├── containers │ │ └── default_map.ts │ ├── path_utils.test.ts │ └── path_utils.ts ├── config.js ├── core │ ├── base_types.ts │ ├── blocklists.ts │ ├── study_processor.ts │ ├── summary.ts │ ├── url_utils.ts │ └── version.ts ├── finch_tracker │ ├── main.ts │ ├── node_utils.ts │ ├── tracker_lib.test.ts │ └── tracker_lib.ts ├── jest.config.js ├── proto │ ├── README.md │ ├── client_variations.proto │ ├── generated │ │ └── .gitignore │ ├── layer.proto │ ├── study.proto │ ├── study.proto.protobuf-ts.patch │ └── variations_seed.proto ├── scripts │ ├── clean.ts │ ├── generate_proto_ts.ts │ ├── lint.ts │ └── tsconfig.json ├── seed_tools │ ├── README.md │ ├── commands │ │ ├── compare_seeds.ts │ │ ├── create.test.ts │ │ ├── create.ts │ │ ├── lint.test.ts │ │ ├── lint.ts │ │ └── split_seed_json.ts │ ├── seed_tools.ts │ └── utils │ │ ├── converters.ts │ │ ├── diff_strings.test.ts │ │ ├── diff_strings.ts │ │ ├── file_utils.ts │ │ ├── legacy_json_to_seed.ts │ │ ├── perf_tools.ts │ │ ├── processed_study.ts │ │ ├── seed_validation.test.ts │ │ ├── seed_validation.ts │ │ ├── studies_to_seed.ts │ │ ├── study_filter_utils.test.ts │ │ ├── study_filter_utils.ts │ │ ├── study_json_utils.test.ts │ │ ├── study_json_utils.ts │ │ ├── study_validation.test.ts │ │ ├── study_validation.ts │ │ ├── version.test.ts │ │ └── version.ts ├── test │ └── data │ │ ├── invalid_seed.bin │ │ ├── invalid_seeds │ │ ├── test1 │ │ │ ├── expected_error.txt │ │ │ └── studies │ │ │ │ ├── AnotherTestStudy.json5 │ │ │ │ └── TestStudy.json5 │ │ └── test2 │ │ │ ├── expected_error.txt │ │ │ └── studies │ │ │ ├── AnotherTestStudy.json5 │ │ │ ├── TestStudy.json5 │ │ │ └── ThirdStudy.json5 │ │ ├── invalid_studies │ │ ├── test1 │ │ │ ├── expected_errors.txt │ │ │ └── studies │ │ │ │ └── TestStudy.json5 │ │ ├── test2 │ │ │ ├── expected_errors.txt │ │ │ └── studies │ │ │ │ └── TestStudy.json5 │ │ ├── test3 │ │ │ ├── expected_errors.txt │ │ │ └── studies │ │ │ │ └── TestStudy.json5 │ │ └── test4 │ │ │ ├── expected_errors.txt │ │ │ └── studies │ │ │ └── study_with_bad_filename_&.json5 │ │ ├── perf_seeds │ │ ├── legacy_seed │ │ │ └── expected_seed.json │ │ └── test1 │ │ │ ├── expected_seed.json │ │ │ └── studies │ │ │ └── TestStudy.json5 │ │ ├── seed1.bin │ │ ├── seed1.bin.processing_expectations │ │ ├── seed1.json │ │ ├── set_seed_version │ │ ├── expected_seed.bin │ │ ├── expected_seed.json │ │ └── studies │ │ │ └── TestStudy.json5 │ │ ├── unformatted_studies │ │ └── test1 │ │ │ ├── expected_output.txt │ │ │ └── studies │ │ │ └── TestStudy.json5 │ │ └── valid_seeds │ │ ├── country_case │ │ ├── expected_seed.json │ │ └── studies │ │ │ └── TestStudy.json5 │ │ ├── test1 │ │ ├── expected_seed.json │ │ └── studies │ │ │ └── TestStudy.json5 │ │ ├── test2 │ │ ├── expected_seed.json │ │ └── studies │ │ │ ├── AnotherTestStudy.json5 │ │ │ └── TestStudy.json5 │ │ └── test3 │ │ ├── expected_seed.json │ │ └── studies │ │ ├── AnotherTestStudy.json5 │ │ └── TestStudy.json5 └── web │ ├── .gitignore │ ├── app │ ├── app.tsx │ ├── experiment_model.ts │ ├── index.tsx │ ├── search_param_manager.ts │ ├── seed_loader.ts │ ├── study_model.test.ts │ └── study_model.ts │ ├── css │ ├── bootstrap.min.css │ └── style.css │ ├── img │ └── brave_lion.svg │ ├── public │ └── index.html │ ├── tsconfig.json │ └── webpack.config.js ├── studies ├── AllowCertainClientHintsStudy.json5 ├── AndroidOnboardingStudy.json5 ├── BraveAIChatDefaultModelStudy.json5 ├── BraveAIChatEnabledStudy.json5 ├── BraveAdblockExperimentalListDefaultStudy.json5 ├── BraveAdblockMobileNotificationsListDefault.json5 ├── BraveAds.CreativeAdModelBasedPredictorRecencyStudy.json5 ├── BraveAdsAdEventStudy.json5 ├── BraveAdsConversionsStudy.json5 ├── BraveAdsExclusionRulesStudy.json5 ├── BraveAdsNewTabPageAdsStudy.json5 ├── BraveAdsRedeemRewardConfirmationStudy.json5 ├── BraveAdsSiteVisitStudy.json5 ├── BraveAdsTextClassificationPageProbabilitiesStudy.json5 ├── BraveAggressiveModeRetirementExperiment.json5 ├── BraveAutoTranslateStudy.json5 ├── BraveCleanupSessionCookiesOnSessionRestore.json5 ├── BraveDayZeroStudy.json5 ├── BraveDebounceStudy.json5 ├── BraveFallbackDoHStudy.json5 ├── BraveFeedUpdateStudy.json5 ├── BraveForgetFirstPartyStorage.json5 ├── BraveGoogleSignInPermissionStudy.json5 ├── BraveHorizontalTabsUpdateEnabledStudy.json5 ├── BraveLocalhostAccessPermissionStudy.json5 ├── BraveP3AConstellationEnabled.json5 ├── BraveP3AJSONOtherDeprecation.json5 ├── BraveP3ANebulaNightlyBeta.json5 ├── BraveP3ANebulaRelease.json5 ├── BraveP3ATypicalJSONDeprecationEnabled.json5 ├── BraveRequestOTRTabRolloutStudy.json5 ├── BraveRewardsAllowSelfCustodyProvidersStudy.json5 ├── BraveRewardsNewRewardsUIStudy.json5 ├── BraveRewardsPlatformCreatorDetectionStudy.json5 ├── BraveRewardsWebUiPanelStudy.json5 ├── BraveRoundTimeStampsStudy.json5 ├── BraveScreenFingerprintingBlockerStudy.json5 ├── BraveSearchAdStudy.json5 ├── BraveSearchPromotionBannerStudy.json5 ├── BraveSearchPromotionBannerStudyOnStable.json5 ├── BraveSearchPromotionButtonStudy.json5 ├── BraveShredFeatureStudy.json5 ├── BraveSplitViewStudy.json5 ├── BraveTranslateStudy.json5 ├── BraveTranslateiOSStudy.json5 ├── BraveUAStudy.json5 ├── BraveWalletAnkrBalancesEnabled.json5 ├── BraveWebViewRoundedCornersStudy.json5 ├── BraveWebcompatExceptionsServiceReleaseStudy.json5 ├── BraveWebcompatExceptionsServiceStudy.json5 ├── CanvasKillSwitches.json5 ├── ClampPlatformVersionClientHint.json5 ├── CloseWatcher.json5 ├── CollectWebGPUSupportMetricsKillSwitch.json5 ├── CookieListDefaultStudy.json5 ├── CosmeticFilteringChildFramesStudy.json5 ├── CrossPlatformVPNStudy.json5 ├── Default1pBlockingStudy.json5 ├── DefaultBraveCommandsStudy.json5 ├── DefaultBraveOmniboxMoreHistoryStudy.json5 ├── DefaultPassthroughCommandDecoderStudy.json5 ├── DefaultPlaylistStudy.json5 ├── DisableCnameUncloakingForAndroid.json5 ├── DisableReduceLanguage.json5 ├── EphemeralStorageStudy.json5 ├── ExtensionsManifestV2Study.json5 ├── ExtensionsManifestV2StudyRelease.json5 ├── HangWatcher.json5 ├── HeapProfilingMacKillSwitch.json5 ├── InnerHTMLParserFastpathStudy.json5 ├── LowerCaseIntentSchemesKillSwitch.json5 ├── MacCoreLocationBackendStudy.json5 ├── MetricsAndCrashSampling.json5 ├── ModuleFileNamePatchStudy.json5 ├── ModuleFileNamePatchStudyRelease.json5 ├── NavigationThreadingOptimizationsCompatOldVersions.json5 ├── NewiOSMenuUIStudy.json5 ├── NewiOSPlaylistUIStudy.json5 ├── PartitionVisitedLinkDatabaseWithSelfLinks.json5 ├── PartitionedCookies.json5 ├── PostFREFixMetricsAndCrashSampling.json5 ├── PrettyPrintJSONDocument.json5 ├── PrivateNetworkAccessKillswitch.json5 ├── RasterInducingScrollKillSwitch.json5 ├── RendererAllocatesImagesKillSwitch.json5 ├── SmilAutoSuspendOnLagKillSwitch.json5 ├── Speedreader TTS.json5 ├── TabStripLayoutOptimization.json5 ├── UndecryptablePasswords.json5 ├── UseFreedesktopSecretKeyProviderKillSwitch.json5 ├── UseWritePixelsYUV.json5 ├── UserActivityStudy.json5 ├── V8StoreStoreEliminationKillSwitch.json5 ├── WebDiscoveryNativeStudy.json5 ├── WhatsNewStudy.json5 ├── WorkaroundNewWindowFlash.json5 └── iOSChromiumWebViewsStudy.json5 └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bin binary 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # The order is important. The last matching pattern takes the most precedence. 2 | 3 | # Griffin maintainers are responsible for all machinery in this repository. 4 | * @brave/griffin-maintainers 5 | 6 | # The seed directory is deprecated and no longer used for the seed generation. 7 | # However, it is used by the perf infrastructure to access the Griffin seed at 8 | # some point in the past. Lock it to Griffin maintainers to prevent any 9 | # unrelated changes. 10 | /seed/** @brave/griffin-maintainers 11 | 12 | # The studies are owned by the uplift team as any change directly affects public 13 | # builds. It's important to update studies in a controlled manner. 14 | /studies/*.json5 @brave/uplift-approvers 15 | -------------------------------------------------------------------------------- /.github/ci/griffin-study-utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import sys 4 | 5 | 6 | def _load_studies(filename="seed/seed.json"): 7 | with open(filename) as fh: 8 | studies = json.load(fh) 9 | return studies 10 | 11 | 12 | def _save_studies(studies, filename="seed/seed.json"): 13 | with open(filename, "w") as fh: 14 | json.dump(studies, fh, indent=4) 15 | # Many editors such as GitHub Web add a newline at the end of the file. 16 | # Append it here too to prevent this as a change in `git diff`. 17 | fh.write("\n") 18 | 19 | 20 | def _create_study( 21 | name: str, 22 | enable_feature: str, 23 | probability_enabled: int, 24 | channel: list[str], 25 | platform: list[str], 26 | min_version: str, 27 | ) -> dict: 28 | study = { 29 | "name": name, 30 | "experiments": [ 31 | { 32 | "name": "Enabled", 33 | "probability_weight": probability_enabled, 34 | "feature_association": {"enable_feature": [enable_feature]}, 35 | }, 36 | {"name": "Default", "probability_weight": 100 - probability_enabled}, 37 | ], 38 | "filter": { 39 | "channel": channel, 40 | "platform": platform, 41 | }, 42 | } 43 | if min_version: 44 | study["filter"]["min_version"] = min_version 45 | return study 46 | 47 | 48 | def _upsert_study( 49 | name: str, 50 | enable_feature: str, 51 | probability_enabled: int, 52 | channel: list[str], 53 | platform: list[str], 54 | min_version: str, 55 | ): 56 | rawstudies = _load_studies() 57 | studies: list[dict] = rawstudies["studies"] 58 | study_to_add = _create_study( 59 | name, 60 | enable_feature, 61 | probability_enabled, 62 | channel, 63 | platform, 64 | min_version, 65 | ) 66 | existing_study = False 67 | for idx, study in enumerate(studies): 68 | if study["name"] == name: 69 | existing_study = True 70 | studies[idx] = study_to_add 71 | # print(studies[idx]) 72 | if not existing_study: 73 | studies.append(study_to_add) 74 | 75 | _save_studies(rawstudies) 76 | 77 | 78 | def fmt(): 79 | studies = _load_studies() 80 | _save_studies(studies) 81 | 82 | 83 | if __name__ == "__main__": 84 | args = sys.argv 85 | # Exec func and args from cli 86 | func = args[1] 87 | match func: 88 | case "upsert_study": 89 | if len(args) < 7: 90 | print("Insufficient arguments supplied! Needs:") 91 | print( 92 | "name enable_feature probability_enabled channel platform [min_version]" 93 | ) 94 | sys.exit(1) 95 | name = args[2] 96 | enable_feature = args[3] 97 | probability_enabled = args[4] 98 | channel = args[5] 99 | platform = args[6] 100 | min_version = "" 101 | if len(args) > 7: 102 | min_version = args[7] 103 | _upsert_study( 104 | name, 105 | enable_feature, 106 | probability_enabled=int(probability_enabled), 107 | channel=channel.split(","), 108 | platform=platform.split(","), 109 | min_version=min_version, 110 | ) 111 | case "fmt": 112 | fmt() 113 | case _: 114 | raise ValueError("Unrecognized function") 115 | -------------------------------------------------------------------------------- /.github/matchers/actionlint-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "actionlint", 5 | "pattern": [ 6 | { 7 | "regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "message": 4, 12 | "code": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>brave/renovate-config" 5 | ], 6 | "packageRules": [ 7 | { 8 | "groupName": "All non-breaking dependency updates", 9 | "matchUpdateTypes": [ 10 | "minor", 11 | "patch", 12 | "pin", 13 | "digest" 14 | ], 15 | "schedule": [ 16 | "before 6am on tuesday" 17 | ] 18 | }, 19 | { 20 | "groupName": "Major dependency updates", 21 | "matchUpdateTypes": [ 22 | "major" 23 | ], 24 | "schedule": [ 25 | "before 6am on tuesday" 26 | ] 27 | } 28 | ], 29 | "schedule": [ 30 | "before 6am on tuesday" 31 | ], 32 | "vulnerabilityAlerts": { 33 | "enabled": true, 34 | "groupName": "Security updates", 35 | "schedule": [ 36 | "at any time" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/delete-test-seed.yml: -------------------------------------------------------------------------------- 1 | name: Delete Test Seed 2 | 3 | on: 4 | pull_request_target: 5 | types: [closed] 6 | workflow_dispatch: 7 | inputs: 8 | pull_request_number: 9 | description: 'Pull Request Number' 10 | required: true 11 | type: number 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | env: 17 | REMOTE_SEED_PATH: 'pull/${{ github.event.pull_request.number || github.event.inputs.pull_request_number }}/seed' 18 | 19 | steps: 20 | - name: Delete test seed 21 | env: 22 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} 23 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} 24 | AWS_REGION: us-west-2 25 | CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} 26 | run: | 27 | if aws s3 ls "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" > /dev/null 2>&1; then 28 | aws s3 rm "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" 29 | aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/$REMOTE_SEED_PATH" 30 | fi 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy-to-production.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Production 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'src/**' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 16 | 17 | - name: Deploy static files 18 | env: 19 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} 20 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} 21 | AWS_REGION: us-west-2 22 | CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} 23 | run: | 24 | npm install 25 | npm run build 26 | cd ./src/web/public 27 | aws s3 sync . s3://brave-production-griffin-origin/ --delete --exclude '*' --include 'index.html' --include 'bundle/*' 28 | aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*" 29 | -------------------------------------------------------------------------------- /.github/workflows/lint-github-actions.yml: -------------------------------------------------------------------------------- 1 | name: Lint GitHub Actions 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/**' 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | 16 | - name: Check workflow files 17 | run: | 18 | echo "::add-matcher::.github/matchers/actionlint-matcher.json" 19 | docker run --rm \ 20 | --network none \ 21 | -v "$PWD/.git:/repo/.git:ro" \ 22 | -v "$PWD/.github/workflows:/repo/.github/workflows:ro" \ 23 | -w /repo \ 24 | rhysd/actionlint:1.7.1 -color 25 | shell: bash 26 | -------------------------------------------------------------------------------- /.github/workflows/scripts/comment.js: -------------------------------------------------------------------------------- 1 | // Creates or updates a single comment which is looked up by the workflow name. 2 | 3 | module.exports = async (github, context, commentBody) => { 4 | const uniqueCommentTag = ``; 5 | commentBody = `${commentBody}\n${uniqueCommentTag}`; 6 | 7 | const comments = await github.rest.issues.listComments({ 8 | issue_number: context.issue.number, 9 | owner: context.repo.owner, 10 | repo: context.repo.repo, 11 | }); 12 | 13 | const existingComment = comments.data.find((comment) => 14 | comment.body.includes(uniqueCommentTag), 15 | ); 16 | 17 | if (existingComment) { 18 | await github.rest.issues.updateComment({ 19 | comment_id: existingComment.id, 20 | owner: context.repo.owner, 21 | repo: context.repo.repo, 22 | body: commentBody, 23 | }); 24 | } else { 25 | await github.rest.issues.createComment({ 26 | issue_number: context.issue.number, 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | body: commentBody, 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /.github/workflows/test-src.yml: -------------------------------------------------------------------------------- 1 | name: Test Griffin Package 2 | 3 | on: 4 | pull_request: 5 | merge_group: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | with: 13 | fetch-depth: '0' # Used to test `seed_tools create --revision ` 14 | 15 | - name: npm ci 16 | run: npm ci 17 | 18 | - name: typecheck 19 | run: npm run typecheck 20 | 21 | - name: lint 22 | run: npm run lint 23 | 24 | - name: run tests 25 | run: npm run test 26 | 27 | - name: build 28 | run: npm run build 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.venv/ 2 | /.vscode 3 | /node_modules/ 4 | **/build/ 5 | 6 | seed.bin 7 | serialnumber 8 | *.json.failed 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.*/** 2 | !/.github/** 3 | src/proto/generated/* 4 | src/test/data/**/*unformatted* 5 | src/web/css/bootstrap.min.css 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | // This file is located at the root level, rather than within the src/ 7 | // directory, to enable formatting for all files across the project, not just 8 | // those within src/. 9 | 10 | /** @type {import("prettier").Config} */ 11 | module.exports = { 12 | plugins: ['prettier-plugin-organize-imports'], 13 | singleQuote: true, 14 | overrides: [ 15 | { 16 | files: '*.json', 17 | excludeFiles: 'tsconfig.json', 18 | options: { 19 | parser: 'json-stringify', 20 | }, 21 | }, 22 | { 23 | files: '*.json5', 24 | options: { 25 | // Sync with JSON5.stringify logic. 26 | plugins: ['prettier-plugin-multiline-arrays'], 27 | trailingComma: 'all', 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | *.test.ts 2 | *.test.js 3 | -------------------------------------------------------------------------------- /crypto/.gitignore: -------------------------------------------------------------------------------- 1 | privatekey.pem 2 | serialnumber 3 | -------------------------------------------------------------------------------- /crypto/crypto_util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/base64" 10 | "encoding/pem" 11 | "errors" 12 | "fmt" 13 | "io/ioutil" 14 | "os" 15 | ) 16 | 17 | // GenerateKeyPair creates both the private key to sign the seed file, as well 18 | // as the public key as used for `kPublicKey` in `variations_seed_store.cc`. 19 | func GenerateKeyPair() error { 20 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | privateKeyBytes, err := x509.MarshalECPrivateKey(privateKey) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | privateKeyPem := pem.EncodeToMemory( 31 | &pem.Block{ 32 | Type: "ECDSA PRIVATE KEY", 33 | Bytes: privateKeyBytes, 34 | }, 35 | ) 36 | 37 | fmt.Printf("%s\n", string(privateKeyPem)) 38 | 39 | err = ioutil.WriteFile("./privatekey.pem", []byte(privateKeyPem), 0644) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | publicKey := &privateKey.PublicKey 45 | publicKeyDer, err := x509.MarshalPKIXPublicKey(publicKey) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | fmt.Printf("public key: % x\n", publicKeyDer) 51 | return nil 52 | } 53 | 54 | // SignSeedData loads the seed file as created by `serialize_variations_seed.py` 55 | // and creates a base64 encoded signature over the sha256 digest, which will be 56 | // sent with the `X-Seed-Signature` header. 57 | // Uses the private key in `./privatekey`. If no private key exists one can be 58 | // generated via `go run ./crypto.go keygen` 59 | func SignSeedData() error { 60 | privateKeyPem, err := ioutil.ReadFile("./privatekey.pem") 61 | if err != nil { 62 | return err 63 | } 64 | 65 | privateKeyBlock, _ := pem.Decode([]byte(privateKeyPem)) 66 | if privateKeyBlock == nil { 67 | return errors.New("Error decoding PEM file") 68 | } 69 | 70 | privateKey, err := x509.ParseECPrivateKey(privateKeyBlock.Bytes) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | seedData, err := ioutil.ReadFile("./seed.bin") 76 | if err != nil { 77 | return err 78 | } 79 | 80 | digest := sha256.Sum256([]byte(seedData)) 81 | 82 | signature, err := ecdsa.SignASN1(rand.Reader, privateKey, digest[:]) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | signature64 := base64.StdEncoding.EncodeToString(signature) 88 | 89 | fmt.Printf("%s\n", signature64) 90 | return nil 91 | } 92 | 93 | func main() { 94 | usageInfo := "Usage: go run ./crypto.go [keygen|sign]" 95 | args := os.Args[1:] 96 | if len(args) == 0 { 97 | println(usageInfo) 98 | os.Exit(0) 99 | } 100 | 101 | cmd := args[0] 102 | var err error 103 | if cmd == "keygen" { 104 | err = GenerateKeyPair() 105 | } else if cmd == "sign" { 106 | err = SignSeedData() 107 | } else { 108 | println(usageInfo) 109 | os.Exit(0) 110 | } 111 | 112 | if err != nil { 113 | fmt.Fprintf(os.Stderr, "%v\n", err) 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crypto/publickey: -------------------------------------------------------------------------------- 1 | 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 2 | 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 3 | 0x04, 0xbb, 0x6e, 0xed, 0x61, 0xf1, 0xfb, 0xf5, 0x4c, 0xfe, 0xda, 0x7b, 0xad, 4 | 0xb6, 0x18, 0x27, 0x42, 0xa2, 0xbd, 0x14, 0x95, 0xb5, 0x11, 0x2d, 0xf4, 0xc4, 5 | 0x89, 0x63, 0x2f, 0x26, 0xa2, 0x18, 0xa1, 0x36, 0xe5, 0x6f, 0x38, 0x45, 0x5d, 6 | 0x40, 0x9a, 0x2a, 0x07, 0xbd, 0xcc, 0x35, 0x33, 0xa5, 0x51, 0xcf, 0x8d, 0xe8, 7 | 0xf7, 0x98, 0xa3, 0x69, 0xad, 0xe4, 0x88, 0xf9, 0xa1, 0x60, 0xc2, 0x6f, 0x84, -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brave-variations", 3 | "version": "0.0.1", 4 | "description": "Brave Variations", 5 | "scripts": { 6 | "build": "npm run build:proto && npm run build:web", 7 | "build:proto": "tsx src/scripts/generate_proto_ts.ts", 8 | "build:web": "webpack -c src/web/webpack.config.js --mode=production", 9 | "clean": "tsx src/scripts/clean.ts", 10 | "lint": "tsx src/scripts/lint.ts", 11 | "postinstall": "npm run typecheck:scripts && npm run build:proto", 12 | "seed_tools": "tsx src/seed_tools/seed_tools.ts", 13 | "serve:dev": "webpack serve -c src/web/webpack.config.js --hot --mode=development", 14 | "test": "jest --config=src/jest.config.js", 15 | "tracker": "tsx src/finch_tracker/main.ts", 16 | "typecheck": "tsc --noEmit", 17 | "typecheck:scripts": "tsc --noEmit --project src/scripts" 18 | }, 19 | "devDependencies": { 20 | "@commander-js/extra-typings": "13.1.0", 21 | "@jest/globals": "29.7.0", 22 | "@protobuf-ts/plugin": "2.11.0", 23 | "@types/diff": "5.2.3", 24 | "@types/jest": "29.5.14", 25 | "@types/node": "22.15.21", 26 | "@types/react": "18.3.22", 27 | "@types/react-dom": "18.3.7", 28 | "@typescript-eslint/eslint-plugin": "8.32.1", 29 | "commander": "13.1.0", 30 | "css-loader": "5.2.7", 31 | "eslint": "8.57.1", 32 | "eslint-config-prettier": "9.1.0", 33 | "eslint-plugin-licenses": "1.0.2", 34 | "eslint-plugin-react": "7.37.5", 35 | "file-loader": "6.2.0", 36 | "jest": "29.7.0", 37 | "prettier": "3.5.3", 38 | "prettier-plugin-multiline-arrays": "3.0.6", 39 | "prettier-plugin-organize-imports": "3.2.4", 40 | "style-loader": "3.3.4", 41 | "ts-jest": "29.3.4", 42 | "ts-loader": "9.5.2", 43 | "tsx": "4.19.4", 44 | "typescript": "5.8.3", 45 | "typescript-eslint": "8.32.1", 46 | "webpack": "5.99.9", 47 | "webpack-cli": "4.10.0", 48 | "webpack-dev-server": "4.15.2" 49 | }, 50 | "type": "commonjs", 51 | "license": "MPL-2.0", 52 | "moduleResolution": "node", 53 | "dependencies": { 54 | "@protobuf-ts/runtime": "2.11.0", 55 | "json5": "2.2.3", 56 | "react": "18.3.1", 57 | "react-dom": "18.3.1", 58 | "react-router": "6.30.1", 59 | "react-router-dom": "6.30.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | module.exports = { 7 | settings: { 8 | react: { 9 | version: 'detect', 10 | }, 11 | }, 12 | env: { 13 | browser: true, 14 | node: true, 15 | es6: true, 16 | }, 17 | extends: [ 18 | 'eslint:recommended', 19 | 'plugin:@typescript-eslint/recommended-type-checked', 20 | 'plugin:@typescript-eslint/stylistic-type-checked', 21 | 'prettier', 22 | 'plugin:react/recommended', 23 | ], 24 | plugins: ['licenses', 'react'], 25 | root: true, 26 | parserOptions: { 27 | project: '../tsconfig.json', 28 | tsconfigRootDir: __dirname, 29 | }, 30 | ignorePatterns: ['**/build/', '/proto/generated/', '/web/public/bundle/'], 31 | overrides: [ 32 | { 33 | extends: ['plugin:@typescript-eslint/disable-type-checked'], 34 | files: ['**/*.js'], 35 | }, 36 | ], 37 | rules: { 38 | '@typescript-eslint/no-explicit-any': 'off', 39 | '@typescript-eslint/no-unsafe-member-access': 'off', 40 | '@typescript-eslint/no-unsafe-argument': 'off', 41 | '@typescript-eslint/no-unsafe-call': 'off', 42 | '@typescript-eslint/no-unsafe-assignment': 'off', 43 | 44 | 'licenses/header': [ 45 | 2, 46 | { 47 | tryUseCreatedYear: true, 48 | comment: { 49 | allow: 'both', 50 | prefer: 'line', 51 | }, 52 | header: [ 53 | 'Copyright (c) {YEAR} The Brave Authors. All rights reserved.', 54 | 'This Source Code Form is subject to the terms of the Mozilla Public', 55 | 'License, v. 2.0. If a copy of the MPL was not distributed with this file,', 56 | 'You can obtain one at https://mozilla.org/MPL/2.0/.', 57 | ], 58 | }, 59 | ], 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | 3 | *.failed 4 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Griffin & Finch Tracker & Seed Tools 2 | 3 | ## Directory structure 4 | 5 | `core` is common Typescript code used by both Tracker and griffin.brave.com. 6 | 7 | `finch_tracker` - NodeJS/TS console app to track seed changes. See https://github.com/brave/finch-data-private/#finch-tracker 8 | 9 | `proto` chromium protobuf files describing seed data format. 10 | 11 | `seed_tools` seed generator and related tools to create the seed from `/studies` directory. 12 | 13 | `test` is supporting code/data to use in tests. 14 | 15 | `web` WebUI hosted on griffin.brave.com. It parses raw seed data and shows them in human readable format. Doesn't use any sophisticated backend, 100% code runs on the client side. 16 | 17 | ## Commands and actions 18 | 19 | `npm run build` to build everything 20 | `npm run lint -- --fix` run lint and try to fix all the problems 21 | `npm run test` to run tests (also used in CI) 22 | 23 | [deploy-to-production](https://github.com/brave/brave-variations/actions/workflows/deploy-to-production.yml) GH action to deploy a new version of griffin.brave.com 24 | 25 | `npm run tracker -- ` to run tracker app 26 | -------------------------------------------------------------------------------- /src/base/containers/default_map.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | export type DefaultFactory = (key?: K) => V; 7 | 8 | export default class DefaultMap extends Map { 9 | private readonly defaultFactory: DefaultFactory; 10 | 11 | constructor(defaultFactory: DefaultFactory) { 12 | super(); 13 | this.defaultFactory = defaultFactory; 14 | } 15 | 16 | get(key: K): V { 17 | let v: V | undefined = super.get(key); 18 | if (v === undefined) { 19 | v = this.defaultFactory(key); 20 | this.set(key, v); 21 | } 22 | return v; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/base/path_utils.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | import { expect, test } from '@jest/globals'; 6 | import * as fs from 'fs'; 7 | import * as path from 'path'; 8 | import * as path_utils from './path_utils'; 9 | 10 | const isWin32 = process.platform === 'win32'; 11 | 12 | test('wsPath', () => { 13 | const packageJsonPath = path_utils.wsPath('//package.json'); 14 | expect(fs.existsSync(packageJsonPath)).toBe(true); 15 | expect(packageJsonPath).toBe( 16 | path.normalize(path.join(path_utils.rootDir, 'package.json')), 17 | ); 18 | }); 19 | 20 | test('asPosix', () => { 21 | if (isWin32) { 22 | expect(path_utils.asPosix('foo\\bar')).toBe('foo/bar'); 23 | } 24 | expect(path_utils.asPosix('foo/bar')).toBe('foo/bar'); 25 | expect(path_utils.asPosix('foo//bar')).toBe('foo/bar'); 26 | }); 27 | -------------------------------------------------------------------------------- /src/base/path_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | import * as fs from 'fs'; 6 | import * as path from 'path'; 7 | 8 | // Use fs.realpathSync to normalize the path(__dirname could be c:\.. or C:\..). 9 | const isWin32 = process.platform === 'win32'; 10 | const dirName = isWin32 ? fs.realpathSync.native(__dirname) : __dirname; 11 | 12 | export const rootDir = path.normalize(path.join(dirName, '../../')); 13 | 14 | // Returns path in the workspace if starts with `//`. Workspace is the root 15 | // directory containing `package.json`. 16 | export function wsPath(p: string): string { 17 | if (p.startsWith('//')) { 18 | p = path.normalize(p.replace('//', rootDir)); 19 | } 20 | return p; 21 | } 22 | 23 | export function asPosix(p: string): string { 24 | p = path.normalize(p); 25 | if (isWin32) { 26 | p = p.replace(/\\/g, '/'); 27 | } 28 | return p; 29 | } 30 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | module.exports = { 6 | blocklistedFeatures: [ 7 | 'AutofillUseApi', 8 | 'Ukm', 9 | 'UkmSamplingRate', 10 | 'HappinessTrackingSurveysForDesktopWhatsNew', 11 | 'VariationsGoogleGroupFiltering', 12 | 'ExpiredHistogramLogic', 13 | 'UMANonUniformityLogNormal', 14 | 'PostFREFixMetricsReporting', 15 | 'UMAPseudoMetricsEffect', 16 | ], 17 | 18 | blocklistedStudies: [ 19 | 'UKM', 20 | 'MetricsAndCrashSampling', 21 | 'MetricsClearLogsOnClonedInstall', 22 | ], 23 | 24 | gpuRelatedFeatures: [ 25 | 'DefaultANGLEVulkan', 26 | 'DefaultPassthroughCommandDecoder', 27 | 'EnableDrDcVulkan', 28 | 'Vulkan', 29 | 'VulkanFromANGLE', 30 | 'VulkanV2', 31 | 'VulkanVMALargeHeapBlockSizeExperiment', 32 | ], 33 | 34 | channelId: 'C05S50MFHPE', // #finch-updates 35 | 36 | // Add your slack ID to get alerts about changing features. 37 | // Please note that alerts are only sent for changes in stable channel, 38 | // not in beta or dev. 39 | // To retrive it use Slack profile => Copy Member ID. 40 | alerts: [ 41 | { 42 | description: 'Kill switches changes detected', 43 | killSwitch: true, // matches to any kill switch change 44 | ids: [ 45 | 'U02DG0ATML3', // @matuchin 46 | 'UE87NRK2A', // @iefremov 47 | 'UB9PF4X5K', // @Terry 48 | ], 49 | }, 50 | { 51 | description: ':x: Processing errors detected', 52 | processingError: true, // matches to any processing error 53 | ids: [ 54 | 'U02DG0ATML3', // @matuchin 55 | 'UE87NRK2A', // @iefremov 56 | ], 57 | }, 58 | { 59 | description: 'GPU related changes detected', 60 | features: [ 61 | 'DefaultANGLEVulkan', 62 | 'DefaultPassthroughCommandDecoder', 63 | 'EnableDrDcVulkan', 64 | 'Vulkan', 65 | 'VulkanFromANGLE', 66 | 'VulkanV2', 67 | 'VulkanVMALargeHeapBlockSizeExperiment', 68 | ], 69 | ids: [ 70 | 'U0D73ULKD', // @serg 71 | ], 72 | }, 73 | { 74 | description: 'WebUSBBlocklist changes detected', 75 | features: ['WebUSBBlocklist'], 76 | ids: [ 77 | 'U02031KK8SY', // @shivan 78 | ], 79 | }, 80 | ], 81 | }; 82 | -------------------------------------------------------------------------------- /src/core/base_types.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | export enum SeedType { 7 | MAIN, // main Brave seed (brave-variations@main) 8 | UPSTREAM, // Chrome seed (Finch) 9 | } 10 | 11 | export class ProcessingOptions { 12 | // The Chromium used by the current stable Brave (i.e. cr117). Usually is 13 | // taken from API. 14 | // Studies that target to older versions are considered as outdated. 15 | minMajorVersion: number; 16 | 17 | // True if study is originated from Brave (Griffin), false for upstream. 18 | isBraveSeed: boolean; 19 | } 20 | -------------------------------------------------------------------------------- /src/core/blocklists.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | import * as config from '../config'; 6 | 7 | class Blocklist { 8 | private readonly regexps: RegExp[] = []; 9 | 10 | constructor(patterns: string[]) { 11 | for (const line of patterns) { 12 | if (line === '') continue; 13 | const len = line.length; 14 | if (len > 2 && line.startsWith('/') && line[len - 1] === '/') { 15 | this.regexps.push(new RegExp(line.substring(1, len - 2))); 16 | } else { 17 | this.regexps.push(new RegExp(`^${line}$`)); 18 | } 19 | } 20 | } 21 | 22 | matches(str: string): boolean { 23 | return this.regexps.find((v) => v.test(str)) !== undefined; 24 | } 25 | } 26 | 27 | const gStudyBlocklist = new Blocklist(config.blocklistedStudies); 28 | const gFeatureBlocklist = new Blocklist(config.blocklistedFeatures); 29 | 30 | export function isStudyNameBlocklisted(studyName: string): boolean { 31 | return gStudyBlocklist.matches(studyName); 32 | } 33 | 34 | export function isFeatureBlocklisted(featureName: string): boolean { 35 | return gFeatureBlocklist.matches(featureName); 36 | } 37 | -------------------------------------------------------------------------------- /src/core/url_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { SeedType } from './base_types'; 7 | 8 | export const variationsMainUrl = 'https://variations.brave.com/seed'; 9 | export const variationsUpstreamUrl = 10 | 'https://griffin.brave.com/finch-data-private/seed.bin'; 11 | 12 | // The API is used to detect the current Chromium major version (i.e. cr117). 13 | // windows-x64 is used just because it's the lagest desktop. 14 | export const getUsedChromiumVersionUrl = 15 | 'https://versions.brave.com/latest/release-windows-x64-chromium.version'; 16 | 17 | function makeSourceGraphUrl(query: string, repo: string, file: string) { 18 | return ( 19 | `https://sourcegraph.com/search?q=context:global` + 20 | `+${encodeURIComponent(query)}` + 21 | `+repo:${encodeURIComponent(repo)}` + 22 | `+file:${encodeURIComponent(file)}` 23 | ); 24 | } 25 | 26 | // Returns a sourcegraph.com link to search the given feature. 27 | export function getFeatureSearchUrl(feature: string): string { 28 | const BRAVE_CORE_OR_CHROME_REPO_PATTERN = 29 | '^(github\\.com/brave/brave-core|github\\.com/chromium/chromium)$'; 30 | const FILES_WITH_FEATURES = '.*(.cc|.h|.mm)(.patch)?'; 31 | 32 | return makeSourceGraphUrl( 33 | `/(BASE_FEATURE|BASE_DECLARE_FEATURE)` + 34 | `\\((\\s*\\w+,)?\\s*(k|\\")?${feature}(Feature)?(\\")?\\s*(,|\\))/`, 35 | BRAVE_CORE_OR_CHROME_REPO_PATTERN, 36 | FILES_WITH_FEATURES, 37 | ); 38 | } 39 | 40 | export function getGitHubStorageUrl(): string { 41 | return 'https://github.com/brave/finch-data-private'; 42 | } 43 | 44 | // Returns a link to see at the raw study config. 45 | export function getStudyRawConfigUrl( 46 | study: string, 47 | seedType: SeedType, 48 | ): string { 49 | if (seedType === SeedType.UPSTREAM) { 50 | return ( 51 | getGitHubStorageUrl() + `/blob/main/study/all-by-name/${study}.json5` 52 | ); 53 | } 54 | return ( 55 | 'https://github.com/search?type=code' + 56 | '&q=repo%3Abrave%2Fbrave-variations' + 57 | `+path%3Astudies%2F+%22name%3A+%27${encodeURIComponent(study)}%27%22` 58 | ); 59 | } 60 | 61 | // Returns a link to see the study config at griffin.brave.com. 62 | export function getGriffinUiUrl(study: string): string { 63 | return `https://griffin.brave.com/?seed=UPSTREAM&search=${study}`; 64 | } 65 | -------------------------------------------------------------------------------- /src/core/version.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | type VersionComponent = number | '*'; 6 | export type VersionPattern = [ 7 | VersionComponent, 8 | VersionComponent, 9 | VersionComponent, 10 | VersionComponent, 11 | ]; 12 | 13 | export type Version = [number, number, number, number]; 14 | 15 | export function matchesMaxVersion(ver: Version, max: VersionPattern): boolean { 16 | for (let i = 0; i < 4; i++) { 17 | const pattern = max[i]; 18 | if (pattern === '*') return true; 19 | if (ver[i] < pattern) return true; 20 | else if (ver[i] > pattern) return false; 21 | } 22 | return true; 23 | } 24 | 25 | export function parseVersionPattern(s: string): VersionPattern { 26 | const v = s.split('.', 4); 27 | const res: VersionPattern = ['*', '*', '*', '*']; 28 | for (let i = 0; i < 4; i++) 29 | if (v[i] !== undefined && v[i] !== '*') res[i] = parseInt(v[i]); 30 | 31 | return res; 32 | } 33 | -------------------------------------------------------------------------------- /src/finch_tracker/node_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | import * as https from 'https'; 6 | import * as path from 'path'; 7 | 8 | export async function downloadUrl(url: string): Promise { 9 | return await new Promise((resolve, reject) => { 10 | const data: any = []; 11 | https 12 | .get(url, (res) => { 13 | res.on('data', (chunk) => { 14 | data.push(chunk); 15 | }); 16 | res.on('end', () => { 17 | resolve(Buffer.concat(data)); 18 | }); 19 | }) 20 | .on('error', reject); 21 | }); 22 | } 23 | 24 | export function getSeedPath(storageDir: string): string { 25 | return path.join(storageDir, 'seed.bin'); 26 | } 27 | 28 | export function getStudyPath(storageDir: string): string { 29 | return path.join(storageDir, 'study'); 30 | } 31 | -------------------------------------------------------------------------------- /src/finch_tracker/tracker_lib.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { execSync } from 'child_process'; 7 | import * as fs from 'fs'; 8 | import * as path from 'path'; 9 | 10 | import { type ProcessingOptions } from '../core/base_types'; 11 | import { 12 | ProcessedStudy, 13 | StudyPriority, 14 | priorityToText, 15 | } from '../core/study_processor'; 16 | import { Study } from '../proto/generated/study'; 17 | import { VariationsSeed } from '../proto/generated/variations_seed'; 18 | import { writeStudyFile } from '../seed_tools/utils/study_json_utils'; 19 | import { downloadUrl, getSeedPath, getStudyPath } from './node_utils'; 20 | 21 | export async function fetchChromeSeedData(): Promise { 22 | const kChromeSeedUrl = 23 | 'https://clientservices.googleapis.com/chrome-variations/seed'; 24 | return await downloadUrl(kChromeSeedUrl); 25 | } 26 | 27 | // Groups studies by name and priority. 28 | export function groupStudies( 29 | seedData: Buffer, 30 | options: ProcessingOptions, 31 | ): Record { 32 | const map: Record = {}; 33 | const seed = VariationsSeed.fromBinary(seedData); 34 | const addStudy = (path: string, study: Study) => { 35 | const list = map[path]; 36 | if (list !== undefined) { 37 | list.push(study); 38 | } else { 39 | map[path] = [study]; 40 | } 41 | }; 42 | 43 | for (const study of seed.study) { 44 | const sanitizedName = path 45 | .normalize(study.name) 46 | .replace(/^(\.\.(\/|\\|$))+/, ''); 47 | const processed = new ProcessedStudy(study, options); 48 | processed.postProcessBeforeSerialization(); 49 | addStudy(`all-by-name/${sanitizedName}`, study); 50 | if (!processed.studyDetails.isOutdated()) { 51 | const priority = processed.getPriority(); 52 | if (priority > StudyPriority.NON_INTERESTING) 53 | addStudy(`${priorityToText(priority)}/${sanitizedName}`, study); 54 | } 55 | } 56 | return map; 57 | } 58 | 59 | // Makes a new git commit (if we have changes), returns the hash. 60 | export function commitAllChanges(directory: string): string | undefined { 61 | const utcDate = new Date().toUTCString(); 62 | const diff = execSync('git status --porcelain', { cwd: directory }); 63 | if (diff.length <= 2) { 64 | console.log('Nothing to commit'); 65 | return undefined; 66 | } 67 | execSync('git add -A', { cwd: directory }); 68 | execSync(`git commit -m "Update seed ${utcDate}"`, { cwd: directory }); 69 | const sha1 = execSync('git rev-parse HEAD', { cwd: directory }) 70 | .toString() 71 | .trim(); 72 | console.log('Changes committed, new commit hash', sha1); 73 | 74 | return sha1; 75 | } 76 | 77 | // Processes and serializes a given seed to disk (including grouping to 78 | // subdirectories/files). 79 | export async function storeDataToDirectory( 80 | seedData: Buffer, 81 | directory: string, 82 | options: ProcessingOptions, 83 | ): Promise { 84 | const studyDirectory = getStudyPath(directory); 85 | fs.rmSync(studyDirectory, { recursive: true, force: true }); 86 | const map = groupStudies(seedData, options); 87 | 88 | for (const [name, study] of Object.entries(map)) { 89 | const fileName = `${studyDirectory}/${name}.json5`; 90 | const dirname = path.dirname(fileName); 91 | fs.mkdirSync(dirname, { recursive: true }); 92 | await writeStudyFile(study, fileName, { isChromium: !options.isBraveSeed }); 93 | } 94 | 95 | // TODO: maybe start to use s3 instead of git one day? 96 | fs.writeFileSync(getSeedPath(directory), seedData); 97 | } 98 | -------------------------------------------------------------------------------- /src/jest.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 7 | module.exports = { 8 | preset: 'ts-jest', 9 | verbose: true, 10 | testPathIgnorePatterns: ['/finch_tracker/build/'], 11 | testEnvironment: 'node', 12 | moduleNameMapper: { 13 | '^src/(.*)$': '/$1', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/proto/README.md: -------------------------------------------------------------------------------- 1 | ### Protobuf-ts and `required` fields 2 | 3 | The `protobuf-ts` library is chosen for typescript-based seed generator as it 4 | provides a robust mechanism to detect and report unknown fields and invalid 5 | values when parsing content from an object-like representation (such as JSON). 6 | 7 | However, the library targets `proto3`, while Variations proto files use 8 | `proto2`, causing `required` fields with default values to be omitted during 9 | serialization. The only `required` fields in variations are `name` and 10 | `probability_weight`. `name` is always non-empty, but `probability_weight` can 11 | be `0`. To ensure `protobuf-ts` serializes `0`, `study.proto` is patched to make 12 | `probability_weight` be `optional` instead of `required`. This forces 13 | `protobuf-ts` to serialize it explicitly even if it's set to `0`. We guarantee 14 | the value is always set by processing studies after JSON parsing. 15 | -------------------------------------------------------------------------------- /src/proto/client_variations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // 5 | // Summary of client variations from experiments. 6 | 7 | syntax = "proto2"; 8 | 9 | option optimize_for = LITE_RUNTIME; 10 | option java_package = "org.chromium.components.variations"; 11 | 12 | package variations; 13 | 14 | // NOTE: If you update this proto, you'll also need to rebuild the JS parser for 15 | // devtools. See //components/variations/proto/devtools/BUILD.gn for details. 16 | message ClientVariations { 17 | // Active client experiment variation IDs. 18 | repeated int32 variation_id = 1; 19 | 20 | // Active client experiment variation IDs that trigger server-side behavior. 21 | repeated int32 trigger_variation_id = 3; 22 | } 23 | -------------------------------------------------------------------------------- /src/proto/generated/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.ts 3 | -------------------------------------------------------------------------------- /src/proto/study.proto.protobuf-ts.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/proto/study.proto b/src/proto/study.proto 2 | index 1117ec8..3cfb1e6 100644 3 | --- a/src/proto/study.proto 4 | +++ b/src/proto/study.proto 5 | @@ -93,7 +93,7 @@ message Study { 6 | 7 | // The cut of the total probability taken for this experiment (the x in 8 | // x / N, where N is the sum of all x’s). Ex: "50" 9 | - required uint32 probability_weight = 2; 10 | + optional uint32 probability_weight = 2; 11 | 12 | // Optional id used to uniquely identify this experiment for Google web 13 | // properties. 14 | -------------------------------------------------------------------------------- /src/proto/variations_seed.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Chromium Authors 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | syntax = "proto2"; 6 | 7 | option optimize_for = LITE_RUNTIME; 8 | option java_package = "org.chromium.components.variations"; 9 | 10 | package variations; 11 | 12 | import "study.proto"; 13 | import "layer.proto"; 14 | 15 | // The VariationsSeed is a protobuf response from the server that contains the 16 | // list of studies and a serial number to uniquely identify its contents. The 17 | // serial number allows the client to easily determine if the list of 18 | // experiments has changed from the previous VariationsSeed seen by the client. 19 | // 20 | // Next tag: 7 21 | message VariationsSeed { 22 | optional string serial_number = 1; 23 | repeated Study study = 2; 24 | 25 | // Lowercase ISO 3166-1 alpha-2 country code of the client, according to IP 26 | // address. Deprecated. 27 | optional string country_code = 3 [deprecated = true]; 28 | 29 | // A version string which identifies the version of the configuration files 30 | // that this seed was generated from. 31 | optional string version = 4; 32 | 33 | // Previous implementation of layers. Do not re-use. 34 | reserved 5; 35 | 36 | // A list of layers where each study can optionally be part of one, with 37 | // clients ensuring that only one study per layer is enabled. 38 | repeated Layer layers = 6; 39 | } 40 | -------------------------------------------------------------------------------- /src/scripts/clean.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { program } from '@commander-js/extra-typings'; 7 | import * as fs from 'fs'; 8 | 9 | const dirsToRemove = ['./src/finch_tracker/build', './src/web/public/bundle']; 10 | 11 | program.description('Cleans build directories').action(main).parse(); 12 | 13 | function main() { 14 | fs.readdirSync('src/proto/generated').forEach((file) => { 15 | if (file.startsWith('proto_bundle')) { 16 | fs.unlinkSync(`src/proto/generated/${file}`); 17 | } 18 | }); 19 | 20 | for (const dir of dirsToRemove) { 21 | if (fs.existsSync(dir)) { 22 | fs.rmSync(dir, { recursive: true }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/scripts/generate_proto_ts.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { program } from '@commander-js/extra-typings'; 7 | import { execSync } from 'child_process'; 8 | import * as fs from 'fs'; 9 | import { wsPath } from '../base/path_utils'; 10 | 11 | const protoDir = wsPath('//src/proto'); 12 | 13 | const protoFiles = [ 14 | `${protoDir}/client_variations.proto`, 15 | `${protoDir}/layer.proto`, 16 | `${protoDir}/study.proto`, 17 | `${protoDir}/variations_seed.proto`, 18 | ]; 19 | 20 | const protoGeneratedDir = wsPath('//src/proto/generated'); 21 | 22 | program 23 | .description('Generates Protobuf TS files') 24 | .option('--generate_patch', 'Generate patch for study.proto') 25 | .action(main) 26 | .parse(); 27 | 28 | interface Options { 29 | generate_patch?: true; 30 | } 31 | 32 | function main(options: Options) { 33 | if (options.generate_patch) { 34 | generateStudyProtoPatch(); 35 | return; 36 | } 37 | 38 | removeGeneratedFiles(); 39 | generateProtobufTs(); 40 | } 41 | 42 | function removeGeneratedFiles() { 43 | const files = fs.readdirSync(protoGeneratedDir); 44 | files.forEach((file) => { 45 | if (/.*\.(ts|js)$/.test(file)) { 46 | fs.unlinkSync(`${protoGeneratedDir}/${file}`); 47 | } 48 | }); 49 | } 50 | 51 | function generateProtobufTs() { 52 | try { 53 | // Apply study.proto patch to make protobuf-ts serialize probability_weight 54 | // field if its value set to 0. 55 | gitApplyStudyProtoPatch(); 56 | execSync( 57 | [ 58 | 'npx', 59 | '--', 60 | 'protoc', 61 | '--ts_out', 62 | protoGeneratedDir, 63 | '--proto_path', 64 | protoDir, 65 | ...protoFiles, 66 | '--ts_opt', 67 | 'use_proto_field_name', 68 | ].join(' '), 69 | ); 70 | } finally { 71 | gitRevertStudyProtoPatch(); 72 | } 73 | } 74 | 75 | function generateStudyProtoPatch() { 76 | fs.writeFileSync( 77 | `${protoDir}/study.proto.protobuf-ts.patch`, 78 | execSync(`git diff ${protoDir}/study.proto`, { 79 | encoding: 'buffer', 80 | }), 81 | ); 82 | } 83 | 84 | function gitApplyStudyProtoPatch() { 85 | execSync(`git apply ${protoDir}/study.proto.protobuf-ts.patch`, { 86 | cwd: protoDir, 87 | stdio: 'inherit', 88 | }); 89 | } 90 | 91 | function gitRevertStudyProtoPatch() { 92 | execSync(`git apply -R ${protoDir}/study.proto.protobuf-ts.patch`, { 93 | cwd: protoDir, 94 | stdio: 'inherit', 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /src/scripts/lint.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { program } from '@commander-js/extra-typings'; 7 | import { execSync } from 'child_process'; 8 | 9 | program 10 | .description( 11 | "The 'lint' command checks for code style issues in the modified files of the\n" + 12 | 'current branch. It identifies the changed files, filters them by supported\n' + 13 | 'types, and runs the corresponding linters. Use this command to ensure code\n' + 14 | 'quality before committing or pushing changes.', 15 | ) 16 | .option('-f, --fix', 'automatically fix problems') 17 | .action(main) 18 | .parse(); 19 | 20 | interface Options { 21 | fix?: true; 22 | } 23 | 24 | function main(options: Options) { 25 | process.exitCode = lintAllFiles(options); 26 | } 27 | 28 | // Returns an array of commands to lint all files. 29 | function getLintAllCommands(options: Options): string[] { 30 | return [ 31 | 'prettier . --ignore-unknown' + (options.fix ? ' --write' : ' --check'), 32 | 'eslint . --config src/.eslintrc.js' + (options.fix ? ' --fix' : ''), 33 | 'npm run seed_tools lint --' + (options.fix ? ' --fix' : ''), 34 | ]; 35 | } 36 | 37 | function lintAllFiles(options: Options): number { 38 | for (const command of getLintAllCommands(options)) { 39 | console.log(`Running: ${command}`); 40 | execSync(command, { stdio: 'inherit' }); 41 | } 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "include": ["./"] 4 | } 5 | -------------------------------------------------------------------------------- /src/seed_tools/README.md: -------------------------------------------------------------------------------- 1 | # Seed tools 2 | 3 | `npm run seed_tools -- ` 4 | 5 | ## Tools 6 | 7 | ### `compare_seeds` 8 | 9 | Compares two seed binary files and displays a human-readable diff. Used for safe 10 | migration from the python seed generator to the typescript seed generator. 11 | 12 | ### `create` 13 | 14 | Generates a `seed.bin` file from study files. 15 | 16 | ### `lint` 17 | 18 | Lints study files. 19 | 20 | ### `split_seed_json` 21 | 22 | Splits a legacy `seed.json` file into individual study files. 23 | 24 | ## Tools help 25 | 26 | Run to get available arguments and options: 27 | 28 | ```bash 29 | npm run seed_tools -- --help 30 | ``` 31 | -------------------------------------------------------------------------------- /src/seed_tools/commands/compare_seeds.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { Command } from '@commander-js/extra-typings'; 7 | import { promises as fs } from 'fs'; 8 | import { VariationsSeed } from '../../proto/generated/variations_seed'; 9 | import diffStrings from '../utils/diff_strings'; 10 | 11 | export default function createCommand() { 12 | return new Command('compare_seeds') 13 | .description('Compare two seed.bin') 14 | .argument('', 'seed1 file') 15 | .argument('', 'seed2 file') 16 | .option('--seed1_serialnumber_file ', 'seed1 serialnumber file') 17 | .option('--seed2_serialnumber_file ', 'seed2 serialnumber file') 18 | .action(main); 19 | } 20 | 21 | interface Options { 22 | seed1_serialnumber_file?: string; 23 | seed2_serialnumber_file?: string; 24 | } 25 | 26 | async function main( 27 | seed1FilePath: string, 28 | seed2FilePath: string, 29 | options: Options, 30 | ) { 31 | const seed1Binary: Buffer = await fs.readFile(seed1FilePath); 32 | const seed2Binary: Buffer = await fs.readFile(seed2FilePath); 33 | const seed1Content = VariationsSeed.fromBinary(seed1Binary); 34 | const seed2Content = VariationsSeed.fromBinary(seed2Binary); 35 | 36 | const seed1Json = VariationsSeed.toJson(seed1Content, { 37 | emitDefaultValues: false, 38 | enumAsInteger: false, 39 | useProtoFieldName: true, 40 | }); 41 | 42 | const seed2Json = VariationsSeed.toJson(seed2Content, { 43 | emitDefaultValues: false, 44 | enumAsInteger: false, 45 | useProtoFieldName: true, 46 | }); 47 | 48 | const seed1JsonString = JSON.stringify(seed1Json, null, 2); 49 | const seed2JsonString = JSON.stringify(seed2Json, null, 2); 50 | 51 | if (seed1JsonString !== seed2JsonString) { 52 | console.error('Seeds are different'); 53 | console.error( 54 | await diffStrings( 55 | seed1JsonString, 56 | seed2JsonString, 57 | seed1FilePath, 58 | seed2FilePath, 59 | ), 60 | ); 61 | process.exit(1); 62 | } 63 | 64 | if (!seed1Binary.equals(seed2Binary)) { 65 | console.error('Seeds semantically equal but binary different'); 66 | process.exit(1); 67 | } 68 | 69 | if (options.seed1_serialnumber_file !== undefined) { 70 | const seed1Serialnumber: string = await fs.readFile( 71 | options.seed1_serialnumber_file, 72 | 'utf8', 73 | ); 74 | if (seed1Content.serial_number !== seed1Serialnumber) { 75 | console.error('Seed1 serial number does not match'); 76 | process.exit(1); 77 | } 78 | } 79 | 80 | if (options.seed2_serialnumber_file !== undefined) { 81 | const seed2Serialnumber: string = await fs.readFile( 82 | options.seed2_serialnumber_file, 83 | 'utf8', 84 | ); 85 | if (seed2Content.serial_number !== seed2Serialnumber) { 86 | console.error('Seed2 serial number does not match'); 87 | process.exit(1); 88 | } 89 | } 90 | 91 | console.log('Seeds are equal'); 92 | } 93 | -------------------------------------------------------------------------------- /src/seed_tools/commands/create.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { Command } from '@commander-js/extra-typings'; 7 | import { createHash } from 'crypto'; 8 | import { promises as fs } from 'fs'; 9 | import { VariationsSeed } from '../../proto/generated/variations_seed'; 10 | import { retainMostProbableExperiments } from '../utils/perf_tools'; 11 | import { readStudiesToSeed } from '../utils/studies_to_seed'; 12 | 13 | export default function createCommand() { 14 | return new Command('create') 15 | .description('Create seed.bin from study files') 16 | .argument( 17 | '[studies_dir]', 18 | 'path to the directory containing study files', 19 | 'studies', 20 | ) 21 | .argument('[output_seed_file]', 'output seed file', 'seed.bin') 22 | .option('--mock_serial_number ', 'mock serial number') 23 | .option( 24 | '--output_serial_number_file ', 25 | 'file path to write the seed serial number', 26 | './serialnumber', 27 | ) 28 | .option( 29 | '--perf_mode', 30 | 'Retains only the most probable experiment in each study.' + 31 | 'Used in the perf tests.', 32 | ) 33 | .option('--revision ', 'Generate seed for a particular revision.') 34 | .option('--version ', 'seed version to set') 35 | .action(createSeed); 36 | } 37 | 38 | interface Options { 39 | mock_serial_number?: string; 40 | output_serial_number_file: string; 41 | perf_mode?: boolean; 42 | revision?: string; 43 | version?: string; 44 | } 45 | 46 | async function createSeed( 47 | studiesDir: string, 48 | outputSeedFile: string, 49 | options: Options, 50 | ) { 51 | const { variationsSeed, errors } = await readStudiesToSeed( 52 | studiesDir, 53 | false, 54 | options.revision, 55 | ); 56 | 57 | if (errors.length > 0) { 58 | console.error(`Seed validation errors:\n${errors.join('\n---\n')}`); 59 | process.exit(1); 60 | } 61 | 62 | const serialNumber = 63 | options.mock_serial_number ?? generateSerialNumber(variationsSeed); 64 | variationsSeed.serial_number = serialNumber; 65 | 66 | variationsSeed.version = options.version ?? '1'; 67 | 68 | console.log('Seed study count:', variationsSeed.study.length); 69 | if (options.perf_mode) { 70 | retainMostProbableExperiments(variationsSeed); 71 | } 72 | const seedBinary = VariationsSeed.toBinary(variationsSeed); 73 | await fs.writeFile(outputSeedFile, seedBinary); 74 | await fs.writeFile(options.output_serial_number_file, serialNumber); 75 | console.log(outputSeedFile, 'created with serial number', serialNumber); 76 | } 77 | 78 | function generateSerialNumber(variationsSeed: VariationsSeed): string { 79 | const seedBinary = VariationsSeed.toBinary(variationsSeed); 80 | const timestamp = String(Date.now()); 81 | const hash = createHash('md5') 82 | .update(seedBinary) 83 | .update(timestamp) 84 | .digest('hex'); 85 | return hash; 86 | } 87 | -------------------------------------------------------------------------------- /src/seed_tools/commands/lint.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { Command } from '@commander-js/extra-typings'; 7 | import { readStudiesToSeed } from '../utils/studies_to_seed'; 8 | 9 | export default function createCommand() { 10 | return new Command('lint') 11 | .description('Lint study files without creating seed.bin') 12 | .argument( 13 | '[studies_dir]', 14 | 'path to the directory containing study files', 15 | 'studies', 16 | ) 17 | .option('--fix', 'fix format errors in-place') 18 | .action(lintStudies); 19 | } 20 | 21 | interface Options { 22 | fix?: true; 23 | } 24 | 25 | async function lintStudies(studiesDir: string, options: Options) { 26 | const { variationsSeed, errors } = await readStudiesToSeed( 27 | studiesDir, 28 | options.fix, 29 | ); 30 | 31 | if (errors.length > 0) { 32 | console.error(`Lint errors:\n${errors.join('\n---\n')}`); 33 | process.exit(1); 34 | } 35 | 36 | console.log('Lint successful. Study count:', variationsSeed.study.length); 37 | } 38 | -------------------------------------------------------------------------------- /src/seed_tools/commands/split_seed_json.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { Command } from '@commander-js/extra-typings'; 7 | import { promises as fs } from 'fs'; 8 | import { parseLegacySeedJson } from '../utils/legacy_json_to_seed'; 9 | import * as study_json_utils from '../utils/study_json_utils'; 10 | 11 | export default function createCommand() { 12 | return new Command('split_seed_json') 13 | .description('Split seed.json into study files') 14 | .argument('', 'path to seed.json') 15 | .argument('', 'output directory') 16 | .action(main); 17 | } 18 | 19 | async function main(seedPath: string, outputDir: string) { 20 | const seedContent = await fs.readFile(seedPath, 'utf8'); 21 | const { studiesMap } = parseLegacySeedJson(seedContent); 22 | await fs.mkdir(outputDir, { recursive: true }); 23 | 24 | // Remove all files in the output directory. 25 | const dirEntries = await fs.readdir(outputDir, { withFileTypes: true }); 26 | for (const dirEntry of dirEntries) { 27 | if (dirEntry.isFile()) { 28 | await fs.unlink(`${outputDir}/${dirEntry.name}`); 29 | } 30 | } 31 | 32 | // Write study files. 33 | for (const study of studiesMap) { 34 | const studyName = study[0]; 35 | const studyArray = study[1]; 36 | const studyFile = `${outputDir}/${studyName}.json5`; 37 | await study_json_utils.writeStudyFile(studyArray, studyFile); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/seed_tools/seed_tools.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { program } from '@commander-js/extra-typings'; 7 | 8 | import compare_seeds from './commands/compare_seeds'; 9 | import create from './commands/create'; 10 | import lint from './commands/lint'; 11 | import split_seed_json from './commands/split_seed_json'; 12 | 13 | program 14 | .name('seed_tools') 15 | .description('Seed tools for manipulating study files.') 16 | .addCommand(compare_seeds()) 17 | .addCommand(create()) 18 | .addCommand(lint()) 19 | .addCommand(split_seed_json()) 20 | .parse(); 21 | -------------------------------------------------------------------------------- /src/seed_tools/utils/converters.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | export function channelToString(channel: string, isChromium: boolean): string { 7 | if (isChromium) return channel; 8 | 9 | switch (channel) { 10 | case 'CANARY': 11 | return 'NIGHTLY'; 12 | case 'STABLE': 13 | return 'RELEASE'; 14 | } 15 | return channel; 16 | } 17 | 18 | export function platformToString(platform: string): string { 19 | return platform.replace(/^PLATFORM_/, ''); 20 | } 21 | 22 | export function stringToChannel(channel: string): string { 23 | switch (channel) { 24 | case 'NIGHTLY': 25 | return 'CANARY'; 26 | case 'RELEASE': 27 | return 'STABLE'; 28 | } 29 | return channel; 30 | } 31 | 32 | export function stringToPlatform(platform: string): string { 33 | return `PLATFORM_${platform}`; 34 | } 35 | -------------------------------------------------------------------------------- /src/seed_tools/utils/diff_strings.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import diffStrings from './diff_strings'; 7 | 8 | describe('diffStrings', () => { 9 | it('should return the diff between two strings', async () => { 10 | const string1 = 'Hello, world!'; 11 | const string2 = 'Hello, brave!'; 12 | const displayFileName1 = 'file1-test.txt'; 13 | const displayFileName2 = 'file2-test.txt'; 14 | 15 | const result = await diffStrings( 16 | string1, 17 | string2, 18 | displayFileName1, 19 | displayFileName2, 20 | ); 21 | 22 | expect(result).toContain(displayFileName1); 23 | expect(result).toContain(displayFileName2); 24 | expect(result).toContain('-Hello, world!'); 25 | expect(result).toContain('+Hello, brave!'); 26 | }); 27 | 28 | it('should return empty diff between two equal strings', async () => { 29 | const string1 = 'Hello, brave!'; 30 | const string2 = 'Hello, brave!'; 31 | const displayFileName1 = 'file1-test.txt'; 32 | const displayFileName2 = 'file2-test.txt'; 33 | 34 | const result = await diffStrings( 35 | string1, 36 | string2, 37 | displayFileName1, 38 | displayFileName2, 39 | ); 40 | 41 | expect(result).toBe(''); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/seed_tools/utils/diff_strings.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { exec } from 'child_process'; 7 | import fs from 'fs/promises'; 8 | import os from 'os'; 9 | import path from 'path'; 10 | import { promisify } from 'util'; 11 | 12 | const execAsync = promisify(exec); 13 | 14 | export default async function diffStrings( 15 | string1: string, 16 | string2: string, 17 | displayFileName1: string, 18 | displayFileName2: string, 19 | ): Promise { 20 | const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'diffString-')); 21 | 22 | const dateStr = Date.now().toString(); 23 | const tmpFile1 = path 24 | .join(tmpDir, `file1-${dateStr}.txt`) 25 | .replaceAll('\\', '/'); 26 | const tmpFile2 = path 27 | .join(tmpDir, `file2-${dateStr}.txt`) 28 | .replaceAll('\\', '/'); 29 | 30 | // Write strings to temporary files. 31 | await fs.writeFile(tmpFile1, string1); 32 | await fs.writeFile(tmpFile2, string2); 33 | 34 | try { 35 | // Run git diff on the temporary files, on non-empty diff it will exit with 36 | // code 1. 37 | await execAsync( 38 | `git diff --no-index --src-prefix= --dst-prefix= -- ${tmpFile1} ${tmpFile2}`, 39 | { encoding: 'utf8' }, 40 | ); 41 | return ''; 42 | } catch (error) { 43 | // Handle the case where git diff returns 1 due to differences. 44 | if (error.code === 1) { 45 | // Remove root forward slashes from the temporary file paths as git diff 46 | // does not include them. 47 | const result = (error.stdout as string) 48 | .replaceAll(tmpFile1.replace(/^\//, ''), displayFileName1) 49 | .replaceAll(tmpFile2.replace(/^\//, ''), displayFileName2); 50 | 51 | return result; 52 | } else { 53 | // Rethrow the error for other exit codes. 54 | throw error; 55 | } 56 | } finally { 57 | await fs.rm(tmpDir, { recursive: true }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/seed_tools/utils/file_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import path from 'path'; 7 | 8 | export function getFileBaseName(filePath: string): string { 9 | return path.basename(filePath, path.extname(filePath)); 10 | } 11 | -------------------------------------------------------------------------------- /src/seed_tools/utils/legacy_json_to_seed.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import DefaultMap from '../../base/containers/default_map'; 7 | import { type Study } from '../../proto/generated/study'; 8 | import { VariationsSeed } from '../../proto/generated/variations_seed'; 9 | 10 | export function parseLegacySeedJson(seedContent: string): { 11 | parsedSeed: VariationsSeed; 12 | studiesMap: DefaultMap; 13 | } { 14 | const seedJson = preprocessSeedJson(JSON.parse(seedContent)); 15 | 16 | // Parse the seed as protobuf json representation. The parse will fail if any 17 | // unknown fields or values are present in the json. 18 | const parsedSeed = VariationsSeed.fromJson(seedJson, { 19 | ignoreUnknownFields: false, 20 | }); 21 | 22 | const studiesMap = new DefaultMap(() => []); 23 | for (const study of parsedSeed.study) { 24 | studiesMap.get(study.name).push(study); 25 | } 26 | 27 | return { parsedSeed, studiesMap }; 28 | } 29 | 30 | function preprocessSeedJson(json: any): any { 31 | json.study = json.studies; 32 | delete json.studies; 33 | 34 | for (const study of json.study) { 35 | if (study.experiments !== undefined) { 36 | study.experiment = study.experiments; 37 | delete study.experiments; 38 | } 39 | for (const experiment of study.experiment) { 40 | if (experiment.parameters !== undefined) { 41 | experiment.param = experiment.parameters; 42 | delete experiment.parameters; 43 | } 44 | } 45 | if (study.filter !== undefined) { 46 | if (study.filter.channel !== undefined) { 47 | study.filter.channel = study.filter.channel.map((channel: string) => { 48 | switch (channel) { 49 | case 'NIGHTLY': 50 | return 'CANARY'; 51 | case 'RELEASE': 52 | return 'STABLE'; 53 | default: 54 | return channel; 55 | } 56 | }); 57 | } 58 | if (study.filter.platform !== undefined) { 59 | study.filter.platform = study.filter.platform.map( 60 | (platform: string) => { 61 | return 'PLATFORM_' + platform; 62 | }, 63 | ); 64 | } 65 | } 66 | } 67 | 68 | return json; 69 | } 70 | -------------------------------------------------------------------------------- /src/seed_tools/utils/perf_tools.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { Study_Experiment } from '../../proto/generated/study'; 7 | import { VariationsSeed } from '../../proto/generated/variations_seed'; 8 | 9 | // A function to process each study by replacing the original list of 10 | // experiments with a list containing only the most probable experiment. 11 | export function retainMostProbableExperiments(seed: VariationsSeed) { 12 | for (const study of seed.study) { 13 | if (study.experiment.length < 1) continue; 14 | let best: Study_Experiment | undefined; 15 | for (const exp of study.experiment) { 16 | if ((exp.probability_weight ?? 0) > (best?.probability_weight ?? 0)) { 17 | best = exp; 18 | } 19 | } 20 | if (!best) { 21 | continue; 22 | } 23 | 24 | best.probability_weight = 100; 25 | study.experiment = [best]; 26 | 27 | // default_experiment_name should be set to work with 28 | // --enable-gpu-benchmarking chromium switch which enforces using 29 | // the default experiment for all studies. 30 | study.default_experiment_name = best.name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/seed_tools/utils/processed_study.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { type Study } from '../../proto/generated/study'; 7 | import * as study_filter_utils from './study_filter_utils'; 8 | 9 | export default class ProcessedStudy { 10 | readonly study: Study; 11 | readonly date_range: study_filter_utils.DateRange; 12 | readonly version_range: study_filter_utils.VersionRange; 13 | readonly os_version_range: study_filter_utils.VersionRange; 14 | 15 | constructor(study: Study) { 16 | this.study = study; 17 | this.date_range = study_filter_utils.getStudyDateRange(study); 18 | this.version_range = study_filter_utils.getStudyVersionRange(study); 19 | this.os_version_range = study_filter_utils.getStudyOsVersionRange(study); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/seed_tools/utils/study_filter_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { type Study } from '../../proto/generated/study'; 7 | import { Version, type VersionOptions } from './version'; 8 | 9 | export type VersionRange = [Version?, Version?]; 10 | export type DateRange = [bigint?, bigint?]; 11 | 12 | export function getStudyDateRange(study: Study): DateRange { 13 | return [study.filter?.start_date, study.filter?.end_date]; 14 | } 15 | 16 | export function getStudyVersionRange(study: Study): VersionRange { 17 | const options = { disallowLeadingZeros: true }; 18 | return [ 19 | maybeCreateVersion(study.filter?.min_version, options), 20 | maybeCreateVersion(study.filter?.max_version, options), 21 | ]; 22 | } 23 | 24 | export function getStudyOsVersionRange(study: Study): VersionRange { 25 | const options = { disallowLeadingZeros: true }; 26 | return [ 27 | maybeCreateVersion(study.filter?.min_os_version, options), 28 | maybeCreateVersion(study.filter?.max_os_version, options), 29 | ]; 30 | } 31 | 32 | function maybeCreateVersion( 33 | version: string | undefined, 34 | options?: VersionOptions, 35 | ): Version | undefined { 36 | return version !== undefined ? new Version(version, options) : undefined; 37 | } 38 | -------------------------------------------------------------------------------- /src/test/data/invalid_seed.bin: -------------------------------------------------------------------------------- 1 | 2 | 1t 3 | AllowCertainClientHintsStudy8J& 4 | Enabled 5 | b 6 | AllowCertainClientHintsJ 7 | DefaultR 104.1.44.59   ((((`"1 -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test1/expected_error.txt: -------------------------------------------------------------------------------- 1 | Feature TestFeature overlaps in studies 2 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test1/studies/AnotherTestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AnotherTestStudy', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '120.*', 21 | platform: [ 22 | 'WINDOWS', 23 | ], 24 | }, 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test1/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | max_version: '120.0.0.1', 30 | platform: [ 31 | 'MAC', 32 | 'WINDOWS', 33 | ], 34 | }, 35 | }, 36 | ] 37 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test2/expected_error.txt: -------------------------------------------------------------------------------- 1 | Feature AnotherFeature overlaps in studies 2 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test2/studies/AnotherTestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AnotherTestStudy', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '120.*', 21 | platform: [ 22 | 'WINDOWS', 23 | ], 24 | }, 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test2/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | 'AnotherFeature', 21 | ], 22 | }, 23 | }, 24 | { 25 | name: 'Default', 26 | probability_weight: 0, 27 | }, 28 | ], 29 | filter: { 30 | max_version: '119.0.0.1', 31 | platform: [ 32 | 'MAC', 33 | 'WINDOWS', 34 | ], 35 | }, 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/test/data/invalid_seeds/test2/studies/ThirdStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ThirdStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AnotherFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'AnotherFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | platform: [ 30 | 'MAC', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test1/expected_errors.txt: -------------------------------------------------------------------------------- 1 | probability_weight is not defined for experiment Enabled 2 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test1/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | feature_association: { 8 | enable_feature: [ 9 | 'TestFeature', 10 | ], 11 | }, 12 | }, 13 | { 14 | name: 'Disabled', 15 | probability_weight: 100, 16 | feature_association: { 17 | disable_feature: [ 18 | 'TestFeature', 19 | ], 20 | }, 21 | }, 22 | { 23 | name: 'Default', 24 | probability_weight: 0, 25 | }, 26 | ], 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test2/expected_errors.txt: -------------------------------------------------------------------------------- 1 | Filter conflict: exclude_locale and locale cannot be set at the same time for study TestStudy 2 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test2/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | locale: [ 17 | 'en', 18 | ], 19 | exclude_locale: [ 20 | 'en-US', 21 | ], 22 | }, 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test3/expected_errors.txt: -------------------------------------------------------------------------------- 1 | Unable to parse field variations.Study.Filter#platform 2 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test3/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | platform: [ 17 | 'MACOS', 18 | ], 19 | }, 20 | }, 21 | ] 22 | -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test4/expected_errors.txt: -------------------------------------------------------------------------------- 1 | Invalid filename name: study_with_bad_filename_& (use only 0-9,a-z,A-Z,_,-) -------------------------------------------------------------------------------- /src/test/data/invalid_studies/test4/studies/study_with_bad_filename_&.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'study', 4 | experiment: [ 5 | { 6 | name: 'Default', 7 | probability_weight: 100, 8 | }, 9 | ], 10 | filter: { 11 | channel: [ 12 | 'RELEASE', 13 | ], 14 | platform: [ 15 | 'MAC', 16 | ], 17 | }, 18 | }, 19 | ] 20 | -------------------------------------------------------------------------------- /src/test/data/perf_seeds/test1/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial_number": "1", 3 | "study": [ 4 | { 5 | "name": "TestStudy", 6 | "consistency": "PERMANENT", 7 | "default_experiment_name": "Disabled", 8 | "experiment": [ 9 | { 10 | "name": "Disabled", 11 | "probability_weight": 100, 12 | "feature_association": { 13 | "disable_feature": [ 14 | "TestFeature" 15 | ] 16 | } 17 | } 18 | ], 19 | "filter": { 20 | "channel": [ 21 | "CANARY", 22 | "BETA", 23 | "STABLE" 24 | ], 25 | "platform": [ 26 | "PLATFORM_WINDOWS", 27 | "PLATFORM_MAC", 28 | "PLATFORM_LINUX", 29 | "PLATFORM_ANDROID" 30 | ] 31 | }, 32 | "activation_type": "ACTIVATE_ON_STARTUP" 33 | } 34 | ], 35 | "version": "1" 36 | } 37 | -------------------------------------------------------------------------------- /src/test/data/perf_seeds/test1/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 30, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 70, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | channel: [ 30 | 'NIGHTLY', 31 | 'BETA', 32 | 'RELEASE', 33 | ], 34 | platform: [ 35 | 'WINDOWS', 36 | 'MAC', 37 | 'LINUX', 38 | 'ANDROID', 39 | ], 40 | }, 41 | }, 42 | ] 43 | -------------------------------------------------------------------------------- /src/test/data/seed1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brave/brave-variations/a35d72e8a2d6b49edf8c5c17daa80f50305b083e/src/test/data/seed1.bin -------------------------------------------------------------------------------- /src/test/data/set_seed_version/expected_seed.bin: -------------------------------------------------------------------------------- 1 | 2 | 1q 3 | TestStudy8J 4 | Enableddb 5 | TestFeatureJ 6 | Disabledb  TestFeatureJ 7 | DefaultR 8 | 92.1.30.57  ((((`"test version value -------------------------------------------------------------------------------- /src/test/data/set_seed_version/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serialNumber": "1", 3 | "study": [ 4 | { 5 | "name": "TestStudy", 6 | "consistency": "PERMANENT", 7 | "experiment": [ 8 | { 9 | "name": "Enabled", 10 | "probabilityWeight": 100, 11 | "featureAssociation": { 12 | "enableFeature": [ 13 | "TestFeature" 14 | ] 15 | } 16 | }, 17 | { 18 | "name": "Disabled", 19 | "probabilityWeight": 0, 20 | "featureAssociation": { 21 | "disableFeature": [ 22 | "TestFeature" 23 | ] 24 | } 25 | }, 26 | { 27 | "name": "Default", 28 | "probabilityWeight": 0 29 | } 30 | ], 31 | "filter": { 32 | "minVersion": "92.1.30.57", 33 | "channel": [ 34 | "CANARY", 35 | "BETA", 36 | "STABLE" 37 | ], 38 | "platform": [ 39 | "PLATFORM_WINDOWS", 40 | "PLATFORM_MAC", 41 | "PLATFORM_LINUX", 42 | "PLATFORM_ANDROID" 43 | ] 44 | }, 45 | "activationType": "ACTIVATE_ON_STARTUP" 46 | } 47 | ], 48 | "version": "test version value" 49 | } 50 | -------------------------------------------------------------------------------- /src/test/data/set_seed_version/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '92.1.30.57', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | 'RELEASE', 34 | ], 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/test/data/unformatted_studies/test1/expected_output.txt: -------------------------------------------------------------------------------- 1 | @@ -7,7 +7,7 @@ 2 | probability_weight: 100, 3 | feature_association: { 4 | enable_feature: [ 5 | - 'TestFeature', 6 | + 'TestFeature', 7 | ], 8 | }, 9 | }, 10 | @@ -21,17 +21,17 @@ 11 | }, 12 | }, 13 | { 14 | - probability_weight: 0, 15 | name: 'Default', 16 | + probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | + min_version: '92.1.30.57', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | - min_version: '92.1.30.57', 27 | platform: [ 28 | 'WINDOWS', 29 | 'MAC', 30 | -------------------------------------------------------------------------------- /src/test/data/unformatted_studies/test1/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | probability_weight: 0, 25 | name: 'Default', 26 | }, 27 | ], 28 | filter: { 29 | channel: [ 30 | 'NIGHTLY', 31 | 'BETA', 32 | 'RELEASE', 33 | ], 34 | min_version: '92.1.30.57', 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/country_case/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial_number": "1", 3 | "study": [ 4 | { 5 | "name": "TestStudy", 6 | "consistency": "PERMANENT", 7 | "experiment": [ 8 | { 9 | "name": "Enabled", 10 | "probability_weight": 100, 11 | "feature_association": { 12 | "enable_feature": [ 13 | "TestFeature" 14 | ] 15 | } 16 | } 17 | ], 18 | "filter": { 19 | "min_version": "92.1.30.57", 20 | "channel": [ 21 | "CANARY" 22 | ], 23 | "platform": [ 24 | "PLATFORM_WINDOWS" 25 | ], 26 | "country": [ 27 | "US", 28 | "us" 29 | ] 30 | }, 31 | "activation_type": "ACTIVATE_ON_STARTUP" 32 | }, 33 | { 34 | "name": "TestStudy", 35 | "consistency": "PERMANENT", 36 | "experiment": [ 37 | { 38 | "name": "Enabled", 39 | "probability_weight": 100, 40 | "feature_association": { 41 | "enable_feature": [ 42 | "TestFeature" 43 | ] 44 | } 45 | } 46 | ], 47 | "filter": { 48 | "min_version": "92.1.30.57", 49 | "channel": [ 50 | "BETA" 51 | ], 52 | "platform": [ 53 | "PLATFORM_WINDOWS" 54 | ], 55 | "country": [ 56 | "us" 57 | ] 58 | }, 59 | "activation_type": "ACTIVATE_ON_STARTUP" 60 | } 61 | ], 62 | "version": "1" 63 | } 64 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/country_case/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '92.1.30.57', 17 | channel: [ 18 | 'NIGHTLY', 19 | ], 20 | platform: [ 21 | 'WINDOWS', 22 | ], 23 | country: [ 24 | 'US', 25 | ], 26 | }, 27 | }, 28 | { 29 | name: 'TestStudy', 30 | experiment: [ 31 | { 32 | name: 'Enabled', 33 | probability_weight: 100, 34 | feature_association: { 35 | enable_feature: [ 36 | 'TestFeature', 37 | ], 38 | }, 39 | }, 40 | ], 41 | filter: { 42 | min_version: '92.1.30.57', 43 | channel: [ 44 | 'BETA', 45 | ], 46 | platform: [ 47 | 'WINDOWS', 48 | ], 49 | country: [ 50 | 'us', 51 | ], 52 | }, 53 | }, 54 | ] 55 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test1/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial_number": "1", 3 | "study": [ 4 | { 5 | "name": "TestStudy", 6 | "consistency": "PERMANENT", 7 | "experiment": [ 8 | { 9 | "name": "Enabled", 10 | "probability_weight": 100, 11 | "feature_association": { 12 | "enable_feature": [ 13 | "TestFeature" 14 | ] 15 | } 16 | }, 17 | { 18 | "name": "Disabled", 19 | "probability_weight": 0, 20 | "feature_association": { 21 | "disable_feature": [ 22 | "TestFeature" 23 | ] 24 | } 25 | }, 26 | { 27 | "name": "Default", 28 | "probability_weight": 0 29 | } 30 | ], 31 | "filter": { 32 | "min_version": "92.1.30.57", 33 | "channel": [ 34 | "CANARY", 35 | "BETA", 36 | "STABLE" 37 | ], 38 | "platform": [ 39 | "PLATFORM_WINDOWS", 40 | "PLATFORM_MAC", 41 | "PLATFORM_LINUX", 42 | "PLATFORM_ANDROID" 43 | ] 44 | }, 45 | "activation_type": "ACTIVATE_ON_STARTUP" 46 | } 47 | ], 48 | "version": "1" 49 | } 50 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test1/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '92.1.30.57', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | 'RELEASE', 34 | ], 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test2/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial_number": "1", 3 | "study": [ 4 | { 5 | "name": "AnotherTestStudy", 6 | "consistency": "PERMANENT", 7 | "experiment": [ 8 | { 9 | "name": "Enabled_Group", 10 | "probability_weight": 0, 11 | "feature_association": { 12 | "enable_feature": [ 13 | "AnotherTestFeature" 14 | ] 15 | } 16 | }, 17 | { 18 | "name": "Disabled_Group", 19 | "probability_weight": 100, 20 | "feature_association": { 21 | "disable_feature": [ 22 | "AnotherTestFeature" 23 | ] 24 | } 25 | }, 26 | { 27 | "name": "Default_Group", 28 | "probability_weight": 0 29 | } 30 | ], 31 | "filter": { 32 | "min_version": "120.*", 33 | "channel": [ 34 | "STABLE" 35 | ], 36 | "platform": [ 37 | "PLATFORM_MAC" 38 | ] 39 | }, 40 | "activation_type": "ACTIVATE_ON_STARTUP" 41 | }, 42 | { 43 | "name": "TestStudy", 44 | "consistency": "PERMANENT", 45 | "experiment": [ 46 | { 47 | "name": "Enabled", 48 | "probability_weight": 100, 49 | "feature_association": { 50 | "enable_feature": [ 51 | "TestFeature" 52 | ] 53 | } 54 | }, 55 | { 56 | "name": "Disabled", 57 | "probability_weight": 0, 58 | "feature_association": { 59 | "disable_feature": [ 60 | "TestFeature" 61 | ] 62 | } 63 | }, 64 | { 65 | "name": "Default", 66 | "probability_weight": 0 67 | } 68 | ], 69 | "filter": { 70 | "min_version": "92.1.30.57", 71 | "channel": [ 72 | "CANARY", 73 | "BETA", 74 | "STABLE" 75 | ], 76 | "platform": [ 77 | "PLATFORM_WINDOWS", 78 | "PLATFORM_MAC", 79 | "PLATFORM_LINUX", 80 | "PLATFORM_ANDROID" 81 | ] 82 | }, 83 | "activation_type": "ACTIVATE_ON_STARTUP" 84 | } 85 | ], 86 | "version": "1" 87 | } 88 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test2/studies/AnotherTestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AnotherTestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled_Group', 7 | probability_weight: 0, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AnotherTestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled_Group', 16 | probability_weight: 100, 17 | feature_association: { 18 | disable_feature: [ 19 | 'AnotherTestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default_Group', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '120.*', 30 | channel: [ 31 | 'RELEASE', 32 | ], 33 | platform: [ 34 | 'MAC', 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test2/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '92.1.30.57', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | 'RELEASE', 34 | ], 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test3/expected_seed.json: -------------------------------------------------------------------------------- 1 | { 2 | "serial_number": "1", 3 | "study": [ 4 | { 5 | "name": "AnotherTestStudy", 6 | "consistency": "PERMANENT", 7 | "experiment": [ 8 | { 9 | "name": "Enabled_Group", 10 | "probability_weight": 0, 11 | "feature_association": { 12 | "enable_feature": [ 13 | "AnotherTestFeature" 14 | ] 15 | } 16 | }, 17 | { 18 | "name": "Disabled_Group", 19 | "probability_weight": 100, 20 | "feature_association": { 21 | "disable_feature": [ 22 | "AnotherTestFeature" 23 | ] 24 | } 25 | }, 26 | { 27 | "name": "Default_Group", 28 | "probability_weight": 0 29 | } 30 | ], 31 | "filter": { 32 | "min_version": "120.*", 33 | "channel": [ 34 | "STABLE" 35 | ], 36 | "platform": [ 37 | "PLATFORM_MAC" 38 | ] 39 | }, 40 | "activation_type": "ACTIVATE_ON_STARTUP" 41 | }, 42 | { 43 | "name": "AnotherTestStudy", 44 | "consistency": "PERMANENT", 45 | "experiment": [ 46 | { 47 | "name": "Enabled_Group", 48 | "probability_weight": 50, 49 | "feature_association": { 50 | "enable_feature": [ 51 | "AnotherTestFeature" 52 | ] 53 | } 54 | }, 55 | { 56 | "name": "Disabled_Group", 57 | "probability_weight": 50, 58 | "feature_association": { 59 | "disable_feature": [ 60 | "AnotherTestFeature" 61 | ] 62 | } 63 | }, 64 | { 65 | "name": "Default_Group", 66 | "probability_weight": 0 67 | } 68 | ], 69 | "filter": { 70 | "min_version": "120.*", 71 | "channel": [ 72 | "BETA" 73 | ], 74 | "platform": [ 75 | "PLATFORM_MAC" 76 | ] 77 | }, 78 | "activation_type": "ACTIVATE_ON_STARTUP" 79 | }, 80 | { 81 | "name": "TestStudy", 82 | "consistency": "PERMANENT", 83 | "experiment": [ 84 | { 85 | "name": "Enabled", 86 | "probability_weight": 100, 87 | "feature_association": { 88 | "enable_feature": [ 89 | "TestFeature" 90 | ] 91 | } 92 | }, 93 | { 94 | "name": "Disabled", 95 | "probability_weight": 0, 96 | "feature_association": { 97 | "disable_feature": [ 98 | "TestFeature" 99 | ] 100 | } 101 | }, 102 | { 103 | "name": "Default", 104 | "probability_weight": 0 105 | } 106 | ], 107 | "filter": { 108 | "min_version": "92.1.30.57", 109 | "channel": [ 110 | "CANARY", 111 | "BETA", 112 | "STABLE" 113 | ], 114 | "platform": [ 115 | "PLATFORM_WINDOWS", 116 | "PLATFORM_MAC", 117 | "PLATFORM_LINUX", 118 | "PLATFORM_ANDROID" 119 | ] 120 | }, 121 | "activation_type": "ACTIVATE_ON_STARTUP" 122 | } 123 | ], 124 | "version": "1" 125 | } 126 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test3/studies/AnotherTestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AnotherTestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled_Group', 7 | probability_weight: 0, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AnotherTestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled_Group', 16 | probability_weight: 100, 17 | feature_association: { 18 | disable_feature: [ 19 | 'AnotherTestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default_Group', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '120.*', 30 | channel: [ 31 | 'RELEASE', 32 | ], 33 | platform: [ 34 | 'MAC', 35 | ], 36 | }, 37 | }, 38 | { 39 | name: 'AnotherTestStudy', 40 | experiment: [ 41 | { 42 | name: 'Enabled_Group', 43 | probability_weight: 50, 44 | feature_association: { 45 | enable_feature: [ 46 | 'AnotherTestFeature', 47 | ], 48 | }, 49 | }, 50 | { 51 | name: 'Disabled_Group', 52 | probability_weight: 50, 53 | feature_association: { 54 | disable_feature: [ 55 | 'AnotherTestFeature', 56 | ], 57 | }, 58 | }, 59 | { 60 | name: 'Default_Group', 61 | probability_weight: 0, 62 | }, 63 | ], 64 | filter: { 65 | min_version: '120.*', 66 | channel: [ 67 | 'BETA', 68 | ], 69 | platform: [ 70 | 'MAC', 71 | ], 72 | }, 73 | }, 74 | ] 75 | -------------------------------------------------------------------------------- /src/test/data/valid_seeds/test3/studies/TestStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TestStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TestFeature', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'TestFeature', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '92.1.30.57', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | 'RELEASE', 34 | ], 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/web/.gitignore: -------------------------------------------------------------------------------- 1 | public/bundle 2 | -------------------------------------------------------------------------------- /src/web/app/experiment_model.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import * as url_utils from '../../core/url_utils'; 7 | import { Study_Experiment } from '../../proto/generated/study'; 8 | import { type StudyModel } from './study_model'; 9 | 10 | export class FeatureModel { 11 | name: string; 12 | link: string; 13 | } 14 | 15 | export class ExperimentModel { 16 | private readonly experiment: Study_Experiment; 17 | private readonly studyModel: StudyModel; 18 | 19 | constructor(experiment: Study_Experiment, studyModel: StudyModel) { 20 | this.experiment = experiment; 21 | this.studyModel = studyModel; 22 | } 23 | 24 | private getFeatures(features?: string[] | null): FeatureModel[] { 25 | if (features == null) { 26 | return []; 27 | } 28 | return features.map((f) => { 29 | return { 30 | name: f, 31 | link: url_utils.getFeatureSearchUrl(f), 32 | }; 33 | }); 34 | } 35 | 36 | enabledFeatures(): FeatureModel[] { 37 | return this.getFeatures( 38 | this.experiment.feature_association?.enable_feature, 39 | ); 40 | } 41 | 42 | disabledFeatures(): FeatureModel[] { 43 | return this.getFeatures( 44 | this.experiment.feature_association?.disable_feature, 45 | ); 46 | } 47 | 48 | parameters(): string[] { 49 | const param = this.experiment.param; 50 | if (param == null) return []; 51 | return param.map((p) => p.name + ': ' + p.value); 52 | } 53 | 54 | name(): string { 55 | return this.experiment.name; 56 | } 57 | 58 | weight(): number { 59 | const totalWeight = this.studyModel.processedStudy.studyDetails.totalWeight; 60 | if (totalWeight === 0) return 0; 61 | return ((this.experiment.probability_weight ?? 0) / totalWeight) * 100; 62 | } 63 | 64 | isMajorGroup(): boolean { 65 | return this.weight() > 50; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/web/app/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import * as React from 'react'; 7 | import { createRoot } from 'react-dom/client'; 8 | import { BrowserRouter } from 'react-router-dom'; 9 | import { App } from './app'; 10 | 11 | import 'css/bootstrap.min.css'; 12 | import 'css/style.css'; 13 | 14 | const root = document.getElementById('root'); 15 | if (root != null) { 16 | const reactRoot = createRoot(root); 17 | reactRoot.render( 18 | 19 | 20 | 21 | 22 | , 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/web/app/search_param_manager.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { type SetURLSearchParams } from 'react-router-dom'; 7 | 8 | import { SeedType } from '../../core/base_types'; 9 | import { StudyFilter, StudyPriority } from '../../core/study_processor'; 10 | 11 | function stringToSeedType(value: string): SeedType | undefined { 12 | const index = Object.values(SeedType).indexOf(value); 13 | if (index >= 0) { 14 | return index as SeedType; 15 | } 16 | return undefined; 17 | } 18 | 19 | export class SearchParamManager { 20 | readonly filter: StudyFilter; 21 | readonly currentSeed: SeedType; 22 | private readonly setParams: (params: Record) => void; 23 | 24 | constructor(params: [URLSearchParams, SetURLSearchParams]) { 25 | const searchParams = params[0]; 26 | const setSearchParams = params[1]; 27 | this.setParams = (params: Record) => { 28 | setSearchParams((prev) => { 29 | for (const [key, value] of Object.entries(params)) { 30 | if (value != null) prev.set(key, value); 31 | else prev.delete(key); 32 | } 33 | return prev; 34 | }); 35 | }; 36 | 37 | const filter = new StudyFilter(); 38 | filter.search = searchParams.get('search') ?? undefined; 39 | this.currentSeed = 40 | stringToSeedType(searchParams.get('seed') ?? 'MAIN') ?? SeedType.MAIN; 41 | filter.minPriority = 42 | this.currentSeed === SeedType.UPSTREAM 43 | ? StudyPriority.STABLE_MIN 44 | : StudyPriority.NON_INTERESTING; 45 | try { 46 | const priorityString = searchParams.get('minPriority'); 47 | if (priorityString != null) { 48 | filter.minPriority = parseInt(priorityString); 49 | } 50 | } catch { 51 | /* empty */ 52 | } 53 | filter.showEmptyGroups = searchParams.get('showEmptyGroups') === 'true'; 54 | filter.includeOutdated = searchParams.get('includeOutdated') === 'true'; 55 | this.filter = filter; 56 | } 57 | 58 | toggleShowEmptyGroups() { 59 | this.setParams({ 60 | showEmptyGroups: this.filter.showEmptyGroups ? null : 'true', 61 | }); 62 | } 63 | 64 | toggleIncludeOutdated() { 65 | this.setParams({ 66 | includeOutdated: this.filter.includeOutdated ? null : 'true', 67 | }); 68 | } 69 | 70 | setMinPriority(minPriority: number) { 71 | this.setParams({ 72 | minPriority: minPriority.toString(), 73 | }); 74 | } 75 | 76 | setSearch(value: string) { 77 | this.setParams({ 78 | search: value === '' ? null : value, 79 | }); 80 | } 81 | 82 | setSeedType(type: SeedType) { 83 | this.setParams({ seed: SeedType[type], search: null }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/web/app/seed_loader.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { SeedType, type ProcessingOptions } from '../../core/base_types'; 7 | import { ProcessedStudy } from '../../core/study_processor'; 8 | import { VariationsSeed } from '../../proto/generated/variations_seed'; 9 | import { StudyListModel, StudyModel } from './study_model'; 10 | 11 | import * as url_utils from '../../core/url_utils'; 12 | 13 | const getCurrentMajorVersion = new Promise((resolve) => { 14 | loadFile(url_utils.getUsedChromiumVersionUrl, 'text') 15 | .then((chromeVersionData) => { 16 | if (chromeVersionData !== undefined) 17 | resolve(chromeVersionData.split('.')[0] ?? 0); 18 | resolve(0); 19 | }) 20 | .catch(() => { 21 | resolve(0); 22 | }); 23 | }); 24 | 25 | async function loadFile( 26 | url: string, 27 | responseType: 'arraybuffer' | 'text', 28 | ): Promise { 29 | return await new Promise((resolve, reject) => { 30 | const xhr = new XMLHttpRequest(); 31 | xhr.open('GET', url, true /* async */); 32 | xhr.responseType = responseType; 33 | xhr.onload = () => { 34 | if (xhr.status === 200) { 35 | resolve(xhr.response); 36 | } else { 37 | reject(new Error('HTTP status:' + xhr.status)); 38 | } 39 | }; 40 | xhr.onerror = () => { 41 | reject(new Error('XHR error')); 42 | }; 43 | xhr.send(null); 44 | }); 45 | } 46 | 47 | async function loadSeedFromUrl(url: string, type: SeedType) { 48 | const data = await loadFile(url, 'arraybuffer'); 49 | const seedBytes = new Uint8Array(data); 50 | const seed = VariationsSeed.fromBinary(seedBytes); 51 | const isBraveSeed = type !== SeedType.UPSTREAM; 52 | 53 | // Desktop/Android could use a different major chrome version. 54 | // Use -1 version for Brave studies to make sure that we don't cut 55 | // anything important. 56 | const minMajorVersion = 57 | (await getCurrentMajorVersion) - (isBraveSeed ? 1 : 0); 58 | const options: ProcessingOptions = { minMajorVersion, isBraveSeed }; 59 | const studies: StudyModel[] = []; 60 | seed.study.forEach((study, index) => { 61 | const processed = new ProcessedStudy(study, options); 62 | const studyDetails = processed.studyDetails; 63 | if (studyDetails.isArchived || studyDetails.isBadStudyFormat) { 64 | return; 65 | } 66 | 67 | const uniqueId = type * 1000000 + index; 68 | studies.push(new StudyModel(processed, type, uniqueId)); 69 | }); 70 | return new StudyListModel(studies); 71 | } 72 | 73 | // Loads all available seeds asynchronously, updates React App state via the callback. 74 | export function loadSeedDataAsync( 75 | cb: (type: SeedType, studyList: StudyListModel) => void, 76 | ) { 77 | loadSeedFromUrl(url_utils.variationsMainUrl, SeedType.MAIN) 78 | .then(cb.bind(cb, SeedType.MAIN)) 79 | .catch(console.error); 80 | loadSeedFromUrl(url_utils.variationsUpstreamUrl, SeedType.UPSTREAM) 81 | .then(cb.bind(cb, SeedType.UPSTREAM)) 82 | .catch(() => { 83 | /* ignore an error, a non-public endpoint */ 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /src/web/app/study_model.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | import { SeedType } from '../../core/base_types'; 7 | import { 8 | type ProcessedStudy, 9 | type StudyFilter, 10 | type StudyPriority, 11 | } from '../../core/study_processor'; 12 | import * as url_utils from '../../core/url_utils'; 13 | import { 14 | Study_Channel, 15 | Study_Filter, 16 | Study_Platform, 17 | } from '../../proto/generated/study'; 18 | import { 19 | channelToString, 20 | platformToString, 21 | } from '../../seed_tools/utils/converters'; 22 | import { ExperimentModel } from './experiment_model'; 23 | 24 | export class StudyModel { 25 | readonly processedStudy: ProcessedStudy; 26 | readonly seedType: SeedType; 27 | readonly id: number; // unique id used as keys in React lists. 28 | 29 | constructor(processedStudy: ProcessedStudy, seedType: SeedType, id: number) { 30 | this.processedStudy = processedStudy; 31 | this.seedType = seedType; 32 | this.id = id; 33 | } 34 | 35 | filter(): Study_Filter | undefined { 36 | return this.processedStudy.study.filter ?? undefined; 37 | } 38 | 39 | priority(): StudyPriority { 40 | return this.processedStudy.getPriority(); 41 | } 42 | 43 | name(): string { 44 | return this.processedStudy.study.name; 45 | } 46 | 47 | filterExperiments(f: StudyFilter): ExperimentModel[] { 48 | const study = this.processedStudy.study; 49 | if (study.experiment == null) return []; 50 | const models: ExperimentModel[] = []; 51 | for (const e of study.experiment) { 52 | if ( 53 | (e.probability_weight ?? 0) > 0 || 54 | f === undefined || 55 | f.showEmptyGroups 56 | ) { 57 | const model = new ExperimentModel(e, this); 58 | models.push(model); 59 | } 60 | } 61 | return models; 62 | } 63 | 64 | platforms(): string[] | undefined { 65 | return this.filter()?.platform?.map((p) => 66 | platformToString(Study_Platform[p]), 67 | ); 68 | } 69 | 70 | channels(): string[] | undefined { 71 | return this.filter()?.channel?.map((c) => 72 | channelToString(Study_Channel[c], this.seedType === SeedType.UPSTREAM), 73 | ); 74 | } 75 | 76 | getConfigUrl(): string { 77 | return url_utils.getStudyRawConfigUrl(this.name(), this.seedType); 78 | } 79 | } 80 | 81 | export class StudyListModel { 82 | readonly processedStudies: StudyModel[]; 83 | constructor(studies: StudyModel[]) { 84 | this.processedStudies = studies; 85 | } 86 | 87 | filterStudies(f: StudyFilter): StudyModel[] { 88 | return this.processedStudies.filter((s) => f.matches(s.processedStudy)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/web/css/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 970px; 3 | } 4 | 5 | .container h1 { 6 | background: url('../img/brave_lion.svg') no-repeat left center/24px; 7 | font-size: 24px; 8 | padding: 0 0 0 32px; 9 | margin: 0; 10 | } 11 | 12 | .heading { 13 | color: inherit; 14 | text-decoration: inherit; 15 | } 16 | 17 | section.navbar { 18 | margin: 15px 0 5px 0; 19 | padding: 0; 20 | } 21 | 22 | .nav-pills .nav-link { 23 | color: #212529 !important; 24 | } 25 | 26 | .nav-pills .nav-link.active { 27 | background-color: #ff5500 !important; 28 | color: #f8f9fa !important; 29 | } 30 | 31 | .card { 32 | width: 100%; 33 | margin: 15px 0; 34 | } 35 | 36 | .card-body { 37 | padding: 0em 1em; 38 | } 39 | 40 | .card h5 { 41 | font-size: 0.8em; 42 | font-weight: normal; 43 | color: grey; 44 | margin: 0; 45 | } 46 | 47 | .list-group-item { 48 | padding: 5px 0; 49 | } 50 | 51 | .exp-item { 52 | color: rgb(80, 80, 80); 53 | } 54 | 55 | .major-exp-item { 56 | color: black; 57 | } 58 | 59 | .study-meta { 60 | font-size: 0.8em; 61 | color: grey; 62 | padding: 0; 63 | margin: 0 0; 64 | } 65 | 66 | .filter-list { 67 | display: flex; 68 | } 69 | 70 | .filter { 71 | display: flex; 72 | justify-content: center; 73 | align-items: center; 74 | font-size: 0.8em; 75 | margin-right: 15px; 76 | } 77 | 78 | .filter-left { 79 | margin-left: auto; 80 | margin-right: 0px; 81 | } 82 | 83 | .filter-checkbox { 84 | vertical-align: middle; 85 | position: relative; 86 | bottom: 1px; 87 | } 88 | 89 | mark { 90 | padding: 0 0 0 0; 91 | } 92 | 93 | .enabled-feature { 94 | color: rgb(51, 88, 43); 95 | padding: 0; 96 | margin: 0 0; 97 | } 98 | 99 | .disabled-feature { 100 | color: rgb(108, 55, 50); 101 | padding: 0; 102 | margin: 0 0; 103 | } 104 | 105 | ul.study-meta li { 106 | display: inline; 107 | } 108 | 109 | ul.study-meta li:after { 110 | content: ', '; 111 | } 112 | 113 | ul.study-meta li:last-child:after { 114 | content: ''; 115 | } 116 | -------------------------------------------------------------------------------- /src/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Brave Variations 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "types": ["browser"], 4 | "compilerOptions": { 5 | "outDir": "build" 6 | }, 7 | "include": ["./**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /src/web/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Brave Authors. All rights reserved. 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 file, 4 | // You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-require-imports 7 | const path = require('path'); 8 | 9 | function isDevMode(argv) { 10 | return process.env.NODE_ENV === 'development' || argv.mode === 'development'; 11 | } 12 | 13 | module.exports = (env, argv) => ({ 14 | entry: './src/web/app/index.tsx', 15 | output: { 16 | path: path.join(__dirname, 'public', 'bundle'), 17 | filename: 'app.js', 18 | }, 19 | devServer: { 20 | devMiddleware: { 21 | index: false, 22 | publicPath: '/bundle', 23 | }, 24 | static: path.resolve(__dirname, 'public'), 25 | }, 26 | resolve: { 27 | extensions: ['.tsx', '.ts', '.js', '.css'], 28 | alias: { 29 | css: path.resolve(__dirname, 'css'), 30 | }, 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.css$/, 36 | use: ['style-loader', 'css-loader'], 37 | }, 38 | { 39 | test: /\.ttf$/, 40 | use: ['url-loader'], 41 | }, 42 | { 43 | test: /.(svg|jpg|jpeg|png)$/, 44 | loader: 'file-loader', 45 | options: { 46 | name: '[name].[ext]', 47 | }, 48 | }, 49 | { 50 | test: /\.tsx?$/, 51 | use: [ 52 | { 53 | loader: require.resolve('ts-loader'), 54 | options: { 55 | transpileOnly: isDevMode(argv), 56 | }, 57 | }, 58 | ], 59 | exclude: /node_modules/, 60 | }, 61 | ], 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /studies/AllowCertainClientHintsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AllowCertainClientHintsStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AllowCertainClientHints', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '104.1.44.59', 21 | channel: [ 22 | 'RELEASE', 23 | 'BETA', 24 | 'NIGHTLY', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/AndroidOnboardingStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'AndroidOnboardingStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'NewAndroidOnboarding', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'ANDROID', 26 | ], 27 | }, 28 | }, 29 | { 30 | name: 'AndroidOnboardingStudy', 31 | experiment: [ 32 | { 33 | name: 'Enabled', 34 | probability_weight: 100, 35 | feature_association: { 36 | enable_feature: [ 37 | 'NewAndroidOnboarding', 38 | ], 39 | }, 40 | }, 41 | { 42 | name: 'Default', 43 | probability_weight: 0, 44 | }, 45 | ], 46 | filter: { 47 | channel: [ 48 | 'RELEASE', 49 | ], 50 | platform: [ 51 | 'ANDROID', 52 | ], 53 | }, 54 | }, 55 | ] 56 | -------------------------------------------------------------------------------- /studies/BraveAIChatEnabledStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAIChatEnabledStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AIChat', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '119.1.60.0', 21 | max_version: '122.1.63.160', 22 | channel: [ 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/BraveAdblockExperimentalListDefaultStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdblockExperimentalListDefaultStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveAdblockExperimentalListDefault', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '123.1.66.53', 21 | channel: [ 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveAdblockMobileNotificationsListDefault.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdblockMobileNotificationsListDefault', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveAdblockMobileNotificationsListDefault', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveAds.CreativeAdModelBasedPredictorRecencyStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAds.CreativeAdModelBasedPredictorRecencyStudy', 4 | experiment: [ 5 | { 6 | name: 'NoRecency', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'CreativeNotificationAdModelBasedPredictor', 11 | 'CreativeNewTabPageAdModelBasedPredictor', 12 | 'CreativeInlineContentAdModelBasedPredictor', 13 | ], 14 | }, 15 | param: [ 16 | { 17 | name: 'last_seen_ad_predictor_weight', 18 | value: '0.0', 19 | }, 20 | { 21 | name: 'last_seen_advertiser_predictor_weight', 22 | value: '0.0', 23 | }, 24 | ], 25 | }, 26 | ], 27 | filter: { 28 | max_version: '128.1.69.51', 29 | channel: [ 30 | 'NIGHTLY', 31 | 'BETA', 32 | 'RELEASE', 33 | ], 34 | platform: [ 35 | 'WINDOWS', 36 | 'MAC', 37 | 'LINUX', 38 | 'ANDROID', 39 | 'IOS', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /studies/BraveAdsAdEventStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsAdEventStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'AdEvent', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'debounce_clicked_ad_event_for', 16 | value: '1s', 17 | }, 18 | { 19 | name: 'deduplicate_clicked_ad_event_for', 20 | value: '1s', 21 | }, 22 | ], 23 | }, 24 | { 25 | name: 'Default', 26 | probability_weight: 0, 27 | }, 28 | ], 29 | filter: { 30 | max_version: '137.1.79.51', 31 | channel: [ 32 | 'NIGHTLY', 33 | 'BETA', 34 | 'RELEASE', 35 | ], 36 | platform: [ 37 | 'WINDOWS', 38 | 'MAC', 39 | 'LINUX', 40 | 'ANDROID', 41 | 'IOS', 42 | ], 43 | }, 44 | }, 45 | ] 46 | -------------------------------------------------------------------------------- /studies/BraveAdsConversionsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsConversionsStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'Conversions', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'creative_set_conversion_cap', 16 | value: '0', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | max_version: '137.1.79.51', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | 'RELEASE', 31 | ], 32 | platform: [ 33 | 'WINDOWS', 34 | 'MAC', 35 | 'LINUX', 36 | 'ANDROID', 37 | 'IOS', 38 | ], 39 | }, 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /studies/BraveAdsExclusionRulesStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsExclusionRulesStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ExclusionRules', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'should_exclude_ad_if_creative_set_exceeds_conversion_cap', 16 | value: '0', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | max_version: '137.1.79.51', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | 'RELEASE', 31 | ], 32 | platform: [ 33 | 'WINDOWS', 34 | 'MAC', 35 | 'LINUX', 36 | 'ANDROID', 37 | 'IOS', 38 | ], 39 | }, 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /studies/BraveAdsNewTabPageAdsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsNewTabPageAdsStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'NewTabPageAds', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'minimum_wait_time', 16 | value: '1m', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | max_version: '137.1.79.51', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | 'RELEASE', 31 | ], 32 | platform: [ 33 | 'WINDOWS', 34 | 'MAC', 35 | 'LINUX', 36 | 'ANDROID', 37 | 'IOS', 38 | ], 39 | }, 40 | }, 41 | { 42 | name: 'BraveAdsNewTabPageAdsStudy', 43 | experiment: [ 44 | { 45 | name: 'Enabled', 46 | probability_weight: 100, 47 | feature_association: { 48 | enable_feature: [ 49 | 'NewTabPageAds', 50 | ], 51 | }, 52 | param: [ 53 | { 54 | name: 'should_support_confirmations_for_non_rewards', 55 | value: 'true', 56 | }, 57 | ], 58 | }, 59 | { 60 | name: 'Default', 61 | probability_weight: 0, 62 | }, 63 | ], 64 | filter: { 65 | min_version: '137.1.80.31', 66 | channel: [ 67 | 'NIGHTLY', 68 | 'BETA', 69 | ], 70 | platform: [ 71 | 'WINDOWS', 72 | 'MAC', 73 | 'LINUX', 74 | 'ANDROID', 75 | 'IOS', 76 | ], 77 | }, 78 | }, 79 | ] 80 | -------------------------------------------------------------------------------- /studies/BraveAdsRedeemRewardConfirmationStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsRedeemRewardConfirmationStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'RedeemRewardConfirmation', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'fetch_payment_token_after', 16 | value: '3s', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | max_version: '133.1.75.34', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | 'RELEASE', 31 | ], 32 | platform: [ 33 | 'WINDOWS', 34 | 'MAC', 35 | 'LINUX', 36 | 'ANDROID', 37 | 'IOS', 38 | ], 39 | }, 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /studies/BraveAdsSiteVisitStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsSiteVisitStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'SiteVisitFeature', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'page_land_after', 16 | value: '5s', 17 | }, 18 | { 19 | name: 'page_land_cap', 20 | value: '0', 21 | }, 22 | ], 23 | }, 24 | { 25 | name: 'Default', 26 | probability_weight: 0, 27 | }, 28 | ], 29 | filter: { 30 | max_version: '127.1.68.26', 31 | channel: [ 32 | 'NIGHTLY', 33 | 'BETA', 34 | 'RELEASE', 35 | ], 36 | platform: [ 37 | 'WINDOWS', 38 | 'MAC', 39 | 'LINUX', 40 | 'ANDROID', 41 | 'IOS', 42 | ], 43 | }, 44 | }, 45 | ] 46 | -------------------------------------------------------------------------------- /studies/BraveAdsTextClassificationPageProbabilitiesStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAdsTextClassificationPageProbabilitiesStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'TextClassification', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'page_probabilities_history_size', 16 | value: '15', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | max_version: '137.1.79.51', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | 'RELEASE', 31 | ], 32 | platform: [ 33 | 'WINDOWS', 34 | 'MAC', 35 | 'LINUX', 36 | 'ANDROID', 37 | 'IOS', 38 | ], 39 | }, 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /studies/BraveAggressiveModeRetirementExperiment.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAggressiveModeRetirementExperiment', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveShowStrictFingerprintingMode', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '120.1.63.110', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/BraveAutoTranslateStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveAutoTranslateStudy', 4 | experiment: [ 5 | { 6 | name: 'AutoTranslateDisabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveEnableAutoTranslate', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '107.1.47.31', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/BraveCleanupSessionCookiesOnSessionRestore.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveCleanupSessionCookiesOnSessionRestore', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveCleanupSessionCookiesOnSessionRestore', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '111.1.50.0', 17 | channel: [ 18 | 'RELEASE', 19 | 'BETA', 20 | 'NIGHTLY', 21 | ], 22 | platform: [ 23 | 'MAC', 24 | ], 25 | }, 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /studies/BraveDayZeroStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveDayZeroStudy_Android', 4 | experiment: [ 5 | { 6 | name: 'EnabledA', 7 | probability_weight: 3, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveDayZeroExperiment', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'variant', 16 | value: 'a', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'EnabledB', 22 | probability_weight: 3, 23 | feature_association: { 24 | enable_feature: [ 25 | 'BraveDayZeroExperiment', 26 | ], 27 | }, 28 | param: [ 29 | { 30 | name: 'variant', 31 | value: 'b', 32 | }, 33 | ], 34 | }, 35 | { 36 | name: 'EnabledC', 37 | probability_weight: 3, 38 | feature_association: { 39 | enable_feature: [ 40 | 'BraveDayZeroExperiment', 41 | ], 42 | }, 43 | param: [ 44 | { 45 | name: 'variant', 46 | value: 'c', 47 | }, 48 | ], 49 | }, 50 | { 51 | name: 'EnabledD', 52 | probability_weight: 3, 53 | feature_association: { 54 | enable_feature: [ 55 | 'BraveDayZeroExperiment', 56 | ], 57 | }, 58 | param: [ 59 | { 60 | name: 'variant', 61 | value: 'd', 62 | }, 63 | ], 64 | }, 65 | { 66 | name: 'Default', 67 | probability_weight: 88, 68 | }, 69 | ], 70 | filter: { 71 | min_version: '136.1.78.94', 72 | channel: [ 73 | 'RELEASE', 74 | 'BETA', 75 | 'NIGHTLY', 76 | ], 77 | platform: [ 78 | 'ANDROID', 79 | ], 80 | }, 81 | }, 82 | { 83 | name: 'BraveDayZeroStudy_Desktop', 84 | experiment: [ 85 | { 86 | name: 'EnabledC', 87 | probability_weight: 18, 88 | feature_association: { 89 | enable_feature: [ 90 | 'BraveDayZeroExperiment', 91 | ], 92 | }, 93 | param: [ 94 | { 95 | name: 'variant', 96 | value: 'c', 97 | }, 98 | ], 99 | }, 100 | { 101 | name: 'EnabledD', 102 | probability_weight: 18, 103 | feature_association: { 104 | enable_feature: [ 105 | 'BraveDayZeroExperiment', 106 | ], 107 | }, 108 | param: [ 109 | { 110 | name: 'variant', 111 | value: 'd', 112 | }, 113 | ], 114 | }, 115 | { 116 | name: 'Default', 117 | probability_weight: 64, 118 | }, 119 | ], 120 | filter: { 121 | min_version: '136.1.78.94', 122 | channel: [ 123 | 'RELEASE', 124 | 'BETA', 125 | 'NIGHTLY', 126 | ], 127 | platform: [ 128 | 'WINDOWS', 129 | ], 130 | }, 131 | }, 132 | ] 133 | -------------------------------------------------------------------------------- /studies/BraveDebounceStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveDebounceStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveDebounce', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'BraveDebounce', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '94.1.32.25', 30 | channel: [ 31 | 'RELEASE', 32 | 'BETA', 33 | 'NIGHTLY', 34 | ], 35 | platform: [ 36 | 'WINDOWS', 37 | 'MAC', 38 | 'LINUX', 39 | 'ANDROID', 40 | ], 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /studies/BraveFallbackDoHStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveFallbackDoHStudy', 4 | experiment: [ 5 | { 6 | name: 'Quad9', 7 | probability_weight: 50, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveFallbackDoHProvider', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'BraveFallbackDoHProviderEndpoint', 16 | value: 'quad9', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Cloudflare', 22 | probability_weight: 50, 23 | feature_association: { 24 | enable_feature: [ 25 | 'BraveFallbackDoHProvider', 26 | ], 27 | }, 28 | param: [ 29 | { 30 | name: 'BraveFallbackDoHProviderEndpoint', 31 | value: 'cloudflare', 32 | }, 33 | ], 34 | }, 35 | ], 36 | filter: { 37 | channel: [ 38 | 'NIGHTLY', 39 | ], 40 | platform: [ 41 | 'WINDOWS', 42 | 'MAC', 43 | 'LINUX', 44 | 'ANDROID', 45 | ], 46 | }, 47 | }, 48 | ] 49 | -------------------------------------------------------------------------------- /studies/BraveFeedUpdateStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveFeedUpdateStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveNewsFeedUpdate', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/BraveForgetFirstPartyStorage.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveForgetFirstPartyStorage', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveForgetFirstPartyStorage', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'BraveForgetFirstPartyStorage', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '114.1.53.83', 30 | channel: [ 31 | 'BETA', 32 | ], 33 | platform: [ 34 | 'WINDOWS', 35 | 'MAC', 36 | 'LINUX', 37 | 'ANDROID', 38 | ], 39 | }, 40 | }, 41 | { 42 | name: 'BraveForgetFirstPartyStorage', 43 | experiment: [ 44 | { 45 | name: 'Enabled', 46 | probability_weight: 100, 47 | feature_association: { 48 | enable_feature: [ 49 | 'BraveForgetFirstPartyStorage', 50 | ], 51 | }, 52 | }, 53 | { 54 | name: 'Disabled', 55 | probability_weight: 0, 56 | feature_association: { 57 | disable_feature: [ 58 | 'BraveForgetFirstPartyStorage', 59 | ], 60 | }, 61 | }, 62 | { 63 | name: 'Default', 64 | probability_weight: 0, 65 | }, 66 | ], 67 | filter: { 68 | min_version: '116.1.57.0', 69 | channel: [ 70 | 'RELEASE', 71 | ], 72 | platform: [ 73 | 'WINDOWS', 74 | 'MAC', 75 | 'LINUX', 76 | 'ANDROID', 77 | ], 78 | }, 79 | }, 80 | { 81 | name: 'BraveForgetFirstPartyStorage', 82 | experiment: [ 83 | { 84 | name: 'Enabled', 85 | probability_weight: 100, 86 | feature_association: { 87 | enable_feature: [ 88 | 'BraveForgetFirstPartyStorage', 89 | ], 90 | }, 91 | }, 92 | { 93 | name: 'Disabled', 94 | probability_weight: 0, 95 | feature_association: { 96 | disable_feature: [ 97 | 'BraveForgetFirstPartyStorage', 98 | ], 99 | }, 100 | }, 101 | { 102 | name: 'Default', 103 | probability_weight: 0, 104 | }, 105 | ], 106 | filter: { 107 | min_version: '112.1.51.57', 108 | channel: [ 109 | 'NIGHTLY', 110 | ], 111 | platform: [ 112 | 'WINDOWS', 113 | 'MAC', 114 | 'LINUX', 115 | 'ANDROID', 116 | ], 117 | }, 118 | }, 119 | ] 120 | -------------------------------------------------------------------------------- /studies/BraveGoogleSignInPermissionStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveGoogleSignInPermissionStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveGoogleSignInPermission', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'BraveGoogleSignInPermission', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '111.1.51.5', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | ], 34 | platform: [ 35 | 'WINDOWS', 36 | 'MAC', 37 | 'LINUX', 38 | 'ANDROID', 39 | ], 40 | }, 41 | }, 42 | { 43 | name: 'BraveGoogleSignInPermissionStudy', 44 | experiment: [ 45 | { 46 | name: 'Enabled', 47 | probability_weight: 100, 48 | feature_association: { 49 | enable_feature: [ 50 | 'BraveGoogleSignInPermission', 51 | ], 52 | }, 53 | }, 54 | { 55 | name: 'Disabled', 56 | probability_weight: 0, 57 | feature_association: { 58 | disable_feature: [ 59 | 'BraveGoogleSignInPermission', 60 | ], 61 | }, 62 | }, 63 | { 64 | name: 'Default', 65 | probability_weight: 0, 66 | }, 67 | ], 68 | filter: { 69 | min_version: '111.1.51.5', 70 | channel: [ 71 | 'RELEASE', 72 | ], 73 | platform: [ 74 | 'WINDOWS', 75 | 'MAC', 76 | 'LINUX', 77 | 'ANDROID', 78 | ], 79 | }, 80 | }, 81 | ] 82 | -------------------------------------------------------------------------------- /studies/BraveHorizontalTabsUpdateEnabledStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveHorizontalTabsUpdateEnabledStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveHorizontalTabsUpdate', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | channel: [ 17 | 'NIGHTLY', 18 | 'BETA', 19 | ], 20 | platform: [ 21 | 'WINDOWS', 22 | 'MAC', 23 | 'LINUX', 24 | ], 25 | }, 26 | }, 27 | { 28 | name: 'BraveHorizontalTabsUpdateEnabledStudy', 29 | experiment: [ 30 | { 31 | name: 'Enabled', 32 | probability_weight: 100, 33 | feature_association: { 34 | enable_feature: [ 35 | 'BraveHorizontalTabsUpdate', 36 | ], 37 | }, 38 | }, 39 | { 40 | name: 'Default', 41 | probability_weight: 0, 42 | }, 43 | ], 44 | filter: { 45 | channel: [ 46 | 'RELEASE', 47 | ], 48 | platform: [ 49 | 'WINDOWS', 50 | 'MAC', 51 | 'LINUX', 52 | ], 53 | }, 54 | }, 55 | ] 56 | -------------------------------------------------------------------------------- /studies/BraveLocalhostAccessPermissionStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveLocalhostAccessPermissionStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveLocalhostAccessPermission', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '115.1.55.5', 21 | channel: [ 22 | 'NIGHTLY', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | 'ANDROID', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/BraveP3AConstellationEnabled.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveP3AConstellationEnabled', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveP3AConstellation', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveP3AJSONOtherDeprecation.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveP3AJSONOtherDeprecation', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveP3AOtherJSONDeprecation', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'LINUX', 28 | 'ANDROID', 29 | 'MAC', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveP3ANebulaNightlyBeta.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveP3ANebulaNightlyBeta', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveP3ADifferentialSampling', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | 'ANDROID', 29 | 'IOS', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveP3ANebulaRelease.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveP3ANebulaRelease', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveP3ADifferentialSampling', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | 'IOS', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/BraveP3ATypicalJSONDeprecationEnabled.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveP3ATypicalJSONDeprecationEnabled', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveP3ATypicalJSONDeprecation', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | 'IOS', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/BraveRequestOTRTabRolloutStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRequestOTRTabRolloutStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveRequestOTRTab', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/BraveRewardsAllowSelfCustodyProvidersStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRewardsAllowSelfCustodyProvidersStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveRewardsAllowSelfCustodyProviders', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | channel: [ 17 | 'NIGHTLY', 18 | 'BETA', 19 | 'RELEASE', 20 | ], 21 | platform: [ 22 | 'WINDOWS', 23 | 'MAC', 24 | 'LINUX', 25 | 'ANDROID', 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /studies/BraveRewardsNewRewardsUIStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRewardsNewRewardsUIStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveRewardsNewRewardsUI', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | { 32 | name: 'BraveRewardsNewRewardsUIStudy', 33 | experiment: [ 34 | { 35 | name: 'Enabled', 36 | probability_weight: 100, 37 | feature_association: { 38 | enable_feature: [ 39 | 'BraveRewardsNewRewardsUI', 40 | ], 41 | }, 42 | }, 43 | { 44 | name: 'Default', 45 | probability_weight: 0, 46 | }, 47 | ], 48 | filter: { 49 | min_version: '133.*', 50 | channel: [ 51 | 'RELEASE', 52 | ], 53 | platform: [ 54 | 'WINDOWS', 55 | 'MAC', 56 | 'LINUX', 57 | ], 58 | }, 59 | }, 60 | { 61 | name: 'BraveRewardsNewRewardsUIStudy', 62 | experiment: [ 63 | { 64 | name: 'Enabled', 65 | probability_weight: 100, 66 | feature_association: { 67 | enable_feature: [ 68 | 'BraveRewardsNewRewardsUI', 69 | ], 70 | }, 71 | }, 72 | { 73 | name: 'Default', 74 | probability_weight: 0, 75 | }, 76 | ], 77 | filter: { 78 | channel: [ 79 | 'NIGHTLY', 80 | 'BETA', 81 | ], 82 | platform: [ 83 | 'ANDROID', 84 | ], 85 | }, 86 | }, 87 | { 88 | name: 'BraveRewardsNewRewardsUIStudy', 89 | experiment: [ 90 | { 91 | name: 'Enabled', 92 | probability_weight: 100, 93 | feature_association: { 94 | enable_feature: [ 95 | 'BraveRewardsNewRewardsUI', 96 | ], 97 | }, 98 | }, 99 | { 100 | name: 'Default', 101 | probability_weight: 0, 102 | }, 103 | ], 104 | filter: { 105 | min_version: '135.1.77.95', 106 | channel: [ 107 | 'RELEASE', 108 | ], 109 | platform: [ 110 | 'ANDROID', 111 | ], 112 | }, 113 | }, 114 | ] 115 | -------------------------------------------------------------------------------- /studies/BraveRewardsPlatformCreatorDetectionStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRewardsPlatformCreatorDetectionStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveRewardsPlatformCreatorDetection', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'BETA', 22 | 'NIGHTLY', 23 | ], 24 | platform: [ 25 | 'ANDROID', 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /studies/BraveRewardsWebUiPanelStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRewardsWebUiPanelStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveRewardsWebUIPanel', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '103.1.43.9', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveRoundTimeStampsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveRoundTimeStampsStudy', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveRoundTimeStamps', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '125.*', 21 | max_version: '130.1.72.75', 22 | channel: [ 23 | 'NIGHTLY', 24 | 'BETA', 25 | 'RELEASE', 26 | ], 27 | platform: [ 28 | 'WINDOWS', 29 | 'MAC', 30 | 'LINUX', 31 | 'ANDROID', 32 | ], 33 | }, 34 | }, 35 | ] 36 | -------------------------------------------------------------------------------- /studies/BraveScreenFingerprintingBlockerStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveScreenFingerprintingBlockerStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveBlockScreenFingerprinting', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '108.1.48.19', 21 | channel: [ 22 | 'NIGHTLY', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | { 32 | name: 'BraveScreenFingerprintingBlockerStudy', 33 | experiment: [ 34 | { 35 | name: 'Enabled', 36 | probability_weight: 100, 37 | feature_association: { 38 | enable_feature: [ 39 | 'BraveBlockScreenFingerprinting', 40 | ], 41 | }, 42 | }, 43 | { 44 | name: 'Default', 45 | probability_weight: 0, 46 | }, 47 | ], 48 | filter: { 49 | min_version: '111.1.50.93', 50 | channel: [ 51 | 'BETA', 52 | ], 53 | platform: [ 54 | 'WINDOWS', 55 | 'MAC', 56 | 'LINUX', 57 | ], 58 | }, 59 | }, 60 | { 61 | name: 'BraveScreenFingerprintingBlockerStudy', 62 | experiment: [ 63 | { 64 | name: 'Enabled', 65 | probability_weight: 100, 66 | feature_association: { 67 | enable_feature: [ 68 | 'BraveBlockScreenFingerprinting', 69 | ], 70 | }, 71 | }, 72 | { 73 | name: 'Default', 74 | probability_weight: 0, 75 | }, 76 | ], 77 | filter: { 78 | min_version: '111.1.50.93', 79 | channel: [ 80 | 'RELEASE', 81 | ], 82 | platform: [ 83 | 'WINDOWS', 84 | 'MAC', 85 | 'LINUX', 86 | ], 87 | }, 88 | }, 89 | ] 90 | -------------------------------------------------------------------------------- /studies/BraveSearchAdStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveSearchAdStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ShouldLaunchBraveAdsAsInProcessService', 11 | 'ShouldAlwaysRunBraveAdsService', 12 | 'ShouldSupportSearchResultAds', 13 | 'ShouldAlwaysTriggerBraveSearchResultAdEvents', 14 | ], 15 | }, 16 | }, 17 | { 18 | name: 'Default', 19 | probability_weight: 0, 20 | }, 21 | ], 22 | filter: { 23 | max_version: '136.1.78.81', 24 | channel: [ 25 | 'NIGHTLY', 26 | 'BETA', 27 | 'RELEASE', 28 | ], 29 | platform: [ 30 | 'WINDOWS', 31 | 'MAC', 32 | 'LINUX', 33 | 'ANDROID', 34 | ], 35 | }, 36 | }, 37 | { 38 | name: 'BraveSearchAdStudy', 39 | experiment: [ 40 | { 41 | name: 'Enabled', 42 | probability_weight: 100, 43 | feature_association: { 44 | enable_feature: [ 45 | 'ShouldAlwaysRunBraveAdsService', 46 | 'ShouldSupportSearchResultAds', 47 | 'ShouldAlwaysTriggerBraveSearchResultAdEvents', 48 | ], 49 | }, 50 | }, 51 | { 52 | name: 'Default', 53 | probability_weight: 0, 54 | }, 55 | ], 56 | filter: { 57 | min_version: '122.1.65.32', 58 | max_version: '136.1.78.81', 59 | channel: [ 60 | 'NIGHTLY', 61 | 'BETA', 62 | 'RELEASE', 63 | ], 64 | platform: [ 65 | 'IOS', 66 | ], 67 | }, 68 | }, 69 | ] 70 | -------------------------------------------------------------------------------- /studies/BraveSearchPromotionBannerStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveSearchPromotionBannerStudy', 4 | experiment: [ 5 | { 6 | name: 'banner_type_B', 7 | probability_weight: 33, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveSearchOmniboxBanner', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'banner_type', 16 | value: 'type_B', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'banner_type_C', 22 | probability_weight: 33, 23 | feature_association: { 24 | enable_feature: [ 25 | 'BraveSearchOmniboxBanner', 26 | ], 27 | }, 28 | param: [ 29 | { 30 | name: 'banner_type', 31 | value: 'type_C', 32 | }, 33 | ], 34 | }, 35 | { 36 | name: 'banner_type_D', 37 | probability_weight: 34, 38 | feature_association: { 39 | enable_feature: [ 40 | 'BraveSearchOmniboxBanner', 41 | ], 42 | }, 43 | param: [ 44 | { 45 | name: 'banner_type', 46 | value: 'type_D', 47 | }, 48 | ], 49 | }, 50 | ], 51 | filter: { 52 | min_version: '117.1.60.41', 53 | channel: [ 54 | 'BETA', 55 | 'NIGHTLY', 56 | ], 57 | platform: [ 58 | 'WINDOWS', 59 | 'MAC', 60 | 'LINUX', 61 | ], 62 | country: [ 63 | 'DE', 64 | 'FR', 65 | 'US', 66 | 'AT', 67 | 'ES', 68 | 'MX', 69 | 'BR', 70 | 'AR', 71 | 'IN', 72 | ], 73 | }, 74 | }, 75 | ] 76 | -------------------------------------------------------------------------------- /studies/BraveSearchPromotionBannerStudyOnStable.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveSearchPromotionBannerStudyOnStable', 4 | experiment: [ 5 | { 6 | name: 'banner_type_B', 7 | probability_weight: 2, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveSearchOmniboxBanner', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'banner_type', 16 | value: 'type_B', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'banner_type_C', 22 | probability_weight: 2, 23 | feature_association: { 24 | enable_feature: [ 25 | 'BraveSearchOmniboxBanner', 26 | ], 27 | }, 28 | param: [ 29 | { 30 | name: 'banner_type', 31 | value: 'type_C', 32 | }, 33 | ], 34 | }, 35 | { 36 | name: 'banner_type_D', 37 | probability_weight: 2, 38 | feature_association: { 39 | enable_feature: [ 40 | 'BraveSearchOmniboxBanner', 41 | ], 42 | }, 43 | param: [ 44 | { 45 | name: 'banner_type', 46 | value: 'type_D', 47 | }, 48 | ], 49 | }, 50 | { 51 | name: 'Default', 52 | probability_weight: 94, 53 | }, 54 | ], 55 | filter: { 56 | min_version: '117.1.60.41', 57 | channel: [ 58 | 'RELEASE', 59 | ], 60 | platform: [ 61 | 'WINDOWS', 62 | 'MAC', 63 | 'LINUX', 64 | ], 65 | country: [ 66 | 'CA', 67 | 'DE', 68 | 'FR', 69 | 'GB', 70 | 'AT', 71 | 'ES', 72 | 'MX', 73 | 'BR', 74 | 'AR', 75 | 'IN', 76 | ], 77 | }, 78 | }, 79 | ] 80 | -------------------------------------------------------------------------------- /studies/BraveSearchPromotionButtonStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveSearchPromotionButtonStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveSearchPromotionOmniboxButton', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '131.1.74.20', 21 | channel: [ 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | ], 30 | country: [ 31 | 'CA', 32 | 'GB', 33 | ], 34 | }, 35 | }, 36 | ] 37 | -------------------------------------------------------------------------------- /studies/BraveShredFeatureStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveShredFeatureStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveShredFeature', 11 | 'BraveShredCacheData', 12 | ], 13 | }, 14 | }, 15 | { 16 | name: 'Default', 17 | probability_weight: 0, 18 | }, 19 | ], 20 | filter: { 21 | min_version: '130.*', 22 | channel: [ 23 | 'RELEASE', 24 | 'BETA', 25 | 'NIGHTLY', 26 | ], 27 | platform: [ 28 | 'IOS', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/BraveSplitViewStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveSplitViewStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveSplitView', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '132.1.76.29', 21 | channel: [ 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | ], 30 | }, 31 | }, 32 | { 33 | name: 'BraveSplitViewStudy', 34 | experiment: [ 35 | { 36 | name: 'Enabled', 37 | probability_weight: 100, 38 | feature_association: { 39 | enable_feature: [ 40 | 'BraveSplitView', 41 | ], 42 | }, 43 | }, 44 | { 45 | name: 'Default', 46 | probability_weight: 0, 47 | }, 48 | ], 49 | filter: { 50 | min_version: '134.1.76.80', 51 | channel: [ 52 | 'RELEASE', 53 | ], 54 | platform: [ 55 | 'WINDOWS', 56 | 'MAC', 57 | 'LINUX', 58 | ], 59 | }, 60 | }, 61 | ] 62 | -------------------------------------------------------------------------------- /studies/BraveTranslateStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveTranslateStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'UseBraveTranslateGo', 11 | 'Translate', 12 | ], 13 | }, 14 | }, 15 | { 16 | name: 'Default', 17 | probability_weight: 0, 18 | }, 19 | ], 20 | filter: { 21 | min_version: '104.1.43.54', 22 | max_version: '106.1.45.44', 23 | channel: [ 24 | 'NIGHTLY', 25 | 'BETA', 26 | 'RELEASE', 27 | ], 28 | platform: [ 29 | 'WINDOWS', 30 | 'MAC', 31 | 'LINUX', 32 | 'ANDROID', 33 | ], 34 | }, 35 | }, 36 | ] 37 | -------------------------------------------------------------------------------- /studies/BraveTranslateiOSStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveTranslateiOSStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveTranslateEnabled', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'IOS', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/BraveUAStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveUAStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'UseBraveUserAgent', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'IOS', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/BraveWalletAnkrBalancesEnabled.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveWalletAnkrBalancesEnabled', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 0, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveWalletAnkrBalances', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 100, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '120.*', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/BraveWebViewRoundedCornersStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveWebViewRoundedCornersStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'brave-web-view-rounded-corners', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | channel: [ 17 | 'NIGHTLY', 18 | 'BETA', 19 | ], 20 | platform: [ 21 | 'WINDOWS', 22 | 'MAC', 23 | 'LINUX', 24 | ], 25 | }, 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /studies/BraveWebcompatExceptionsServiceReleaseStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveWebcompatExceptionsServiceReleaseStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveWebcompatExceptionsService', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '128.1.69.150', 21 | channel: [ 22 | 'RELEASE', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | 'ANDROID', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/BraveWebcompatExceptionsServiceStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'BraveWebcompatExceptionsServiceStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveWebcompatExceptionsService', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '128.1.70.66', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/CanvasKillSwitches.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CanvasKillSwitches', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'Canvas2DHibernation', 11 | 'EvictionUnlocksResources', 12 | ], 13 | }, 14 | }, 15 | ], 16 | filter: { 17 | max_version: '125.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/ClampPlatformVersionClientHint.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ClampPlatformVersionClientHint', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'ClampPlatformVersionClientHint', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '114.1.52.78', 17 | channel: [ 18 | 'RELEASE', 19 | 'BETA', 20 | 'NIGHTLY', 21 | ], 22 | platform: [ 23 | 'WINDOWS', 24 | 'MAC', 25 | 'LINUX', 26 | 'ANDROID', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/CloseWatcher.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CloseWatcher', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'CloseWatcher', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '120.*', 17 | max_version: '122.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/CollectWebGPUSupportMetricsKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CollectWebGPUSupportMetricsKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_CollectWebGPUSupportMetrics', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'CollectWebGPUSupportMetrics', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '119.*', 17 | max_version: '119.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'LINUX', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/CookieListDefaultStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CookieListDefaultStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveAdblockCookieListDefault', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/CosmeticFilteringChildFramesStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CosmeticFilteringChildFramesStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveAdblockCosmeticFilteringChildFrames', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '106.1.46.14', 21 | channel: [ 22 | 'BETA', 23 | 'NIGHTLY', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/CrossPlatformVPNStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'CrossPlatformVPNStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveVPN', 11 | 'BraveVPNLinkSubscriptionAndroidUI', 12 | ], 13 | }, 14 | }, 15 | { 16 | name: 'Default', 17 | probability_weight: 0, 18 | }, 19 | ], 20 | filter: { 21 | min_version: '110.1.49.120', 22 | channel: [ 23 | 'NIGHTLY', 24 | 'BETA', 25 | 'RELEASE', 26 | ], 27 | platform: [ 28 | 'WINDOWS', 29 | 'MAC', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/Default1pBlockingStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'Default1pBlockingStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 0, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveAdblockDefault1pBlocking', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 100, 17 | feature_association: { 18 | disable_feature: [ 19 | 'BraveAdblockDefault1pBlocking', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '92.1.30.57', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | ], 34 | platform: [ 35 | 'WINDOWS', 36 | 'MAC', 37 | 'LINUX', 38 | 'ANDROID', 39 | ], 40 | }, 41 | }, 42 | { 43 | name: 'Default1pBlockingStudy', 44 | experiment: [ 45 | { 46 | name: 'Enabled', 47 | probability_weight: 0, 48 | feature_association: { 49 | enable_feature: [ 50 | 'BraveAdblockDefault1pBlocking', 51 | ], 52 | }, 53 | }, 54 | { 55 | name: 'Disabled', 56 | probability_weight: 100, 57 | feature_association: { 58 | disable_feature: [ 59 | 'BraveAdblockDefault1pBlocking', 60 | ], 61 | }, 62 | }, 63 | { 64 | name: 'Default', 65 | probability_weight: 0, 66 | }, 67 | ], 68 | filter: { 69 | min_version: '92.1.30.57', 70 | channel: [ 71 | 'RELEASE', 72 | ], 73 | platform: [ 74 | 'WINDOWS', 75 | 'MAC', 76 | 'LINUX', 77 | 'ANDROID', 78 | ], 79 | }, 80 | }, 81 | ] 82 | -------------------------------------------------------------------------------- /studies/DefaultBraveCommandsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DefaultBraveCommandsStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveCommands', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/DefaultBraveOmniboxMoreHistoryStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DefaultBraveOmniboxMoreHistoryStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'HistoryMoreSearchResults', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/DefaultPassthroughCommandDecoderStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DefaultPassthroughCommandDecoderStudy', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'DefaultPassthroughCommandDecoder', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '125.*', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/DefaultPlaylistStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DefaultPlaylistStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'Playlist', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/DisableCnameUncloakingForAndroid.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DisableCnameUncloakingForAndroid', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveAdblockCnameUncloaking', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'ANDROID', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/DisableReduceLanguage.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'DisableReduceLanguage', 4 | experiment: [ 5 | { 6 | name: 'Default', 7 | probability_weight: 0, 8 | feature_association: { 9 | disable_feature: [ 10 | 'BraveReduceLanguage', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Enabled', 16 | probability_weight: 100, 17 | feature_association: { 18 | enable_feature: [ 19 | 'BraveReduceLanguage', 20 | ], 21 | }, 22 | }, 23 | ], 24 | filter: { 25 | channel: [ 26 | 'NIGHTLY', 27 | 'BETA', 28 | 'RELEASE', 29 | ], 30 | platform: [ 31 | 'WINDOWS', 32 | 'MAC', 33 | 'LINUX', 34 | 'ANDROID', 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /studies/EphemeralStorageStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'EphemeralStorageStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'EphemeralStorage', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'EphemeralStorage', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '89.1.21.73', 30 | channel: [ 31 | 'RELEASE', 32 | ], 33 | platform: [ 34 | 'WINDOWS', 35 | 'MAC', 36 | 'LINUX', 37 | 'ANDROID', 38 | ], 39 | }, 40 | }, 41 | { 42 | name: 'EphemeralStorageStudy', 43 | experiment: [ 44 | { 45 | name: 'Enabled', 46 | probability_weight: 100, 47 | feature_association: { 48 | enable_feature: [ 49 | 'EphemeralStorage', 50 | ], 51 | }, 52 | }, 53 | { 54 | name: 'Disabled', 55 | probability_weight: 0, 56 | feature_association: { 57 | disable_feature: [ 58 | 'EphemeralStorage', 59 | ], 60 | }, 61 | }, 62 | { 63 | name: 'Default', 64 | probability_weight: 0, 65 | }, 66 | ], 67 | filter: { 68 | min_version: '89.1.21.73', 69 | channel: [ 70 | 'NIGHTLY', 71 | 'BETA', 72 | ], 73 | platform: [ 74 | 'WINDOWS', 75 | 'MAC', 76 | 'LINUX', 77 | 'ANDROID', 78 | ], 79 | }, 80 | }, 81 | ] 82 | -------------------------------------------------------------------------------- /studies/ExtensionsManifestV2Study.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ExtensionsManifestV2Study', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ExtensionsManifestV2', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '125.1.68.25', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/ExtensionsManifestV2StudyRelease.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ExtensionsManifestV2StudyRelease', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ExtensionsManifestV2', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '125.1.68.25', 21 | channel: [ 22 | 'RELEASE', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/HangWatcher.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'HangWatcher', 4 | experiment: [ 5 | { 6 | name: 'HangWatcherEnableDumps', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'EnableHangWatcher', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'ui_thread_log_level', 16 | value: '2', 17 | }, 18 | { 19 | name: 'io_thread_log_level', 20 | value: '2', 21 | }, 22 | ], 23 | }, 24 | ], 25 | filter: { 26 | min_version: '97.1.36.14', 27 | channel: [ 28 | 'NIGHTLY', 29 | 'BETA', 30 | ], 31 | platform: [ 32 | 'WINDOWS', 33 | 'MAC', 34 | ], 35 | }, 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /studies/HeapProfilingMacKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'HeapProfilingMacKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'HeapProfilerReporting', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '122.*', 17 | max_version: '125.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'MAC', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/InnerHTMLParserFastpathStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'InnerHTMLParserFastpathStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'InnerHTMLParserFastpath', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | max_version: '114.1.52.999', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/LowerCaseIntentSchemesKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'LowerCaseIntentSchemesKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_KillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'LowerCaseIntentSchemes', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '136.1.78.94', 17 | max_version: '138.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'ANDROID', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/MacCoreLocationBackendStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'MacCoreLocationBackendStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'MacCoreLocationBackend', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'MacCoreLocationBackend', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '91.1.26.74', 30 | channel: [ 31 | 'RELEASE', 32 | 'BETA', 33 | 'NIGHTLY', 34 | ], 35 | platform: [ 36 | 'MAC', 37 | ], 38 | }, 39 | }, 40 | ] 41 | -------------------------------------------------------------------------------- /studies/MetricsAndCrashSampling.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'MetricsAndCrashSampling', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'MetricsReporting', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'ANDROID', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/ModuleFileNamePatchStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ModuleFileNamePatchStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ModuleFileNamePatch', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '112.1.51.89', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/ModuleFileNamePatchStudyRelease.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'ModuleFileNamePatchStudyRelease', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 15, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ModuleFileNamePatch', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 85, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '112.1.51.89', 21 | channel: [ 22 | 'RELEASE', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /studies/NavigationThreadingOptimizationsCompatOldVersions.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'NavigationThreadingOptimizationsCompatOldVersions', 4 | experiment: [ 5 | { 6 | name: 'Disabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'NavigationThreadingOptimizations', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | max_version: '102.1.39.0', 21 | channel: [ 22 | 'RELEASE', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/NewiOSMenuUIStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'NewiOSMenuUIStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'ModernBrowserMenuEnabled', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'RELEASE', 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'IOS', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/NewiOSPlaylistUIStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'NewiOSPlaylistUIStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'NewPlaylistUI', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | ], 24 | platform: [ 25 | 'IOS', 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /studies/PartitionVisitedLinkDatabaseWithSelfLinks.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'PartitionVisitedLinkDatabaseWithSelfLinks', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'PartitionVisitedLinkDatabaseWithSelfLinks', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '134.1.77.83', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | 'ANDROID', 30 | ], 31 | }, 32 | }, 33 | { 34 | name: 'PartitionVisitedLinkDatabaseWithSelfLinks_Release', 35 | experiment: [ 36 | { 37 | name: 'Enabled', 38 | probability_weight: 100, 39 | feature_association: { 40 | enable_feature: [ 41 | 'PartitionVisitedLinkDatabaseWithSelfLinks', 42 | ], 43 | }, 44 | }, 45 | { 46 | name: 'Default', 47 | probability_weight: 0, 48 | }, 49 | ], 50 | filter: { 51 | min_version: '134.1.77.83', 52 | channel: [ 53 | 'RELEASE', 54 | ], 55 | platform: [ 56 | 'WINDOWS', 57 | 'MAC', 58 | 'LINUX', 59 | 'ANDROID', 60 | ], 61 | }, 62 | }, 63 | ] 64 | -------------------------------------------------------------------------------- /studies/PartitionedCookies.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'PartitionedCookies', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'PartitionedCookies', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '122.1.63.0', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | 'ANDROID', 31 | ], 32 | }, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /studies/PostFREFixMetricsAndCrashSampling.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'PostFREFixMetricsAndCrashSampling', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'PostFREFixMetricsReporting', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | 'BETA', 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'ANDROID', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/PrettyPrintJSONDocument.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'PrettyPrintJSONDocument', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'PrettyPrintJSONDocument', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '117.0.0.0', 17 | max_version: '119.1.61.0', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/PrivateNetworkAccessKillswitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'PrivateNetworkAccessKillswitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_PrivateNetworkAccessKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'PrivateNetworkAccessForNavigations', 11 | 'PrivateNetworkAccessForNavigationsWarningOnly', 12 | ], 13 | }, 14 | }, 15 | ], 16 | filter: { 17 | min_version: '123.*', 18 | max_version: '125.*', 19 | channel: [ 20 | 'NIGHTLY', 21 | 'BETA', 22 | 'RELEASE', 23 | ], 24 | platform: [ 25 | 'WINDOWS', 26 | 'MAC', 27 | 'LINUX', 28 | 'ANDROID', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/RasterInducingScrollKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'RasterInducingScrollKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'RasterInducingScroll', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '131.*', 17 | max_version: '137.*', 18 | channel: [ 19 | 'BETA', 20 | 'NIGHTLY', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'LINUX', 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /studies/RendererAllocatesImagesKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'RendererAllocatesImagesKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_RendererAllocatesImages', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'RendererAllocatesImages', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '122.*', 17 | max_version: '124.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'MAC', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/SmilAutoSuspendOnLagKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'SmilAutoSuspendOnLagKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_EmergencyKillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'SmilAutoSuspendOnLag', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '118.*', 17 | max_version: '120.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/Speedreader TTS.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'Speedreader TTS', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'Speedreader', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'tts', 16 | value: 'true', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | channel: [ 27 | 'NIGHTLY', 28 | 'BETA', 29 | 'RELEASE', 30 | ], 31 | platform: [ 32 | 'WINDOWS', 33 | 'MAC', 34 | ], 35 | }, 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /studies/TabStripLayoutOptimization.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'TabStripLayoutOptimization', 4 | experiment: [ 5 | { 6 | name: 'Disabled_TabStripLayoutOptimization', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'TabStripLayoutOptimization', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '135.*', 17 | max_version: '136.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'ANDROID', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /studies/UndecryptablePasswords.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'UndecryptablePasswords', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'SkipUndecryptablePasswords', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '127.1.68.107', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'WINDOWS', 28 | 'MAC', 29 | 'LINUX', 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /studies/UseFreedesktopSecretKeyProviderKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'UseFreedesktopSecretKeyProviderKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Default', 7 | probability_weight: 0, 8 | }, 9 | { 10 | name: 'Enabled', 11 | probability_weight: 100, 12 | feature_association: { 13 | disable_feature: [ 14 | 'UseFreedesktopSecretKeyProvider', 15 | ], 16 | }, 17 | }, 18 | ], 19 | filter: { 20 | min_version: '135.*', 21 | channel: [ 22 | 'NIGHTLY', 23 | 'BETA', 24 | 'RELEASE', 25 | ], 26 | platform: [ 27 | 'LINUX', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/UseWritePixelsYUV.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'UseWritePixelsYUV', 4 | experiment: [ 5 | { 6 | name: 'Disabled_UseWritePixelsYUV', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'UseWritePixelsYUV', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | min_version: '123.*', 17 | max_version: '125.*', 18 | channel: [ 19 | 'NIGHTLY', 20 | 'BETA', 21 | 'RELEASE', 22 | ], 23 | platform: [ 24 | 'WINDOWS', 25 | 'MAC', 26 | 'LINUX', 27 | 'ANDROID', 28 | ], 29 | }, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /studies/UserActivityStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'UserActivityStudy', 4 | experiment: [ 5 | { 6 | name: 'Triggers', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'UserActivity', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'triggers', 16 | value: '0D0B14110D0B14110D0B14110D0B1411=-1.0;0D0B1411070707=-1.0;07070707=-1.0;0B1512=0.5;0B1513=0.5;0B1514=0.5;06=1.0;04=0.5', 17 | }, 18 | { 19 | name: 'time_window', 20 | value: '1h', 21 | }, 22 | { 23 | name: 'threshold', 24 | value: '0.5', 25 | }, 26 | ], 27 | }, 28 | { 29 | name: 'Default', 30 | probability_weight: 0, 31 | }, 32 | ], 33 | filter: { 34 | min_version: '108.1.47.73', 35 | channel: [ 36 | 'NIGHTLY', 37 | 'BETA', 38 | 'RELEASE', 39 | ], 40 | platform: [ 41 | 'WINDOWS', 42 | 'MAC', 43 | 'LINUX', 44 | ], 45 | }, 46 | }, 47 | ] 48 | -------------------------------------------------------------------------------- /studies/V8StoreStoreEliminationKillSwitch.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'V8StoreStoreEliminationKillSwitch', 4 | experiment: [ 5 | { 6 | name: 'Disabled_KillSwitch', 7 | probability_weight: 100, 8 | feature_association: { 9 | disable_feature: [ 10 | 'V8Flag_turbo_store_elimination', 11 | ], 12 | }, 13 | }, 14 | ], 15 | filter: { 16 | max_version: '138.*', 17 | channel: [ 18 | 'NIGHTLY', 19 | 'BETA', 20 | 'RELEASE', 21 | ], 22 | platform: [ 23 | 'WINDOWS', 24 | 'MAC', 25 | 'LINUX', 26 | 'ANDROID', 27 | ], 28 | }, 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /studies/WebDiscoveryNativeStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'WebDiscoveryNativeStudy_AndroidNightly', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveWebDiscoveryNative', 11 | ], 12 | }, 13 | param: [ 14 | { 15 | name: 'patterns_path', 16 | value: 'patterns-android.gz', 17 | }, 18 | ], 19 | }, 20 | { 21 | name: 'Default', 22 | probability_weight: 0, 23 | }, 24 | ], 25 | filter: { 26 | min_version: '134.1.78.42', 27 | channel: [ 28 | 'NIGHTLY', 29 | ], 30 | platform: [ 31 | 'ANDROID', 32 | ], 33 | }, 34 | }, 35 | { 36 | name: 'WebDiscoveryNativeStudy_AndroidBeta', 37 | experiment: [ 38 | { 39 | name: 'Enabled', 40 | probability_weight: 100, 41 | feature_association: { 42 | enable_feature: [ 43 | 'BraveWebDiscoveryNative', 44 | ], 45 | }, 46 | param: [ 47 | { 48 | name: 'patterns_path', 49 | value: 'patterns-android.gz', 50 | }, 51 | ], 52 | }, 53 | { 54 | name: 'Default', 55 | probability_weight: 0, 56 | }, 57 | ], 58 | filter: { 59 | min_version: '134.1.77.82', 60 | channel: [ 61 | 'BETA', 62 | ], 63 | platform: [ 64 | 'ANDROID', 65 | ], 66 | }, 67 | }, 68 | { 69 | name: 'WebDiscoveryNativeStudy_AndroidRelease', 70 | experiment: [ 71 | { 72 | name: 'Enabled', 73 | probability_weight: 100, 74 | feature_association: { 75 | enable_feature: [ 76 | 'BraveWebDiscoveryNative', 77 | ], 78 | }, 79 | param: [ 80 | { 81 | name: 'patterns_path', 82 | value: 'patterns-android.gz', 83 | }, 84 | ], 85 | }, 86 | { 87 | name: 'Default', 88 | probability_weight: 0, 89 | }, 90 | ], 91 | filter: { 92 | min_version: '134.1.76.78', 93 | channel: [ 94 | 'RELEASE', 95 | ], 96 | platform: [ 97 | 'ANDROID', 98 | ], 99 | }, 100 | }, 101 | ] 102 | -------------------------------------------------------------------------------- /studies/WhatsNewStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'WhatsNewStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | param: [ 9 | { 10 | name: 'target_major_version_stable', 11 | value: '1.65', 12 | }, 13 | ], 14 | }, 15 | { 16 | name: 'Default', 17 | probability_weight: 0, 18 | }, 19 | ], 20 | filter: { 21 | min_version: '119.1.60.114', 22 | channel: [ 23 | 'RELEASE', 24 | ], 25 | platform: [ 26 | 'WINDOWS', 27 | 'MAC', 28 | 'LINUX', 29 | ], 30 | }, 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /studies/WorkaroundNewWindowFlash.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'WorkaroundNewWindowFlash', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'BraveWorkaroundNewWindowFlash', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Disabled', 16 | probability_weight: 0, 17 | feature_association: { 18 | disable_feature: [ 19 | 'BraveWorkaroundNewWindowFlash', 20 | ], 21 | }, 22 | }, 23 | { 24 | name: 'Default', 25 | probability_weight: 0, 26 | }, 27 | ], 28 | filter: { 29 | min_version: '127.1.70.49', 30 | channel: [ 31 | 'NIGHTLY', 32 | 'BETA', 33 | ], 34 | platform: [ 35 | 'WINDOWS', 36 | ], 37 | }, 38 | }, 39 | { 40 | name: 'WorkaroundNewWindowFlash', 41 | experiment: [ 42 | { 43 | name: 'Enabled', 44 | probability_weight: 100, 45 | feature_association: { 46 | enable_feature: [ 47 | 'BraveWorkaroundNewWindowFlash', 48 | ], 49 | }, 50 | }, 51 | { 52 | name: 'Disabled', 53 | probability_weight: 0, 54 | feature_association: { 55 | disable_feature: [ 56 | 'BraveWorkaroundNewWindowFlash', 57 | ], 58 | }, 59 | }, 60 | { 61 | name: 'Default', 62 | probability_weight: 0, 63 | }, 64 | ], 65 | filter: { 66 | min_version: '127.1.70.49', 67 | channel: [ 68 | 'RELEASE', 69 | ], 70 | platform: [ 71 | 'WINDOWS', 72 | ], 73 | }, 74 | }, 75 | ] 76 | -------------------------------------------------------------------------------- /studies/iOSChromiumWebViewsStudy.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: 'iOSChromiumWebViewsStudy', 4 | experiment: [ 5 | { 6 | name: 'Enabled', 7 | probability_weight: 100, 8 | feature_association: { 9 | enable_feature: [ 10 | 'UseChromiumWebViews', 11 | ], 12 | }, 13 | }, 14 | { 15 | name: 'Default', 16 | probability_weight: 0, 17 | }, 18 | ], 19 | filter: { 20 | channel: [ 21 | 'NIGHTLY', 22 | ], 23 | platform: [ 24 | 'IOS', 25 | ], 26 | }, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "noImplicitAny": true, 5 | "module": "CommonJS", 6 | "target": "es2022", 7 | "allowJs": true, 8 | "moduleResolution": "node", 9 | "baseUrl": ".", 10 | "typeRoots": ["./node_modules/@types"], 11 | "jsx": "react-jsx", 12 | "outDir": "build", 13 | // optional 14 | "forceConsistentCasingInFileNames": true, 15 | "noImplicitReturns": true, 16 | "strictNullChecks": true, 17 | // tsx-specific: 18 | "isolatedModules": true, 19 | "esModuleInterop": true 20 | }, 21 | // This config should cover all files in the project, but exclude all build 22 | // outputs. 23 | "include": ["./src"], 24 | "exclude": ["**/build/", "**/bundle/"] 25 | } 26 | --------------------------------------------------------------------------------