Compare commits

...

23 Commits
master ... v131

Author SHA1 Message Date
harvey186 ff6bef97c7 v131 2024-10-12 13:22:28 +02:00
harvey186 7a6de5f3e6 v131 2024-10-12 11:57:06 +02:00
harvey186 c64e459308 V129 2024-08-26 10:00:43 +02:00
harvey186 331c06960a v125 2024-06-01 16:29:02 +02:00
harvey186 d46b64ddc0 v124 2024-04-14 13:28:59 +02:00
harvey186 91005272fc icon 2024-04-14 13:28:14 +02:00
harvey186 2ed87e4857 v124 2024-04-14 13:12:20 +02:00
harvey186 9f0ee49d8a v122 2024-02-22 09:46:49 +01:00
harvey186 e55c715cbd fenix-patches 2024-02-22 09:45:49 +01:00
harvey186 cd1540afd8 v122 2024-02-21 18:22:05 +01:00
harvey186 9057022ade v122 2024-02-20 09:42:09 +01:00
harvey186 174f975690 v122 2024-02-18 09:19:35 +01:00
harvey186 8aff189c63 v122 2024-01-30 15:56:09 +01:00
harvey186 ecd7c8bc60 add_LeOSearch_default 2023-12-25 17:58:29 +01:00
harvey186 6f9d2411cc add_swisscows_default 2023-12-25 17:32:55 +01:00
harvey186 a7051380f1 search_engine 2023-12-25 10:18:31 +01:00
harvey186 5cd56eb469 name_corrections 2023-12-24 17:38:36 +01:00
harvey186 5a1a6f14e8 welcome_screens 2023-12-24 15:50:08 +01:00
harvey186 53ba8ca814 search 2023-12-24 15:45:58 +01:00
harvey186 23fdd5cdbb Homescreen_change 2023-12-24 15:34:24 +01:00
harvey186 717c9ab863 splash screen 2023-12-24 15:23:02 +01:00
harvey186 7cd623870e v121 2023-12-24 13:38:45 +01:00
harvey186 35dc7b49a9 Name and icon changes _ v121 2023-12-24 13:35:20 +01:00
9188 changed files with 836739 additions and 225493 deletions

View File

@ -83,9 +83,11 @@ projects:
- support-rusthttp
- support-rustlog
- support-test
- support-test-fakes
- support-test-libstate
- support-utils
- support-webextensions
- tooling-lint
- ui-autocomplete
- ui-colors
- ui-icons

78
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: "🐞 Bug report"
description: Create a report to help us improve.
title: "[Bug]: "
labels: ["🐞 bug", "needs:triage"]
body:
- type: markdown
attributes:
value: |
- Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean.
- Have a look at ["I want to file an issue!"][info] for more information.
[info]: https://github.com/fork-maintainers/iceraven-browser#i-want-to-file-an-issue
- type: textarea
attributes:
label: Steps to reproduce
description: Steps to reproduce the behaviour.
placeholder: |
1. Have a tab open..
2. Go to..
3. Click on..
4. Observe..
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
placeholder: A menu should open..
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour
placeholder: The app closes unexpectedly..
validations:
required: true
- type: markdown
attributes:
value: |
# Device information
- type: input
attributes:
label: Device name
description: The name of the device model and manufacturer.
placeholder: Google Pixel 2
validations:
required: false
- type: input
attributes:
label: Android version
description: You can find the Android version information in the About section of your device's system settings.
placeholder: Android 10
validations:
required: true
- type: input
attributes:
label: Iceraven version
description: You can find this information in Settings -> About Iceraven.
placeholder: 2.22.0
validations:
required: true
- type: textarea
attributes:
label: Device logs
description: |
Device logs or crash information can greatly aid in debugging. You can find some details here on how to [retrieve device logs or crash IDs][log].
[log]: https://github.com/fork-maintainers/iceraven-browser/blob/iceraven/docs/Logging-Crash-Information.md
validations:
required: false
- type: textarea
attributes:
label: Additional information
description: |
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.
validations:
required: false

View File

@ -0,0 +1,40 @@
name: "⭐️ Feature request"
description: Suggest an idea for this project
title: "[Feature Request]: "
labels: ["🌟 feature request"]
body:
- type: markdown
attributes:
value: |
- Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean
- Every issue should have exactly one feature request described in it. Please do not file feedback list tickets as it is difficult to parse them and address their individual points
- Feature Requests are better when theyre open-ended instead of demanding a specific solution e.g: “I want an easier way to do X” instead of “add Y”
- Read https://github.com/fork-maintainers/iceraven-browser#i-want-to-file-an-issue for more information
- type: textarea
attributes:
label: Feature Summary
description: What is the user problem or growth opportunity you want to see solved?
validations:
required: true
- type: textarea
attributes:
label: Feature Research
description: How do you know that this problem exists today? Why is this important?
validations:
required: true
- type: textarea
attributes:
label: Alternative Solution
description: Other possible solutions, if any.
validations:
required: false
- type: textarea
attributes:
label: Target beneficiaries
description: Who will benefit from it?
validations:
required: true

96
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,96 @@
name: CI
on:
push:
branches:
- iceraven
jobs:
release-automation:
name: Build App
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'true'
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: temurin
- name: Install Android SDK with pieces Gradle skips
run: ./automation/iceraven/install-sdk.sh
- name: Inspect memory
run: free -h
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Patch on the fly
run: |
sed -i 's#\.\./version.txt#\./version.txt#g' android-components/plugins/config/src/main/java/ConfigPlugin.kt
./automation/iceraven/patch_android_components.sh
- name: Relpace strings
run: |
sed -i 's/Firefox/Iceraven/g' app/src/*/res/values*/*strings.xml
sed -i '/about_content/s/Mozilla/@forkmaintainers/' app/src/*/res/values*/*strings.xml
- name: Build forkRelease variant of app
uses: gradle/gradle-build-action@v2
env:
# Try to stop the daemon from magically vanishing by adding random memory-related arguments.
# See <https://stackoverflow.com/a/70010526> and <https://stackoverflow.com/a/70756876>
# The runner seems to have ~6 gigs of memory, so we make sure to stay under that.
# We have Java 11 so we don't have a perm size anymore.
GRADLE_OPTS: -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=2g -Xms1g -Xmx3g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
with:
gradle-home-cache-cleanup: true
gradle-executable: /usr/bin/time
arguments: -v ./gradlew app:assemblefenixForkRelease -x lintVitalFenixForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
- name: Setup build tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
- name: Create signed APKs
uses: abhijitvalluri/sign-apks@v0.8
with:
releaseDirectory: app/build/outputs/apk/fenix/forkRelease/
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
alias: ${{ secrets.DEBUG_ALIAS }}
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Upload arm64 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-arm64-v8a-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-arm64-v8a-forkRelease.apk
- name: Upload armeabi apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-armeabi-v7a-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-armeabi-v7a-forkRelease.apk
- name: Upload x86 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-x86-forkRelease.apk
- name: Upload x86_64 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86_64-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-x86_64-forkRelease.apk

View File

@ -0,0 +1,133 @@
name: Android build PR
on: [pull_request]
jobs:
run-build:
runs-on: ubuntu-20.04
if: ${{ false }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: "Clean & Assemble Debug"
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: clean app:assembleDebug
run-testDebugUnitTest:
runs-on: ubuntu-20.04
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'MickeyMoz'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: "Test Debug Unit Tests"
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: testDebugUnitTest
run-detekt:
runs-on: ubuntu-20.04
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'MickeyMoz'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: "Detekt"
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: detekt
- name: Archive detekt results
uses: actions/upload-artifact@v2
with:
name: detekt report
path: build/reports/detekt.html
run-ktlint:
runs-on: ubuntu-20.04
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'MickeyMoz'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: "Ktlint"
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: ktlint
run-lintDebug:
runs-on: ubuntu-20.04
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'MickeyMoz'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: "Lint Debug"
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: lintDebug
- name: Archive lint results
uses: actions/upload-artifact@v2
with:
name: lintDebug report
path: app/build/reports/lint-results-debug.html
run-ui:
runs-on: macos-11
if: ${{ false }}
timeout-minutes: 60
strategy:
matrix:
api-level: [28]
target: [google_apis]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run subset of UI Tests
uses: reactivecircus/android-emulator-runner@v2.21.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: pixel_2
script:
"JAVA_HOME=$JAVA_HOME_11_X64 && ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=\
org.mozilla.fenix.ui.NavigationToolbarTest#visitURLTest"
- name: Upload Test Artifacts
uses: actions/upload-artifact@v2
with:
name: test-report
path: app/build/reports

View File

@ -0,0 +1,44 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: "Fenix - Sync Strings"
on:
schedule:
- cron: '0 2 * * *'
jobs:
main:
name: "Sync Strings"
runs-on: ubuntu-20.04
steps:
- name: "Discover Fenix Beta Version"
id: fenix-beta-version
uses: mozilla-mobile/fenix-beta-version@4.1.0
- name: "Skip non-beta versions"
uses: andymckay/cancel-action@0.2
if: ${{ steps.fenix-beta-version.outputs.beta_version == '' }}
- name: "Checkout Master Branch"
uses: actions/checkout@v2
with:
path: main
ref: main
- name: "Checkout Beta Branch"
uses: actions/checkout@v2
with:
path: beta
ref: "releases_v${{ steps.fenix-beta-version.outputs.beta_version }}.0.0"
- name: "Sync Strings"
uses: mozilla-mobile/sync-strings-action@1.0.1
with:
src: main
dst: beta
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: beta
branch: automation/sync-strings-${{ steps.fenix-beta-version.outputs.beta_version }}
title: "Sync Strings from main to releases_${{steps.fenix-beta-version.outputs.beta_version}}.0"
body: "This (automated) PR syncs strings from `main` to `releases_${{steps.fenix-beta-version.outputs.beta_version}}.0.0`"

View File

@ -0,0 +1,23 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: "Fenix - Update Android-Components"
on:
schedule:
- cron: '*/15 * * * *'
jobs:
main:
name: "Update Android-Components"
runs-on: ubuntu-20.04
steps:
- name: "Update Android-Components"
uses: mozilla-mobile/relbot@5.0.2
if: github.repository == 'mozilla-mobile/fenix'
with:
project: fenix
command: update-android-components
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,42 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: "Fenix - Update Nimbus Experiments"
on:
schedule:
- cron: '*/30 * * * *'
workflow_dispatch: {}
jobs:
update-nimbus-experiments:
name: "Update Nimbus Experiments"
runs-on: ubuntu-latest
steps:
- name: "Checkout Main Branch"
uses: actions/checkout@v3
with:
path: fenix
ref: main
fetch-depth: 0
- name: "Update Experiments JSON"
id: update-experiments-json
uses: mozilla-mobile/update-experiments@v3
with:
repo-path: fenix
output-path: app/src/main/res/raw/initial_experiments.json
experimenter-url: https://experimenter.services.mozilla.com/api/v6/experiments-first-run/
app-name: fenix
branch: automation/update-nimbus-experiments
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
if: steps.update-experiments-json.outputs.changed == 1 && steps.update-experiments-json.outputs.changed-branch == 1
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: fenix
branch: automation/update-nimbus-experiments
commit-message: "update initial_experiments.json based on the current first-run experiments in experimenter"
title: "Update initial experiments JSON for Nimbus"
body: "This (automated) PR updates the initial_experiments.json on the `main` branch"
delete-branch: true

View File

@ -0,0 +1,10 @@
---
name: Glean probe-scraper
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
glean-probe-scraper:
uses: mozilla/probe-scraper/.github/workflows/glean.yaml@main

View File

@ -0,0 +1,21 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: Issue Linker
on:
pull_request_target:
branches:
- main
jobs:
issue_linker:
runs-on: ubuntu-latest
steps:
- name: Issue Linker
uses: gabrielluong/issue-linker@1.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
commit-regexp: "#(\\d+)+"
section: "### GitHub Automation"

View File

@ -0,0 +1,20 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: Tag Milestone
on:
issues:
types:
- closed
jobs:
milestone:
runs-on: ubuntu-latest
if: github.actor == 'mergify[bot]'
steps:
- name: Tag Milestone
uses: gabrielluong/milestone@1.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,22 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: QA Needed
on:
pull_request_target:
branches:
- main
jobs:
qa_needed:
runs-on: ubuntu-latest
steps:
- name: QA Needed
uses: gabrielluong/qa-needed@1.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
label: "eng:reopen-for-qa"
check-regexp: "- \\[([ xX]?)\\] \\*\\*QA Needed\\*\\*"
commit-regexp: "#(\\d+)+"

View File

@ -0,0 +1,21 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
name: Reopen for QA Needed
on:
issues:
types:
- closed
jobs:
reopen_qa_needed:
runs-on: ubuntu-latest
steps:
- name: Reopen for QA Needed
uses: gabrielluong/reopen-for-qa-needed@1.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
reopen-label: "eng:reopen-for-qa"
qa-label: "eng:qa:needed"

150
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,150 @@
name: Release
on:
create:
jobs:
release-automation:
name: Build App
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'true'
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: temurin
- name: Install Android SDK with pieces Gradle skips
run: ./automation/iceraven/install-sdk.sh
- name: Inspect memory
run: free -h
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Patch on the fly
run: |
sed -i 's#\.\./version.txt#\./version.txt#g' android-components/plugins/config/src/main/java/ConfigPlugin.kt
./automation/iceraven/patch_android_components.sh
- name: Relpace strings
run: |
sed -i 's/Firefox/Iceraven/g' app/src/*/res/values*/*strings.xml
sed -i '/about_content/s/Mozilla/@forkmaintainers/' app/src/*/res/values*/*strings.xml
- name: Build forkRelease variant of app
uses: gradle/gradle-build-action@v2
env:
# Try to stop the daemon from magically vanishing by adding random memory-related arguments.
# See <https://stackoverflow.com/a/70010526> and <https://stackoverflow.com/a/70756876>
# The runner seems to have ~6 gigs of memory, so we make sure to stay under that.
# We have Java 11 so we don't have a perm size anymore.
GRADLE_OPTS: -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=2g -Xms1g -Xmx3g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
with:
gradle-home-cache-cleanup: true
gradle-executable: /usr/bin/time
arguments: -v ./gradlew app:assemblefenixForkRelease -x lintVitalFenixForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
- name: Setup build tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
- name: Create signed APKs
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: abhijitvalluri/sign-apks@v0.8
with:
releaseDirectory: app/build/outputs/apk/fenix/forkRelease/
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
alias: ${{ secrets.DEBUG_ALIAS }}
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Create changelog
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
run: |
PREVIOUS_RELEASE_TAG=$(git tag --list iceraven-* --sort=-creatordate | tail -n+2 | head -n 1)
CURRENT_RELEASE_TAG=${{ github.event.ref }}
CURRENT_RELEASE_TAG_CAPITALIZE=${CURRENT_RELEASE_TAG^}
CURRENT_RELEASE_TAG_CAPITALIZE=$(echo $CURRENT_RELEASE_TAG_CAPITALIZE | tr '-' ' ')
echo "CURRENT_RELEASE_TAG_CAPITALIZE=${CURRENT_RELEASE_TAG_CAPITALIZE}" >> $GITHUB_ENV
FENIX_TAG=$(cat version.txt | tr -d '\n')
echo -e "## Release info\n" >> temp_changelog.md
echo '```' >> temp_changelog.md
echo "Iceraven: $(echo $CURRENT_RELEASE_TAG | tr -d 'iceraven-')" >> temp_changelog.md
echo "Fenix: ${FENIX_TAG}" >> temp_changelog.md
echo -e '```\n' >> temp_changelog.md
echo -e "## News\n" >> temp_changelog.md
echo -e "## Change log\n" >> temp_changelog.md
echo "[${PREVIOUS_RELEASE_TAG}...${CURRENT_RELEASE_TAG}](https://github.com/${{ github.repository }}/compare/${PREVIOUS_RELEASE_TAG}...${CURRENT_RELEASE_TAG})" >> temp_changelog.md
- name: Create Release
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.event.ref }}
release_name: "${{ env.CURRENT_RELEASE_TAG_CAPITALIZE }}"
draft: false
prerelease: false
body_path: temp_changelog.md
- name: Upload arm64 apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-arm64-v8a-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-arm64-v8a-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload armeabi apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-armeabi-v7a-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-armeabi-v7a-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload x86 apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-x86-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload x86_64 apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86_64-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-x86_64-forkRelease.apk
asset_content_type: application/vnd.android.package-archive

File diff suppressed because it is too large Load Diff

View File

@ -1,156 +0,0 @@
Subject: [PATCH] name and icon
---
Index: app/build.gradle
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle (revision 4dca5f634ae61ddeead5fc3444c3ab692ad98536)
+++ b/app/build.gradle (revision 31c3e83236b2bd980d168f651af7a022b5e6a8b3)
@@ -31,7 +31,7 @@
}
defaultConfig {
- applicationId "io.github.forkmaintainers"
+ applicationId "com.leos"
minSdkVersion config.minSdkVersion
compileSdk config.compileSdkVersion
targetSdkVersion config.targetSdkVersion
@@ -159,15 +159,15 @@
forkDebug {
shrinkResources false
minifyEnabled false
- applicationIdSuffix ".iceraven.debug"
+ applicationIdSuffix ".leosium.debug"
pseudoLocalesEnabled true
// Need to replicate default debug config features
signingConfig signingConfigs.debug
debuggable true
- def deepLinkSchemeValue = "iceraven-debug"
+ def deepLinkSchemeValue = "leosium-debug"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders.putAll([
- "sharedUserId": "io.github.forkmaintainers.iceraven.sharedID",
+ "sharedUserId": "io.github.forkmaintainers.leosium.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
])
// Use custom default allowed addon list
@@ -178,11 +178,11 @@
}
forkRelease releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
- applicationIdSuffix ".iceraven"
- def deepLinkSchemeValue = "iceraven"
+ applicationIdSuffix ".leosium"
+ def deepLinkSchemeValue = "leosium"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders.putAll([
- "sharedUserId": "io.github.forkmaintainers.iceraven.sharedID",
+ "sharedUserId": "io.github.forkmaintainers.leosium.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
])
// Use custom default allowed addon list
Index: app/src/forkRelease/res/values/static_strings.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/forkRelease/res/values/static_strings.xml b/app/src/forkRelease/res/values/static_strings.xml
--- a/app/src/forkRelease/res/values/static_strings.xml (revision 4dca5f634ae61ddeead5fc3444c3ab692ad98536)
+++ b/app/src/forkRelease/res/values/static_strings.xml (revision 31c3e83236b2bd980d168f651af7a022b5e6a8b3)
@@ -4,5 +4,5 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<!-- Name of the application -->
- <string name="app_name" translatable="false">Iceraven</string>
+ <string name="app_name" translatable="false">LeOSium</string>
</resources>
Index: app/src/main/java/org/mozilla/fenix/settings/quicksettings/protections/cookiebanners/dialog/CookieBannerReEngagementDialogCompose.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/protections/cookiebanners/dialog/CookieBannerReEngagementDialogCompose.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/protections/cookiebanners/dialog/CookieBannerReEngagementDialogCompose.kt
--- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/protections/cookiebanners/dialog/CookieBannerReEngagementDialogCompose.kt (revision 4dca5f634ae61ddeead5fc3444c3ab692ad98536)
+++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/protections/cookiebanners/dialog/CookieBannerReEngagementDialogCompose.kt (revision 31c3e83236b2bd980d168f651af7a022b5e6a8b3)
@@ -42,7 +42,7 @@
private fun CookieBannerReEngagementDialogComposePreview() {
FirefoxTheme {
CookieBannerReEngagementDialogCompose(
- dialogTitle = "Allow Iceraven to reject cookie banners?",
+ dialogTitle = "Allow LeOSium to reject cookie banners?",
dialogText =
"Automatically reject cookie requests, when possible. Otherwise, " +
"accept all cookies to dismiss cookie banners.",
Index: app/src/main/res/values/static_strings.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/res/values/static_strings.xml b/app/src/main/res/values/static_strings.xml
--- a/app/src/main/res/values/static_strings.xml (revision 4dca5f634ae61ddeead5fc3444c3ab692ad98536)
+++ b/app/src/main/res/values/static_strings.xml (revision 31c3e83236b2bd980d168f651af7a022b5e6a8b3)
@@ -4,8 +4,8 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<!-- Name of the application -->
- <string name="app_name" translatable="false">Iceraven Fenix</string>
- <string name="firefox" translatable="false">Iceraven</string>
+ <string name="app_name" translatable="false">LeOSium</string>
+ <string name="firefox" translatable="false">LeOSium</string>
<!-- Preference for developers -->
<string name="preference_leakcanary" translatable="false">LeakCanary</string>
@@ -21,7 +21,7 @@
<!-- Default title for pinned Wikipedia top site that links to Wikipedia home page -->
<string name="default_top_site_wikipedia" translatable="false">Wikipedia</string>
<!-- Default title for pinned Google top site that links to Google home page -->
- <string name="default_top_site_google" translatable="false">Google</string>
+ <string name="default_top_site_google" translatable="false">Goolag</string>
<!-- Application Services abbreviation used in AboutFragment -->
<string name="app_services_abbreviation" translatable="false">AS</string>
<!-- Name for the Pocket product -->
@@ -59,17 +59,17 @@
<!-- Preference to override the Push server -->
<string name="preferences_override_push_server">Custom Push server</string>
<!-- Quit app button title for the Sync Debug preferences -->
- <string name="preferences_sync_debug_quit_button_title">Stop Firefox</string>
+ <string name="preferences_sync_debug_quit_button_title">Stop LeOSium</string>
<!-- Quit app button summary for the Sync Debug preferences -->
- <string name="preferences_sync_debug_quit_button_summary">Custom server changes will take effect on the next Firefox run.</string>
+ <string name="preferences_sync_debug_quit_button_summary">Custom server changes will take effect on the next LeOSium run.</string>
<!-- Label for enabling the Tabs Tray to Compose changes -->
<string name="preferences_debug_settings_tabs_tray_to_compose" translatable="false">Enable Tabs Tray to Compose rewrite</string>
<!-- Label for enabling the Compose Top Sites -->
<string name="preferences_debug_settings_compose_top_sites" translatable="false">Enable Compose Top Sites</string>
<!-- Label for enabling translations -->
- <string name="preferences_debug_settings_translations" translatable="false">Enable Firefox Translations</string>
+ <string name="preferences_debug_settings_translations" translatable="false">Enable LeOSium Translations</string>
<!-- Label for enabling Firefox Suggest -->
- <string name="preferences_debug_settings_fxsuggest" translatable="false">Enable Firefox Suggest</string>
+ <string name="preferences_debug_settings_fxsuggest" translatable="false">Enable LeOSium Suggest</string>
<!-- A secret menu option in the tabs tray for making a tab inactive for testing. -->
<string name="inactive_tabs_menu_item">Make inactive</string>
@@ -86,15 +86,15 @@
<string name="profiler_stop">Stop Profiler</string>
<string name="profiler_settings_title">Profiler Settings</string>
- <string name="profiler_filter_firefox">Firefox</string>
+ <string name="profiler_filter_firefox">LeOSium</string>
<string name="profiler_running">Profiler is currently running</string>
- <string name="profiler_filter_firefox_explain">Recommended preset for profiling Firefox</string>
+ <string name="profiler_filter_firefox_explain">Recommended preset for profiling LeOSium</string>
<string name="profiler_filter_graphics">Graphics</string>
- <string name="profiler_filter_graphics_explain">Preset for investigating graphics bugs in Firefox</string>
+ <string name="profiler_filter_graphics_explain">Preset for investigating graphics bugs in LeOSium</string>
<string name="profiler_filter_media">Media</string>
- <string name="profiler_filter_media_explain">Preset for investigating audio and video bugs in Firefox</string>
+ <string name="profiler_filter_media_explain">Preset for investigating audio and video bugs in LeOSium</string>
<string name="profiler_filter_networking">Networking</string>
- <string name="profiler_filter_networking_explain">Preset for investigating networking bugs in Firefox</string>
+ <string name="profiler_filter_networking_explain">Preset for investigating networking bugs in LeOSium</string>
<string name="profiler_start_dialog_started">Profiler started</string>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,75 +0,0 @@
Subject: [PATCH] Homescreen Name change LeOSium
---
Index: app/src/main/res/values/strings.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml (revision eadb87d91b869ff759556dafdc6c7388ddcdbab0)
+++ b/app/src/main/res/values/strings.xml (revision 46413d074bd41596719d59a4779eaf591f84af41)
@@ -301,7 +301,7 @@
<!-- Notification pre-permission dialog -->
<!-- Enable notification pre permission dialog title
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
- <string name="onboarding_home_enable_notifications_title">Notifications help you do more with %s</string>
+ <string name="onboarding_home_enable_notifications_title">Notifications help you do more with LeOSium</string>
<!-- Enable notification pre permission dialog description with rationale
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="onboarding_home_enable_notifications_description">Sync your tabs between devices, manage downloads, get tips about making the most of %ss privacy protection, and more.</string>
@@ -346,13 +346,13 @@
<string name="juno_onboarding_enable_notifications_title_nimbus" moz:removedIn="120" tools:ignore="UnusedResources">Notifications help you do more with LeOSium</string>
<!-- Title for enable notification permission screen used by Nimbus experiments. Nimbus experiments do not support string placeholders.
Note: The word "LeOSium" should NOT be translated -->
- <string name="juno_onboarding_enable_notifications_title_nimbus_2">Notifications help you stay safer with Firefox</string>
+ <string name="juno_onboarding_enable_notifications_title_nimbus_2">Notifications help you stay safer with LeOSium</string>
<!-- Description for enable notification permission screen used by Nimbus experiments. Nimbus experiments do not support string placeholders.
Note: The word "Firefox" should NOT be translated -->
<string name="juno_onboarding_enable_notifications_description_nimbus" moz:removedIn="120" tools:ignore="UnusedResources">Send tabs between devices, manage downloads, and get tips on getting the most out of LeOSium.</string>
<!-- Description for enable notification permission screen used by Nimbus experiments. Nimbus experiments do not support string placeholders.
Note: The word "Firefox" should NOT be translated -->
- <string name="juno_onboarding_enable_notifications_description_nimbus_2">Securely send tabs between your devices and discover other privacy features in Firefox.</string>
+ <string name="juno_onboarding_enable_notifications_description_nimbus_2">Securely send tabs between your devices and discover other privacy features in LeOSium.</string>
<!-- Text for the button to request notification permission on the device -->
<string name="juno_onboarding_enable_notifications_positive_button" tools:ignore="UnusedResources">Turn on notifications</string>
<!-- Text for the button dismiss the screen and move on with the flow -->
@@ -672,7 +672,7 @@
<!-- Title of the Nimbus message for add-ons general availability-->
<string name="addon_ga_message_title" tools:ignore="UnusedResources">New add-ons now available</string>
<!-- Body of the Nimbus message for add-ons general availability. 'Firefox' intentionally hardcoded here-->
- <string name="addon_ga_message_body" tools:ignore="UnusedResources">Check out 100+ new extensions that let you make Firefox your own.</string>
+ <string name="addon_ga_message_body" tools:ignore="UnusedResources">Check out 100+ new extensions that let you make LeOSium your own.</string>
<!-- Button text of the Nimbus message for add-ons general availability. -->
<string name="addon_ga_message_button" tools:ignore="UnusedResources">Explore add-ons</string>
@@ -738,7 +738,7 @@
<!-- Name of the "receive tabs" notification channel. Displayed in the "App notifications" system settings for the app -->
<string name="fxa_received_tab_channel_name">Received tabs</string>
<!-- Description of the "receive tabs" notification channel. Displayed in the "App notifications" system settings for the app -->
- <string name="fxa_received_tab_channel_description">Notifications for tabs received from other Firefox devices.</string>
+ <string name="fxa_received_tab_channel_description">Notifications for tabs received from other LeOSium devices.</string>
<!-- The body for these is the URL of the tab received -->
<string name="fxa_tab_received_notification_name">Tab Received</string>
<!-- %s is the device name -->
@@ -1302,10 +1302,10 @@
<string name="notification_marketing_channel_name">Marketing</string>
<!-- Title shown in the notification that pops up to remind the user to set fenix as default browser.
The app name is in the text, due to limitations with localizing Nimbus experiments -->
- <string name="nimbus_notification_default_browser_title" tools:ignore="UnusedResources">Firefox is fast and private</string>
+ <string name="nimbus_notification_default_browser_title" tools:ignore="UnusedResources">LeOSium is fast and private</string>
<!-- Text shown in the notification that pops up to remind the user to set fenix as default browser.
The app name is in the text, due to limitations with localizing Nimbus experiments -->
- <string name="nimbus_notification_default_browser_text" tools:ignore="UnusedResources">Make Firefox your default browser</string>
+ <string name="nimbus_notification_default_browser_text" tools:ignore="UnusedResources">Make LeOSium your default browser</string>
<!-- Title shown in the notification that pops up to re-engage the user -->
<string name="notification_re_engagement_title">Try private browsing</string>
<!-- Text shown in the notification that pops up to re-engage the user.
@@ -1324,7 +1324,7 @@
<!-- Survey -->
<!-- Text shown in the fullscreen message that pops up to ask user to take a short survey.
The app name is in the text, due to limitations with localizing Nimbus experiments -->
- <string name="nimbus_survey_message_text">Please help make Firefox better by taking a short survey.</string>
+ <string name="nimbus_survey_message_text">Please help make LeOSium better by taking a short survey.</string>
<!-- Preference for taking the short survey. -->
<string name="preferences_take_survey">Take Survey</string>
<!-- Preference for not taking the short survey. -->

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
Subject: [PATCH] mull changes
---
Index: android-components
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/android-components b/android-components
--- a/android-components (revision 74a70efef04b9054656ee084aea0d719dda2bfad)
+++ b/android-components (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
@@ -1,1 +1,1 @@
-c16aea836d4ca3525cdb7d571d264d6c8d49c609
\ No newline at end of file
+5956319a9bbae9daedd519fcd60f435e8ccbbe37
\ No newline at end of file
Index: app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
--- a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt (revision 74a70efef04b9054656ee084aea0d719dda2bfad)
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
@@ -116,7 +116,6 @@
@Suppress("ComplexMethod")
private fun coreMenuItems(): List<BrowserMenuItem> {
- val settings = context.components.settings
val bookmarksItem = BrowserMenuImageText(
context.getString(R.string.library_bookmarks),
@@ -228,7 +227,7 @@
helpItem,
customizeHomeItem,
settingsItem,
- if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null,
+ quitItem,
).also { items ->
items.getHighlight()?.let { onHighlightPresent(it) }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
Subject: [PATCH] search changes
---
Index: components/feature/search/src/main/java/mozilla/components/feature/search/storage/SearchEngineReader.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/components/feature/search/src/main/java/mozilla/components/feature/search/storage/SearchEngineReader.kt b/components/feature/search/src/main/java/mozilla/components/feature/search/storage/SearchEngineReader.kt
--- a/components/feature/search/src/main/java/mozilla/components/feature/search/storage/SearchEngineReader.kt (revision 5956319a9bbae9daedd519fcd60f435e8ccbbe37)
+++ b/components/feature/search/src/main/java/mozilla/components/feature/search/storage/SearchEngineReader.kt (revision 9db93731521a490cd943a530870beaa308c64612)
@@ -39,13 +39,13 @@
"leit-is",
"coccoc",
"brave",
- "ddghtml",
"ddglite",
"metager",
"mojeek",
"qwantlite",
"startpage",
"baidu",
+ "leosearch"
)
/**

View File

@ -1,986 +0,0 @@
Subject: [PATCH] search changes
---
Index: android-components
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/android-components b/android-components
--- a/android-components (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
+++ b/android-components (revision 47a15ec0995100b316f7a3ccf319c8d17b4633ca)
@@ -1,1 +1,1 @@
-5956319a9bbae9daedd519fcd60f435e8ccbbe37
\ No newline at end of file
+9db93731521a490cd943a530870beaa308c64612
\ No newline at end of file
Index: app/src/main/java/org/mozilla/fenix/utils/Settings.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt (revision 47a15ec0995100b316f7a3ccf319c8d17b4633ca)
@@ -733,12 +733,12 @@
val useStandardTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
- true,
+ false,
)
val useStrictTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict_default),
- false,
+ true,
)
val useCustomTrackingProtection by booleanPreference(
Index: app/src/main/res/xml/tracking_protection_preferences.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/res/xml/tracking_protection_preferences.xml b/app/src/main/res/xml/tracking_protection_preferences.xml
--- a/app/src/main/res/xml/tracking_protection_preferences.xml (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
+++ b/app/src/main/res/xml/tracking_protection_preferences.xml (revision 47a15ec0995100b316f7a3ccf319c8d17b4633ca)
@@ -16,13 +16,13 @@
android:title="@string/preference_enhanced_tracking_protection"
app:iconSpaceReserved="false" />
<org.mozilla.fenix.settings.RadioButtonInfoPreference
- android:defaultValue="true"
+ android:defaultValue="false"
android:dependency="@string/pref_key_tracking_protection"
android:key="@string/pref_key_tracking_protection_standard_option"
android:summary="@string/preference_enhanced_tracking_protection_standard_description_5"
android:title="@string/preference_enhanced_tracking_protection_standard_default_1" />
<org.mozilla.fenix.settings.RadioButtonInfoPreference
- android:defaultValue="false"
+ android:defaultValue="true"
android:dependency="@string/pref_key_tracking_protection"
android:key="@string/pref_key_tracking_protection_strict_default"
android:summary="@string/preference_enhanced_tracking_protection_strict_description_4"
Index: automation/iceraven/assets/list.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/automation/iceraven/assets/list.json b/automation/iceraven/assets/list.json
--- a/automation/iceraven/assets/list.json (revision 17814c1db3be943336f191ead73b1c08f67e8b56)
+++ b/automation/iceraven/assets/list.json (revision 47a15ec0995100b316f7a3ccf319c8d17b4633ca)
@@ -1,9 +1,9 @@
{
"default": {
- "searchDefault": "DuckDuckGo",
- "searchOrder": ["Google", "Bing"],
+ "searchDefault": "LeOSearch",
+ "searchOrder": ["ddg", "brave"],
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia", "startpage", "brave"
]
},
"regionOverrides": {
@@ -12,49 +12,49 @@
"ach": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia", "startpage", "brave"
]
}
},
"an": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-an", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-an", "startpage", "brave"
]
}
},
"ar": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-ar", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-ar", "startpage", "brave"
]
}
},
"as": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-as", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-as", "startpage", "brave"
]
}
},
"ast": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-ast", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-ast", "startpage", "brave"
]
}
},
"az": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "azerdict", "wikipedia-az", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "azerdict", "wikipedia-az", "startpage", "brave"
]
}
},
"be": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-be", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-be", "startpage", "brave"
]
},
"BY": {
@@ -81,133 +81,133 @@
"bg": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "pazaruvaj", "wikipedia-bg", "startpage", "brave"
+ "leosearch", "bing", "ddg", "pazaruvaj", "wikipedia-bg", "startpage", "brave"
]
}
},
"bn": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-bn", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-bn", "startpage", "brave"
]
}
},
"bn-BD": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-bn", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-bn", "startpage", "brave"
]
}
},
"bn-IN": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "rediff", "wikipedia-bn", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "rediff", "wikipedia-bn", "startpage", "brave"
]
}
},
"br": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-br", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-br", "startpage", "brave"
]
}
},
"bs": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-bs", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-bs", "startpage", "brave"
]
}
},
"ca": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-ca", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-ca", "startpage", "brave"
]
}
},
"cak": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
+ "leosearch", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
]
}
},
"cs": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "mapy-cz", "seznam-cz", "wikipedia-cz", "startpage", "brave"
+ "leosearch", "bing", "ddg", "mapy-cz", "seznam-cz", "wikipedia-cz", "startpage", "brave"
]
}
},
"cy": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "ddg", "wikipedia-cy", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "ddg", "wikipedia-cy", "startpage", "brave"
]
}
},
"da": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazon-co-uk", "ddg", "wikipedia-da", "startpage", "brave"
+ "leosearch", "amazon-co-uk", "ddg", "wikipedia-da", "startpage", "brave"
]
}
},
"de": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-de", "ddg", "ecosia", "qwant", "wikipedia-de", "ebay-de", "startpage", "brave"
+ "leosearch", "bing", "amazon-de", "ddg", "ecosia", "qwant", "wikipedia-de", "ebay-de", "startpage", "brave"
]
}
},
"de-AT": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-de", "ddg", "ecosia", "qwant", "wikipedia-de", "ebay-at", "startpage", "brave"
+ "leosearch", "bing", "amazon-de", "ddg", "ecosia", "qwant", "wikipedia-de", "ebay-at", "startpage", "brave"
]
}
},
"dsb": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-de", "ddg", "wikipedia-dsb", "ebay-de", "startpage", "brave"
+ "leosearch", "bing", "amazon-de", "ddg", "wikipedia-dsb", "ebay-de", "startpage", "brave"
]
}
},
"el": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "skroutz", "wikipedia-el", "startpage", "brave"
+ "leosearch", "bing", "ddg", "skroutz", "wikipedia-el", "startpage", "brave"
]
}
},
"en-AU": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-au", "ddg", "wikipedia", "ebay-au", "startpage", "brave"
+ "leosearch", "bing", "amazon-au", "ddg", "wikipedia", "ebay-au", "startpage", "brave"
]
}
},
"en-CA": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-ca", "ddg", "wikipedia", "ebay-ca", "startpage", "brave"
+ "leosearch", "bing", "amazon-ca", "ddg", "wikipedia", "ebay-ca", "startpage", "brave"
]
}
},
"en-IE": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "ddg", "qwant", "wikipedia", "ebay-ie", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "ddg", "qwant", "wikipedia", "ebay-ie", "startpage", "brave"
]
}
},
"en-GB": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "ddg", "qwant", "wikipedia", "ebay-co-uk", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "ddg", "qwant", "wikipedia", "ebay-co-uk", "startpage", "brave"
]
},
"BY": {
@@ -234,7 +234,7 @@
"en-US": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "ebay", "wikipedia", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "ebay", "wikipedia", "startpage", "brave"
]
},
"BY": {
@@ -261,245 +261,245 @@
"en-ZA": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia", "startpage", "brave"
]
}
},
"eo": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "reta-vortaro", "wikipedia-eo", "startpage", "brave"
+ "leosearch", "bing", "ddg", "reta-vortaro", "wikipedia-eo", "startpage", "brave"
]
}
},
"es-AR": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "mercadolibre-ar", "wikipedia-es", "startpage", "brave"
+ "leosearch", "ddg", "mercadolibre-ar", "wikipedia-es", "startpage", "brave"
]
}
},
"es-CL": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "mercadolibre-cl", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "ddg", "mercadolibre-cl", "wikipedia-es", "startpage", "brave"
]
}
},
"es-ES": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-es", "amazon-es", "ebay-es", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-es", "amazon-es", "ebay-es", "startpage", "brave"
]
}
},
"es-MX": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "mercadolibre-mx", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "mercadolibre-mx", "wikipedia-es", "startpage", "brave"
]
}
},
"et": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazon-co-uk", "ddg", "wikipedia-et", "startpage", "brave"
+ "leosearch", "amazon-co-uk", "ddg", "wikipedia-et", "startpage", "brave"
]
}
},
"eu": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "elebila", "wikipedia-eu", "startpage", "brave"
+ "leosearch", "bing", "ddg", "elebila", "wikipedia-eu", "startpage", "brave"
]
}
},
"fa": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-fa", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-fa", "startpage", "brave"
]
}
},
"ff": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-fr", "ddg", "wikipedia-fr", "startpage", "brave"
+ "leosearch", "bing", "amazon-fr", "ddg", "wikipedia-fr", "startpage", "brave"
]
}
},
"fi": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazondotcom", "ddg", "wikipedia-fi", "startpage", "brave"
+ "leosearch", "amazondotcom", "ddg", "wikipedia-fi", "startpage", "brave"
]
}
},
"fr-BE": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "qwant", "wikipedia-fr", "ebay-befr", "startpage", "brave"
+ "leosearch", "bing", "ddg", "qwant", "wikipedia-fr", "ebay-befr", "startpage", "brave"
]
}
},
"fr-CA": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-ca", "ddg", "wikipedia-fr", "ebay-ca", "startpage", "brave"
+ "leosearch", "bing", "amazon-ca", "ddg", "wikipedia-fr", "ebay-ca", "startpage", "brave"
]
}
},
"fr-FR": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "qwant", "wikipedia-fr", "amazon-fr", "ebay-fr", "startpage", "brave"
+ "leosearch", "bing", "ddg", "qwant", "wikipedia-fr", "amazon-fr", "ebay-fr", "startpage", "brave"
]
}
},
"fr": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "qwant", "wikipedia-fr", "startpage", "brave"
+ "leosearch", "bing", "ddg", "qwant", "wikipedia-fr", "startpage", "brave"
]
}
},
"fy-NL": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-fy-NL", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-fy-NL", "startpage", "brave"
]
}
},
"ga-IE": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazon-co-uk", "ddg", "wikipedia-ga-IE", "startpage", "brave"
+ "leosearch", "amazon-co-uk", "ddg", "wikipedia-ga-IE", "startpage", "brave"
]
}
},
"gd": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "faclair-beag", "wikipedia-gd", "startpage", "brave"
+ "leosearch", "bing", "ddg", "faclair-beag", "wikipedia-gd", "startpage", "brave"
]
}
},
"gl": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-gl", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-gl", "startpage", "brave"
]
}
},
"gn": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-gn", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-gn", "startpage", "brave"
]
}
},
"gu-IN": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-gu", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-gu", "startpage", "brave"
]
}
},
"he": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-he", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-he", "startpage", "brave"
]
}
},
"hi-IN": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-hi", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-hi", "startpage", "brave"
]
}
},
"hr": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "ddg", "wikipedia-hr", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "ddg", "wikipedia-hr", "startpage", "brave"
]
}
},
"hsb": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-de", "ddg", "wikipedia-hsb", "ebay-de", "startpage", "brave"
+ "leosearch", "bing", "amazon-de", "ddg", "wikipedia-hsb", "ebay-de", "startpage", "brave"
]
}
},
"hu": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "sztaki-en-hu", "vatera", "wikipedia-hu", "startpage", "brave"
+ "leosearch", "ddg", "sztaki-en-hu", "vatera", "wikipedia-hu", "startpage", "brave"
]
}
},
"hy-AM": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-hy-AM", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-hy-AM", "startpage", "brave"
]
}
},
"ia": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-ia", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-ia", "startpage", "brave"
]
}
},
"id": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-id", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-id", "startpage", "brave"
]
}
},
"is": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "leit-is", "wikipedia-is", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "leit-is", "wikipedia-is", "startpage", "brave"
]
}
},
"it": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-it", "amazon-it", "ebay-it", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-it", "amazon-it", "ebay-it", "startpage", "brave"
]
}
},
"ja": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "amazon-jp", "bing", "ddg", "wikipedia-ja", "yahoo-jp", "startpage", "brave"
+ "leosearch", "amazon-jp", "bing", "ddg", "wikipedia-ja", "yahoo-jp", "startpage", "brave"
]
}
},
"ka": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-ka", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-ka", "startpage", "brave"
]
}
},
"kab": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-kab", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-kab", "startpage", "brave"
]
}
},
"kk": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-kk", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-kk", "startpage", "brave"
]
},
"KZ": {
@@ -526,203 +526,203 @@
"km": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-km", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-km", "startpage", "brave"
]
}
},
"kn": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-kn", "wiktionary-kn", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-kn", "wiktionary-kn", "startpage", "brave"
]
}
},
"ko": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "danawa-kr", "daum-kr", "startpage", "brave"
+ "leosearch", "ddg", "danawa-kr", "daum-kr", "startpage", "brave"
]
}
},
"lij": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-it", "ddg", "wikipedia-lij", "ebay-it", "startpage", "brave"
+ "leosearch", "bing", "amazon-it", "ddg", "wikipedia-lij", "ebay-it", "startpage", "brave"
]
}
},
"lo": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-lo", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-lo", "startpage", "brave"
]
}
},
"lt": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-lt", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-lt", "startpage", "brave"
]
}
},
"ltg": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "wikipedia-ltg", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "wikipedia-ltg", "startpage", "brave"
]
}
},
"lv": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "salidzinilv", "wikipedia-lv", "startpage", "brave"
+ "leosearch", "ddg", "salidzinilv", "wikipedia-lv", "startpage", "brave"
]
}
},
"mai": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-hi", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-hi", "startpage", "brave"
]
}
},
"meh": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
]
}
},
"mix": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
]
}
},
"ml": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-ml", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-ml", "startpage", "brave"
]
}
},
"mr": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "rediff", "wikipedia-mr", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "rediff", "wikipedia-mr", "startpage", "brave"
]
}
},
"ms": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-ms", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-ms", "startpage", "brave"
]
}
},
"my": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-my", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-my", "startpage", "brave"
]
}
},
"nb-NO": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "gulesider-mobile-NO", "wikipedia-NO", "startpage", "brave"
+ "leosearch", "ddg", "gulesider-mobile-NO", "wikipedia-NO", "startpage", "brave"
]
}
},
"ne-NP": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-ne", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-ne", "startpage", "brave"
]
}
},
"nl-NL": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-nl", "amazon-nl", "ebay-nl", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-nl", "amazon-nl", "ebay-nl", "startpage", "brave"
]
}
},
"nl": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-nl", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-nl", "startpage", "brave"
]
}
},
"nn-NO": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "gulesider-mobile-NO", "wikipedia-NN", "startpage", "brave"
+ "leosearch", "ddg", "gulesider-mobile-NO", "wikipedia-NN", "startpage", "brave"
]
}
},
"oc": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-oc", "wiktionary-oc", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-oc", "wiktionary-oc", "startpage", "brave"
]
}
},
"or": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-or", "wiktionary-or", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-or", "wiktionary-or", "startpage", "brave"
]
}
},
"pa-IN": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-pa", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-pa", "startpage", "brave"
]
}
},
"pl": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-pl", "ebay-pl", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-pl", "ebay-pl", "startpage", "brave"
]
}
},
"pt-BR": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-pt", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-pt", "startpage", "brave"
]
}
},
"pt-PT": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-pt", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-pt", "startpage", "brave"
]
}
},
"rm": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "leo_ende_de", "pledarigrond", "wikipedia-rm", "startpage", "brave"
+ "leosearch", "bing", "ddg", "leo_ende_de", "pledarigrond", "wikipedia-rm", "startpage", "brave"
]
}
},
"ro": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-ro", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-ro", "startpage", "brave"
]
}
},
"ru": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-ru", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-ru", "startpage", "brave"
]
},
"RU": {
@@ -749,77 +749,77 @@
"sk": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "slovnik-sk", "wikipedia-sk", "startpage", "brave"
+ "leosearch", "ddg", "slovnik-sk", "wikipedia-sk", "startpage", "brave"
]
}
},
"sl": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "ceneje", "odpiralni", "wikipedia-sl", "startpage", "brave"
+ "leosearch", "ddg", "ceneje", "odpiralni", "wikipedia-sl", "startpage", "brave"
]
}
},
"son": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "bing", "amazon-fr", "wikipedia-fr", "startpage", "brave"
+ "leosearch", "ddg", "bing", "amazon-fr", "wikipedia-fr", "startpage", "brave"
]
}
},
"sq": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-co-uk", "ddg", "wikipedia-sq", "startpage", "brave"
+ "leosearch", "bing", "amazon-co-uk", "ddg", "wikipedia-sq", "startpage", "brave"
]
}
},
"sr": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-sr", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-sr", "startpage", "brave"
]
}
},
"sv-SE": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "prisjakt-sv-SE", "ddg", "wikipedia-sv-SE", "amazon-se", "ebay-ch", "startpage", "brave"
+ "leosearch", "prisjakt-sv-SE", "ddg", "wikipedia-sv-SE", "amazon-se", "ebay-ch", "startpage", "brave"
]
}
},
"ta": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-ta", "wiktionary-ta", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-ta", "wiktionary-ta", "startpage", "brave"
]
}
},
"te": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-te", "wiktionary-te", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-te", "wiktionary-te", "startpage", "brave"
]
}
},
"th": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-th", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-th", "startpage", "brave"
]
}
},
"tl": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "startpage", "brave"
]
}
},
"tr": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-tr", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-tr", "startpage", "brave"
]
},
"TR": {
@@ -846,70 +846,70 @@
"trs": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-es", "startpage", "brave"
]
}
},
"uk": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "ddg", "wikipedia-uk", "startpage", "brave"
+ "leosearch", "ddg", "wikipedia-uk", "startpage", "brave"
]
}
},
"ur": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazon-in", "ddg", "wikipedia-ur", "startpage", "brave"
+ "leosearch", "bing", "amazon-in", "ddg", "wikipedia-ur", "startpage", "brave"
]
}
},
"uz": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "amazondotcom", "ddg", "wikipedia-uz", "startpage", "brave"
+ "leosearch", "bing", "amazondotcom", "ddg", "wikipedia-uz", "startpage", "brave"
]
}
},
"vi": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "coccoc", "ddg", "wikipedia-vi", "startpage", "brave"
+ "leosearch", "coccoc", "ddg", "wikipedia-vi", "startpage", "brave"
]
}
},
"wo": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-wo", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-wo", "startpage", "brave"
]
}
},
"xh": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia", "startpage", "brave"
]
}
},
"zam": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-es", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-es", "startpage", "brave"
]
}
},
"zh-CN": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "baidu", "bing", "ddg", "wikipedia-zh-CN", "startpage", "brave"
+ "leosearch", "baidu", "bing", "ddg", "wikipedia-zh-CN", "startpage", "brave"
]
}
},
"zh-TW": {
"default": {
"visibleDefaultEngines": [
- "google-b-m", "bing", "ddg", "wikipedia-zh-TW", "startpage", "brave"
+ "leosearch", "bing", "ddg", "wikipedia-zh-TW", "startpage", "brave"
]
}
}

File diff suppressed because one or more lines are too long

112813
013-x.patch

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
Subject: [PATCH] x
---
Index: blobs.sh
===================================================================
diff --git a/blobs.sh b/blobs.sh
deleted file mode 100644
--- a/blobs.sh (revision c577fca8445181a70382fe6c12544a47d5092b92)
+++ /dev/null (revision c577fca8445181a70382fe6c12544a47d5092b92)
@@ -1,6 +0,0 @@
-sed -i \
- -e '/Deps.mozilla_lib_push_firebase/d' \
- -e '/Deps.adjust/d; /Deps.installreferrer/d; /Deps.google_ads_id/d' \
- -e '/Deps.google_play_store/d' \
- app/build.gradle
-

View File

@ -1,9 +0,0 @@
Subject: [PATCH] x
---
diff --git a/app/src/main/res.tar.gz b/app/src/main/res.tar.gz
deleted file mode 100644
index e7b09525829f66e1cfb9c04c03ea1c3a9a2615f7..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001

View File

@ -1,997 +0,0 @@
Subject: [PATCH] v120
---
Index: fenix-liberate.patch
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/fenix-liberate.patch b/fenix-liberate.patch
new file mode 100644
--- /dev/null (revision 2d11da25be368a0d5b2a0cf89e09aa073fcb0cd0)
+++ b/fenix-liberate.patch (revision 2d11da25be368a0d5b2a0cf89e09aa073fcb0cd0)
@@ -0,0 +1,985 @@
+diff --git a/fenix/app/build.gradle b/fenix/app/build.gradle
+index 78d698dad9..0fbf4cb1cf 100644
+--- a/fenix/app/build.gradle
++++ b/fenix/app/build.gradle
+@@ -576,7 +576,6 @@ dependencies {
+
+ implementation project(':lib-crash')
+ implementation project(':lib-crash-sentry')
+- implementation project(':lib-push-firebase')
+ implementation project(':lib-state')
+ implementation project(':lib-dataprotect')
+
+@@ -615,15 +614,6 @@ dependencies {
+ implementation FenixDependencies.protobuf_javalite
+ implementation ComponentsDependencies.google_material
+
+- implementation FenixDependencies.adjust
+- implementation FenixDependencies.installreferrer // Required by Adjust
+-
+- implementation FenixDependencies.google_ads_id // Required for the Google Advertising ID
+-
+- // Required for in-app reviews
+- implementation FenixDependencies.google_play_review
+- implementation FenixDependencies.google_play_review_ktx
+-
+ implementation FenixDependencies.androidx_profileinstaller
+
+ androidTestImplementation ComponentsDependencies.androidx_test_uiautomator
+diff --git a/fenix/app/proguard-rules.pro b/fenix/app/proguard-rules.pro
+index e269f65775..e870f06bef 100644
+--- a/fenix/app/proguard-rules.pro
++++ b/fenix/app/proguard-rules.pro
+@@ -1,22 +1,5 @@
+ -dontobfuscate
+
+-####################################################################################################
+-# Sentry
+-####################################################################################################
+-
+-# Recommended config via https://docs.sentry.io/clients/java/modules/android/#manual-integration
+-# Since we don't obfuscate, we don't need to use their Gradle plugin to upload ProGuard mappings.
+--keepattributes LineNumberTable,SourceFile
+--dontwarn org.slf4j.**
+--dontwarn javax.**
+-
+-# Our addition: this class is saved to disk via Serializable, which ProGuard doesn't like.
+-# If we exclude this, upload silently fails (Sentry swallows a NPE so we don't crash).
+-# I filed https://github.com/getsentry/sentry-java/issues/572
+-#
+-# If Sentry ever mysteriously stops working after we upgrade it, this could be why.
+--keep class io.sentry.event.Event { *; }
+-
+ ####################################################################################################
+ # Android and GeckoView built-ins
+ ####################################################################################################
+@@ -69,58 +52,6 @@
+
+ -keep class org.mozilla.fenix.**ViewModel { *; }
+
+-####################################################################################################
+-# Adjust
+-####################################################################################################
+-
+--keep public class com.adjust.sdk.** { *; }
+--keep class com.google.android.gms.common.ConnectionResult {
+- int SUCCESS;
+-}
+--keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
+- com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
+-}
+--keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
+- java.lang.String getId();
+- boolean isLimitAdTrackingEnabled();
+-}
+--keep public class com.android.installreferrer.** { *; }
+--keep class dalvik.system.VMRuntime {
+- java.lang.String getRuntime();
+-}
+--keep class android.os.Build {
+- java.lang.String[] SUPPORTED_ABIS;
+- java.lang.String CPU_ABI;
+-}
+--keep class android.content.res.Configuration {
+- android.os.LocaledList getLocales();
+- java.util.Locale locale;
+-}
+--keep class android.os.LocaleList {
+- java.util.Locale get(int);
+-}
+-
+-# Keep code generated from Glean Metrics
+--keep class org.mozilla.fenix.GleanMetrics.** { *; }
+-
+-# Keep motionlayout internal methods
+-# https://github.com/mozilla-mobile/fenix/issues/2094
+--keep class androidx.constraintlayout.** { *; }
+-
+-# Keep adjust relevant classes
+--keep class com.adjust.sdk.** { *; }
+--keep class com.google.android.gms.common.ConnectionResult {
+- int SUCCESS;
+-}
+--keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
+- com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
+-}
+--keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
+- java.lang.String getId();
+- boolean isLimitAdTrackingEnabled();
+-}
+--keep public class com.android.installreferrer.** { *; }
+-
+ # Keep Android Lifecycle methods
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1596302
+ -keep class androidx.lifecycle.** { *; }
+diff --git a/fenix/app/src/main/java/com/adjust/sdk/Adjust.java b/fenix/app/src/main/java/com/adjust/sdk/Adjust.java
+new file mode 100644
+index 0000000000..7e644e2fa2
+--- /dev/null
++++ b/fenix/app/src/main/java/com/adjust/sdk/Adjust.java
+@@ -0,0 +1,44 @@
++/*
++ * Copyright (c) 2012-2017 adjust GmbH,
++ * http://www.adjust.com
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files (the
++ * "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++package com.adjust.sdk;
++
++import android.content.Context;
++
++public class Adjust {
++ public static void onCreate(AdjustConfig adjustConfig) {
++ }
++
++ public static void onResume() {
++ }
++
++ public static void onPause() {
++ }
++
++ public static void setEnabled(boolean enabled) {
++ }
++
++ public static void gdprForgetMe(final Context context) {
++ }
++}
+diff --git a/fenix/app/src/main/java/com/adjust/sdk/AdjustAttribution.java b/fenix/app/src/main/java/com/adjust/sdk/AdjustAttribution.java
+new file mode 100644
+index 0000000000..ab6b3badbd
+--- /dev/null
++++ b/fenix/app/src/main/java/com/adjust/sdk/AdjustAttribution.java
+@@ -0,0 +1,49 @@
++/*
++ * Copyright (c) 2012-2017 adjust GmbH,
++ * http://www.adjust.com
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files (the
++ * "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++package com.adjust.sdk;
++
++import java.io.Serializable;
++
++public class AdjustAttribution implements Serializable {
++ public String network;
++ public String campaign;
++ public String adgroup;
++ public String creative;
++
++ @Override
++ public boolean equals(Object other) {
++ return false;
++ }
++
++ @Override
++ public int hashCode() {
++ return 0;
++ }
++
++ @Override
++ public String toString() {
++ return "";
++ }
++}
+diff --git a/fenix/app/src/main/java/com/adjust/sdk/AdjustConfig.java b/fenix/app/src/main/java/com/adjust/sdk/AdjustConfig.java
+new file mode 100644
+index 0000000000..6753dd7d9f
+--- /dev/null
++++ b/fenix/app/src/main/java/com/adjust/sdk/AdjustConfig.java
+@@ -0,0 +1,46 @@
++/*
++ * Copyright (c) 2012-2017 adjust GmbH,
++ * http://www.adjust.com
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files (the
++ * "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++package com.adjust.sdk;
++
++import android.content.Context;
++
++import java.util.List;
++
++public class AdjustConfig {
++ public static final String ENVIRONMENT_SANDBOX = "sandbox";
++ public static final String ENVIRONMENT_PRODUCTION = "production";
++
++ public AdjustConfig(Context context, String appToken, String environment) {
++ }
++
++ public AdjustConfig(Context context, String appToken, String environment, boolean allowSuppressLogLevel) {
++ }
++
++ public void setOnAttributionChangedListener(OnAttributionChangedListener onAttributionChangedListener) {
++ }
++
++ public void setLogLevel(LogLevel logLevel) {
++ }
++}
+diff --git a/fenix/app/src/main/java/com/adjust/sdk/LogLevel.java b/fenix/app/src/main/java/com/adjust/sdk/LogLevel.java
+new file mode 100644
+index 0000000000..27ac3de544
+--- /dev/null
++++ b/fenix/app/src/main/java/com/adjust/sdk/LogLevel.java
+@@ -0,0 +1,43 @@
++/*
++ * Copyright (c) 2012-2017 adjust GmbH,
++ * http://www.adjust.com
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files (the
++ * "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++package com.adjust.sdk;
++
++import android.util.Log;
++
++/**
++ * Created by pfms on 11/03/15.
++ */
++public enum LogLevel {
++ VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), INFO(Log.INFO), WARN(Log.WARN), ERROR(Log.ERROR), ASSERT(Log.ASSERT), SUPRESS(8);
++ final int androidLogLevel;
++
++ LogLevel(final int androidLogLevel) {
++ this.androidLogLevel = androidLogLevel;
++ }
++
++ public int getAndroidLogLevel() {
++ return androidLogLevel;
++ }
++}
+diff --git a/fenix/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java b/fenix/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java
+new file mode 100644
+index 0000000000..7efa1c6804
+--- /dev/null
++++ b/fenix/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java
+@@ -0,0 +1,29 @@
++/*
++ * Copyright (c) 2012-2017 adjust GmbH,
++ * http://www.adjust.com
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files (the
++ * "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++package com.adjust.sdk;
++
++public interface OnAttributionChangedListener {
++ void onAttributionChanged(AdjustAttribution attribution);
++}
+diff --git a/fenix/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java b/fenix/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java
+new file mode 100644
+index 0000000000..0f2a47b141
+--- /dev/null
++++ b/fenix/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java
+@@ -0,0 +1,23 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package com.google.android.gms.ads.identifier;
++
++import android.content.Context;
++
++public class AdvertisingIdClient {
++
++ public static final class Info {
++
++ public String getId() {
++ return "";
++ }
++
++ }
++
++ public static Info getAdvertisingIdInfo(Context context) {
++ return new Info();
++ }
++
++}
+diff --git a/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesNotAvailableException.java b/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesNotAvailableException.java
+new file mode 100644
+index 0000000000..d3bff12497
+--- /dev/null
++++ b/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesNotAvailableException.java
+@@ -0,0 +1,8 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package com.google.android.gms.common;
++
++public class GooglePlayServicesNotAvailableException extends Exception {
++}
+diff --git a/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesRepairableException.java b/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesRepairableException.java
+new file mode 100644
+index 0000000000..b72a7cdb16
+--- /dev/null
++++ b/fenix/app/src/main/java/com/google/android/gms/common/GooglePlayServicesRepairableException.java
+@@ -0,0 +1,8 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package com.google.android.gms.common;
++
++public class GooglePlayServicesRepairableException extends Exception {
++}
+diff --git a/fenix/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java b/fenix/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java
+new file mode 100644
+index 0000000000..4d5fd8153d
+--- /dev/null
++++ b/fenix/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java
+@@ -0,0 +1,42 @@
++// Copyright 2020 Google LLC
++//
++// Licensed under the Apache License, Version 2.0 (the "License");
++// you may not use this file except in compliance with the License.
++// You may obtain a copy of the License at
++//
++// http://www.apache.org/licenses/LICENSE-2.0
++//
++// Unless required by applicable law or agreed to in writing, software
++// distributed under the License is distributed on an "AS IS" BASIS,
++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++// See the License for the specific language governing permissions and
++// limitations under the License.
++package com.google.firebase.messaging;
++
++import android.app.Service;
++import android.content.Intent;
++import android.os.Binder;
++import android.os.IBinder;
++
++public class FirebaseMessagingService extends Service {
++
++ private final IBinder mBinder = new Binder();
++
++ public void onMessageReceived(RemoteMessage message) {
++ }
++
++ public void onMessageSent(String msgId) {
++ }
++
++ public void onNewToken(String token) {
++ }
++
++ public void onSendError(String msgId, Exception exception) {
++ }
++
++ @Override
++ public IBinder onBind(Intent intent) {
++ return mBinder;
++ }
++
++}
+diff --git a/fenix/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java b/fenix/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java
+new file mode 100644
+index 0000000000..9ad59a31e4
+--- /dev/null
++++ b/fenix/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java
+@@ -0,0 +1,33 @@
++// Copyright 2020 Google LLC
++//
++// Licensed under the Apache License, Version 2.0 (the "License");
++// you may not use this file except in compliance with the License.
++// You may obtain a copy of the License at
++//
++// http://www.apache.org/licenses/LICENSE-2.0
++//
++// Unless required by applicable law or agreed to in writing, software
++// distributed under the License is distributed on an "AS IS" BASIS,
++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++// See the License for the specific language governing permissions and
++// limitations under the License.
++package com.google.firebase.messaging;
++
++import android.os.Parcel;
++import android.os.Parcelable;
++import java.util.Map;
++
++public class RemoteMessage implements Parcelable {
++
++ public int describeContents() {
++ return 0;
++ }
++
++ public void writeToParcel(Parcel out, int flags) {
++ }
++
++ public Map<String, String> getData() {
++ return null;
++ }
++
++}
+diff --git a/fenix/app/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt b/fenix/app/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt
+new file mode 100644
+index 0000000000..b50a6f03a2
+--- /dev/null
++++ b/fenix/app/src/main/java/mozilla/components/lib/push/firebase/AbstractFirebasePushService.kt
+@@ -0,0 +1,32 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package mozilla.components.lib.push.firebase
++
++import android.content.Context
++import com.google.firebase.messaging.FirebaseMessagingService
++import com.google.firebase.messaging.RemoteMessage
++import mozilla.components.concept.push.PushService
++
++abstract class AbstractFirebasePushService() : FirebaseMessagingService(), PushService {
++
++ override fun start(context: Context) {
++ }
++
++ override fun onNewToken(newToken: String) {
++ }
++
++ override fun onMessageReceived(remoteMessage: RemoteMessage?) {
++ }
++
++ final override fun stop() {
++ }
++
++ override fun deleteToken() {
++ }
++
++ override fun isServiceAvailable(context: Context): Boolean {
++ return false
++ }
++}
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+index d3d63c17b9..7228e67716 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+@@ -18,7 +18,7 @@ object FeatureFlags {
+ * This feature does not only depend on this flag. It requires the AMO collection override to
+ * be enabled which is behind the Secret Settings.
+ * */
+- val customExtensionCollectionFeature = Config.channel.isNightlyOrDebug || Config.channel.isBeta
++ val customExtensionCollectionFeature = true
+
+ /**
+ * Pull-to-refresh allows you to pull the web content down far enough to have the page to
+@@ -37,7 +37,7 @@ object FeatureFlags {
+ fun isPocketRecommendationsFeatureEnabled(context: Context): Boolean {
+ val langTag = LocaleManager.getCurrentLocale(context)
+ ?.toLanguageTag() ?: getSystemDefault().toLanguageTag()
+- return listOf("en-US", "en-CA").contains(langTag)
++ return false && listOf("en-US", "en-CA").contains(langTag)
+ }
+
+ /**
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
+index 3cd954a5bb..31f4e6fe34 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
+@@ -137,11 +137,7 @@ class Analytics(
+ MetricController.create(
+ listOf(
+ GleanMetricsService(context),
+- AdjustMetricsService(
+- application = context as Application,
+- storage = metricsStorage,
+- crashReporter = crashReporter,
+- ),
++ AdjustMetricsService(context as Application),
+ InstallReferrerMetricsService(context),
+ ),
+ isDataTelemetryEnabled = { context.settings().isTelemetryEnabled },
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
+index d077ca2972..a55a167ad5 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
+@@ -10,7 +10,6 @@ import android.content.Context
+ import androidx.compose.runtime.Composable
+ import androidx.compose.ui.platform.LocalContext
+ import androidx.core.app.NotificationManagerCompat
+-import com.google.android.play.core.review.ReviewManagerFactory
+ import mozilla.components.feature.addons.AddonManager
+ import mozilla.components.feature.addons.amo.AMOAddonsProvider
+ import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
+@@ -171,7 +170,6 @@ class Components(private val context: Context) {
+
+ val reviewPromptController by lazyMonitored {
+ ReviewPromptController(
+- manager = ReviewManagerFactory.create(context),
+ reviewSettings = FenixReviewSettings(settings),
+ )
+ }
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt
+index f6d7a2b0df..366c256bdb 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt
+@@ -510,8 +510,14 @@ class Core(
+ } else {
+ defaultTopSites.add(
+ Pair(
+- context.getString(R.string.default_top_site_google),
+- SupportUtils.GOOGLE_URL,
++ context.getString(R.string.default_top_site_fdroid),
++ SupportUtils.FDROID_URL,
++ ),
++ )
++ defaultTopSites.add(
++ Pair(
++ context.getString(R.string.default_top_site_eff),
++ SupportUtils.EFF_URL,
+ ),
+ )
+
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt
+index 33e8704adc..ffa85e3e46 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt
+@@ -6,8 +6,6 @@ package org.mozilla.fenix.components
+
+ import android.app.Activity
+ import androidx.annotation.VisibleForTesting
+-import com.google.android.play.core.review.ReviewInfo
+-import com.google.android.play.core.review.ReviewManager
+ import kotlinx.coroutines.Dispatchers.Main
+ import kotlinx.coroutines.withContext
+ import org.mozilla.fenix.GleanMetrics.ReviewPrompt
+@@ -45,24 +43,9 @@ class FenixReviewSettings(
+ * Controls the Review Prompt behavior.
+ */
+ class ReviewPromptController(
+- private val manager: ReviewManager,
+ private val reviewSettings: ReviewSettings,
+ private val timeNowInMillis: () -> Long = { System.currentTimeMillis() },
+- private val tryPromptReview: suspend (Activity) -> Unit = { activity ->
+- val flow = manager.requestReviewFlow()
+-
+- withContext(Main) {
+- flow.addOnCompleteListener {
+- if (it.isSuccessful) {
+- manager.launchReviewFlow(activity, it.result)
+- recordReviewPromptEvent(
+- it.result.toString(),
+- reviewSettings.numberOfAppLaunches,
+- Date(),
+- )
+- }
+- }
+- }
++ private val tryPromptReview: suspend (Activity) -> Unit = { _ ->
+ },
+ ) {
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
+index b979a08026..ae125ebd52 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
+@@ -10,25 +10,13 @@ import android.os.Bundle
+ import android.util.Log
+ import com.adjust.sdk.Adjust
+ import com.adjust.sdk.AdjustConfig
+-import com.adjust.sdk.AdjustEvent
+-import com.adjust.sdk.Constants.ADJUST_PREINSTALL_SYSTEM_PROPERTY_PATH
+ import com.adjust.sdk.LogLevel
+-import kotlinx.coroutines.CoroutineDispatcher
+-import kotlinx.coroutines.CoroutineScope
+-import kotlinx.coroutines.Dispatchers
+-import kotlinx.coroutines.launch
+-import mozilla.components.lib.crash.CrashReporter
+ import org.mozilla.fenix.BuildConfig
+ import org.mozilla.fenix.Config
+ import org.mozilla.fenix.GleanMetrics.FirstSession
+ import org.mozilla.fenix.ext.settings
+
+-class AdjustMetricsService(
+- private val application: Application,
+- private val storage: MetricsStorage,
+- private val crashReporter: CrashReporter,
+- private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
+-) : MetricsService {
++class AdjustMetricsService(private val application: Application) : MetricsService {
+ override val type = MetricServiceType.Marketing
+
+ override fun start() {
+@@ -42,15 +30,12 @@ class AdjustMetricsService(
+ return
+ }
+
+- System.setProperty(ADJUST_PREINSTALL_SYSTEM_PROPERTY_PATH, "/preload/etc/adjust.preinstall")
+-
+ val config = AdjustConfig(
+ application,
+ BuildConfig.ADJUST_TOKEN,
+ AdjustConfig.ENVIRONMENT_PRODUCTION,
+ true,
+ )
+- config.setPreinstallTrackingEnabled(true)
+
+ val installationPing = FirstSessionPing(application)
+
+@@ -94,26 +79,8 @@ class AdjustMetricsService(
+ Adjust.gdprForgetMe(application.applicationContext)
+ }
+
+- @Suppress("TooGenericExceptionCaught")
+- override fun track(event: Event) {
+- CoroutineScope(dispatcher).launch {
+- try {
+- if (event is Event.GrowthData) {
+- if (storage.shouldTrack(event)) {
+- Adjust.trackEvent(AdjustEvent(event.tokenName))
+- storage.updateSentState(event)
+- } else {
+- storage.updatePersistentState(event)
+- }
+- }
+- } catch (e: Exception) {
+- crashReporter.submitCaughtException(e)
+- }
+- }
+- }
+-
+- override fun shouldTrack(event: Event): Boolean =
+- event is Event.GrowthData
++ override fun track(event: Event) { /* noop */ }
++ override fun shouldTrack(event: Event): Boolean = false
+
+ companion object {
+ private const val LOGTAG = "AdjustMetricsService"
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/InstallReferrerMetricsService.kt b/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/InstallReferrerMetricsService.kt
+index a65690bdce..5e88212347 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/InstallReferrerMetricsService.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/InstallReferrerMetricsService.kt
+@@ -8,8 +8,6 @@ import android.content.Context
+ import android.net.UrlQuerySanitizer
+ import android.os.RemoteException
+ import androidx.annotation.VisibleForTesting
+-import com.android.installreferrer.api.InstallReferrerClient
+-import com.android.installreferrer.api.InstallReferrerStateListener
+ import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution
+ import org.mozilla.fenix.ext.settings
+ import org.mozilla.fenix.utils.Settings
+@@ -23,60 +21,13 @@ import java.net.URLDecoder
+ class InstallReferrerMetricsService(private val context: Context) : MetricsService {
+ override val type = MetricServiceType.Marketing
+
+- private var referrerClient: InstallReferrerClient? = null
+-
+ override fun start() {
+ if (context.settings().utmParamsKnown) {
+ return
+ }
+-
+- val timerId = PlayStoreAttribution.attributionTime.start()
+- val client = InstallReferrerClient.newBuilder(context).build()
+- referrerClient = client
+-
+- client.startConnection(
+- object : InstallReferrerStateListener {
+- override fun onInstallReferrerSetupFinished(responseCode: Int) {
+- PlayStoreAttribution.attributionTime.stopAndAccumulate(timerId)
+- when (responseCode) {
+- InstallReferrerClient.InstallReferrerResponse.OK -> {
+- // Connection established.
+- try {
+- val response = client.installReferrer
+- recordInstallReferrer(context.settings(), response.installReferrer)
+- context.settings().utmParamsKnown = true
+- } catch (e: RemoteException) {
+- // NOOP.
+- // We can't do anything about this.
+- }
+- }
+-
+- InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
+- // API not available on the current Play Store app.
+- context.settings().utmParamsKnown = true
+- }
+-
+- InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
+- // Connection couldn't be established.
+- }
+- }
+- // End the connection, and null out the client.
+- stop()
+- }
+-
+- override fun onInstallReferrerServiceDisconnected() {
+- // Try to restart the connection on the next request to
+- // Google Play by calling the startConnection() method.
+- referrerClient = null
+- }
+- },
+- )
+ }
+
+- override fun stop() {
+- referrerClient?.endConnection()
+- referrerClient = null
+- }
++ override fun stop() { /* noop */ }
+
+ override fun track(event: Event) = Unit
+
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/TopSiteItemViewHolder.kt b/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/TopSiteItemViewHolder.kt
+index d69f27e570..171f54a6d5 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/TopSiteItemViewHolder.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/TopSiteItemViewHolder.kt
+@@ -176,6 +176,12 @@ class TopSiteItemViewHolder(
+ SupportUtils.MEITUAN_URL -> {
+ binding.faviconImage.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_meituan))
+ }
++ SupportUtils.FDROID_URL -> {
++ binding.faviconImage.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_fdroid))
++ }
++ SupportUtils.EFF_URL -> {
++ binding.faviconImage.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_eff))
++ }
+ else -> {
+ itemView.context.components.core.icons.loadIntoView(binding.faviconImage, topSite.url)
+ }
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
+index e867be39f2..a1667b1c2b 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
+@@ -38,6 +38,8 @@ object SupportUtils {
+ const val GOOGLE_US_URL = "https://www.google.com/webhp?client=firefox-b-1-m&channel=ts"
+ const val GOOGLE_XX_URL = "https://www.google.com/webhp?client=firefox-b-m&channel=ts"
+ const val WHATS_NEW_URL = "https://www.mozilla.org/firefox/android/notes"
++ const val FDROID_URL = "https://f-droid.org/"
++ const val EFF_URL = "https://www.eff.org/"
+
+ enum class SumoTopic(internal val topicStr: String) {
+ HELP("faq-android"),
+diff --git a/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+index 01ed68bbe6..c35ab607f7 100644
+--- a/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
++++ b/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+@@ -332,17 +332,17 @@ class Settings(private val appContext: Context) : PreferencesHolder {
+
+ var isTelemetryEnabled by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_telemetry),
+- default = true,
++ default = false,
+ )
+
+ var isMarketingTelemetryEnabled by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_marketing_telemetry),
+- default = !Config.channel.isMozillaOnline,
++ default = false,
+ )
+
+ var isExperimentationEnabled by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_experimentation),
+- default = true,
++ default = false,
+ )
+
+ var isOverrideTPPopupsForPerformanceTest = false
+@@ -1553,7 +1553,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
+ var showPocketRecommendationsFeature by lazyFeatureFlagPreference(
+ appContext.getPreferenceKey(R.string.pref_key_pocket_homescreen_recommendations),
+ featureFlag = FeatureFlags.isPocketRecommendationsFeatureEnabled(appContext),
+- default = { homescreenSections[HomeScreenSection.POCKET] == true },
++ default = { false },
+ )
+
+ /**
+@@ -1611,7 +1611,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
+ */
+ var showContileFeature by booleanPreference(
+ key = appContext.getPreferenceKey(R.string.pref_key_enable_contile),
+- default = true,
++ default = false,
+ )
+
+ /**
+diff --git a/fenix/app/src/main/res/values/static_strings_extra.xml b/fenix/app/src/main/res/values/static_strings_extra.xml
+new file mode 100644
+index 0000000000..101f4e0d0a
+--- /dev/null
++++ b/fenix/app/src/main/res/values/static_strings_extra.xml
+@@ -0,0 +1,8 @@
++<?xml version="1.0" encoding="utf-8" standalone="yes"?>
++<!-- This Source Code Form is subject to the terms of the Mozilla Public
++ - License, v. 2.0. If a copy of the MPL was not distributed with this
++ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
++<resources>
++ <string name="default_top_site_fdroid" translatable="false">F-Droid</string>
++ <string name="default_top_site_eff" translatable="false">EFF</string>
++</resources>
+diff --git a/fenix/app/src/main/res/xml/preferences.xml b/fenix/app/src/main/res/xml/preferences.xml
+index 2e0366e11b..4a684b7fb6 100644
+--- a/fenix/app/src/main/res/xml/preferences.xml
++++ b/fenix/app/src/main/res/xml/preferences.xml
+@@ -133,11 +133,6 @@
+ app:iconSpaceReserved="false"
+ android:title="@string/preferences_notifications" />
+
+- <androidx.preference.Preference
+- android:key="@string/pref_key_data_choices"
+- app:iconSpaceReserved="false"
+- android:title="@string/preferences_data_collection" />
+-
+ </androidx.preference.PreferenceCategory>
+
+ <PreferenceCategory
+@@ -189,11 +184,6 @@
+ android:title="@string/preferences_category_about"
+ app:iconSpaceReserved="false"
+ android:layout="@layout/preference_category_no_icon_style">
+- <androidx.preference.Preference
+- android:key="@string/pref_key_rate"
+- app:iconSpaceReserved="false"
+- android:title="@string/preferences_rate" />
+-
+ <androidx.preference.Preference
+ android:key="@string/pref_key_about"
+ app:iconSpaceReserved="false"
+diff --git a/fenix/app/src/main/res/xml/site_permissions_details_exceptions_preferences.xml b/fenix/app/src/main/res/xml/site_permissions_details_exceptions_preferences.xml
+index 6bb8cfbbe6..1c15b5897c 100644
+--- a/fenix/app/src/main/res/xml/site_permissions_details_exceptions_preferences.xml
++++ b/fenix/app/src/main/res/xml/site_permissions_details_exceptions_preferences.xml
+@@ -3,7 +3,8 @@
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+ <androidx.preference.PreferenceScreen
+- xmlns:android="http://schemas.android.com/apk/res/android">
++ xmlns:android="http://schemas.android.com/apk/res/android"
++ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <androidx.preference.Preference
+ android:icon="@drawable/ic_camera_enabled"
+ android:key="@string/pref_key_phone_feature_camera"
+@@ -44,7 +45,8 @@
+ android:icon="@drawable/ic_link"
+ android:key="@string/pref_key_browser_feature_media_key_system_access"
+ android:title="@string/preference_phone_feature_media_key_system_access"
+- android:summary="@string/preference_option_phone_feature_ask_to_allow"/>
++ android:summary="@string/preference_option_phone_feature_ask_to_allow"
++ app:isPreferenceVisible="false"/>
+
+ <androidx.preference.Preference
+ android:icon="@drawable/ic_autoplay"
+diff --git a/fenix/app/src/main/res/xml/site_permissions_preferences.xml b/fenix/app/src/main/res/xml/site_permissions_preferences.xml
+index 64c90b599b..621f184e68 100644
+--- a/fenix/app/src/main/res/xml/site_permissions_preferences.xml
++++ b/fenix/app/src/main/res/xml/site_permissions_preferences.xml
+@@ -64,6 +64,7 @@
+ android:key="@string/pref_key_browser_feature_media_key_system_access"
+ android:title="@string/preference_phone_feature_media_key_system_access"
+ android:summary="@string/preference_option_phone_feature_ask_to_allow"
++ app:isPreferenceVisible="false"
+ app:allowDividerBelow="true"/>
+
+ <androidx.preference.Preference
+diff --git a/fenix/plugins/fenixdependencies/src/main/java/FenixDependenciesPlugin.kt b/fenix/plugins/fenixdependencies/src/main/java/FenixDependenciesPlugin.kt
+index fff754c099..35599805e6 100644
+--- a/fenix/plugins/fenixdependencies/src/main/java/FenixDependenciesPlugin.kt
++++ b/fenix/plugins/fenixdependencies/src/main/java/FenixDependenciesPlugin.kt
+@@ -70,9 +70,6 @@ object FenixDependencies {
+ const val protobuf_javalite = "com.google.protobuf:protobuf-javalite:${FenixVersions.protobuf}"
+ const val protobuf_compiler = "com.google.protobuf:protoc:${FenixVersions.protobuf}"
+
+- const val adjust = "com.adjust.sdk:adjust-android:${FenixVersions.adjust}"
+- const val installreferrer = "com.android.installreferrer:installreferrer:${FenixVersions.installreferrer}"
+-
+ const val mockk = "io.mockk:mockk:${FenixVersions.mockk}"
+ const val mockk_android = "io.mockk:mockk-android:${FenixVersions.mockk}"
+ const val falcon = "com.jraska:falcon:${FenixVersions.falcon}"
+@@ -97,12 +94,6 @@ object FenixDependencies {
+
+ const val mockwebserver = "com.squareup.okhttp3:mockwebserver:${FenixVersions.mockwebserver}"
+
+- const val google_ads_id = "com.google.android.gms:play-services-ads-identifier:${FenixVersions.google_ads_id_version}"
+-
+- // Required for in-app reviews
+- const val google_play_review = "com.google.android.play:review:${FenixVersions.google_play_review_version}"
+- const val google_play_review_ktx = "com.google.android.play:review-ktx:${FenixVersions.google_play_review_version}"
+-
+ const val junitApi = "org.junit.jupiter:junit-jupiter-api:${FenixVersions.junit}"
+ const val junitParams = "org.junit.jupiter:junit-jupiter-params:${FenixVersions.junit}"
+ const val junitEngine = "org.junit.jupiter:junit-jupiter-engine:${FenixVersions.junit}"

127
README.md
View File

@ -1,27 +1,136 @@
# LeOSium Browser!
# Iceraven Browser! [![CI](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/ci.yml) ![Release](https://img.shields.io/github/v/release/fork-maintainers/iceraven-browser)
Definitely not brought to you by Mozilla!
LeOSium Browser is a fork of LeOSium.... thx fork-maintainer for the sources !!!!
LeOSium Browser is a web browser for Android, based on [Mozilla's Fenix version of Firefox](https://github.com/mozilla-mobile/fenix/), [GeckoView](https://mozilla.github.io/geckoview/) and [Mozilla Android Components](https://mozac.org/).
Iceraven Browser is a web browser for Android, based on [Mozilla's Fenix version of Firefox](https://github.com/mozilla-mobile/fenix/), [GeckoView](https://mozilla.github.io/geckoview/) and [Mozilla Android Components](https://mozac.org/).
Our goal is to be a close fork of the new Firefox for Android that seeks to provide users with more options, more opportunities to customize (including a broad extension library), and more information about the pages they visit and how their browsers are interacting with those pages.
Notable features include:
* `about:config` support
* The ability to *attempt* to install a much longer list of add-ons than Mozilla's Fenix version of Firefox accepts. Currently the browser queries [this AMO collection](https://addons.mozilla.org/en-US/firefox/collections/16201230/What-I-want-on-Fenix/) **Most of them will not work**, because they depend on code that Mozilla is still working on writing in `android-components`, but you may attempt to install them. If you don't see an add-on you want, you can [request it](https://github.com/fork-maintainers/LeOSium-browser/issues/new).
* The ability to *attempt* to install a much longer list of add-ons than Mozilla's Fenix version of Firefox accepts. Currently the browser queries [this AMO collection](https://addons.mozilla.org/en-US/firefox/collections/16201230/What-I-want-on-Fenix/) **Most of them will not work**, because they depend on code that Mozilla is still working on writing in `android-components`, but you may attempt to install them. If you don't see an add-on you want, you can [request it](https://github.com/fork-maintainers/iceraven-browser/issues/new).
* Option to suspend tabs to avoid being killed for memory (https://bugzilla.mozilla.org/show_bug.cgi?id=1807364)
* **No warranties or guarantees of security or updates or even stability**! Note that LeOSium Browser includes some unstable code written by Mozilla, with our own added modifications on top, all shipped with the stable version of GeckoView engine. Hence, the browser may contain bugs introduced upstream. Binaries are currently built automatically by our Github release automation. These binaries are signed with a debug key. When we finally publish this somewhere official like F-droid, we will sign the apks with a proper key suitable for public release. Due to the current way we create the releases and sign them, you may not want to rely on such "alpha" quality software as your primary web browser, as it will have bugs. So, use this browser only if you are comfortable with these limitations/potential risks.
* Option not to display recently visited websites at HomePage
* **No warranties or guarantees of security or updates or even stability**! Note that Iceraven Browser includes some unstable code written by Mozilla, with our own added modifications on top, all shipped with the stable version of GeckoView engine. Hence, the browser may contain bugs introduced upstream. Binaries are currently built automatically by our Github release automation. These binaries are signed with a debug key. When we finally publish this somewhere official like F-droid, we will sign the apks with a proper key suitable for public release. Due to the current way we create the releases and sign them, you may not want to rely on such "alpha" quality software as your primary web browser, as it will have bugs. So, use this browser only if you are comfortable with these limitations/potential risks.
**Note/Disclaimer:** LeOSium Browser could not exist without the hardworking folks at the Mozilla Corporation who work on the Mozilla Android Components and Firefox projects, but it is not an official Mozilla product, and is not provided, endorsed, vetted, approved, or secured by Mozilla.
**Note/Disclaimer:** Iceraven Browser could not exist without the hardworking folks at the Mozilla Corporation who work on the Mozilla Android Components and Firefox projects, but it is not an official Mozilla product, and is not provided, endorsed, vetted, approved, or secured by Mozilla.
In addition, we intend to try to cut down on telemetry and proprietary code to as great of an extent as possible as long as doing so does not compromise the user experience or make the fork too hard to maintain. Right now, we believe that no telemetry should be being sent to Mozilla anymore, but we cannot guarantee this; data may still be sent. Because of the way we have implemented this, the app may still appear to contain trackers when analyzed by tools that look for the presence of known tracking libraries. These detected trackers should actually be non-functional substitutes, many of which are sourced [from here](https://gitlab.com/relan/fennecbuild/-/blob/master/fenix-liberate.patch). **If you catch the app actually sending data to Mozilla, Adjust, Leanplum, Firebase, or any other such service, please open an issue!** Presumably any data that reaches Mozilla is governed by Mozilla's privacy policy, but as LeOSium Browser is, again **not a Mozilla product**, we can make no promises.
In addition, we intend to try to cut down on telemetry and proprietary code to as great of an extent as possible as long as doing so does not compromise the user experience or make the fork too hard to maintain. Right now, we believe that no telemetry should be being sent to Mozilla anymore, but we cannot guarantee this; data may still be sent. Because of the way we have implemented this, the app may still appear to contain trackers when analyzed by tools that look for the presence of known tracking libraries. These detected trackers should actually be non-functional substitutes, many of which are sourced [from here](https://gitlab.com/relan/fennecbuild/-/blob/master/fenix-liberate.patch). **If you catch the app actually sending data to Mozilla, Adjust, Leanplum, Firebase, or any other such service, please open an issue!** Presumably any data that reaches Mozilla is governed by Mozilla's privacy policy, but as Iceraven Browser is, again **not a Mozilla product**, we can make no promises.
LeOSium Browser combines the power of Fenix (of which we are a fork) and the spirit of Fennec, with a respectful nod toward the grand tradition of Netscape Navigator, from which all Gecko-based projects came, including the earliest of our predecessors, the old Mozilla Phoenix and Mozilla Firefox desktop browsers.
Iceraven Browser combines the power of Fenix (of which we are a fork) and the spirit of Fennec, with a respectful nod toward the grand tradition of Netscape Navigator, from which all Gecko-based projects came, including the earliest of our predecessors, the old Mozilla Phoenix and Mozilla Firefox desktop browsers.
That said, LeOSium Browser is an independent all-volunteer project, and has no affiliation with Netscape, Netscape Navigator, Mozilla, Mozilla Firefox, Mozila Phoenix, Debian, Debian Iceweasel, Parabola GNU/Linux-libre Iceweasel, America Online, or Verizon, among others. :) Basically, if you don't like the browser, it's not their fault. :)
That said, Iceraven Browser is an independent all-volunteer project, and has no affiliation with Netscape, Netscape Navigator, Mozilla, Mozilla Firefox, Mozila Phoenix, Debian, Debian Iceweasel, Parabola GNU/Linux-libre Iceweasel, America Online, or Verizon, among others. :) Basically, if you don't like the browser, it's not their fault. :)
## 📥 Installation
Right now, releases are published as `.apk` files, through Github. You should download and install the appropriate one for your device.
1. **Determine what version you need**. If you have a newer, 64-bit device, or a device with more than 4 GB of memory, you probably want the `arm64-v8a` version. **Any ordinary phone or tablet should be able to use the `armeabi-v7a` version**, but it will be limited to using no more than 4 GB of memory. You almost certainly don't want the `x86` or `x86_64` versions; they are in case you are running Android on a PC.
2. [**Download the APK for the latest release from the Releases page**](https://github.com/fork-maintainers/iceraven-browser/releases). Make sure to pick the version you chose in step 1.
3. **Install the APK**. You will need to enable installation of apps from "unknown" (to Google) sources, and installatiuon of apps *by* whatever app you used to open the downloaded APK (i.e. your browser or file manager). Android will try to dissuade you from doing this, and suggest that it is dangerous. Iceraven is a browser for people who enjoy danger.
4. **Enjoy Iceraven**. Make sure to install the add-ons that are essential for you in the main menu under "Add-Ons". You may want to set Iceraven as your device's default browser app. If you do this, it will be able to provide so-called "Chrome" [custom tabs](https://developers.google.com/web/android/custom-tabs) for other applications, allowing you to use your add-ons there.
## 🔨 Building
1. Set up the environment. We need the Android SDK at `$ANDROID_SDK_ROOT` and a Java JDK at `$JAVA_HOME` that isn't the Ubuntu Java 8 one. We want environment variables that look something like:
```sh
# Where does our system install the JDK? This is the right path for the Ubuntu Java 11 JDK, if it is installed.
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
# Where did we install the Android SDK?
export ANDROID_SDK_ROOT=$HOME/android-sdk/android-sdk-linux/
```
If we don't have the Android SDK, we can install it thusly on Linux:
```sh
mkdir -p $HOME/android-sdk/android-sdk-linux
cd $HOME/android-sdk/android-sdk-linux
mkdir -p licenses
echo "8933bad161af4178b1185d1a37fbf41ea5269c55" >> licenses/android-sdk-license
echo "d56f5187479451eabf01fb78af6dfcb131a6481e" >> licenses/android-sdk-license
echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" >> licenses/android-sdk-license
mkdir cmdline-tools
cd cmdline-tools
wget "$(curl -s https://developer.android.com/studio | grep -oP "https://dl.google.com/android/repository/commandlinetools-linux-[0-9]+_latest.zip")"
unzip commandlinetools-linux-*_latest.zip
cd ..
```
2. Clone the project.
```sh
git clone --recursive https://github.com/fork-maintainers/iceraven-browser
```
4. Go inside `iceraven-browser`. That's where the build is coordinated from.
```sh
cd iceraven-browser
```
5. Configure the project. For your personal use you need to sign the apk file. The simplest way to do this is to use the debug key that is auto-generated by Android SDK. This is not a great idea for releasing, but acceptable for your personal use. You can configure it as follows:
```sh
echo "autosignReleaseWithDebugKey=" >> local.properties
```
6. Build the project. To build the Iceraven-branded release APKs, you can do:
```sh
./gradlew app:assemblefenixForkRelease -PversionName="$(git describe --tags HEAD)"
```
(If you don't use the `app:` prefix, you might get complaints about the build system being `unable to locate the objcopy executable`.)
The APKs will show up in `app/build/outputs/apk/fenix/forkRelease/`.
## Getting Involved
This is an all-volunteer project. No one is getting paid (at least not by the project itself.).
Therefore, everyone should feel free to open issues and pull requests. Join the club!
Developers are especially welcome, wanted, and needed.
## I want to open a Pull Request!
We encourage you to participate in this open source project. We love Pull Requests, Bug Reports, ideas, (security) code reviews or any other kind of positive contribution.
### How to Appease the Linter
If you are getting errors form `./gradelw ktlint`, try running `./gradlew ktlintFormat` to let `ktlint` decide how to lay out your code, instead of just yelling at you that you can't read its mind.
### 🙅 How to skip CI checks for PRs 🙅
If you want to skip Github CI checks in a PR, please add the following to the PR title exactly: `[skip ci]`.
Also, please include the exact phrase `[skip ci]` in every commit message. This is to avoid Travis CI checks as well as skipping Github CI checks after merging the commits to the `fork` branch.
This is useful to do **if** you are sure that your changes do not effect the app's code (ex: changes to `README.md`).
## 🚀 Release automation 🚀
We have now setup release automation so that Github actions automatically trigger a release build and publish a release when we push a tag to the repository.
**NOTE**: The tag should be of the format `iceraven-x.y.z`, where `x.y.z` is the release version, for the automation to kick in and also so that the built app will have the correct version name.
## ✏️ I want to file an issue!
Great! We encourage you to participate in this open source project. We love Pull Requests, Bug Reports, ideas, (security) code reviews or any other kind of positive contribution.
To make it easier to triage, we have these issue requirements:
* Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean.
* Every issue should have **exactly** one bug/feature request described in it. Please do not file meta feedback list tickets as it is difficult to parse them and address their individual points.
* Feature Requests are better when theyre open-ended instead of demanding a specific solution -ie “I want an easier way to do X” instead of “add Y”
* Issues are not the place to go off topic or debate.
* While we do not yet have Community Participation Guidelines of our own, we ask that you show respect to everyone and treat others as you would like to be treated. Behavior that would violate [Mozilla's Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) is almost certainly unwelcome. However, as a small project without community managers, we cannot promise prompt and consistent enforcement.
Please keep in mind that even though a feature you have in mind may seem like a small ask, as a small team, we have to prioritize our planned work and every new feature adds complexity and maintenance and may take up design, research, product, and engineering time. We appreciate everyones passion but we will not be able to incorporate every feature request or even fix every bug. That being said, just because we haven't replied, doesn't mean we don't care about the issue, please be patient with our response times as we're very busy.
## License

@ -1 +0,0 @@
Subproject commit 89ae956b10d2bfee7dc7832ae41ae349f25ef5b9

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
# Maven group ID used for all components
componentsGroupId: "org.mozilla.components"
# Synchronized build configuration for all modules
jvmTargetCompatibility: 17
compileSdkVersion: 35
minSdkVersion: 21
targetSdkVersion: 34

View File

@ -0,0 +1,15 @@
# This is an .editorconfig that ktlint will "stop" at.
# If an .editorconfig file exists in any parent directory of the checkout
# directory, ktlint will fail because it uses those settings to determine what
# the indentation should be.
root = True
[*.kt]
indent_size = 4
indent_style = space
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma=true

12
android-components/.gitattributes vendored Normal file
View File

@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Treat generated API docs as binary. We do not want them to pollute
# our diffs.
docs/api/**/* -text -diff linguist-generated=true
# Treat the public suffix list file as text
**/publicsuffixes diff

View File

@ -0,0 +1 @@
,ich,pop-os,12.10.2024 11:30,file:///home/ich/.config/libreoffice/4;

View File

@ -0,0 +1,2 @@
The changelog is now hosted here:
https://mozilla-mobile.github.io/android-components/changelog/

View File

@ -0,0 +1,325 @@
# Android components
[![Task Status](https://firefox-ci-tc.services.mozilla.com/api/github/v1/repository/mozilla-mobile/android-components/main/badge.svg)](https://firefox-ci-tc.services.mozilla.com/api/github/v1/repository/mozilla-mobile/android-components/main/latest)
[![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/mozilla-mobile/android-components&style=flat)](https://mergify.io)
[![chat.mozilla.org](https://img.shields.io/badge/chat-on%20matrix-51bb9c)](https://chat.mozilla.org/#/room/#android-components:mozilla.org)
_A collection of Android libraries to build browsers or browser-like applications._
For more information **[see the website](https://mozilla-mobile.github.io/android-components/)**.
A fully-featured reference browser implementation based on the components can be found in the [reference-browser repository](https://github.com/mozilla-mobile/reference-browser).
# Getting Involved
We encourage you to participate in this open source project. We love pull requests, bug reports, ideas, (security) code reviews or any kind of positive contribution.
Before you attempt to make a contribution please read the [Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/).
* Matrix: [android-components:mozilla.org chat room](https://chat.mozilla.org/#/room/#android-components:mozilla.org) ([How to connect](https://wiki.mozilla.org/Matrix#Connect_to_Matrix)).
* Localization happens on [Pontoon](https://pontoon.mozilla.org/projects/android-l10n/). Please get in touch with delphine (at) mozilla (dot) com directly for more information.
# Maven repository
All components are getting published on [maven.mozilla.org](https://maven.mozilla.org/).
To use them, you need to add the following to your project's top-level build file, in the `allprojects` block (see e.g. the [reference-browser](https://github.com/mozilla-mobile/reference-browser/blob/main/build.gradle)):
```groovy
repositories {
maven {
url "https://maven.mozilla.org/maven2"
}
}
```
Each module that uses a component needs to specify it in its build file, in the `dependencies` block. For example, to use the `Base` component (in the `support`) collection, you need:
```groovy
dependencies {
implementation 'org.mozilla.components:support-base:+'
}
```
## Nightly builds
Nightly builds are created every day from the `main` branch and published on [nightly.maven.mozilla.org](https://nightly.maven.mozilla.org).
# Components
* 🔴 **In Development** - Not ready to be used in shipping products.
* ⚪ **Preview** - This component is almost/partially ready and can be tested in products.
* 🔵 **Production ready** - Used by shipping products.
## Browser
High-level components for building browser(-like) apps.
* 🔵 [**Awesomebar**](components/browser/awesomebar/README.md) - A customizable [Awesome Bar](https://support.mozilla.org/en-US/kb/awesome-bar-search-firefox-bookmarks-history-tabs) implementation for browsers.
* 🔵 [**Domains**](components/browser/domains/README.md) Localized and customizable domain lists for auto-completion in browsers.
* 🔵 [**Engine-Gecko**](components/browser/engine-gecko/README.md) - *Engine* implementation based on [GeckoView](https://wiki.mozilla.org/Mobile/GeckoView).
* 🔵 [**Engine-System**](components/browser/engine-system/README.md) - *Engine* implementation based on the system's WebView.
* 🔵 [**Errorpages**](components/browser/errorpages/README.md) - Responsive browser error pages for Android apps.
* 🔵 [**Icons**](components/browser/icons/README.md) - A component for loading and storing website icons (like [Favicons](https://en.wikipedia.org/wiki/Favicon)).
* 🔵 [**Menu**](components/browser/menu/README.md) - A generic menu with customizable items primarily for browser toolbars.
* ⚪ [**Menu 2**](components/browser/menu2/README.md) - A generic menu with customizable items primarily for browser toolbars.
* 🔵 [**Session-Storage**](components/browser/session-storage/README.md) - Component for saving and restoring the browser state.
* 🔵 [**State**](components/browser/state/README.md) - Component for maintaining the centralized state of the browser and its components.
* 🔵 [**Storage-Sync**](components/browser/storage-sync/README.md) - A syncable implementation of browser storage backed by [application-services' Places lib](https://github.com/mozilla/application-services).
* 🔵 [**Tabstray**](components/browser/tabstray/README.md) - A customizable tabs tray for browsers.
* 🔵 [**Thumbnails**](components/browser/thumbnails/README.md) - A component for loading and storing website thumbnails (screenshot of the website).
* 🔵 [**Toolbar**](components/browser/toolbar/README.md) - A customizable toolbar for browsers.
## Concept
_API contracts and abstraction layers for browser components._
* 🔵 [**Awesomebar**](components/concept/awesomebar/README.md) - An abstract definition of an awesome bar component.
* 🔵 [**Engine**](components/concept/engine/README.md) - Abstraction layer that allows hiding the actual browser engine implementation.
* 🔵 [**Fetch**](components/concept/fetch/README.md) - An abstract definition of an HTTP client for fetching resources.
* 🔵 [**Push**](components/concept/push/README.md) - An abstract definition of a push service component.
* 🔵 [**Storage**](components/concept/storage/README.md) - Abstract definition of a browser storage component.
* 🔵 [**Tabstray**](components/concept/tabstray/README.md) - Abstract definition of a tabs tray component.
* 🔵 [**Toolbar**](components/concept/toolbar/README.md) - Abstract definition of a browser toolbar component.
## Feature
_Combined components to implement feature-specific use cases._
* 🔵 [**Accounts**](components/feature/accounts/README.md) - A component that connects an FxaAccountManager from [service-firefox-accounts](components/service/firefox-accounts/README.md) with [feature-tabs](components/feature/tabs/README.md) in order to facilitate authentication flows.
* 🔵 [**Accounts Push**](components/feature/accounts-push/README.md) - Feature of use cases for FxA Account that work with push support.
* 🔵 [**Autofill**](components/feature/autofill/README.md) - A component that provides support for Android's Autofill framework.
* 🔵 [**Awesomebar**](components/feature/awesomebar/README.md) - A component that connects a [concept-awesomebar](components/concept/awesomebar/README.md) implementation to a [concept-toolbar](components/concept/toolbar/README.md) implementation and provides implementations of various suggestion providers.
* 🔴 [**Containers**](components/feature/containers/README.md) - A component for working with contextual identities also known as containers.
* 🔵 [**Context Menu**](components/feature/contextmenu/README.md) - A component for displaying context menus when *long-pressing* web content.
* 🔵 [**Custom Tabs**](components/feature/customtabs/README.md) - A component for providing [Custom Tabs](https://developer.chrome.com/multidevice/android/customtabs) functionality in browsers.
* 🔵 [**Downloads**](components/feature/downloads/README.md) - A component to perform downloads using the [Android downloads manager](https://developer.android.com/reference/android/app/DownloadManager).
* 🔵 [**Intent**](components/feature/intent/README.md) - A component that provides intent processing functionality by combining various other feature modules.
* ⚪ [**Progressive Web Apps (PWA)**](components/feature/pwa/README.md) - A component that provides functionality for supporting Progressive Web Apps (PWA).
* 🔵 [**Reader View**](components/feature/readerview/README.md) - A component that provides Reader View functionality.
* 🔵 [**QR**](components/feature/qr/README.md) - A component that provides functionality for scanning QR codes.
* 🔵 [**Search**](components/feature/search/README.md) - A component that connects an (concept) engine implementation with the browser search module.
* 🔵 [**Session**](components/feature/session/README.md) - A component that connects an (concept) engine implementation with the browser session and storage modules.
* 🔵 [**Share**](components/feature/share/README.md) - Feature implementation for saving and sorting recent apps used for sharing.
* 🔵 [**Sync**](components/feature/sync/README.md) -A component that provides synchronization orchestration for groups of (concept) SyncableStore objects.
* 🔵 [**Tabs**](components/feature/tabs/README.md) - A component that connects a tabs tray implementation with the session and toolbar modules.
* 🔵 [**Tab Collections**](components/feature/tab-collections/README.md) - Feature implementation for saving, restoring and organizing collections of tabs.
* 🔵 [**Toolbar**](components/feature/toolbar/README.md) - A component that connects a (concept) toolbar implementation with the browser session module.
* 🔵 [**Top Sites**](components/feature/top-sites/README.md) - Feature implementation for saving and removing top sites.
* 🔵 [**Prompts**](components/feature/prompts/README.md) - A component that will handle all the common prompt dialogs from web content.
* 🔵 [**Push**](components/feature/push/README.md) - A component that provides Autopush messages with help from a supported push service.
* 🔵 [**Find In Page**](components/feature/findinpage/README.md) - A component that provides an UI widget for [find in page functionality](https://support.mozilla.org/en-US/kb/search-contents-current-page-text-or-links).
* 🔵 [**Remote Tabs**](components/feature/remotetabs/README.md) - Feature that provides access to other device's tabs in the same account.
* 🔵 [**Site Permissions**](components/feature/sitepermissions/README.md) - A feature for showing site permission request prompts.
* 🔵 [**WebAuthn**](components/feature/webauthn/README.md) - A feature that provides WebAuthn functionality for supported engines.
* 🔵 [**Web Notifications**](components/feature/webnotifications/README.md) - A component for displaying web notifications.
* 🔵 [**WebCompat**](components/feature/webcompat/README.md) - A feature to enable website-hotfixing via the Web Compatibility System-Addon.
* 🔵 [**WebCompat Reporter**](components/feature/webcompat-reporter/README.md) - A feature that enables users to report site issues to Mozilla's Web Compatibility team for further diagnosis.
* 🔵 [**Web Add-ons**](components/feature/addons/README.md) - A feature that provides functionality for managing add-ons.
## UI
_Generic low-level UI components for building apps._
* 🔵 [**Autocomplete**](components/ui/autocomplete/README.md) - A set of components to provide autocomplete functionality.
* 🔵 [**Colors**](components/ui/colors/README.md) - The standard set of [Photon](https://design.firefox.com/photon/) colors.
* 🔵 [**Fonts**](components/ui/fonts/README.md) - The standard set of fonts used by Mozilla Android products.
* 🔵 [**Icons**](components/ui/icons/README.md) - A collection of often used browser icons.
* 🔵 [**Tabcounter**](components/ui/tabcounter/README.md) - A button that shows the current tab count and can animate state changes.
## Service
_Components and libraries to interact with backend services._
* 🔵 [**Firefox Accounts (FxA)**](components/service/firefox-accounts/README.md) - A library for integrating with Firefox Accounts.
* 🔵 [**Firefox Sync - Logins**](components/service/sync-logins/README.md) - A library for integrating with Firefox Sync - Logins.
* 🔵 [**Firefox Sync - Autofill**](components/service/sync-autofill/README.md) - A library for integrating with Firefox Sync - Autofill.
* 🔵 [**Glean**](components/service/glean/README.md) - A client-side telemetry SDK for collecting metrics and sending them to Mozilla's telemetry service (eventually replacing [service-telemetry](components/service/telemetry/README.md)).
* 🔵 [**Location**](components/service/location/README.md) - A library for accessing Mozilla's and other location services.
* 🔴 [**Nimbus**](components/service/nimbus/README.md) - A wrapper for the Nimbus SDK.
* 🔵 [**Pocket**](components/service/pocket/README.md) - A library for communicating with the Pocket API.
* 🔵 [**Contile**](components/service/contile/README.md) - A library for communicating with the Contile services API.
## Support
_Supporting components with generic helper code._
* 🔵 [**Android Test**](components/support/android-test/README.md) - A collection of helpers for testing components in instrumented (on device) tests (`src/androidTest`).
* 🔵 [**Base**](components/support/base/README.md) - Base component containing building blocks for components.
* 🔵 [**Ktx**](components/support/ktx/README.md) - A set of Kotlin extensions on top of the Android framework and Kotlin standard library.
* 🔵 [**Test**](components/support/test/README.md) - A collection of helpers for testing components in local unit tests (`src/test`).
* 🔵 [**Test Appservices**](components/support/test-appservices/README.md) - A component for synchronizing Application Services' unit testing dependencies used in Android Components.
* 🔵 [**Test LibState**](components/support/test-libstate/README.md) - A collection of helpers for testing functionality that relies on the lib-state component in local unit tests (`src/test`).
* 🔵 [**Utils**](components/support/utils/README.md) - Generic utility classes to be shared between projects.
* 🔵 [**Webextensions**](components/support/webextensions/README.md) - A component containing building blocks for features implemented as web extensions.
## Standalone libraries
* 🔵 [**Crash**](components/lib/crash/README.md) - A generic crash reporter component that can report crashes to multiple services.
* 🔵 [**Dataprotect**](components/lib/dataprotect/README.md) - A component using AndroidKeyStore to protect user data.
* 🔵 [**Fetch-HttpURLConnection**](components/lib/fetch-httpurlconnection/README.md) - A [concept-fetch](concept/fetch/README.md) implementation using [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection.html).
* 🔵 [**Fetch-OkHttp**](components/lib/fetch-okhttp/README.md) - A [concept-fetch](concept/fetch/README.md) implementation using [OkHttp](https://github.com/square/okhttp).
* ⚪ [**JEXL**](components/lib/jexl/README.md) - Javascript Expression Language: Context-based expression parser and evaluator.
* 🔵 [**Public Suffix List**](components/lib/publicsuffixlist/README.md) - A library for reading and using the [public suffix list](https://publicsuffix.org/).
* 🔵 [**Push-Firebase**](components/lib/push-firebase/README.md) - A [concept-push](concept/push/README.md) implementation using [Firebase Cloud Messaging](https://firebase.google.com/products/cloud-messaging/).
* 🔵 [**State**](components/lib/state/README.md) - A library for maintaining application state.
## Tooling
* 🔵 [**Fetch-Tests**](components/tooling/fetch-tests/README.md) - A generic test suite for components that implement [concept-fetch](concept/fetch/README.md).
* 🔵 [**Lint**](components/tooling/lint/README.md) - Custom Lint rules for the components repository.
# Sample apps
_Sample apps using various components._
* [**Browser**](samples/browser) - A simple browser composed from browser components. This sample application is only a very basic browser. For a full-featured reference browser implementation see the **[reference-browser repository](https://github.com/mozilla-mobile/reference-browser)**.
* [**Crash**](samples/crash) - An app showing the integration of the `lib-crash` component.
* [**Firefox Accounts (FxA)**](samples/firefox-accounts) - A simple app demoing Firefox Accounts integration.
* [**Firefox Sync**](samples/sync) - A simple app demoing general Firefox Sync integration, with bookmarks and history.
* [**Firefox Sync - Logins**](samples/sync-logins) - A simple app demoing Firefox Sync (Logins) integration.
* [**DataProtect**](samples/dataprotect) - An app demoing how to use the [**Dataprotect**](components/lib/dataprotect/README.md) component to load and store encrypted data in `SharedPreferences`.
* [**Glean**](samples/glean) - An app demoing how to use the [**Glean**](components/service/glean/README.md) library to collect and send telemetry data.
* [**Toolbar**](samples/toolbar) - An app demoing multiple customized toolbars using the [**browser-toolbar**](components/browser/toolbar/README.md) component.
# Building #
## Command line ##
```
$ git clone https://github.com/mozilla-mobile/android-components.git
$ cd android-components
$ ./gradlew assemble
```
## Android Studio ##
If the environment variable `JAVA_HOME` is not defined, you will need to set it. If you would like to use the JDK installed by Android Studio, here's how to find it:
1. Open Android Studio.
2. Select "Configure".
3. Select "Default Project Structure". You should now see the Android JDK location.
4. Set the environment variable `JAVA_HOME` to the location. (How you set an environment variable depends on your OS.)
5. Restart Android Studio.
Once the environment variable is set, you can import the project into Android Studio with the default wizard options.
If your build fails, you may find you get more instructive error messages by attempting the build at the command line.
# Coding Standards #
## Style ##
We follow the style enforced by [ktlint](https://ktlint.github.io/) and [detekt](https://github.com/detekt/detekt). See [how to configure Android Studio appropriately](https://github.com/pinterest/ktlint#option-1-recommended).
To check your style, run:
```
./gradlew ktlint
./gradlew detekt
```
## Documentation ##
We use `README.md` files for each component.
If you fix a bug or change an API, you should update [docs/changelog.md](https://github.com/mozilla-mobile/android-components/blob/main/docs/changelog.md).
## Testing ##
You are expected to both add tests for code that you write and make sure that your changes do not
cause existing tests to fail. You may find these command lines helpful:
```
./gradlew test # Run all tests
./gradlew :support-ktx:testdebugunittest # Run unit tests for a specified module
```
See also [how to measure code coverage](https://mozac.org/contributing/code-coverage).
## Accessibility ##
If your code has user-facing changes, follow [Android accessibility best practices](https://github.com/mozilla-mobile/shared-docs/blob/main/android/accessibility_guide.md).
# License
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/

View File

@ -0,0 +1,19 @@
android {
lint {
warningsAsErrors true
abortOnError (project.name != "support-test")
// With our L10N process its totally possible to have missing or (temporarily) extra translations.
disable 'MissingTranslation',
'ExtraTranslation',
// We do not want to enforce this as a generic rule for all languages (see #6117, #6056, #6118)
'TypographyEllipsis',
// https://github.com/mozilla-mobile/android-components/issues/10641
'UnspecifiedImmutableFlag',
// https://github.com/mozilla-mobile/android-components/issues/10643
'UnusedResources',
// "We do not impose rules on locales"
// https://github.com/mozilla-mobile/android-components/pull/11069
'TypographyDashes'
}
}

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Purpose: Publish android packages to local maven repo, but only if changed since last publish.
# Dependencies: None
# Usage: ./automation/publish_to_maven_local_if_modified.py
import argparse
import hashlib
import os
import subprocess
import sys
import time
from pathlib import Path
def fatal_err(msg):
print(f"\033[31mError: {msg}\033[0m")
exit(1)
def run_cmd_checked(*args, **kwargs):
"""Run a command, throwing an exception if it exits with non-zero status."""
kwargs["check"] = True
return subprocess.run(*args, **kwargs)
def find_project_root():
"""Find the absolute path of the project repository root."""
# As a convention, we expect this file in [project-root]/automation/.
automation_dir = Path(__file__).parent
# Therefore the automation dir's parent is the project root we're looking for.
return automation_dir.parent
LAST_CONTENTS_HASH_FILE = ".lastAutoPublishContentsHash"
GITIGNORED_FILES_THAT_AFFECT_THE_BUILD = ["local.properties"]
parser = argparse.ArgumentParser(
description="Publish android packages to local maven repo, but only if changed since last publish"
)
parser.parse_args()
root_dir = find_project_root()
if str(root_dir) != os.path.abspath(os.curdir):
fatal_err(
f"This only works if run from the repo root ({root_dir!r} != {os.path.abspath(os.curdir)!r})"
)
# Calculate a hash reflecting the current state of the repo.
contents_hash = hashlib.sha256()
contents_hash.update(
run_cmd_checked(["git", "rev-parse", "HEAD"], capture_output=True).stdout
)
contents_hash.update(b"\x00")
# Get a diff of all tracked (staged and unstaged) files.
changes = run_cmd_checked(["git", "diff", "HEAD", "."], capture_output=True).stdout
contents_hash.update(changes)
contents_hash.update(b"\x00")
# But unfortunately it can only tell us the names of untracked
# files, and it won't tell us anything about files that are in
# .gitignore but can still affect the build.
untracked_files = []
# Get a list of all untracked files sans standard exclusions.
# -o is for getting other (i.e. untracked) files
# --exclude-standard is to handle standard Git exclusions: .git/info/exclude, .gitignore in each directory,
# and the user's global exclusion file.
changes_others = run_cmd_checked(
["git", "ls-files", "-o", "--exclude-standard"], capture_output=True
).stdout
changes_lines = iter(ln.strip() for ln in changes_others.split(b"\n"))
try:
ln = next(changes_lines)
while ln:
untracked_files.append(ln)
ln = next(changes_lines)
except StopIteration:
pass
# Then, account for some excluded files that we care about.
untracked_files.extend(GITIGNORED_FILES_THAT_AFFECT_THE_BUILD)
# Finally, get hashes of everything.
# Skip files that don't exist, e.g. missing GITIGNORED_FILES_THAT_AFFECT_THE_BUILD. `hash-object` errors out if it gets
# a non-existent file, so we hope that disk won't change between this filter and the cmd run just below.
filtered_untracked = [nm for nm in untracked_files if os.path.isfile(nm)]
# Reading contents of the files is quite slow when there are lots of them, so delegate to `git hash-object`.
git_hash_object_cmd = ["git", "hash-object"]
git_hash_object_cmd.extend(filtered_untracked)
changes_untracked = run_cmd_checked(git_hash_object_cmd, capture_output=True).stdout
contents_hash.update(changes_untracked)
contents_hash.update(b"\x00")
contents_hash = contents_hash.hexdigest()
# If the contents hash has changed since last publish, re-publish.
last_contents_hash = ""
try:
with open(LAST_CONTENTS_HASH_FILE) as f:
last_contents_hash = f.read().strip()
except FileNotFoundError:
pass
if contents_hash == last_contents_hash:
print("Contents have not changed, no need to publish")
else:
print("Contents have changed, publishing")
if sys.platform.startswith("win"):
run_cmd_checked(
["gradlew.bat", "publishToMavenLocal", f"-Plocal={time.time_ns()}"],
shell=True,
)
else:
run_cmd_checked(
["./gradlew", "publishToMavenLocal", f"-Plocal={time.time_ns()}"]
)
with open(LAST_CONTENTS_HASH_FILE, "w") as f:
f.write(contents_hash)
f.write("\n")

View File

@ -0,0 +1,31 @@
# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
# Flank Documentation: https://flank.github.io/flank/
gcloud:
results-bucket: android-components_test_artifacts
record-video: true
timeout: 30m
async: false
num-flaky-test-attempts: 2
app: /APP/PATH
test: /TEST/PATH
auto-google-login: false
use-orchestrator: true
environment-variables:
clearPackageData: true
directories-to-pull:
- /sdcard/screenshots
performance-metrics: true
device:
- model: Pixel2.arm
version: 28
locale: en_US
flank:
project: GOOGLE_PROJECT
max-test-shards: -1
num-test-runs: 1
output-style: compact
full-junit-result: true

View File

@ -0,0 +1,31 @@
# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
# Flank Documentation: https://flank.github.io/flank/
gcloud:
results-bucket: android-components_test_artifacts
record-video: true
timeout: 30m
async: false
num-flaky-test-attempts: 1
app: /app/path
test: /test/path
auto-google-login: false
use-orchestrator: true
environment-variables:
clearPackageData: true
directories-to-pull:
- /sdcard/screenshots
performance-metrics: true
device:
- model: Pixel2
version: 28
flank:
project: GOOGLE_PROJECT
max-test-shards: -1
num-test-runs: 1
output-style: compact
full-junit-result: true
repeat-tests: 1

View File

@ -0,0 +1,144 @@
#!/usr/bin/python3
import argparse
import sys
import xml
from pathlib import Path
from beautifultable import BeautifulTable
from junitparser import Attr, Failure, JUnitXml, TestCase, TestSuite
def parse_args(cmdln_args):
parser = argparse.ArgumentParser(
description="Parse and print UI test JUnit results"
)
parser.add_argument(
"--results",
type=Path,
help="Directory containing task artifact results",
required=True,
)
return parser.parse_args(args=cmdln_args)
class test_suite(TestSuite):
flakes = Attr()
class test_case(TestCase):
flaky = Attr()
def parse_print_failure_results(results):
"""
Parses the given JUnit test results and prints a formatted table of failures and flaky tests.
Args:
results (JUnitXml): Parsed JUnit XML results.
Returns:
int: The number of test failures.
The function processes each test suite and each test case within the suite.
If a test case has a result that is an instance of Failure, it is added to the table.
The test case is marked as 'Flaky' if the flaky attribute is set to "true", otherwise it is marked as 'Failure'.
Example of possible JUnit XML (FullJUnitReport.xml):
<testsuites>
<testsuite name="ExampleSuite" tests="2" failures="1" flakes="1" time="0.003">
<testcase classname="example.TestClass" name="testSuccess" flaky="true" time="0.001">
<failure message="Assertion failed">Expected true but was false</failure>
</testcase>
<testcase classname="example.TestClass" name="testFailure" time="0.002">
<failure message="Assertion failed">Expected true but was false</failure>
<failure message="Assertion failed">Expected true but was false</failure>
</testcase>
</testsuite>
</testsuites>
"""
table = BeautifulTable(maxwidth=256)
table.columns.header = ["UI Test", "Outcome", "Details"]
table.columns.alignment = BeautifulTable.ALIGN_LEFT
table.set_style(BeautifulTable.STYLE_GRID)
failure_count = 0
# Dictionary to store the last seen failure details for each test case
last_seen_failures = {}
for suite in results:
cur_suite = test_suite.fromelem(suite)
for case in cur_suite:
cur_case = test_case.fromelem(case)
if cur_case.result:
for entry in case.result:
if isinstance(entry, Failure):
flaky_status = getattr(cur_case, "flaky", "false") == "true"
if flaky_status:
test_id = "%s#%s" % (case.classname, case.name)
details = (
entry.text.replace("\t", " ") if entry.text else ""
)
# Check if the current failure details are different from the last seen ones
if details != last_seen_failures.get(test_id, ""):
table.rows.append(
[
test_id,
"Flaky",
details,
]
)
last_seen_failures[test_id] = details
else:
test_id = "%s#%s" % (case.classname, case.name)
details = (
entry.text.replace("\t", " ") if entry.text else ""
)
# Check if the current failure details are different from the last seen ones
if details != last_seen_failures.get(test_id, ""):
table.rows.append(
[
test_id,
"Failure",
details,
]
)
print(f"TEST-UNEXPECTED-FAIL | {test_id} | {details}")
failure_count += 1
# Update the last seen failure details for this test case
last_seen_failures[test_id] = details
print(table)
return failure_count
def load_results_file(filename):
ret = None
try:
f = open(filename, "r")
try:
ret = JUnitXml.fromfile(f)
except xml.etree.ElementTree.ParseError as e:
print(f"Error parsing {filename} file: {e}")
finally:
f.close()
except IOError as e:
print(e)
return ret
def main():
args = parse_args(sys.argv[1:])
failure_count = 0
junitxml = load_results_file(args.results.joinpath("FullJUnitReport.xml"))
if junitxml:
failure_count = parse_print_failure_results(junitxml)
return failure_count
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,93 @@
#!/usr/bin/python3
from __future__ import print_function
import argparse
import json
import sys
from pathlib import Path
import yaml
def parse_args(cmdln_args):
parser = argparse.ArgumentParser(description="Parse UI test logs an results")
parser.add_argument(
"--output-md",
type=argparse.FileType("w", encoding="utf-8"),
help="Output markdown file.",
required=True,
)
parser.add_argument(
"--log",
type=argparse.FileType("r", encoding="utf-8"),
help="Log output of flank.",
required=True,
)
parser.add_argument(
"--results", type=Path, help="Directory containing flank results", required=True
)
parser.add_argument(
"--exit-code", type=int, help="Exit code of flank.", required=True
)
parser.add_argument("--device-type", help="Type of device ", required=True)
parser.add_argument(
"--report-treeherder-failures",
help="Report failures in treeherder format.",
required=False,
action="store_true",
)
return parser.parse_args(args=cmdln_args)
def extract_android_args(log):
return yaml.safe_load(log.split("AndroidArgs\n")[1].split("RunTests\n")[0])
def main():
args = parse_args(sys.argv[1:])
log = args.log.read()
matrix_ids = json.loads(args.results.joinpath("matrix_ids.json").read_text())
# with args.results.joinpath("flank.yml") as f:
# flank_config = yaml.safe_load(f)
android_args = extract_android_args(log)
print = args.output_md.write
print("# Devices\n")
print(yaml.safe_dump(android_args["gcloud"]["device"]))
print("# Results\n")
print("| matrix | result | logs | details \n")
print("| --- | --- | --- | --- |\n")
for matrix, matrix_result in matrix_ids.items():
print(
"| {matrixId} | {outcome} | [logs]({webLink}) | {axes[0][details]}\n".format(
**matrix_result
)
)
if (
args.report_treeherder_failures
and matrix_result["outcome"] != "success"
and matrix_result["outcome"] != "flaky"
):
# write failures to test log in format known to treeherder logviewer
sys.stdout.write(
f"TEST-UNEXPECTED-FAIL | {matrix_result['outcome']} | {matrix_result['webLink']} | {matrix_result['axes'][0]['details']}\n"
)
print("---\n")
print("# References & Documentation\n")
print(
"* [Automated UI Testing Documentation](https://github.com/mozilla-mobile/shared-docs/blob/main/android/ui-testing.md)\n"
)
print(
"* Mobile Test Engineering on [Confluence](https://mozilla-hub.atlassian.net/wiki/spaces/MTE/overview) | [Slack](https://mozilla.slack.com/archives/C02KDDS9QM9) | [Alerts](https://mozilla.slack.com/archives/C0134KJ4JHL)\n"
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# This script does the following:
# 1. Retrieves glcoud service account token
# 2. Activates gcloud service account
# 3. Connects to google Firebase (using TestArmada's Flank tool)
# 4. Executes UI tests
# 5. Puts test artifacts into the test_artifacts folder
# NOTE:
# Flank supports sharding across multiple devices at a time, but gcloud API
# only supports 1 defined APK per test run.
# If a command fails then do not proceed and fail this script too.
set -e
#########################
# The command line help #
#########################
display_help() {
echo "Usage: $0 Component_Name Build_Variant [Number_Shards...]"
echo
echo "Examples:"
echo "To run component/browser tests on ARM device shard (1 test / shard)"
echo "$ execute-firebase-test.sh component arm"
echo
echo "To run component/feature tests on X86 device (on 3 shards)"
echo "$ execute-firebase-test.sh feature x86 3"
echo
echo "To run UI samples/sampleName tests"
echo "$ execute-firebase-test.sh sample-sampleName arm 1"
echo
}
# Basic parameter check
if [[ $# -lt 2 ]]; then
echo "Your command line contains $# arguments"
display_help
exit 1
fi
component="$1" # browser, concept, feature
device_type="$2" # arm | x86
if [[ ! -z "$3" ]]; then
num_shards=$3
fi
JAVA_BIN="/usr/bin/java"
PATH_TEST="./automation/taskcluster/androidTest"
FLANK_BIN="/builds/worker/test-tools/flank.jar"
FLANK_CONF_ARM="${PATH_TEST}/flank-arm.yml"
FLANK_CONF_X86="${PATH_TEST}/flank-x86.yml"
ARTIFACT_DIR="/builds/worker/artifacts"
RESULTS_DIR="${ARTIFACT_DIR}/results"
echo
echo "ACTIVATE SERVICE ACCT"
echo
# this is where the Google Testcloud project ID is set
gcloud config set project "$GOOGLE_PROJECT"
echo
gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
echo
echo
# From now on disable exiting on error. If the tests fail we want to continue
# and try to download the artifacts. We will exit with the actual error code later.
set +e
if [[ "${device_type,,}" == "x86" ]]
then
flank_template="$FLANK_CONF_X86"
else
flank_template="$FLANK_CONF_ARM"
fi
# Remove samples- from the component for each APK path
samples=${component//samples-}
# If tests are for components, the path is different than for samples
if [[ "${component}" != samples-* ]]
then
# Case 1: tests for any component (but NOT samples, NOT real UI tests)
APK_APP="./samples/browser/build/outputs/apk/gecko/debug/samples-browser-gecko-debug.apk"
if [[ "${component}" == *"-"* ]]
then
regex='([a-z]*)-(.*)'
[[ "$component" =~ $regex ]]
APK_TEST="./components/${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/build/outputs/apk/androidTest/debug/${component}-debug-androidTest.apk"
else
APK_TEST="./components/${component}/engine-gecko/build/outputs/apk/androidTest/debug/browser-engine-gecko-debug-androidTest.apk"
fi
elif [[ "${component}" == "samples-browser" ]]
then
# Case 2: tests for browser sample (gecko sample only)
APK_APP="./samples/${samples}/build/outputs/apk/gecko/debug/samples-${samples}-gecko-debug.apk"
APK_TEST="./samples/${samples}/build/outputs/apk/androidTest/gecko/debug/samples-{$samples}-gecko-debug-androidTest.apk"
else
# Case 3: tests for non-browser samples (i.e. samples-glean)
APK_APP="./samples/${samples}/build/outputs/apk/debug/samples-${samples}-debug.apk"
APK_TEST="./samples/${samples}/build/outputs/apk/androidTest/debug/samples-${samples}-debug-androidTest.apk"
fi
# function to exit script with exit code from test run.
# (Only 0 if all test executions passed)
function failure_check() {
echo
echo
if [[ $exitcode -ne 0 ]]; then
echo "ERROR: UI test run failed, please check above URL"
else
echo "All UI test(s) have passed!"
fi
echo
echo "RESULTS"
echo
mkdir -p ${ARTIFACT_DIR}/github
chmod +x ${PATH_TEST}/parse-ui-test.py
chmod +x ${PATH_TEST}/parse-ui-test-fromfile.py
${PATH_TEST}/parse-ui-test-fromfile.py \
--results "${RESULTS_DIR}"
if [[ $? -ne 0 ]]; then
${PATH_TEST}/parse-ui-test.py \
--exit-code "${exitcode}" \
--log flank.log \
--results "${RESULTS_DIR}" \
--output-md "${ARTIFACT_DIR}/github/customCheckRunText.md" \
--device-type "${device_type}"
else
${PATH_TEST}/parse-ui-test.py \
--exit-code "${exitcode}" \
--log flank.log \
--results "${RESULTS_DIR}" \
--output-md "${ARTIFACT_DIR}/github/customCheckRunText.md" \
--device-type "${device_type}" \
--report-treeherder-failures
fi
echo
echo
}
echo
echo "EXECUTE TEST(S)"
echo
set -o pipefail && $JAVA_BIN -jar $FLANK_BIN android run \
--config=$flank_template \
--max-test-shards=$num_shards \
--app=$APK_APP --test=$APK_TEST \
--local-result-dir="${RESULTS_DIR}" \
--project=$GOOGLE_PROJECT \
--client-details=commit=${MOBILE_HEAD_REV:-None},pullRequest=${PULL_REQUEST_NUMBER:-None} \
| tee flank.log
exitcode=$?
failure_check
exit $exitcode

View File

@ -0,0 +1,356 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
import org.gradle.internal.logging.text.StyledTextOutput.Style
import org.gradle.internal.logging.text.StyledTextOutputFactory
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static org.gradle.api.tasks.testing.TestResult.ResultType
buildscript {
repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven {
url repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true
}
}
}
}
dependencies {
classpath ComponentsDependencies.tools_androidgradle
classpath ComponentsDependencies.tools_kotlingradle
}
// Variables in plugins {} aren't directly supported. Hack around it by setting an
// intermediate variable which can pull from FenixDependencies.kt and be used later.
ext {
detekt_plugin = Versions.detekt
python_envs_plugin = Versions.python_envs_plugin
ksp_plugin = Versions.ksp_plugin
}
}
plugins {
id("io.gitlab.arturbosch.detekt").version("$detekt_plugin")
id("com.google.devtools.ksp").version("$ksp_plugin")
}
allprojects {
repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven {
url repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true
}
}
}
maven {
url "${gradle.mozconfig.topobjdir}/gradle/maven"
}
}
}
subprojects {
apply plugin: 'jacoco'
// Enable Kotlin warnings as errors for all modules
tasks.withType(KotlinCompile).configureEach {
kotlinOptions.allWarningsAsErrors = true
}
project.configurations.configureEach {
// Dependencies can't depend on a different major version of Glean than A-C itself.
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.mozilla.telemetry'
&& details.requested.name.contains('glean') ) {
def requested = details.requested.version.tokenize(".")
def defined = Versions.mozilla_glean.tokenize(".")
// Check the major version
if (requested[0] != defined[0]) {
throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${Versions.mozilla_glean}")
} else {
// Enforce that all (transitive) dependencies are using the defined Glean version
details.useVersion Versions.mozilla_glean
}
}
}
resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') }
if (toBeSelected != null) {
select(toBeSelected)
}
because 'use GeckoView Glean instead of standalone Glean'
}
}
if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) {
if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) {
ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir"
}
ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir"
apply from: "${topsrcdir}/substitute-local-geckoview.gradle"
}
afterEvaluate {
if (it.hasProperty('android')) {
jacoco {
toolVersion = Versions.jacoco
}
// Format test output
tasks.matching {it instanceof Test}.configureEach() {
systemProperty "robolectric.logging", "stdout"
systemProperty "logging.test-mode", "true"
systemProperty "javax.net.ssl.trustStoreType", "JKS"
testLogging.events = []
def out = services.get(StyledTextOutputFactory).create("an-ouput")
beforeSuite { descriptor ->
if (descriptor.getClassName() != null) {
out.style(Style.Header).println("\nSUITE: " + descriptor.getClassName())
}
}
beforeTest { descriptor ->
out.style(Style.Description).println(" TEST: " + descriptor.getName())
}
onOutput { descriptor, event ->
logger.lifecycle(" " + event.message.trim())
}
afterTest { descriptor, result ->
switch (result.getResultType()) {
case ResultType.SUCCESS:
out.style(Style.Success).println(" SUCCESS")
break
case ResultType.FAILURE:
def testId = descriptor.getClassName() + "." + descriptor.getName()
out.style(Style.Failure).println(" TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException())
break
case ResultType.SKIPPED:
out.style(Style.Info).println(" SKIPPED")
break
}
logger.lifecycle("")
}
}
dependencies {
lintChecks project(':tooling-lint')
}
kotlin {
jvmToolchain(config.jvmTargetCompatibility)
}
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
packagingOptions {
resources {
excludes += ['META-INF/atomicfu.kotlin_module', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
// Required dependencies using byte-buddy; remove after this is
// fixed by: https://issuetracker.google.com/issues/170131605
excludes.add("META-INF/licenses/ASM")
pickFirsts += ['win32-x86-64/attach_hotspot_windows.dll', 'win32-x86/attach_hotspot_windows.dll']
}
}
androidResources {
ignoreAssetsPattern "manifest.template.json"
}
tasks.withType(KotlinCompile).configureEach {
kotlinOptions.freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
}
}
if (project.hasProperty("coverage") && project.name != "support-test") {
android.buildTypes.all { buildType ->
tasks.withType(Test).configureEach() {
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*']
}
finalizedBy { "jacoco${buildType.name.capitalize()}TestReport" }
}
tasks.register("jacoco${buildType.name.capitalize()}TestReport", JacocoReport) {
reports {
xml.required = true
html.required = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${buildType.name}", excludes: fileFilter)
def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${buildType.name}", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories.setFrom(files([mainSrc]))
classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree]))
getExecutionData().setFrom(fileTree(project.layout.buildDirectory).include([
"jacoco/test${buildType.name.capitalize()}UnitTest.exec"
]))
}
}
android {
buildTypes {
debug {
testCoverageEnabled true
}
}
}
}
}
}
tasks.withType(KotlinCompile).configureEach {
// Translate Kotlin messages like "w: ..." and "e: ..." into
// "...: warning: ..." and "...: error: ...", to make Treeherder understand.
def listener = {
if (it.startsWith("e: warnings found")) {
return
}
if (it.startsWith('w: ') || it.startsWith('e: ')) {
def matches = (it =~ /([ew]): (.+):(\d+):(\d+) (.*)/)
if (!matches) {
logger.quiet "kotlinc message format has changed!"
if (it.startsWith('w: ')) {
// For warnings, don't continue because we don't want to throw an
// exception. For errors, we want the exception so that the new error
// message format gets translated properly.
return
}
}
def (_, type, file, line, column, message) = matches[0]
type = (type == 'w') ? 'warning' : 'error'
// Use logger.lifecycle, which does not go through stderr again.
logger.lifecycle "$file:$line:$column: $type: $message"
}
} as StandardOutputListener
doFirst {
logging.addStandardErrorListener(listener)
}
doLast {
logging.removeStandardErrorListener(listener)
}
}
}
if (findProject(":geckoview") == null) {
// Avoid adding this task if it already exists in a different root project.
tasks.register("clean", Delete) {
delete rootProject.layout.buildDirectory
}
}
detekt {
input = files("$projectDir/components", "$projectDir/buildSrc", "$projectDir/samples")
config = files("$projectDir/config/detekt.yml")
baseline = file("$projectDir/config/detekt-baseline.xml")
reports {
html {
enabled = true
destination = file("$projectDir/build/reports/detekt.html")
}
xml {
enabled = false
}
txt {
enabled = false
}
}
}
tasks.withType(Detekt).configureEach() {
// Custom detekt rules should be build before
// See https://arturbosch.github.io/detekt/extensions.html#pitfalls
dependsOn(":tooling-detekt:assemble")
autoCorrect = true
exclude "**/build.gradle.kts"
exclude "**/src/androidTest/**"
exclude "**/src/iosTest/**"
exclude "**/src/test/**"
exclude "**/test/src/**"
exclude "**/build/**"
exclude "**/resources/**"
exclude "**/tmp/**"
exclude "**/tooling/fetch/tests/**"
exclude "**/tooling/fetch-tests/**"
exclude "**/src/main/assets/extensions/**"
exclude "**/docs/**"
}
// Apply same path exclusions as for the main task
tasks.withType(DetektCreateBaselineTask).configureEach() {
exclude "**/src/androidTest/**"
exclude "**/src/test/**"
exclude "**/test/src/**"
exclude "**/build/**"
exclude "**/resources/**"
exclude "**/tmp/**"
exclude "**/tooling/fetch/tests/**"
exclude "**/tooling/fetch-tests/**"
}
configurations {
ktlint
}
dependencies {
ktlint("com.pinterest:ktlint:${Versions.ktlint}") {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
}
}
detektPlugins project(":tooling-detekt")
}
tasks.register("ktlint", JavaExec) {
group = "verification"
description = "Check Kotlin code style."
classpath = configurations.ktlint
mainClass.set("com.pinterest.ktlint.Main")
args "components/**/*.kt" , "samples/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt", "--baseline=ktlint-baseline.xml",
"--reporter=json,output=build/reports/ktlint/ktlint.json", "--reporter=plain"
}
tasks.register("ktlintFormat", JavaExec) {
group = "formatting"
description = "Fix Kotlin code style deviations."
classpath = configurations.ktlint
mainClass.set("com.pinterest.ktlint.Main")
args "-F", "components/**/*.kt" , "samples/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt", "--baseline=ktlint-baseline.xml"
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
tasks.register("listRepositories") {
doLast {
println "Repositories:"
project.repositories.each { println "Name: " + it.name + "; url: " + it.url }
}
}

View File

@ -0,0 +1,67 @@
# [Android Components](../../../README.md) > Browser > Domains
This component provides APIs for managing localized and customizable domain lists (see [Domains](#domains) and [CustomDomains](#customdomains)). It also contains auto-complete functionality for these lists (see [DomainAutoCompleteProvider](#domainautocompleteprovider)) which can be used in conjuction with our [UI autocomplete component](../../ui/autocomplete/README.md).
## Usage
### Setting up the dependency
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
```Groovy
implementation "org.mozilla.components:browser-domains:{latest-version}"
```
### Domains
The `Domains` object is used to load the built-in localized domain lists which are shipped as part of this component. These lists are grouped by country and can be found [in our repository](src/main/assets/domains).
```Kotlin
// Load the domain lists for all countries in the default locale (fallback is US)
val domains = Domains.load(context)
```
### CustomDomains
The `CustomDomains` object can be used to manage a custom domain list which will be stored in `SharedPreferences`.
```Kotlin
// Load the custom domain list
val domains = CustomDomains.load(context)
// Save custom domains
CustomDomains.save(context, listOf("mozilla.org", "getpocket.com"))
// Remove custom domains
CustomDomains.remove(context, listOf("nolongerexists.org"))
```
### DomainAutoCompleteProvider
The class provides auto-complete functionality for both `Domains` and `CustomDomains`.
```Kotlin
// Initialize the provider
val provider = DomainAutocompleteProvider()
provider.initialize(
context,
useShippedDomains = true,
useCustomDomains = true,
loadDomainsFromDisk = true
)
```
Note that when `loadDomainsFromDisk` is set to true there is no need to manually call `load` on either `Domains` or `CustomDomains`.
```Kotlin
// Autocomplete domain lists
val result = provider.autocomplete("moz")
```
The result will contain the autocompleted text (`result.text`), the URL (`result.url`), and the source of the match (`result.source`), which is either `DEFAULT_LIST` if a result was found in the shipped domain list or `CUSTOM_LIST` otherwise. The custom domain list takes precendece over the built-in shipped domain list and the API will only return the first match.
## License
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/

View File

@ -0,0 +1,40 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
defaultConfig {
minSdkVersion config.minSdkVersion
compileSdk config.compileSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
namespace 'mozilla.components.browser.domains'
}
dependencies {
implementation project(':concept-toolbar')
implementation ComponentsDependencies.kotlin_coroutines
testImplementation project(':support-test')
testImplementation ComponentsDependencies.androidx_test_junit
testImplementation ComponentsDependencies.testing_robolectric
testImplementation ComponentsDependencies.testing_coroutines
}
apply from: '../../../android-lint.gradle'
apply from: '../../../publish.gradle'
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description)

View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,4 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<manifest />

View File

@ -0,0 +1,50 @@
google.com.br
youtube.com
google.com
facebook.com
globo.com
uol.com.br
blastingnews.com
live.com
mercadolivre.com.br
yahoo.com
blogspot.com.br
wikipedia.org
whatsapp.com
netflix.com
olx.com.br
instagram.com
msn.com
metropoles.com
fatosdesconhecidos.com.br
twitter.com
caixa.gov.br
uptodown.com
aliexpress.com
curapelanatureza.com.br
wordpress.com
abril.com.br
americanas.com.br
correios.com.br
reclameaqui.com.br
bet365.com
onclkds.com
bol.uol.com.br
techtudo.com.br
fazenda.gov.br
microsoft.com
folha.uol.com.br
linkedin.com
tumblr.com
sp.gov.br
reddit.com
bb.com.br
pinterest.com
itau.com.br
letras.mus.br
otvfoco.com.br
vagalume.com.br
portalinteressante.com
myappolicious.com.br
thewhizmarketing.com
twitch.tv

View File

@ -0,0 +1,49 @@
google.com
youtube.com
facebook.com
reddit.com
amazon.com
wikipedia.org
yahoo.com
twitter.com
netflix.com
ebay.com
imgur.com
linkedin.com
instagram.com
diply.com
craigslist.org
live.com
office.com
twitch.tv
tumblr.com
pinterest.com
espn.com
cnn.com
bing.com
wikia.com
chase.com
imdb.com
nytimes.com
paypal.com
blogspot.com
apple.com
yelp.com
stackoverflow.com
bankofamerica.com
wordpress.com
github.com
microsoft.com
wellsfargo.com
zillow.com
salesforce.com
msn.com
walmart.com
weather.com
dropbox.com
buzzfeed.com
intuit.com
washingtonpost.com
soundcloud.com
huffingtonpost.com
indeed.com

View File

@ -0,0 +1,50 @@
google.de
youtube.com
google.com
facebook.com
amazon.de
ebay.de
wikipedia.org
ebay-kleinanzeigen.de
web.de
yahoo.com
ok.ru
gmx.net
reddit.com
vk.com
t-online.de
twitter.com
spiegel.de
mail.ru
instagram.com
live.com
chip.de
bild.de
paypal.com
bing.com
twitch.tv
whatsapp.com
yandex.ru
gutefrage.net
mobile.de
google.ru
blogspot.de
tumblr.com
bs.to
focus.de
linkedin.com
netflix.com
wordpress.com
imgur.com
postbank.de
welt.de
streamcloud.eu
microsoft.com
immobilienscout24.de
msn.com
dict.cc
otto.de
xing.com
amazon.com
heise.de
github.com

View File

@ -0,0 +1,50 @@
google.fr
youtube.com
google.com
facebook.com
wikipedia.org
amazon.fr
leboncoin.fr
yahoo.com
live.com
twitter.com
orange.fr
free.fr
linkedin.com
lemonde.fr
instagram.com
reddit.com
lefigaro.fr
ebay.fr
cdiscount.com
jeuxvideo.com
zone-telechargement.ws
labanquepostale.fr
blogspot.fr
allocine.fr
msn.com
commentcamarche.net
pole-emploi.fr
vk.com
sfr.fr
lequipe.fr
twitch.tv
francetvinfo.fr
20minutes.fr
pinterest.com
netflix.com
programme-tv.net
credit-agricole.fr
linternaute.com
github.com
wordpress.com
caf.fr
aliexpress.com
dailymotion.com
tumblr.com
t411.ai
stackoverflow.com
microsoft.com
meteofrance.com
onclkds.com
bfmtv.com

View File

@ -0,0 +1,49 @@
google.co.uk
youtube.com
google.com
facebook.com
reddit.com
bbc.co.uk
amazon.co.uk
wikipedia.org
ebay.co.uk
twitter.com
live.com
yahoo.com
instagram.com
diply.com
linkedin.com
imgur.com
netflix.com
theguardian.com
dailymail.co.uk
twitch.tv
imdb.com
paypal.com
office.com
tumblr.com
www.gov.uk
wikia.com
givemesport.com
amazon.com
bing.com
wordpress.com
telegraph.co.uk
rightmove.co.uk
pinterest.com
gumtree.com
msn.com
microsoft.com
stackoverflow.com
booking.com
vk.com
tripadvisor.co.uk
lloydsbank.co.uk
apple.com
service.gov.uk
onclkds.com
github.com
independent.co.uk
bt.com
vice.com
hsbc.co.uk

View File

@ -0,0 +1,444 @@
google.com
facebook.com
amazon.com
youtube.com
yahoo.com
ebay.com
wikipedia.org
twitter.com
reddit.com
go.com
craigslist.org
live.com
netflix.com
pinterest.com
bing.com
linkedin.com
imgur.com
espn.go.com
walmart.com
tumblr.com
target.com
paypal.com
cnn.com
chase.com
instagram.com
bestbuy.com
blogspot.com
nytimes.com
msn.com
imdb.com
apple.com
bankofamerica.com
diply.com
huffingtonpost.com
yelp.com
wellsfargo.com
etsy.com
weather.com
wordpress.com
buzzfeed.com
zillow.com
kohls.com
aol.com
homedepot.com
foxnews.com
microsoft.com
comcast.net
wikia.com
groupon.com
macys.com
washingtonpost.com
outbrain.com
xfinity.com
usps.com
hulu.com
americanexpress.com
slickdeals.net
pandora.com
office.com
cnet.com
indeed.com
capitalone.com
nfl.com
ups.com
ask.com
verizonwireless.com
newegg.com
usatoday.com
forbes.com
dailymail.co.uk
dropbox.com
att.com
costco.com
gfycat.com
lowes.com
gap.com
about.com
tripadvisor.com
fedex.com
baidu.com
vice.com
nordstrom.com
adobe.com
bbc.com
twitch.tv
allrecipes.com
retailmenot.com
stackoverflow.com
citi.com
sears.com
jcpenney.com
webmd.com
nih.gov
answers.com
foodnetwork.com
discovercard.com
cbssports.com
overstock.com
businessinsider.com
office365.com
theguardian.com
staples.com
bleacherreport.com
verizon.com
github.com
wayfair.com
salesforce.com
zulily.com
wsj.com
flickr.com
goodreads.com
realtor.com
nbcnews.com
ebates.com
ancestry.com
wunderground.com
instructure.com
people.com
stackexchange.com
drudgereport.com
fidelity.com
southwest.com
deviantart.com
thesaurus.com
intuit.com
woot.com
pch.com
soundcloud.com
force.com
samsclub.com
ign.com
qvc.com
npr.org
patch.com
dell.com
accuweather.com
vimeo.com
expedia.com
trulia.com
ca.gov
swagbucks.com
spotify.com
bedbathandbeyond.com
nypost.com
aliexpress.com
blackboard.com
ticketmaster.com
ikea.com
feedly.com
usaa.com
tmz.com
quora.com
lifehacker.com
kayak.com
reference.com
zappos.com
gizmodo.com
slate.com
faithtap.com
adp.com
abcnews.go.com
sephora.com
cbs.com
latimes.com
shutterfly.com
t-mobile.com
littlethings.com
glassdoor.com
bloomberg.com
cbsnews.com
wikihow.com
walgreens.com
usbank.com
blogger.com
weebly.com
gamestop.com
food.com
time.com
kickstarter.com
okcupid.com
aa.com
weather.gov
nametests.com
fandango.com
engadget.com
steamcommunity.com
thekitchn.com
nba.com
mashable.com
hp.com
gamefaqs.com
delta.com
coupons.com
eonline.com
surveymonkey.com
kmart.com
barnesandnoble.com
meetup.com
bhphotovideo.com
fanduel.com
quizlet.com
nydailynews.com
sbnation.com
nbcsports.com
bbc.co.uk
ew.com
nike.com
rottentomatoes.com
steampowered.com
reuters.com
qq.com
today.com
mapquest.com
audible.com
priceline.com
whitepages.com
united.com
myfitnesspal.com
icloud.com
forever21.com
theatlantic.com
microsoftstore.com
theverge.com
gawker.com
houzz.com
mayoclinic.org
rei.com
sfgate.com
lifebuzz.com
discover.com
pnc.com
pof.com
iflscience.com
popsugar.com
creditkarma.com
telegraph.co.uk
airbnb.com
buzzlie.com
cnbc.com
deadspin.com
sina.com.cn
legacy.com
thedailybeast.com
samsung.com
nextdoor.com
evite.com
shopify.com
yellowpages.com
pcmag.com
redfin.com
weibo.com
alibaba.com
cabelas.com
battle.net
foxsports.com
taobao.com
eventbrite.com
victoriassecret.com
theblaze.com
dealnews.com
cbslocal.com
cvs.com
dailymotion.com
ecollege.com
gofundme.com
fitbit.com
instructables.com
godaddy.com
babycenter.com
squarespace.com
llbean.com
dickssportinggoods.com
6pm.com
myway.com
hsn.com
wired.com
officedepot.com
ozztube.com
usmagazine.com
match.com
cracked.com
evernote.com
box.com
starbucks.com
kbb.com
mlb.com
marriott.com
si.com
jezebel.com
pbs.org
consumerreports.org
roblox.com
urbandictionary.com
kotaku.com
xbox.com
marketwatch.com
refinery29.com
wikimedia.org
tvguide.com
politico.com
barclaycardus.com
abc.go.com
mint.com
topix.com
theblackfriday.com
aarp.org
hotnewhiphop.com
yourdailydish.com
sprint.com
vox.com
cafemom.com
nbc.com
dailykos.com
azlyrics.com
autotrader.com
hilton.com
irs.gov
monster.com
mailchimp.com
webex.com
landsend.com
wix.com
usnews.com
jcrew.com
jet.com
capitalone360.com
sharepoint.com
schwab.com
ulta.com
vistaprint.com
rollingstone.com
biblegateway.com
gamespot.com
io9.com
opentable.com
hm.com
duckduckgo.com
chron.com
photobucket.com
shareasale.com
directv.com
avg.com
oracle.com
hotels.com
timewarnercable.com
chicagotribune.com
ehow.com
primewire.ag
abs-cbnnews.com
salon.com
greatergood.com
epicurious.com
fool.com
patheos.com
custhelp.com
purdue.edu
tickld.com
frys.com
indiatimes.com
amazon.co.uk
zendesk.com
tigerdirect.com
stubhub.com
healthcare.gov
archive.org
qualtrics.com
ravelry.com
cars.com
redbox.com
jalopnik.com
speedtest.net
harvard.edu
slideshare.net
kinja.com
nesn.com
michaels.com
mit.edu
bodybuilding.com
edmunds.com
nhl.com
zergnet.com
techcrunch.com
pogo.com
mozilla.org
naver.com
giphy.com
bankrate.com
msnbc.com
digitaltrends.com
fanfiction.net
skype.com
disney.go.com
norton.com
androidcentral.com
tomshardware.com
thefreedictionary.com
liveleak.com
247sports.com
merriam-webster.com
wnd.com
earthlink.net
independent.co.uk
drugs.com
rotoworld.com
nationalgeographic.com
ae.com
noaa.gov
arstechnica.com
thinkgeek.com
stanford.edu
bizjournals.com
hootsuite.com
genius.com
goodhousekeeping.com
vanguard.com
ny.gov
citibankonline.com
booking.com
mic.com
orbitz.com
dominos.com
medium.com
wow.com
urbanoutfitters.com
douban.com
timeanddate.com
draftkings.com
livestrong.com
livingsocial.com
cox.net
theonion.com
marthastewart.com
comenity.net
worldlifestyle.com
disney.com
realsimple.com
vrbo.com
playstation.com
potterybarn.com
zazzle.com
ksl.com
tdbank.com
sourceforge.net
careerbuilder.com

View File

@ -0,0 +1,50 @@
google.com.hk
youtube.com
google.com
facebook.com
yahoo.com
discuss.com.hk
aastocks.com
wikipedia.org
baidu.com
taobao.com
pixnet.net
bastillepost.com
nextmedia.com
whatsapp.com
instagram.com
price.com.hk
ettoday.net
qq.com
hsbc.com.hk
tmall.com
live.com
hkgolden.com
reddit.com
beautyexchange.com.hk
etnet.com.hk
on.cc
amazon.com
twitter.com
uwants.com
presslogic.com
unwire.hk
gamer.com.tw
hangseng.com
hk01.com
twitch.tv
linkedin.com
teepr.com
hkjc.com
apple.com
bomb01.com
sina.com.cn
weibo.com
dcfever.com
thestandnews.com
office.com
openrice.com
tumblr.com
tvb.com
alipay.com
stackoverflow.com

View File

@ -0,0 +1,50 @@
google.com
google.co.id
youtube.com
detik.com
tribunnews.com
facebook.com
yahoo.com
tokopedia.com
liputan6.com
kompas.com
bukalapak.com
kaskus.co.id
kapanlagi.com
wordpress.com
merdeka.com
okezone.com
elevenia.co.id
lazada.co.id
uzone.id
bintang.com
brilio.net
popads.net
instagram.com
bola.net
wikipedia.org
blogspot.com
onclkds.com
dream.co.id
viva.co.id
alodokter.com
tempo.co
suara.com
wowkeren.com
idntimes.com
bola.com
sindonews.com
republika.co.id
kompasiana.com
vemale.com
blanja.com
cnnindonesia.com
olx.co.id
lk21.org
popcash.net
blibli.com
poptm.com
nonton.movie
indexmovie.me
adexchangeprediction.com
subscene.com

View File

@ -0,0 +1,50 @@
google.pl
youtube.com
facebook.com
google.com
allegro.pl
onet.pl
wp.pl
wikipedia.org
olx.pl
vk.com
interia.pl
wykop.pl
gazeta.pl
filmweb.pl
instagram.com
wiocha.pl
cda.pl
aliexpress.com
otomoto.pl
mbank.pl
reddit.com
ceneo.pl
tvn24.pl
twitter.com
gumtree.pl
blogspot.com
kwejk.pl
wyborcza.pl
joemonster.org
stackoverflow.com
twitch.tv
o2.pl
ipko.pl
steamcommunity.com
github.com
chomikuj.pl
centrum24.pl
linkedin.com
money.pl
librus.pl
demotywatory.pl
sport.pl
microsoft.com
zalukaj.com
wikia.com
jbzdy.pl
imgur.com
flashscore.pl
gry-online.pl
pudelek.pl

View File

@ -0,0 +1,50 @@
vk.com
google.ru
yandex.ru
youtube.com
mail.ru
ok.ru
google.com
avito.ru
aliexpress.com
wikipedia.org
instagram.com
sberbank.ru
gismeteo.ru
rambler.ru
kinogo.club
kinopoisk.ru
drom.ru
facebook.com
pikabu.ru
drive2.ru
rutracker.org
twitch.tv
rbc.ru
hh.ru
gosuslugi.ru
lenta.ru
pochta.ru
wildberries.ru
wikia.com
4pda.ru
fb.ru
seasonvar.ru
kp.ru
znanija.com
ucoz.ru
narod.ru
mts.ru
infourok.ru
ebay.com
ozon.ru
worldoftanks.ru
mos.ru
vesti.ru
nnmclub.to
microsoft.com
rp5.ru
2gis.ru
consultant.ru
fotostrana.ru
dnevnik.ru

View File

@ -0,0 +1,49 @@
google.com.sg
youtube.com
google.com
facebook.com
yahoo.com
wikipedia.org
reddit.com
blogspot.sg
live.com
instagram.com
qoo10.sg
whatsapp.com
linkedin.com
dbs.com.sg
amazon.com
twitter.com
wordpress.com
onclkds.com
office.com
allsingaporestuff.com
baidu.com
lazada.sg
straitstimes.com
singpass.gov.sg
google.co.id
taobao.com
tumblr.com
gomovies.to
wikia.com
hardwarezone.com.sg
nus.edu.sg
msn.com
microsoft.com
carousell.com
kissanime.ru
ocbc.com
stackoverflow.com
ntu.edu.sg
thepiratebay.org
aliexpress.com
imgur.com
dropbox.com
apple.com
channelnewsasia.com
imdb.com
twitch.tv
abs-cbn.com
jobstreet.com.sg
uob.com.sg

View File

@ -0,0 +1,50 @@
google.com.tw
pixnet.net
youtube.com
facebook.com
ettoday.net
google.com
yahoo.com
ltn.com.tw
nownews.com
setn.com
momoshop.com.tw
wikipedia.org
ck101.com
ptt.cc
tvbs.com.tw
104.com.tw
gamer.com.tw
appledaily.com.tw
pchome.com.tw
ruten.com.tw
ctitv.com.tw
teepr.com
life.tw
blogspot.tw
dcard.tw
baidu.com
udn.com
mobile01.com
eyny.com
epochtimes.com
qoolquiz.com
bomb01.com
talk.tw
ipetgroup.com
storm.mg
123kubo.com
cmoney.tw
taobao.com
twitch.tv
instagram.com
xuite.net
sina.com.tw
1111.com.tw
businessweekly.com.tw
elle.com.tw
twitter.com
books.com.tw
591.com.tw
everydayhealth.com.tw
techbang.com

View File

@ -0,0 +1,49 @@
google.com
youtube.com
facebook.com
reddit.com
amazon.com
wikipedia.org
yahoo.com
twitter.com
netflix.com
ebay.com
imgur.com
linkedin.com
instagram.com
diply.com
craigslist.org
live.com
office.com
twitch.tv
tumblr.com
pinterest.com
espn.com
cnn.com
bing.com
wikia.com
chase.com
imdb.com
nytimes.com
paypal.com
blogspot.com
apple.com
yelp.com
stackoverflow.com
bankofamerica.com
wordpress.com
github.com
microsoft.com
wellsfargo.com
zillow.com
salesforce.com
msn.com
walmart.com
weather.com
dropbox.com
buzzfeed.com
intuit.com
washingtonpost.com
soundcloud.com
huffingtonpost.com
indeed.com

View File

@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import android.content.Context
import android.content.SharedPreferences
/**
* Contains functionality to manage custom domains for auto-completion.
*/
object CustomDomains {
private const val PREFERENCE_NAME = "custom_autocomplete"
private const val KEY_DOMAINS = "custom_domains"
private const val SEPARATOR = "@<;>@"
/**
* Loads the previously added/saved custom domains from preferences.
*
* @param context the application context
* @return list of custom domains
*/
fun load(context: Context): List<String> =
preferences(context).getString(KEY_DOMAINS, "")!!
.split(SEPARATOR)
.filter { !it.isEmpty() }
/**
* Saves the provided domains to preferences.
*
* @param context the application context
* @param domains list of domains
*/
fun save(context: Context, domains: List<String>) {
preferences(context)
.edit()
.putString(KEY_DOMAINS, domains.joinToString(separator = SEPARATOR))
.apply()
}
/**
* Adds the provided domain to preferences.
*
* @param context the application context
* @param domain the domain to add
*/
fun add(context: Context, domain: String) {
val domains = mutableListOf<String>()
domains.addAll(load(context))
domains.add(domain)
save(context, domains)
}
/**
* Removes the provided domain from preferences.
*
* @param context the application context
* @param domains the domain to remove
*/
fun remove(context: Context, domains: List<String>) {
save(context, load(context) - domains)
}
private fun preferences(context: Context): SharedPreferences =
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
}

View File

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
/**
* Class intended for internal use which encapsulates meta data about a domain.
*/
data class Domain(val protocol: String, val hasWww: Boolean, val host: String) {
internal val url: String
get() = "$protocol${if (hasWww) "www." else "" }$host"
companion object {
private const val PROTOCOL_INDEX = 1
private const val WWW_INDEX = 2
private const val HOST_INDEX = 3
private const val DEFAULT_PROTOCOL = "http://"
private val urlMatcher = Regex("""(https?://)?(www.)?(.+)?""")
fun create(url: String): Domain {
val result = urlMatcher.find(url)
return result?.let {
val protocol = it.groups[PROTOCOL_INDEX]?.value ?: DEFAULT_PROTOCOL
val hasWww = it.groups[WWW_INDEX]?.value == "www."
val host = it.groups[HOST_INDEX]?.value ?: throw IllegalStateException()
return Domain(protocol, hasWww, host)
} ?: throw IllegalStateException()
}
}
}
internal fun Iterable<String>.into(): List<Domain> {
return this.map { Domain.create(it) }
}

View File

@ -0,0 +1,151 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.util.Locale
/**
* Provides autocomplete functionality for domains, based on a provided list
* of assets (see [Domains]) and/or a custom domain list managed by
* [CustomDomains].
*/
// FIXME delete this https://github.com/mozilla-mobile/android-components/issues/1358
@Deprecated(
"Use `ShippedDomainsProvider` or `CustomDomainsProvider`",
ReplaceWith(
"ShippedDomainsProvider()/CustomDomainsProvider()",
"mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider",
"mozilla.components.browser.domains.autocomplete.CustomDomainsProvider",
),
)
class DomainAutoCompleteProvider {
object AutocompleteSource {
const val DEFAULT_LIST = "default"
const val CUSTOM_LIST = "custom"
}
/**
* Represents a result of auto-completion.
*
* @property text the result text starting with the raw search text as passed
* to [autocomplete] followed by the completion text (e.g. moz => mozilla.org)
* @property url the complete url (containing the protocol) as provided
* when the domain was saved. (e.g. https://mozilla.org)
* @property source the source identifier of the autocomplete source
* @property size total number of available autocomplete domains
* in this source
*/
data class Result(val text: String, val url: String, val source: String, val size: Int)
// We compute these on worker threads; make sure results are immediately visible on the UI thread.
@Volatile
internal var customDomains = emptyList<Domain>()
@Volatile
internal var shippedDomains = emptyList<Domain>()
private var useCustomDomains = false
private var useShippedDomains = true
/**
* Computes an autocomplete suggestion for the given text, and invokes the
* provided callback, passing the result.
*
* @param rawText text to be auto completed
* @return the result of auto-completion. If no match is found an empty
* result object is returned.
*/
@Suppress("ReturnCount")
fun autocomplete(rawText: String): Result {
if (useCustomDomains) {
val result = tryToAutocomplete(rawText, customDomains, AutocompleteSource.CUSTOM_LIST)
if (result != null) {
return result
}
}
if (useShippedDomains) {
val result = tryToAutocomplete(rawText, shippedDomains, AutocompleteSource.DEFAULT_LIST)
if (result != null) {
return result
}
}
return Result("", "", "", 0)
}
/**
* Initializes this provider instance by making sure the shipped and/or custom
* domains are loaded.
*
* @param context the application context
* @param useShippedDomains true (default) if the domains provided by this
* module should be used, otherwise false.
* @param useCustomDomains true if the custom domains provided by
* [CustomDomains] should be used, otherwise false (default).
* @param loadDomainsFromDisk true (default) if domains should be loaded,
* otherwise false. This parameter is for testing purposes only.
*/
fun initialize(
context: Context,
useShippedDomains: Boolean = true,
useCustomDomains: Boolean = false,
loadDomainsFromDisk: Boolean = true,
) {
this.useCustomDomains = useCustomDomains
this.useShippedDomains = useShippedDomains
if (!loadDomainsFromDisk) {
return
}
if (!useCustomDomains && !useShippedDomains) {
return
}
CoroutineScope(Dispatchers.IO).launch {
if (useCustomDomains) {
customDomains = async { CustomDomains.load(context).into() }.await()
}
if (useShippedDomains) {
shippedDomains = async { Domains.load(context).into() }.await()
}
}
}
@Suppress("ReturnCount")
private fun tryToAutocomplete(rawText: String, domains: List<Domain>, source: String): Result? {
// Search terms are all lowercase already, we just need to lowercase the search text
val searchText = rawText.lowercase(Locale.US)
domains.forEach {
val wwwDomain = "www.${it.host}"
if (wwwDomain.startsWith(searchText)) {
return Result(getResultText(rawText, wwwDomain), it.url, source, domains.size)
}
if (it.host.startsWith(searchText)) {
return Result(getResultText(rawText, it.host), it.url, source, domains.size)
}
}
return null
}
/**
* Our autocomplete list is all lower case, however the search text might
* be mixed case. Our autocomplete EditText code does more string comparison,
* which fails if the suggestion doesn't exactly match searchText (ie.
* if casing differs). It's simplest to just build a suggestion
* that exactly matches the search text - which is what this method is for:
*/
private fun getResultText(rawSearchText: String, autocomplete: String) =
rawSearchText + autocomplete.substring(rawSearchText.length)
}

View File

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import android.os.LocaleList
import android.text.TextUtils
import java.io.IOException
import java.util.Locale
/**
* Contains functionality to access domain lists shipped as part of this
* module's assets.
*/
object Domains {
/**
* Loads the domains applicable to the app's locale, plus the domains
* in the 'global' list.
*
* @param context the application context
* @return list of domains
*/
fun load(context: Context): List<String> {
return load(context, getCountriesInDefaultLocaleList())
}
internal fun load(context: Context, countries: Set<String>): List<String> {
val domains = LinkedHashSet<String>()
val availableLists = getAvailableDomainLists(context)
// First initialize the country specific lists following the default locale order
countries
.filter { availableLists.contains(it) }
.forEach { loadDomainsForLanguage(context, domains, it) }
// And then add domains from the global list
loadDomainsForLanguage(context, domains, "global")
return domains.toList()
}
private fun getAvailableDomainLists(context: Context): Set<String> {
val availableDomains = LinkedHashSet<String>()
val assetManager = context.assets
val domains = try {
assetManager.list("domains") ?: emptyArray<String>()
} catch (e: IOException) {
emptyArray<String>()
}
availableDomains.addAll(domains)
return availableDomains
}
private fun loadDomainsForLanguage(context: Context, domains: MutableSet<String>, country: String) {
val assetManager = context.assets
val languageDomains = try {
assetManager.open("domains/$country").bufferedReader().readLines()
} catch (e: IOException) {
emptyList<String>()
}
domains.addAll(languageDomains)
}
private fun getCountriesInDefaultLocaleList(): Set<String> {
val countries = java.util.LinkedHashSet<String>()
val addIfNotEmpty = { c: String -> if (!TextUtils.isEmpty(c)) countries.add(c.lowercase(Locale.US)) }
if (SDK_INT >= VERSION_CODES.N) {
val list = LocaleList.getDefault()
for (i in 0 until list.size()) {
addIfNotEmpty(list.get(i).country)
}
} else {
addIfNotEmpty(Locale.getDefault().country)
}
return countries
}
}

View File

@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains.autocomplete
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import mozilla.components.browser.domains.CustomDomains
import mozilla.components.browser.domains.Domain
import mozilla.components.browser.domains.Domains
import mozilla.components.browser.domains.into
import mozilla.components.concept.toolbar.AutocompleteProvider
import mozilla.components.concept.toolbar.AutocompleteResult
import java.util.Locale
enum class DomainList(val listName: String) {
DEFAULT("default"),
CUSTOM("custom"),
}
/**
* Provides autocomplete functionality for domains based on provided list of assets (see [Domains]).
*/
class ShippedDomainsProvider(override val autocompletePriority: Int = 0) :
BaseDomainAutocompleteProvider(DomainList.DEFAULT, Domains.asLoader())
/**
* Provides autocomplete functionality for domains based on a list managed by [CustomDomains].
*/
class CustomDomainsProvider(override val autocompletePriority: Int = 0) :
BaseDomainAutocompleteProvider(DomainList.CUSTOM, CustomDomains.asLoader())
typealias DomainsLoader = (Context) -> List<Domain>
private fun Domains.asLoader(): DomainsLoader = { context: Context -> load(context).into() }
private fun CustomDomains.asLoader(): DomainsLoader = { context: Context -> load(context).into() }
/**
* Provides common autocomplete functionality powered by domain lists.
*
* @param list source of domains
* @param domainsLoader provider for all available domains
*/
open class BaseDomainAutocompleteProvider(
private val list: DomainList,
private val domainsLoader: DomainsLoader,
override val autocompletePriority: Int = 0,
) : AutocompleteProvider, CoroutineScope by CoroutineScope(Dispatchers.IO) {
// We compute 'domains' on the worker thread; make sure it's immediately visible on the UI thread.
@Volatile
var domains: List<Domain> = emptyList()
fun initialize(context: Context) {
launch {
domains = async { domainsLoader(context) }.await()
}
}
/**
* Computes an autocomplete suggestion for the given text, and invokes the
* provided callback, passing the result.
*
* @param query text to be auto completed
* @return the result of auto-completion, or null if no match is found.
*/
override suspend fun getAutocompleteSuggestion(query: String): AutocompleteResult? {
// Search terms are all lowercase already, we just need to lowercase the search text
val searchText = query.lowercase(Locale.US)
domains.forEach {
val wwwDomain = "www.${it.host}"
if (wwwDomain.startsWith(searchText)) {
return AutocompleteResult(
input = searchText,
text = getResultText(query, wwwDomain),
url = it.url,
source = list.listName,
totalItems = domains.size,
)
}
if (it.host.startsWith(searchText)) {
return AutocompleteResult(
input = searchText,
text = getResultText(query, it.host),
url = it.url,
source = list.listName,
totalItems = domains.size,
)
}
}
return null
}
/**
* Our autocomplete list is all lower case, however the search text might
* be mixed case. Our autocomplete EditText code does more string comparison,
* which fails if the suggestion doesn't exactly match searchText (ie.
* if casing differs). It's simplest to just build a suggestion
* that exactly matches the search text - which is what this method is for:
*/
private fun getResultText(rawSearchText: String, autocomplete: String) =
rawSearchText + autocomplete.substring(rawSearchText.length)
}

View File

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import android.content.Context
import android.os.Looper.getMainLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider
import mozilla.components.browser.domains.autocomplete.DomainList
import mozilla.components.browser.domains.autocomplete.DomainsLoader
import mozilla.components.concept.toolbar.AutocompleteProvider
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows.shadowOf
@RunWith(AndroidJUnit4::class)
class BaseDomainAutocompleteProviderTest {
@Test
fun `empty provider with DEFAULT list returns nothing`() {
val provider = createAndInitProvider(testContext, DomainList.DEFAULT) {
emptyList()
}
assertNoCompletion(provider, "m")
assertNoCompletion(provider, "mo")
assertNoCompletion(provider, "moz")
assertNoCompletion(provider, "g")
assertNoCompletion(provider, "go")
assertNoCompletion(provider, "goo")
assertNoCompletion(provider, "w")
assertNoCompletion(provider, "www")
}
@Test
fun `empty provider with CUSTOM list returns nothing`() {
val provider = createAndInitProvider(testContext, DomainList.CUSTOM) {
emptyList()
}
assertNoCompletion(provider, "m")
assertNoCompletion(provider, "mo")
assertNoCompletion(provider, "moz")
assertNoCompletion(provider, "g")
assertNoCompletion(provider, "go")
assertNoCompletion(provider, "goo")
assertNoCompletion(provider, "w")
assertNoCompletion(provider, "www")
}
@Test
fun `non-empty provider with DEFAULT list returns completion`() {
val domains = listOf("mozilla.org", "google.com", "facebook.com").into()
val list = DomainList.DEFAULT
val domainsCount = domains.size
val provider = createAndInitProvider(testContext, list) { domains }
shadowOf(getMainLooper()).idle()
assertCompletion(provider, list, domainsCount, "m", "m", "mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "moz", "moz", "mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www", "www", "www.mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www.face", "www.face", "www.facebook.com", "http://facebook.com")
assertCompletion(provider, list, domainsCount, "M", "m", "Mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "MOZ", "moz", "MOZilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www.GOO", "www.goo", "www.GOOgle.com", "http://google.com")
assertCompletion(provider, list, domainsCount, "WWW.GOOGLE.", "www.google.", "WWW.GOOGLE.com", "http://google.com")
assertCompletion(provider, list, domainsCount, "www.facebook.com", "www.facebook.com", "www.facebook.com", "http://facebook.com")
assertCompletion(provider, list, domainsCount, "facebook.com", "facebook.com", "facebook.com", "http://facebook.com")
assertNoCompletion(provider, "wwww")
assertNoCompletion(provider, "yahoo")
}
@Test
fun `non-empty provider with CUSTOM list returns completion`() {
val domains = listOf("mozilla.org", "google.com", "facebook.com").into()
val list = DomainList.CUSTOM
val domainsCount = domains.size
val provider = createAndInitProvider(testContext, list) { domains }
shadowOf(getMainLooper()).idle()
assertCompletion(provider, list, domainsCount, "m", "m", "mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "moz", "moz", "mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www", "www", "www.mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www.face", "www.face", "www.facebook.com", "http://facebook.com")
assertCompletion(provider, list, domainsCount, "M", "m", "Mozilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "MOZ", "moz", "MOZilla.org", "http://mozilla.org")
assertCompletion(provider, list, domainsCount, "www.GOO", "www.goo", "www.GOOgle.com", "http://google.com")
assertCompletion(provider, list, domainsCount, "WWW.GOOGLE.", "www.google.", "WWW.GOOGLE.com", "http://google.com")
assertCompletion(provider, list, domainsCount, "www.facebook.com", "www.facebook.com", "www.facebook.com", "http://facebook.com")
assertCompletion(provider, list, domainsCount, "facebook.com", "facebook.com", "facebook.com", "http://facebook.com")
assertNoCompletion(provider, "wwww")
assertNoCompletion(provider, "yahoo")
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun assertCompletion(
provider: AutocompleteProvider,
domainSource: DomainList,
sourceSize: Int,
input: String,
expectedInput: String,
completion: String,
expectedUrl: String,
) = runTest {
val result = provider.getAutocompleteSuggestion(input)!!
assertTrue("Autocompletion shouldn't be empty", result.text.isNotEmpty())
assertEquals("Autocompletion input", expectedInput, result.input)
assertEquals("Autocompletion completion", completion, result.text)
assertEquals("Autocompletion source list", domainSource.listName, result.source)
assertEquals("Autocompletion url", expectedUrl, result.url)
assertEquals("Autocompletion source list size", sourceSize, result.totalItems)
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun assertNoCompletion(provider: AutocompleteProvider, input: String) = runTest {
val result = provider.getAutocompleteSuggestion(input)
assertNull("Result should be null", result)
}
private fun createAndInitProvider(context: Context, list: DomainList, loader: DomainsLoader): AutocompleteProvider =
object : BaseDomainAutocompleteProvider(list, loader) {
override val coroutineContext = super.coroutineContext + Dispatchers.Main
}.apply { initialize(context) }

View File

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CustomDomainsTest {
@Before
fun setUp() {
testContext.getSharedPreferences("custom_autocomplete", Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}
@ExperimentalCoroutinesApi
@Test
fun customListIsEmptyByDefault() {
val domains = CustomDomains.load(testContext)
assertEquals(0, domains.size)
}
@Test
fun saveAndRemoveDomains() {
CustomDomains.save(
testContext,
listOf(
"mozilla.org",
"example.org",
"example.com",
),
)
var domains = CustomDomains.load(testContext)
assertEquals(3, domains.size)
CustomDomains.remove(testContext, listOf("example.org", "example.com"))
domains = CustomDomains.load(testContext)
assertEquals(1, domains.size)
assertEquals("mozilla.org", domains.elementAt(0))
}
@Test
fun addAndLoadDomains() {
CustomDomains.add(testContext, "mozilla.org")
val domains = CustomDomains.load(testContext)
assertEquals(1, domains.size)
assertEquals("mozilla.org", domains.elementAt(0))
}
@Test
fun saveAndLoadDomains() {
CustomDomains.save(
testContext,
listOf(
"mozilla.org",
"example.org",
"example.com",
),
)
val domains = CustomDomains.load(testContext)
assertEquals(3, domains.size)
assertEquals("mozilla.org", domains.elementAt(0))
assertEquals("example.org", domains.elementAt(1))
assertEquals("example.com", domains.elementAt(2))
}
}

View File

@ -0,0 +1,116 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@file:Suppress("DEPRECATION")
package mozilla.components.browser.domains
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.domains.DomainAutoCompleteProvider.AutocompleteSource.CUSTOM_LIST
import mozilla.components.browser.domains.DomainAutoCompleteProvider.AutocompleteSource.DEFAULT_LIST
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
/**
* While [DomainAutoCompleteProvider] exists (even if it's deprecated) we need to test it.
*/
@RunWith(AndroidJUnit4::class)
class DomainAutoCompleteProviderTest {
@Test
fun autocompletionWithShippedDomains() {
val provider = DomainAutoCompleteProvider().also {
it.initialize(
testContext,
useShippedDomains = true,
useCustomDomains = false,
loadDomainsFromDisk = false,
)
it.shippedDomains = listOf("mozilla.org", "google.com", "facebook.com").into()
it.customDomains = emptyList()
}
val size = provider.shippedDomains.size
assertCompletion(provider, "m", DEFAULT_LIST, size, "mozilla.org", "http://mozilla.org")
assertCompletion(provider, "www", DEFAULT_LIST, size, "www.mozilla.org", "http://mozilla.org")
assertCompletion(provider, "www.face", DEFAULT_LIST, size, "www.facebook.com", "http://facebook.com")
assertCompletion(provider, "MOZ", DEFAULT_LIST, size, "MOZilla.org", "http://mozilla.org")
assertCompletion(provider, "www.GOO", DEFAULT_LIST, size, "www.GOOgle.com", "http://google.com")
assertCompletion(provider, "WWW.GOOGLE.", DEFAULT_LIST, size, "WWW.GOOGLE.com", "http://google.com")
assertCompletion(provider, "www.facebook.com", DEFAULT_LIST, size, "www.facebook.com", "http://facebook.com")
assertCompletion(provider, "facebook.com", DEFAULT_LIST, size, "facebook.com", "http://facebook.com")
assertNoCompletion(provider, "wwww")
assertNoCompletion(provider, "yahoo")
}
@Test
fun autocompletionWithCustomDomains() {
val domains = listOf("facebook.com", "google.com", "mozilla.org")
val customDomains = listOf("gap.com", "www.fanfiction.com", "https://mobile.de")
val provider = DomainAutoCompleteProvider().also {
it.initialize(
testContext,
useShippedDomains = true,
useCustomDomains = true,
loadDomainsFromDisk = false,
)
it.shippedDomains = domains.into()
it.customDomains = customDomains.into()
}
assertCompletion(provider, "f", CUSTOM_LIST, customDomains.size, "fanfiction.com", "http://www.fanfiction.com")
assertCompletion(provider, "fa", CUSTOM_LIST, customDomains.size, "fanfiction.com", "http://www.fanfiction.com")
assertCompletion(provider, "fac", DEFAULT_LIST, domains.size, "facebook.com", "http://facebook.com")
assertCompletion(provider, "g", CUSTOM_LIST, customDomains.size, "gap.com", "http://gap.com")
assertCompletion(provider, "go", DEFAULT_LIST, domains.size, "google.com", "http://google.com")
assertCompletion(provider, "ga", CUSTOM_LIST, customDomains.size, "gap.com", "http://gap.com")
assertCompletion(provider, "m", CUSTOM_LIST, customDomains.size, "mobile.de", "https://mobile.de")
assertCompletion(provider, "mo", CUSTOM_LIST, customDomains.size, "mobile.de", "https://mobile.de")
assertCompletion(provider, "mob", CUSTOM_LIST, customDomains.size, "mobile.de", "https://mobile.de")
assertCompletion(provider, "moz", DEFAULT_LIST, domains.size, "mozilla.org", "http://mozilla.org")
}
@Test
fun autocompletionWithoutDomains() {
val filter = DomainAutoCompleteProvider()
assertNoCompletion(filter, "mozilla")
}
private fun assertCompletion(
provider: DomainAutoCompleteProvider,
text: String,
domainSource: String,
sourceSize: Int,
completion: String,
expectedUrl: String,
) {
val result = provider.autocomplete(text)
assertFalse(result.text.isEmpty())
assertEquals(completion, result.text)
assertEquals(domainSource, result.source)
assertEquals(expectedUrl, result.url)
assertEquals(sourceSize, result.size)
}
private fun assertNoCompletion(provider: DomainAutoCompleteProvider, text: String) {
val result = provider.autocomplete(text)
assertTrue(result.text.isEmpty())
assertTrue(result.url.isEmpty())
assertTrue(result.source.isEmpty())
assertEquals(0, result.size)
}
}

View File

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import org.junit.Assert
import org.junit.Test
class DomainTest {
@Test
fun domainCreation() {
val firstItem = Domain.create("https://mozilla.com")
Assert.assertTrue(firstItem.protocol == "https://")
Assert.assertFalse(firstItem.hasWww)
Assert.assertTrue(firstItem.host == "mozilla.com")
val secondItem = Domain.create("www.mozilla.com")
Assert.assertTrue(secondItem.protocol == "http://")
Assert.assertTrue(secondItem.hasWww)
Assert.assertTrue(secondItem.host == "mozilla.com")
}
@Test
fun domainCanCreateUrl() {
val firstItem = Domain.create("https://mozilla.com")
Assert.assertEquals("https://mozilla.com", firstItem.url)
val secondItem = Domain.create("www.mozilla.com")
Assert.assertEquals("http://www.mozilla.com", secondItem.url)
}
@Test(expected = IllegalStateException::class)
fun domainCreationWithBadURLThrowsException() {
Domain.create("http://www.")
}
}

View File

@ -0,0 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DomainsTest {
@Test
fun loadDomains() {
val domains = Domains.load(testContext, setOf("us"))
Assert.assertFalse(domains.isEmpty())
Assert.assertTrue(domains.contains("reddit.com"))
}
@Test
fun loadDomainsWithDefaultCountries() {
val domains = Domains.load(testContext)
Assert.assertFalse(domains.isEmpty())
// From the global list
Assert.assertTrue(domains.contains("mozilla.org"))
}
}

View File

@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.domains
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider
import mozilla.components.browser.domains.autocomplete.CustomDomainsProvider
import mozilla.components.browser.domains.autocomplete.DomainList
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Test
class ProvidersTest {
@Test
fun autocompletionWithShippedDomains() {
val provider = ShippedDomainsProvider()
provider.domains = listOf("mozilla.org", "google.com", "facebook.com").into()
val size = provider.domains.size
assertCompletion(provider, "m", DomainList.DEFAULT, size, "mozilla.org", "http://mozilla.org")
assertCompletion(provider, "www", DomainList.DEFAULT, size, "www.mozilla.org", "http://mozilla.org")
assertCompletion(provider, "www.face", DomainList.DEFAULT, size, "www.facebook.com", "http://facebook.com")
assertCompletion(provider, "MOZ", DomainList.DEFAULT, size, "MOZilla.org", "http://mozilla.org")
assertCompletion(provider, "www.GOO", DomainList.DEFAULT, size, "www.GOOgle.com", "http://google.com")
assertCompletion(provider, "WWW.GOOGLE.", DomainList.DEFAULT, size, "WWW.GOOGLE.com", "http://google.com")
assertCompletion(provider, "www.facebook.com", DomainList.DEFAULT, size, "www.facebook.com", "http://facebook.com")
assertCompletion(provider, "facebook.com", DomainList.DEFAULT, size, "facebook.com", "http://facebook.com")
assertNoCompletion(provider, "wwww")
assertNoCompletion(provider, "yahoo")
}
@Test
fun autocompletionWithCustomDomains() {
val customDomains = listOf("gap.com", "www.fanfiction.com", "https://mobile.de")
val provider = CustomDomainsProvider()
provider.domains = customDomains.into()
assertCompletion(provider, "f", DomainList.CUSTOM, customDomains.size, "fanfiction.com", "http://www.fanfiction.com")
assertCompletion(provider, "fa", DomainList.CUSTOM, customDomains.size, "fanfiction.com", "http://www.fanfiction.com")
assertCompletion(provider, "g", DomainList.CUSTOM, customDomains.size, "gap.com", "http://gap.com")
assertCompletion(provider, "ga", DomainList.CUSTOM, customDomains.size, "gap.com", "http://gap.com")
assertCompletion(provider, "m", DomainList.CUSTOM, customDomains.size, "mobile.de", "https://mobile.de")
assertCompletion(provider, "mo", DomainList.CUSTOM, customDomains.size, "mobile.de", "https://mobile.de")
assertCompletion(provider, "mob", DomainList.CUSTOM, customDomains.size, "mobile.de", "https://mobile.de")
}
@Test
fun autocompletionWithoutDomains() {
val filter = CustomDomainsProvider()
assertNoCompletion(filter, "mozilla")
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun assertCompletion(
provider: BaseDomainAutocompleteProvider,
text: String,
domainSource: DomainList,
sourceSize: Int,
completion: String,
expectedUrl: String,
) = runTest {
val result = provider.getAutocompleteSuggestion(text)!!
assertFalse(result.text.isEmpty())
assertEquals(completion, result.text)
assertEquals(domainSource.listName, result.source)
assertEquals(expectedUrl, result.url)
assertEquals(sourceSize, result.totalItems)
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun assertNoCompletion(provider: BaseDomainAutocompleteProvider, text: String) = runTest {
assertNull(provider.getAutocompleteSuggestion(text))
}
}

View File

@ -0,0 +1,41 @@
# [Android Components](../../../README.md) > Browser > Engine-Gecko
[*Engine*](../../concept/engine/README.md) implementation based on [GeckoView](https://wiki.mozilla.org/Mobile/GeckoView).
## Usage
### Setting up the dependency
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
```Groovy
implementation "org.mozilla.components:browser-engine-gecko:{latest-version}"
```
### Integration with the Glean SDK
#### Before using this component
Products sending telemetry and using this component *must request* a data-review following [this process](https://wiki.mozilla.org/Firefox/Data_Collection).
The [Glean SDK](../../../components/service/glean/README.md) can be used to collect [Gecko Telemetry](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/index.html).
Applications using both this component and the Glean SDK should setup the Gecko Telemetry delegate
as shown below:
```Kotlin
val builder = GeckoRuntimeSettings.Builder()
val runtimeSettings = builder
.telemetryDelegate(GeckoGleanAdapter()) // Sets up the delegate!
.build()
// Create the Gecko runtime.
GeckoRuntime.create(context, runtimeSettings)
```
#### Adding new metrics
New Gecko metrics can be added as described [in the Firefox Telemetry docs](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/start/adding-a-new-probe.html).
## License
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/

View File

@ -0,0 +1,195 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
buildscript {
repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven {
url repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true
}
}
}
}
dependencies {
classpath "${ApplicationServicesConfig.groupId}:tooling-nimbus-gradle:${ApplicationServicesConfig.version}"
classpath "org.mozilla.telemetry:glean-gradle-plugin:${Versions.mozilla_glean}"
}
}
plugins {
id "com.jetbrains.python.envs" version "$python_envs_plugin"
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
defaultConfig {
minSdkVersion config.minSdkVersion
compileSdk config.compileSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
resources {
excludes += ['META-INF/proguard/androidx-annotations.pro']
}
}
buildFeatures {
buildConfig true
}
namespace 'mozilla.components.browser.engine.gecko'
}
// Set configuration for the Glean parser to extract metrics.yaml
// file from AAR dependencies of this project rather than look
// for it into the project directory.
ext.allowMetricsFromAAR = true
dependencies {
implementation project(':concept-engine')
implementation project(':concept-fetch')
implementation project(':support-ktx')
implementation project(':support-utils')
implementation(project(':service-nimbus')) {
exclude group: 'org.mozilla.telemetry', module: 'glean-native'
}
implementation ComponentsDependencies.kotlin_coroutines
if (findProject(":geckoview") != null) {
api project(':geckoview')
} else {
api getGeckoViewDependency()
}
implementation ComponentsDependencies.androidx_core_ktx
implementation ComponentsDependencies.androidx_paging
implementation ComponentsDependencies.androidx_datastore_preferences
implementation ComponentsDependencies.androidx_lifecycle_livedata
testImplementation ComponentsDependencies.androidx_test_core
testImplementation ComponentsDependencies.androidx_test_junit
testImplementation ComponentsDependencies.testing_robolectric
testImplementation ComponentsDependencies.testing_coroutines
testImplementation ComponentsDependencies.testing_mockwebserver
testImplementation project(':support-test')
testImplementation project(':tooling-fetch-tests')
// We only compile against Glean. It's up to the app to add those dependencies
// if it wants to collect GeckoView telemetry through the Glean SDK.
compileOnly ComponentsDependencies.mozilla_glean
testImplementation ComponentsDependencies.mozilla_glean
testImplementation ComponentsDependencies.androidx_work_testing
androidTestImplementation ComponentsDependencies.androidx_test_core
androidTestImplementation ComponentsDependencies.androidx_test_runner
androidTestImplementation ComponentsDependencies.androidx_test_rules
androidTestImplementation project(':tooling-fetch-tests')
}
ext.gleanNamespace = "mozilla.telemetry.glean"
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
apply from: '../../../android-lint.gradle'
apply from: '../../../publish.gradle'
apply plugin: "org.mozilla.appservices.nimbus-gradle-plugin"
nimbus {
// The path to the Nimbus feature manifest file
manifestFile = "geckoview.fml.yaml"
channels = [
debug: "debug",
release: "release",
]
// This is an optional value, and updates the plugin to use a copy of application
// services. The path should be relative to the root project directory.
// *NOTE*: This example will not work for all projects, but should work for Fenix, Focus, and Android Components
applicationServicesDir = gradle.hasProperty('localProperties.autoPublish.application-services.dir')
? gradle.getProperty('localProperties.autoPublish.application-services.dir') : null
}
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description)
// Non-official versions are like "61.0a1", where "a1" is the milestone.
// This simply strips that off, leaving "61.0" in this example.
def getAppVersionWithoutMilestone() {
return gradle.mozconfig.substs.MOZ_APP_VERSION.replaceFirst(/a[0-9]/, "")
}
// Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
def getBuildId() {
if (System.env.MOZ_BUILD_DATE) {
if (System.env.MOZ_BUILD_DATE.length() == 14) {
return System.env.MOZ_BUILD_DATE
}
logger.warn("Ignoring invalid MOZ_BUILD_DATE: ${System.env.MOZ_BUILD_DATE}")
}
return file("${gradle.mozconfig.topobjdir}/buildid.h").getText('utf-8').split()[2]
}
def getVersionNumber() {
def appVersion = getAppVersionWithoutMilestone()
def parts = appVersion.split('\\.')
def version = parts[0] + "." + parts[1] + "." + getBuildId()
if (!gradle.mozconfig.substs.MOZILLA_OFFICIAL && !gradle.mozconfig.substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
// Use -SNAPSHOT versions locally to enable the local GeckoView substitution flow.
version += "-SNAPSHOT"
}
return version
}
def getArtifactSuffix() {
def suffix = ""
// Release artifacts don't specify the channel, for the sake of simplicity.
if (gradle.mozconfig.substs.MOZ_UPDATE_CHANNEL != 'release') {
suffix += "-${gradle.mozconfig.substs.MOZ_UPDATE_CHANNEL}"
}
return suffix
}
def getArtifactId() {
def id = "geckoview" + getArtifactSuffix()
if (!gradle.mozconfig.substs.MOZ_ANDROID_GECKOVIEW_LITE) {
id += "-omni"
}
if (gradle.mozconfig.substs.MOZILLA_OFFICIAL && !gradle.mozconfig.substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
// In automation, per-architecture artifacts identify
// the architecture; multi-architecture artifacts don't.
// When building locally, we produce a "skinny AAR" with
// one target architecture masquerading as a "fat AAR"
// to enable Gradle composite builds to substitute this
// project into consumers easily.
id += "-${gradle.mozconfig.substs.ANDROID_CPU_ARCH}"
}
return id
}
def getGeckoViewDependency() {
// on try, relax geckoview version pin to allow for --use-existing-task
if ('https://hg.mozilla.org/try' == System.env.GECKO_HEAD_REPOSITORY) {
rootProject.logger.lifecycle("Getting geckoview on try: ${getArtifactId()}:+")
return "org.mozilla.geckoview:${getArtifactId()}:+"
}
rootProject.logger.lifecycle("Getting geckoview: ${getArtifactId()}:${getVersionNumber()}")
return "org.mozilla.geckoview:${getArtifactId()}:${getVersionNumber()}"
}

View File

@ -0,0 +1,8 @@
# Metrics definitions have moved
Metrics definitions for projects using `engine-gecko` moved to the Glean Dictionary.
For Firefox for Android those definitions can be found at:
[https://dictionary.telemetry.mozilla.org/apps/fenix](https://dictionary.telemetry.mozilla.org/apps/fenix)
This file is kept only for historical reference.

View File

@ -0,0 +1,24 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
about:
description: GeckoView features configurable via Nimbus
android:
package: mozilla.components.browser.engine.gecko
class: .GeckoNimbus
channels:
- debug
- release
features:
pdfjs:
description: "PDF.js features"
variables:
download-button:
description: "Download button"
type: Boolean
default: true
open-in-app-button:
description: "Open in app button"
type: Boolean
default: true

View File

@ -0,0 +1,30 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# IMPORTANT NOTE: this file is here only as a safety measure, to make
# sure the correct code is generated even though the GeckoView AAR file
# reports an empty metrics.yaml file. The metric in this file is currently
# disabled and not supposed to collect any data.
---
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
test.glean.geckoview:
streaming:
type: timing_distribution
gecko_datapoint: TELEMETRY_TEST_STREAMING
disabled: true
description: |
A test-only, disabled metric. This is required to guarantee
that a `GleanGeckoHistogramMapping` is always generated, even
though the GeckoView AAR exports no metric. Please note that
the data-review field below contains no review, since this
metric is disabled and not allowed to collect any data.
bugs:
- https://bugzilla.mozilla.org/1566374
data_reviews:
- https://bugzilla.mozilla.org/1566374
notification_emails:
- glean-team@mozilla.com
expires: never

View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,126 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.fetch.geckoview
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
import mozilla.components.concept.fetch.Client
import org.junit.Assert.assertTrue
import org.junit.Test
@MediumTest
class GeckoViewFetchTestCases : mozilla.components.tooling.fetch.tests.FetchTestCases() {
override fun createNewClient(): Client = GeckoViewFetchClient(ApplicationProvider.getApplicationContext())
@Test
@UiThreadTest
fun clientInstance() {
assertTrue(createNewClient() is GeckoViewFetchClient)
}
@Test
@UiThreadTest
override fun get200WithGzippedBody() {
super.get200WithGzippedBody()
}
@Test
@UiThreadTest
override fun get200OverridingDefaultHeaders() {
super.get200OverridingDefaultHeaders()
}
@Test
@UiThreadTest
override fun get200WithDuplicatedCacheControlRequestHeaders() {
super.get200WithDuplicatedCacheControlRequestHeaders()
}
@Test
@UiThreadTest
override fun get200WithDuplicatedCacheControlResponseHeaders() {
super.get200WithDuplicatedCacheControlResponseHeaders()
}
@Test
@UiThreadTest
override fun get200WithHeaders() {
super.get200WithHeaders()
}
@Test
@UiThreadTest
override fun get200WithReadTimeout() {
super.get200WithReadTimeout()
}
@Test
@UiThreadTest
override fun get200WithStringBody() {
super.get200WithStringBody()
}
@Test
@UiThreadTest
override fun get302FollowRedirects() {
super.get302FollowRedirects()
}
@Test
@UiThreadTest
override fun get302FollowRedirectsDisabled() {
super.get302FollowRedirectsDisabled()
}
@Test
@UiThreadTest
override fun get404WithBody() {
super.get404WithBody()
}
@Test
@UiThreadTest
override fun post200WithBody() {
super.post200WithBody()
}
@Test
@UiThreadTest
override fun put201FileUpload() {
super.put201FileUpload()
}
@Test
@UiThreadTest
override fun get200WithCookiePolicy() {
super.get200WithCookiePolicy()
}
@Test
@UiThreadTest
override fun get200WithContentTypeCharset() {
super.get200WithContentTypeCharset()
}
@Test
@UiThreadTest
override fun get200WithCacheControl() {
super.get200WithCacheControl()
}
@Test
@UiThreadTest
override fun getThrowsIOExceptionWhenHostNotReachable() {
super.getThrowsIOExceptionWhenHostNotReachable()
}
@Test
@UiThreadTest
override fun getDataUri() {
super.getDataUri()
}
}

View File

@ -0,0 +1,4 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<manifest />

View File

@ -0,0 +1,73 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko
import android.util.JsonReader
import android.util.JsonWriter
import mozilla.components.concept.engine.EngineSessionState
import org.json.JSONException
import org.json.JSONObject
import org.mozilla.geckoview.GeckoSession
import java.io.IOException
private const val GECKO_STATE_KEY = "GECKO_STATE"
class GeckoEngineSessionState internal constructor(
internal val actualState: GeckoSession.SessionState?,
) : EngineSessionState {
override fun writeTo(writer: JsonWriter) {
with(writer) {
beginObject()
name(GECKO_STATE_KEY)
value(actualState.toString())
endObject()
flush()
}
}
companion object {
fun fromJSON(json: JSONObject): GeckoEngineSessionState = try {
val state = json.getString(GECKO_STATE_KEY)
GeckoEngineSessionState(
GeckoSession.SessionState.fromString(state),
)
} catch (e: JSONException) {
GeckoEngineSessionState(null)
}
/**
* Creates a [GeckoEngineSessionState] from the given [JsonReader].
*/
fun from(reader: JsonReader): GeckoEngineSessionState = try {
reader.beginObject()
val rawState = if (reader.hasNext()) {
val key = reader.nextName()
if (key != GECKO_STATE_KEY) {
throw AssertionError("Unknown state key: $key")
}
reader.nextString()
} else {
null
}
reader.endObject()
GeckoEngineSessionState(
rawState?.let { GeckoSession.SessionState.fromString(it) },
)
} catch (e: IOException) {
GeckoEngineSessionState(null)
} catch (e: JSONException) {
// Internally GeckoView uses org.json and currently may throw JSONException in certain cases
// https://github.com/mozilla-mobile/android-components/issues/9332
GeckoEngineSessionState(null)
}
}
}

View File

@ -0,0 +1,247 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.core.view.ViewCompat
import mozilla.components.browser.engine.gecko.activity.GeckoViewActivityContextDelegate
import mozilla.components.browser.engine.gecko.selection.GeckoSelectionActionDelegate
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
import mozilla.components.concept.engine.selection.SelectionActionDelegate
import org.mozilla.geckoview.BasicSelectionActionDelegate
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import java.lang.ref.WeakReference
import androidx.core.view.OnApplyWindowInsetsListener as AndroidxOnApplyWindowInsetsListener
/**
* Gecko-based EngineView implementation.
*/
class GeckoEngineView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr), EngineView {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var geckoView = object : NestedGeckoView(context) {
override fun onAttachedToWindow() {
try {
super.onAttachedToWindow()
} catch (e: IllegalStateException) {
// This is to debug "display already acquired" crashes
val otherActivityClassName =
this.session?.accessibility?.view?.context?.javaClass?.simpleName
val otherActivityClassHashcode =
this.session?.accessibility?.view?.context?.hashCode()
val activityClassName = context.javaClass.simpleName
val activityClassHashCode = context.hashCode()
val msg = "ATTACH VIEW: Current activity: $activityClassName hashcode " +
"$activityClassHashCode Other activity: $otherActivityClassName " +
"hashcode $otherActivityClassHashcode"
throw IllegalStateException(msg, e)
}
}
override fun onDetachedFromWindow() {
// We are releasing the session before GeckoView gets detached from the window. Otherwise
// GeckoView will close the session automatically and we do not want that.
releaseSession()
super.onDetachedFromWindow()
}
}.apply {
// Explicitly mark this view as important for autofill. The default "auto" doesn't seem to trigger any
// autofill behavior for us here.
@Suppress("WrongConstant")
ViewCompat.setImportantForAutofill(this, View.IMPORTANT_FOR_ACCESSIBILITY_YES)
}
internal fun setColorScheme(preferredColorScheme: PreferredColorScheme) {
var colorScheme = preferredColorScheme
if (preferredColorScheme == PreferredColorScheme.System) {
colorScheme =
if (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
== Configuration.UI_MODE_NIGHT_YES
) {
PreferredColorScheme.Dark
} else {
PreferredColorScheme.Light
}
}
if (colorScheme == PreferredColorScheme.Dark) {
geckoView.coverUntilFirstPaint(DARK_COVER)
} else {
geckoView.coverUntilFirstPaint(Color.WHITE)
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var currentSession: GeckoEngineSession? = null
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var currentSelection: BasicSelectionActionDelegate? = null
override var selectionActionDelegate: SelectionActionDelegate? = null
init {
addView(geckoView)
/**
* With the current design, we have a [NestedGeckoView] inside this
* [GeckoEngineView]. In our supported embedders, we wrap this with the
* AndroidX `SwipeRefreshLayout` to enable features like Pull-To-Refresh:
*
* ```
* SwipeRefreshLayout
* GeckoEngineView
* NestedGeckoView
* ```
*
* `SwipeRefreshLayout` only looks at the direct child to see if it has nested scrolling
* enabled. As we embed [NestedGeckoView] inside [GeckoEngineView], we change the hierarchy
* so that [NestedGeckoView] is no longer the direct child of `SwipeRefreshLayout`.
*
* To fix this we enable nested scrolling on the GeckoEngineView to emulate this
* information. This is required information for `View.requestDisallowInterceptTouchEvent`
* to work correctly in the [NestedGeckoView].
*/
isNestedScrollingEnabled = true
}
/**
* Render the content of the given session.
*/
@Synchronized
override fun render(session: EngineSession) {
val internalSession = session as GeckoEngineSession
currentSession = session
if (geckoView.session != internalSession.geckoSession) {
geckoView.session?.let {
// Release a previously assigned session. Otherwise GeckoView will close it
// automatically.
detachSelectionActionDelegate(it)
geckoView.releaseSession()
}
try {
geckoView.setSession(internalSession.geckoSession)
attachSelectionActionDelegate(internalSession.geckoSession)
} catch (e: IllegalStateException) {
// This is to debug "display already acquired" crashes
val otherActivityClassName =
internalSession.geckoSession.accessibility.view?.context?.javaClass?.simpleName
val otherActivityClassHashcode =
internalSession.geckoSession.accessibility.view?.context?.hashCode()
val activityClassName = context.javaClass.simpleName
val activityClassHashCode = context.hashCode()
val msg = "SET SESSION: Current activity: $activityClassName hashcode " +
"$activityClassHashCode Other activity: $otherActivityClassName " +
"hashcode $otherActivityClassHashcode"
throw IllegalStateException(msg, e)
}
}
}
private fun attachSelectionActionDelegate(session: GeckoSession) {
val delegate = GeckoSelectionActionDelegate.maybeCreate(context, selectionActionDelegate)
if (delegate != null) {
session.selectionActionDelegate = delegate
currentSelection = delegate
}
}
private fun detachSelectionActionDelegate(session: GeckoSession?) {
if (currentSelection != null) {
session?.selectionActionDelegate = null
currentSelection = null
}
}
@Synchronized
override fun release() {
detachSelectionActionDelegate(currentSession?.geckoSession)
currentSession = null
geckoView.releaseSession()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
release()
}
override fun canClearSelection() = !currentSelection?.selection?.text.isNullOrEmpty()
override fun canScrollVerticallyUp() = currentSession?.let { it.scrollY > 0 } != false
override fun canScrollVerticallyDown() =
true // waiting for this issue https://bugzilla.mozilla.org/show_bug.cgi?id=1507569
override fun getInputResultDetail() = geckoView.inputResultDetail
override fun setVerticalClipping(clippingHeight: Int) {
geckoView.setVerticalClipping(clippingHeight)
}
override fun setDynamicToolbarMaxHeight(height: Int) {
geckoView.setDynamicToolbarMaxHeight(height)
}
override fun setActivityContext(context: Context?) {
geckoView.activityContextDelegate = GeckoViewActivityContextDelegate(WeakReference(context))
}
override fun captureThumbnail(onFinish: (Bitmap?) -> Unit) {
val geckoResult = geckoView.capturePixels()
geckoResult.then(
{ bitmap ->
onFinish(bitmap)
GeckoResult()
},
{
onFinish(null)
GeckoResult<Void>()
},
)
}
override fun clearSelection() {
currentSelection?.clearSelection()
}
override fun setVisibility(visibility: Int) {
// GeckoView doesn't react to onVisibilityChanged so we need to propagate ourselves for now:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1630775
// We do this to prevent the content from resizing when the view is not visible:
// https://github.com/mozilla-mobile/android-components/issues/6664
geckoView.visibility = visibility
super.setVisibility(visibility)
}
override fun addWindowInsetsListener(
key: String,
listener: AndroidxOnApplyWindowInsetsListener?,
) = geckoView.addWindowInsetsListener(key, listener)
override fun removeWindowInsetsListener(key: String) = geckoView.removeWindowInsetsListener(key)
companion object {
internal const val DARK_COVER = 0xFF2A2A2E.toInt()
}
}

View File

@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.CancellableOperation
import org.mozilla.geckoview.GeckoResult
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
/**
* Wait for a GeckoResult to be complete in a co-routine.
*/
suspend fun <T> GeckoResult<T>.await() = suspendCoroutine<T?> { continuation ->
then(
{
continuation.resume(it)
GeckoResult<Void>()
},
{
continuation.resumeWithException(it)
GeckoResult<Void>()
},
)
}
/**
* Converts a [GeckoResult] to a [CancellableOperation].
*/
fun <T> GeckoResult<T>.asCancellableOperation(): CancellableOperation {
val geckoResult = this
return object : CancellableOperation {
override fun cancel(): Deferred<Boolean> {
val result = CompletableDeferred<Boolean>()
geckoResult.cancel().then(
{
result.complete(it ?: false)
GeckoResult<Void>()
},
{ throwable ->
result.completeExceptionally(throwable)
GeckoResult<Void>()
},
)
return result
}
}
}
/**
* Create a GeckoResult from a co-routine.
*/
@Suppress("TooGenericExceptionCaught")
fun <T> CoroutineScope.launchGeckoResult(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T,
) = GeckoResult<T>().apply {
launch(context, start) {
try {
val value = block()
complete(value)
} catch (exception: Throwable) {
completeExceptionally(exception)
}
}
}

View File

@ -0,0 +1,146 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import mozilla.components.browser.engine.gecko.content.blocking.GeckoTrackingProtectionException
import mozilla.components.browser.engine.gecko.ext.geckoTrackingProtectionPermission
import mozilla.components.browser.engine.gecko.ext.isExcludedForTrackingProtection
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
import mozilla.components.concept.engine.content.blocking.TrackingProtectionExceptionStorage
import mozilla.components.support.ktx.kotlin.getOrigin
import mozilla.components.support.ktx.kotlin.stripDefaultPort
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY
/**
* A [TrackingProtectionExceptionStorage] implementation to store tracking protection exceptions.
*/
internal class GeckoTrackingProtectionExceptionStorage(
private val runtime: GeckoRuntime,
) : TrackingProtectionExceptionStorage {
internal var scope = CoroutineScope(Dispatchers.IO)
override fun contains(session: EngineSession, onResult: (Boolean) -> Unit) {
val url = (session as GeckoEngineSession).currentUrl
if (!url.isNullOrEmpty()) {
getPermissions(url) { permissions ->
val contains = permissions.isNotEmpty()
onResult(contains)
}
} else {
onResult(false)
}
}
override fun fetchAll(onResult: (List<TrackingProtectionException>) -> Unit) {
runtime.storageController.allPermissions.accept { permissions ->
val trackingExceptions = permissions.filterTrackingProtectionExceptions()
onResult(trackingExceptions.map { exceptions -> exceptions.toTrackingProtectionException() })
}
}
private fun List<ContentPermission>?.filterTrackingProtectionExceptions() =
this.orEmpty().filter { it.isExcludedForTrackingProtection }
private fun List<ContentPermission>?.filterTrackingProtectionExceptions(url: String) =
this.orEmpty()
.filter {
it.isExcludedForTrackingProtection && it.uri.getOrigin().orEmpty()
.stripDefaultPort() == url
}
override fun add(session: EngineSession, persistInPrivateMode: Boolean) {
val geckoEngineSession = (session as GeckoEngineSession)
if (persistInPrivateMode) {
addPersistentPrivateException(geckoEngineSession)
} else {
geckoEngineSession.geckoTrackingProtectionPermission?.let {
runtime.storageController.setPermission(it, VALUE_ALLOW)
}
}
geckoEngineSession.notifyObservers {
onExcludedOnTrackingProtectionChange(true)
}
}
internal fun addPersistentPrivateException(geckoEngineSession: GeckoEngineSession) {
val permission = geckoEngineSession.geckoTrackingProtectionPermission
permission?.let {
runtime.storageController.setPrivateBrowsingPermanentPermission(it, VALUE_ALLOW)
}
}
override fun remove(session: EngineSession) {
val geckoEngineSession = (session as GeckoEngineSession)
val url = geckoEngineSession.currentUrl ?: return
geckoEngineSession.notifyObservers {
onExcludedOnTrackingProtectionChange(false)
}
remove(url)
}
override fun remove(exception: TrackingProtectionException) {
if (exception is GeckoTrackingProtectionException) {
remove(exception.contentPermission)
} else {
remove(exception.url)
}
}
@VisibleForTesting
internal fun remove(url: String) {
val storage = runtime.storageController
getPermissions(url) { permissions ->
permissions.forEach { geckoPermissions ->
storage.setPermission(geckoPermissions, VALUE_DENY)
}
}
}
// This is a workaround until https://bugzilla.mozilla.org/show_bug.cgi?id=1723280 gets addressed
private fun getPermissions(url: String, onFinish: (List<ContentPermission>) -> Unit) {
val localUrl = url.getOrigin().orEmpty().stripDefaultPort()
val storage = runtime.storageController
if (localUrl.isNotEmpty()) {
storage.allPermissions.accept { permissions ->
onFinish(permissions.filterTrackingProtectionExceptions(localUrl))
}
} else {
onFinish(emptyList())
}
}
@VisibleForTesting
internal fun remove(contentPermission: ContentPermission) {
runtime.storageController.setPermission(contentPermission, VALUE_DENY)
}
override fun removeAll(activeSessions: List<EngineSession>?, onRemove: () -> Unit) {
val storage = runtime.storageController
activeSessions?.forEach { engineSession ->
engineSession.notifyObservers {
onExcludedOnTrackingProtectionChange(false)
}
}
storage.allPermissions.accept { permissions ->
val trackingExceptions = permissions.filterTrackingProtectionExceptions()
trackingExceptions.forEach {
storage.setPermission(it, VALUE_DENY)
}
onRemove.invoke()
}
}
}
private fun ContentPermission.toTrackingProtectionException(): GeckoTrackingProtectionException {
return GeckoTrackingProtectionException(uri, privateMode, this)
}

View File

@ -0,0 +1,257 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko
import android.annotation.SuppressLint
import android.content.Context
import android.view.MotionEvent
import androidx.annotation.VisibleForTesting
import androidx.core.view.NestedScrollingChild
import androidx.core.view.NestedScrollingChildHelper
import androidx.core.view.ViewCompat
import mozilla.components.concept.engine.InputResultDetail
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoView
import org.mozilla.geckoview.PanZoomController
/**
* geckoView that supports nested scrolls (for using in a CoordinatorLayout).
*
* This code is a simplified version of the NestedScrollView implementation
* which can be found in the support library:
* [android.support.v4.widget.NestedScrollView]
*
* Based on:
* https://github.com/takahirom/webview-in-coordinatorlayout
*/
@Suppress("ClickableViewAccessibility")
open class NestedGeckoView(context: Context) : GeckoView(context), NestedScrollingChild {
@VisibleForTesting
internal var lastY: Int = 0
@VisibleForTesting
internal val scrollOffset = IntArray(2)
private val scrollConsumed = IntArray(2)
private var gestureCanReachParent = true
/**
* Represents the initial scroll.
*/
enum class InitialScrollDirection {
NOT_YET,
DOWN,
UP,
}
private var initialScrollDirection = InitialScrollDirection.NOT_YET
private var initialDownY: Float = 0f
@VisibleForTesting
internal var nestedOffsetY: Int = 0
@VisibleForTesting
internal var childHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)
/**
* How user's MotionEvent will be handled.
*
* @see InputResultDetail
*/
internal var inputResultDetail = InputResultDetail.newInstance(true)
init {
isNestedScrollingEnabled = true
}
@Suppress("ComplexMethod")
override fun onTouchEvent(ev: MotionEvent): Boolean {
val event = MotionEvent.obtain(ev)
val action = ev.actionMasked
val eventY = event.y.toInt()
when (action) {
MotionEvent.ACTION_MOVE -> {
val allowScroll = !shouldPinOnScreen() && inputResultDetail.isTouchHandledByBrowser()
var deltaY = lastY - eventY
if (allowScroll && dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) {
deltaY -= scrollConsumed[1]
event.offsetLocation(0f, (-scrollOffset[1]).toFloat())
nestedOffsetY += scrollOffset[1]
}
lastY = eventY - scrollOffset[1]
if (allowScroll && dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) {
lastY -= scrollOffset[1]
event.offsetLocation(0f, scrollOffset[1].toFloat())
nestedOffsetY += scrollOffset[1]
}
if (initialScrollDirection == InitialScrollDirection.NOT_YET) {
if (event.y > initialDownY) {
initialScrollDirection = InitialScrollDirection.UP
} else if (event.y < initialDownY) {
initialScrollDirection = InitialScrollDirection.DOWN
} else {
// We may want to disallow pull-to-refresh in the case of
// scrolling left or right initially?
}
}
}
MotionEvent.ACTION_DOWN -> {
// A new gesture started. Ask GV if it can handle this.
parent?.requestDisallowInterceptTouchEvent(true)
updateInputResult(event)
initialScrollDirection = InitialScrollDirection.NOT_YET
nestedOffsetY = 0
lastY = eventY
initialDownY = event.y
// The event should be handled either by onTouchEvent,
// either by onTouchEventForResult, never by both.
// Early return if we sent it to updateInputResult(..) which calls onTouchEventForResult.
event.recycle()
return true
}
// We don't care about other touch events
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// inputResultDetail needs to be reset here and not in the next ACTION_DOWN, because
// its value is used by other features that poll for the value via
// `EngineView.getInputResultDetail`. Not resetting this in ACTION_CANCEL/ACTION_UP
// would then mean we send stale information to those features from a previous
// gesture's result.
inputResultDetail = InputResultDetail.newInstance(true)
stopNestedScroll()
// Allow touch event interception here so that the next ACTION_DOWN event can be properly
// intercepted by the parent.
parent?.requestDisallowInterceptTouchEvent(false)
gestureCanReachParent = true
}
}
// Execute event handler from parent class in all cases
val eventHandled = callSuperOnTouchEvent(event)
// Recycle previously obtained event
event.recycle()
return eventHandled
}
@VisibleForTesting
internal fun callSuperOnTouchEvent(event: MotionEvent): Boolean {
return super.onTouchEvent(event)
}
@SuppressLint("WrongThread") // Lint complains startNestedScroll() needs to be called on the main thread
@VisibleForTesting
internal fun updateInputResult(event: MotionEvent) {
val eventAction = event.actionMasked
superOnTouchEventForDetailResult(event)
.accept {
// Since the response from APZ is async, we could theoretically have a response
// which is out of time when we get the ACTION_MOVE events, and we do not want
// to forward this to the parent pre-emptively.
if (!gestureCanReachParent) {
return@accept
}
inputResultDetail = inputResultDetail.copy(
it?.handledResult(),
it?.scrollableDirections(),
it?.overscrollDirections(),
)
if (eventAction == MotionEvent.ACTION_DOWN) {
// Gesture can reach the parent only if the content is already at the top
gestureCanReachParent = inputResultDetail.canOverscrollTop()
when (initialScrollDirection) {
InitialScrollDirection.NOT_YET -> {
// If the event wasn't used in GeckoView, allow touch event interception.
if (gestureCanReachParent && !inputResultDetail.isTouchHandledByWebsite()) {
parent?.requestDisallowInterceptTouchEvent(false)
}
}
InitialScrollDirection.UP -> {
// If we received the InputResultDetail from Gecko after we've sent the
// first touch move event to Gecko, that means there's at least a touch
// event listener whether to prevent the event or not, or CSS touch-action
// properties in the contents, thus if the result isn't
// `INPUT_HANDLED_CONTENT`, we can allow pull-to-refresh.
if (gestureCanReachParent && !inputResultDetail.isTouchHandledByWebsite()) {
parent?.requestDisallowInterceptTouchEvent(false)
}
}
InitialScrollDirection.DOWN -> {
parent?.requestDisallowInterceptTouchEvent(true)
}
}
}
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
}
}
@VisibleForTesting
internal open fun superOnTouchEventForDetailResult(
event: MotionEvent,
): GeckoResult<PanZoomController.InputResultDetail> =
super.onTouchEventForDetailResult(event)
override fun setNestedScrollingEnabled(enabled: Boolean) {
childHelper.isNestedScrollingEnabled = enabled
}
override fun isNestedScrollingEnabled(): Boolean {
return childHelper.isNestedScrollingEnabled
}
override fun startNestedScroll(axes: Int): Boolean {
return childHelper.startNestedScroll(axes)
}
override fun stopNestedScroll() {
childHelper.stopNestedScroll()
}
override fun hasNestedScrollingParent(): Boolean {
return childHelper.hasNestedScrollingParent()
}
override fun dispatchNestedScroll(
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
offsetInWindow: IntArray?,
): Boolean {
return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
}
override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean {
return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
}
override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean {
return childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
}
override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {
return childHelper.dispatchNestedPreFling(velocityX, velocityY)
}
}

View File

@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.activity
import android.app.PendingIntent
import android.content.Intent
import mozilla.components.concept.engine.activity.ActivityDelegate
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoRuntime
import java.lang.ref.WeakReference
/**
* A wrapper for the [ActivityDelegate] to communicate with the Gecko-based delegate.
*/
internal class GeckoActivityDelegate(
private val delegateRef: WeakReference<ActivityDelegate>,
) : GeckoRuntime.ActivityDelegate {
private val logger = Logger(GeckoActivityDelegate::javaClass.name)
override fun onStartActivityForResult(intent: PendingIntent): GeckoResult<Intent> {
val result: GeckoResult<Intent> = GeckoResult()
val delegate = delegateRef.get()
if (delegate == null) {
logger.warn("No activity delegate attached. Cannot request FIDO auth.")
result.completeExceptionally(RuntimeException("Activity for result failed; no delegate attached."))
return result
}
delegate.startIntentSenderForResult(intent.intentSender) { data ->
if (data != null) {
result.complete(data)
} else {
result.completeExceptionally(RuntimeException("Activity for result failed."))
}
}
return result
}
}

View File

@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.activity
import mozilla.components.concept.engine.activity.OrientationDelegate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.AllowOrDeny.ALLOW
import org.mozilla.geckoview.AllowOrDeny.DENY
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.OrientationController
/**
* Default [OrientationController.OrientationDelegate] implementation that delegates both the behavior
* and the returned value to a [OrientationDelegate].
*/
internal class GeckoScreenOrientationDelegate(
private val delegate: OrientationDelegate,
) : OrientationController.OrientationDelegate {
override fun onOrientationLock(requestedOrientation: Int): GeckoResult<AllowOrDeny> {
val result = GeckoResult<AllowOrDeny>()
when (delegate.onOrientationLock(requestedOrientation)) {
true -> result.complete(ALLOW)
false -> result.complete(DENY)
}
return result
}
override fun onOrientationUnlock() {
delegate.onOrientationUnlock()
}
}

View File

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.activity
import android.app.Activity
import android.content.Context
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.geckoview.GeckoView
import java.lang.ref.WeakReference
/**
* GeckoViewActivityContextDelegate provides an active Activity to GeckoView or null. Not to be confused
* with the runtime delegate of [GeckoActivityDelegate], which is tightly coupled to webauthn.
* See bug 1806191 for more information on delegate differences.
*
* @param contextRef A reference to an active Activity context or null for GeckoView to use.
*/
class GeckoViewActivityContextDelegate(
private val contextRef: WeakReference<Context?>,
) : GeckoView.ActivityContextDelegate {
private val logger = Logger("GeckoViewActivityContextDelegate")
init {
if (contextRef.get() == null) {
logger.warn("Activity context is null.")
} else if (contextRef.get() !is Activity) {
logger.warn("A non-activity context was set.")
}
}
/**
* Used by GeckoView to get an Activity context for operations such as printing.
* @return An active Activity context or null.
*/
override fun getActivityContext(): Context? {
val context = contextRef.get()
if ((context == null)) {
return null
}
return context
}
}

View File

@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.autofill
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress
import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry
import mozilla.components.browser.engine.gecko.ext.toLoginEntry
import mozilla.components.concept.storage.CreditCard
import mozilla.components.concept.storage.CreditCardsAddressesStorageDelegate
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginStorageDelegate
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoResult
/**
* Gecko credit card and login storage delegate that handles runtime storage requests. This allows
* the Gecko runtime to call the underlying storage to handle requests for fetching, saving and
* updating of autocomplete items in the storage.
*
* @param creditCardsAddressesStorageDelegate An instance of [CreditCardsAddressesStorageDelegate].
* Provides methods for retrieving [CreditCard]s from the underlying storage.
* @param loginStorageDelegate An instance of [LoginStorageDelegate].
* Provides read/write methods for the [Login] storage.
*/
class GeckoAutocompleteStorageDelegate(
private val creditCardsAddressesStorageDelegate: CreditCardsAddressesStorageDelegate,
private val loginStorageDelegate: LoginStorageDelegate,
) : Autocomplete.StorageDelegate {
override fun onAddressFetch(): GeckoResult<Array<Autocomplete.Address>>? {
val result = GeckoResult<Array<Autocomplete.Address>>()
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(IO) {
val addresses = creditCardsAddressesStorageDelegate.onAddressesFetch()
.map { it.toAutocompleteAddress() }
.toTypedArray()
result.complete(addresses)
}
return result
}
override fun onCreditCardFetch(): GeckoResult<Array<Autocomplete.CreditCard>> {
val result = GeckoResult<Array<Autocomplete.CreditCard>>()
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(IO) {
val key = creditCardsAddressesStorageDelegate.getOrGenerateKey()
val creditCards = creditCardsAddressesStorageDelegate.onCreditCardsFetch()
.mapNotNull {
val plaintextCardNumber =
creditCardsAddressesStorageDelegate.decrypt(key, it.encryptedCardNumber)?.number
if (plaintextCardNumber == null) {
null
} else {
Autocomplete.CreditCard.Builder()
.guid(it.guid)
.name(it.billingName)
.number(plaintextCardNumber)
.expirationMonth(it.expiryMonth.toString())
.expirationYear(it.expiryYear.toString())
.build()
}
}
.toTypedArray()
result.complete(creditCards)
}
return result
}
override fun onCreditCardSave(creditCard: Autocomplete.CreditCard) {
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(IO) {
creditCardsAddressesStorageDelegate.onCreditCardSave(creditCard.toCreditCardEntry())
}
}
override fun onLoginSave(login: Autocomplete.LoginEntry) {
loginStorageDelegate.onLoginSave(login.toLoginEntry())
}
override fun onLoginFetch(domain: String): GeckoResult<Array<Autocomplete.LoginEntry>> {
val result = GeckoResult<Array<Autocomplete.LoginEntry>>()
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(IO) {
val storedLogins = loginStorageDelegate.onLoginFetch(domain)
val logins = storedLogins.await()
.map { it.toLoginEntry() }
.toTypedArray()
result.complete(logins)
}
return result
}
}

View File

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.content.blocking
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission
/**
* Represents a site that will be ignored by the tracking protection policies.
* @property url The url of the site to be ignored.
* @property privateMode Indicates if this exception should persisted in private mode.
* @property contentPermission The associated gecko content permission of this exception.
*/
data class GeckoTrackingProtectionException(
override val url: String,
val privateMode: Boolean = false,
val contentPermission: ContentPermission,
) : TrackingProtectionException

View File

@ -0,0 +1,128 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.cookiebanners
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.browser.engine.gecko.await
import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode
import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode.DISABLED
import mozilla.components.concept.engine.cookiehandling.CookieBannersStorage
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.StorageController
/**
* A storage to store [CookieBannerHandlingMode] using GeckoView APIs.
*/
class GeckoCookieBannersStorage(
runtime: GeckoRuntime,
private val reportSiteDomainsRepository: ReportSiteDomainsRepository,
) : CookieBannersStorage {
private val geckoStorage: StorageController = runtime.storageController
private val mainScope = CoroutineScope(Dispatchers.Main)
override suspend fun addException(
uri: String,
privateBrowsing: Boolean,
) {
setGeckoException(uri, DISABLED, privateBrowsing)
}
override suspend fun isSiteDomainReported(siteDomain: String): Boolean {
return reportSiteDomainsRepository.isSiteDomainReported(siteDomain)
}
override suspend fun saveSiteDomain(siteDomain: String) {
reportSiteDomainsRepository.saveSiteDomain(siteDomain)
}
override suspend fun addPersistentExceptionInPrivateMode(uri: String) {
setPersistentPrivateGeckoException(uri, DISABLED)
}
override suspend fun findExceptionFor(
uri: String,
privateBrowsing: Boolean,
): CookieBannerHandlingMode? {
return queryExceptionInGecko(uri, privateBrowsing)
}
override suspend fun hasException(uri: String, privateBrowsing: Boolean): Boolean? {
val result = findExceptionFor(uri, privateBrowsing)
return if (result != null) {
result == DISABLED
} else {
null
}
}
override suspend fun removeException(uri: String, privateBrowsing: Boolean) {
removeGeckoException(uri, privateBrowsing)
}
@VisibleForTesting
internal fun removeGeckoException(uri: String, privateBrowsing: Boolean) {
geckoStorage.removeCookieBannerModeForDomain(uri, privateBrowsing)
}
@VisibleForTesting
internal fun setGeckoException(
uri: String,
mode: CookieBannerHandlingMode,
privateBrowsing: Boolean,
) {
geckoStorage.setCookieBannerModeForDomain(
uri,
mode.mode,
privateBrowsing,
)
}
@VisibleForTesting
internal fun setPersistentPrivateGeckoException(
uri: String,
mode: CookieBannerHandlingMode,
) {
geckoStorage.setCookieBannerModeAndPersistInPrivateBrowsingForDomain(
uri,
mode.mode,
)
}
@VisibleForTesting
@Suppress("TooGenericExceptionCaught")
internal suspend fun queryExceptionInGecko(
uri: String,
privateBrowsing: Boolean,
): CookieBannerHandlingMode? {
return try {
withContext(mainScope.coroutineContext) {
geckoStorage.getCookieBannerModeForDomain(uri, privateBrowsing).await()
?.toCookieBannerHandlingMode() ?: throw IllegalArgumentException(
"An error happened trying to find cookie banners mode for the " +
"uri $uri and private browsing mode $privateBrowsing",
)
}
} catch (e: Exception) {
// This normally happen on internal sites like about:config or ip sites.
val disabledErrors = listOf("NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS", "NS_ERROR_HOST_IS_IP_ADDRESS")
if (disabledErrors.any { (e.message ?: "").contains(it) }) {
Logger("GeckoCookieBannersStorage").error("Unable to query cookie banners exception", e)
null
} else {
throw e
}
}
}
}
@VisibleForTesting
internal fun Int.toCookieBannerHandlingMode(): CookieBannerHandlingMode {
return CookieBannerHandlingMode.entries.first { it.mode == this }
}

Some files were not shown because too many files have changed in this diff Show More