From 59203c63bb777a3bacec32fb8830fba33540e809 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:29 +0200 Subject: Adding upstream version 127.0. Signed-off-by: Daniel Baumann --- .../android/actors/GeckoViewContentParent.sys.mjs | 4 +- .../android/actors/GeckoViewSettingsChild.sys.mjs | 2 +- mobile/android/android-components/.buildconfig.yml | 38 + mobile/android/android-components/README.md | 2 + mobile/android/android-components/build.gradle | 22 - .../components/browser/engine-gecko/build.gradle | 3 +- .../components/browser/engine/gecko/GeckoEngine.kt | 4 + .../browser/engine/gecko/GeckoEngineSession.kt | 19 +- .../browser/engine/gecko/GeckoEngineView.kt | 36 +- .../engine/gecko/prompt/GeckoPromptDelegate.kt | 9 +- .../translate/GeckoTranslateSessionDelegate.kt | 9 +- .../browser/engine/gecko/GeckoEngineSessionTest.kt | 8 +- .../browser/engine/gecko/GeckoEngineTest.kt | 22 + .../browser/engine/gecko/GeckoEngineViewTest.kt | 9 - .../translate/GeckoTranslateSessionDelegateTest.kt | 22 +- .../src/main/res/raw/domain_blocklist.json | 19382 ++++++++----------- .../src/main/res/raw/domain_safelist.json | 18901 +++++++----------- .../errorpages/src/main/assets/errorPageScripts.js | 158 +- .../errorpages/src/main/assets/error_page_js.html | 106 +- .../errorpages/src/main/res/values-azb/strings.xml | 15 + .../errorpages/src/main/res/values-su/strings.xml | 8 +- .../src/main/res/values-sv-rSE/strings.xml | 2 +- .../main/assets/extensions/browser-icons/icons.js | 103 +- .../components/browser/icons/BrowserIcons.kt | 13 +- .../browser/icons/loader/HttpIconLoader.kt | 47 +- .../browser/icons/loader/MemoryInfoProvider.kt | 32 + .../icons/loader/NonBlockingHttpIconLoader.kt | 3 +- .../components/browser/icons/BrowserIconsTest.kt | 26 +- .../browser/icons/loader/HttpIconLoaderTest.kt | 175 +- .../icons/loader/NonBlockingHttpIconLoaderTest.kt | 21 +- .../components/browser/menu/item/CustomTooltip.kt | 3 +- .../menu/src/main/res/values-be/strings.xml | 8 +- .../menu/src/main/res/values-br/strings.xml | 6 +- .../menu/src/main/res/values-cak/strings.xml | 14 +- .../menu/src/main/res/values-eo/strings.xml | 14 +- .../menu/src/main/res/values-eu/strings.xml | 14 +- .../menu/src/main/res/values-kab/strings.xml | 8 +- .../menu/src/main/res/values-sc/strings.xml | 12 +- .../menu2/ext/BrowserMenuPositioningTest.kt | 5 +- .../src/androidTest/assets/index.html | 8 +- .../browser/state/action/BrowserAction.kt | 49 +- .../middleware/CreateEngineSessionMiddleware.kt | 6 +- .../engine/middleware/EngineDelegateMiddleware.kt | 4 +- .../state/engine/middleware/LinkingMiddleware.kt | 13 +- .../engine/middleware/TranslationsMiddleware.kt | 160 +- .../browser/state/ext/TabSessionState.kt | 18 + .../state/reducer/TranslationsStateReducer.kt | 100 +- .../state/state/TranslationsBrowserState.kt | 4 + .../browser/state/state/TranslationsState.kt | 2 - .../browser/state/action/TranslationsActionTest.kt | 101 +- .../middleware/EngineDelegateMiddlewareTest.kt | 1 + .../engine/middleware/LinkingMiddlewareTest.kt | 4 +- .../middleware/TranslationsMiddlewareTest.kt | 181 +- .../browser/state/ext/TabSessionStateTest.kt | 34 + .../toolbar/src/main/res/values-nb-rNO/strings.xml | 3 +- .../toolbar/src/main/res/values-sc/strings.xml | 5 +- .../toolbar/src/main/res/values/strings.xml | 1 + .../components/browser/toolbar2/README.md | 37 + .../components/browser/toolbar2/build.gradle | 56 + .../components/browser/toolbar2/proguard-rules.pro | 21 + .../browser/toolbar2/src/main/AndroidManifest.xml | 4 + .../components/browser/toolbar2/BrowserToolbar.kt | 691 + .../browser/toolbar2/display/DisplayToolbar.kt | 711 + .../browser/toolbar2/display/DisplayToolbarView.kt | 51 + .../browser/toolbar2/display/HighlightView.kt | 91 + .../browser/toolbar2/display/MenuButton.kt | 106 + .../browser/toolbar2/display/OriginView.kt | 198 + .../toolbar2/display/SiteSecurityIconView.kt | 48 + .../toolbar2/display/TrackingProtectionIconView.kt | 135 + .../browser/toolbar2/edit/EditToolbar.kt | 415 + .../browser/toolbar2/facts/ToolbarFacts.kt | 60 + .../browser/toolbar2/internal/ActionContainer.kt | 134 + .../browser/toolbar2/internal/ActionWrapper.kt | 16 + ...ac_browser_toolbar_icons_vertical_separator.xml | 9 + .../main/res/drawable/mozac_dot_notification.xml | 16 + .../main/res/drawable/mozac_ic_site_security.xml | 11 + ...mozac_ic_tracking_protection_off_for_a_site.xml | 12 + ..._tracking_protection_on_no_trackers_blocked.xml | 12 + ..._ic_tracking_protection_on_trackers_blocked.xml | 12 + .../mozac_browser_toolbar_displaytoolbar.xml | 175 + .../layout/mozac_browser_toolbar_edittoolbar.xml | 116 + .../toolbar2/src/main/res/values-am/strings.xml | 18 + .../toolbar2/src/main/res/values-an/strings.xml | 16 + .../toolbar2/src/main/res/values-ar/strings.xml | 18 + .../toolbar2/src/main/res/values-ast/strings.xml | 18 + .../toolbar2/src/main/res/values-az/strings.xml | 16 + .../toolbar2/src/main/res/values-azb/strings.xml | 19 + .../toolbar2/src/main/res/values-ban/strings.xml | 8 + .../toolbar2/src/main/res/values-be/strings.xml | 19 + .../toolbar2/src/main/res/values-bg/strings.xml | 19 + .../toolbar2/src/main/res/values-bn/strings.xml | 18 + .../toolbar2/src/main/res/values-br/strings.xml | 19 + .../toolbar2/src/main/res/values-bs/strings.xml | 19 + .../toolbar2/src/main/res/values-ca/strings.xml | 18 + .../toolbar2/src/main/res/values-cak/strings.xml | 19 + .../toolbar2/src/main/res/values-ceb/strings.xml | 18 + .../toolbar2/src/main/res/values-ckb/strings.xml | 18 + .../toolbar2/src/main/res/values-co/strings.xml | 19 + .../toolbar2/src/main/res/values-cs/strings.xml | 19 + .../toolbar2/src/main/res/values-cy/strings.xml | 19 + .../toolbar2/src/main/res/values-da/strings.xml | 19 + .../toolbar2/src/main/res/values-de/strings.xml | 19 + .../toolbar2/src/main/res/values-dsb/strings.xml | 19 + .../toolbar2/src/main/res/values-el/strings.xml | 19 + .../src/main/res/values-en-rCA/strings.xml | 19 + .../src/main/res/values-en-rGB/strings.xml | 19 + .../toolbar2/src/main/res/values-eo/strings.xml | 19 + .../src/main/res/values-es-rAR/strings.xml | 19 + .../src/main/res/values-es-rCL/strings.xml | 19 + .../src/main/res/values-es-rES/strings.xml | 19 + .../src/main/res/values-es-rMX/strings.xml | 18 + .../toolbar2/src/main/res/values-es/strings.xml | 19 + .../toolbar2/src/main/res/values-et/strings.xml | 18 + .../toolbar2/src/main/res/values-eu/strings.xml | 19 + .../toolbar2/src/main/res/values-fa/strings.xml | 18 + .../toolbar2/src/main/res/values-ff/strings.xml | 16 + .../toolbar2/src/main/res/values-fi/strings.xml | 19 + .../toolbar2/src/main/res/values-fr/strings.xml | 19 + .../toolbar2/src/main/res/values-fur/strings.xml | 19 + .../src/main/res/values-fy-rNL/strings.xml | 19 + .../src/main/res/values-ga-rIE/strings.xml | 16 + .../toolbar2/src/main/res/values-gd/strings.xml | 18 + .../toolbar2/src/main/res/values-gl/strings.xml | 19 + .../toolbar2/src/main/res/values-gn/strings.xml | 19 + .../src/main/res/values-gu-rIN/strings.xml | 16 + .../src/main/res/values-hi-rIN/strings.xml | 16 + .../toolbar2/src/main/res/values-hil/strings.xml | 8 + .../toolbar2/src/main/res/values-hr/strings.xml | 18 + .../toolbar2/src/main/res/values-hsb/strings.xml | 19 + .../toolbar2/src/main/res/values-hu/strings.xml | 19 + .../src/main/res/values-hy-rAM/strings.xml | 19 + .../toolbar2/src/main/res/values-ia/strings.xml | 19 + .../toolbar2/src/main/res/values-in/strings.xml | 18 + .../toolbar2/src/main/res/values-is/strings.xml | 19 + .../toolbar2/src/main/res/values-it/strings.xml | 19 + .../toolbar2/src/main/res/values-iw/strings.xml | 19 + .../toolbar2/src/main/res/values-ja/strings.xml | 19 + .../toolbar2/src/main/res/values-ka/strings.xml | 18 + .../toolbar2/src/main/res/values-kaa/strings.xml | 18 + .../toolbar2/src/main/res/values-kab/strings.xml | 19 + .../toolbar2/src/main/res/values-kk/strings.xml | 19 + .../toolbar2/src/main/res/values-kmr/strings.xml | 18 + .../toolbar2/src/main/res/values-kn/strings.xml | 16 + .../toolbar2/src/main/res/values-ko/strings.xml | 19 + .../toolbar2/src/main/res/values-ldrtl/dimens.xml | 8 + .../toolbar2/src/main/res/values-lij/strings.xml | 16 + .../toolbar2/src/main/res/values-lo/strings.xml | 18 + .../toolbar2/src/main/res/values-lt/strings.xml | 18 + .../toolbar2/src/main/res/values-mix/strings.xml | 18 + .../toolbar2/src/main/res/values-ml/strings.xml | 16 + .../toolbar2/src/main/res/values-mr/strings.xml | 16 + .../toolbar2/src/main/res/values-my/strings.xml | 18 + .../src/main/res/values-nb-rNO/strings.xml | 19 + .../src/main/res/values-ne-rNP/strings.xml | 18 + .../toolbar2/src/main/res/values-nl/strings.xml | 19 + .../src/main/res/values-nn-rNO/strings.xml | 19 + .../toolbar2/src/main/res/values-oc/strings.xml | 19 + .../toolbar2/src/main/res/values-or/strings.xml | 16 + .../src/main/res/values-pa-rIN/strings.xml | 19 + .../src/main/res/values-pa-rPK/strings.xml | 18 + .../toolbar2/src/main/res/values-pl/strings.xml | 19 + .../src/main/res/values-pt-rBR/strings.xml | 19 + .../src/main/res/values-pt-rPT/strings.xml | 19 + .../toolbar2/src/main/res/values-rm/strings.xml | 19 + .../toolbar2/src/main/res/values-ro/strings.xml | 16 + .../toolbar2/src/main/res/values-ru/strings.xml | 19 + .../toolbar2/src/main/res/values-sat/strings.xml | 19 + .../toolbar2/src/main/res/values-sc/strings.xml | 19 + .../toolbar2/src/main/res/values-si/strings.xml | 19 + .../toolbar2/src/main/res/values-sk/strings.xml | 19 + .../toolbar2/src/main/res/values-skr/strings.xml | 19 + .../toolbar2/src/main/res/values-sl/strings.xml | 19 + .../toolbar2/src/main/res/values-sq/strings.xml | 19 + .../toolbar2/src/main/res/values-sr/strings.xml | 18 + .../toolbar2/src/main/res/values-su/strings.xml | 19 + .../src/main/res/values-sv-rSE/strings.xml | 19 + .../toolbar2/src/main/res/values-szl/strings.xml | 18 + .../toolbar2/src/main/res/values-ta/strings.xml | 18 + .../toolbar2/src/main/res/values-te/strings.xml | 18 + .../toolbar2/src/main/res/values-tg/strings.xml | 19 + .../toolbar2/src/main/res/values-th/strings.xml | 18 + .../toolbar2/src/main/res/values-tl/strings.xml | 18 + .../toolbar2/src/main/res/values-tok/strings.xml | 14 + .../toolbar2/src/main/res/values-tr/strings.xml | 19 + .../toolbar2/src/main/res/values-trs/strings.xml | 18 + .../toolbar2/src/main/res/values-tt/strings.xml | 18 + .../toolbar2/src/main/res/values-tzm/strings.xml | 10 + .../toolbar2/src/main/res/values-ug/strings.xml | 19 + .../toolbar2/src/main/res/values-uk/strings.xml | 19 + .../toolbar2/src/main/res/values-ur/strings.xml | 18 + .../toolbar2/src/main/res/values-uz/strings.xml | 18 + .../toolbar2/src/main/res/values-vec/strings.xml | 16 + .../toolbar2/src/main/res/values-vi/strings.xml | 19 + .../toolbar2/src/main/res/values-yo/strings.xml | 18 + .../src/main/res/values-zh-rCN/strings.xml | 19 + .../src/main/res/values-zh-rTW/strings.xml | 19 + .../src/main/res/values/attrs_browser_toolbar.xml | 33 + .../toolbar2/src/main/res/values/dimens.xml | 31 + .../browser/toolbar2/src/main/res/values/ids.xml | 8 + .../toolbar2/src/main/res/values/strings.xml | 22 + .../browser/toolbar2/AsyncFilterListenerTest.kt | 350 + .../browser/toolbar2/BrowserToolbarTest.kt | 1044 + .../browser/toolbar2/display/DisplayToolbarTest.kt | 824 + .../browser/toolbar2/display/HighlightViewTest.kt | 67 + .../browser/toolbar2/display/MenuButtonTest.kt | 156 + .../display/TrackingProtectionIconViewTest.kt | 43 + .../browser/toolbar2/edit/EditToolbarTest.kt | 290 + .../toolbar2/internal/ActionContainerTest.kt | 99 + .../org.mockito.plugins.MockMaker | 2 + .../components/compose/cfr/CFRPopupLayout.kt | 160 + .../engine/translate/TranslationEngineState.kt | 2 + .../engine/translate/TranslationOperation.kt | 7 + .../engine/webextension/WebExtensionDelegate.kt | 7 + .../src/test/resources/manifests/example_mdn.json | 67 +- .../src/test/resources/manifests/spec_typical.json | 78 +- .../test/resources/manifests/twitter_mobile.json | 29 +- .../components/concept/sync/AccountEvent.kt | 6 + .../mozilla/components/concept/sync/Devices.kt | 1 + .../components/concept/sync/OAuthAccount.kt | 19 + .../components/feature/accounts-push/build.gradle | 3 + .../feature/accounts/push/CloseTabsFeature.kt | 93 + .../feature/accounts/push/CloseTabsUseCases.kt | 63 + .../feature/accounts/push/SendTabFeature.kt | 8 +- .../feature/accounts/push/CloseTabsFeatureTest.kt | 136 + .../feature/accounts/push/CloseTabsUseCasesTest.kt | 100 + .../feature/accounts/push/EventsObserverTest.kt | 48 - .../accounts/push/TabReceivedEventsObserverTest.kt | 48 + .../accounts/push/TabsClosedEventsObserverTest.kt | 183 + .../assets/extensions/fxawebchannel/background.js | 10 +- .../extensions/fxawebchannel/fxawebchannel.js | 12 +- .../feature/accounts/FirefoxAccountsAuthFeature.kt | 13 +- .../feature/accounts/FxaWebChannelFeature.kt | 36 + .../accounts/FirefoxAccountsAuthFeatureTest.kt | 14 +- .../feature/accounts/FxaWebChannelFeatureTest.kt | 86 + .../components/feature/addons/AddonManager.kt | 8 +- .../feature/addons/ui/PermissionsDialogFragment.kt | 10 +- .../addons/src/main/res/values-be/strings.xml | 14 + .../addons/src/main/res/values-br/strings.xml | 34 +- .../addons/src/main/res/values-cak/strings.xml | 68 +- .../addons/src/main/res/values-eo/strings.xml | 68 +- .../addons/src/main/res/values-eu/strings.xml | 68 +- .../addons/src/main/res/values-it/strings.xml | 20 +- .../addons/src/main/res/values-kab/strings.xml | 28 +- .../addons/src/main/res/values-ko/strings.xml | 2 +- .../addons/src/main/res/values-nn-rNO/strings.xml | 28 +- .../addons/src/main/res/values-sc/strings.xml | 60 +- .../addons/src/main/res/values-si/strings.xml | 8 + .../addons/src/main/res/values-su/strings.xml | 30 +- .../addons/src/test/java/AddonManagerTest.kt | 11 +- .../addons/ui/PermissionsDialogFragmentTest.kt | 10 +- .../amo_search_localized_single_result.json | 73 +- .../resources/amo_search_multiple_results.json | 85 +- .../test/resources/amo_search_single_result.json | 73 +- .../addons/src/test/resources/collection.json | 73 +- .../resources/collection_with_empty_values.json | 50 +- .../src/test/resources/localized_collection.json | 73 +- .../app-links/src/main/res/values-sk/strings.xml | 2 +- .../components/feature/contextmenu/build.gradle | 1 + .../feature/downloads/DownloadMiddleware.kt | 12 +- .../feature/downloads/DownloadNotification.kt | 7 +- .../downloads/src/main/res/values-ast/strings.xml | 3 - .../components/feature/findinpage/build.gradle | 1 + .../components/feature/media/build.gradle | 1 + .../media/src/main/res/values-br/strings.xml | 19 + .../media/src/main/res/values-nn-rNO/strings.xml | 6 + .../media/src/main/res/values-su/strings.xml | 9 +- .../components/feature/prompts/PromptFeature.kt | 37 +- .../prompts/dialog/TextPromptDialogFragment.kt | 13 + .../prompts/src/main/res/values-br/strings.xml | 76 +- .../prompts/src/main/res/values-kab/strings.xml | 2 + .../prompts/src/main/res/values-nn-rNO/strings.xml | 13 + .../prompts/src/main/res/values-sc/strings.xml | 68 +- .../prompts/src/main/res/values-sl/strings.xml | 68 +- .../prompts/src/main/res/values-su/strings.xml | 70 +- .../src/main/res/values/quarantined_strings.xml | 2 +- .../feature/prompts/PromptFeatureTest.kt | 133 + .../prompts/dialog/TextPromptDialogFragmentTest.kt | 29 +- .../feature/pwa/WebAppShortcutManager.kt | 3 +- .../components/feature/qr/build.gradle | 1 + .../extensions/readerview/manifest.template.json | 5 +- .../extensions/readerview/readerview-background.js | 24 +- .../extensions/readerview/readerview-content.js | 64 +- .../assets/extensions/readerview/readerview.html | 14 +- .../assets/extensions/readerview/readerview.js | 194 +- .../src/main/assets/extensions/ads/adsTelemetry.js | 72 +- .../assets/extensions/search/searchTelemetry.js | 54 +- .../search/src/main/assets/search/list.json | 683 +- .../src/main/assets/searchplugins/amazon-au.xml | 12 - .../src/main/assets/searchplugins/amazon-ca.xml | 13 - .../src/main/assets/searchplugins/amazon-co-uk.xml | 13 - .../src/main/assets/searchplugins/amazon-de.xml | 13 - .../src/main/assets/searchplugins/amazon-es.xml | 13 - .../src/main/assets/searchplugins/amazon-fr.xml | 13 - .../src/main/assets/searchplugins/amazon-in.xml | 13 - .../src/main/assets/searchplugins/amazon-it.xml | 13 - .../src/main/assets/searchplugins/amazon-nl.xml | 13 - .../src/main/assets/searchplugins/amazon-se.xml | 13 - .../src/main/assets/searchplugins/amazondotcom.xml | 13 - .../feature/search/internal/SearchUrlBuilder.kt | 6 +- .../search/middleware/SearchMiddlewareTest.kt | 215 +- .../storage/BundledSearchEnginesStorageTest.kt | 18 +- .../components/feature/session/SessionFeature.kt | 17 + .../components/feature/session/SessionUseCases.kt | 6 - .../feature/session/SessionFeatureTest.kt | 74 +- .../feature/session/SessionUseCasesTest.kt | 1 - .../src/main/res/values-ast/strings.xml | 4 +- .../components/feature/tabs/TabsUseCases.kt | 2 + .../extensions/webcompat-reporter/manifest.json | 11 +- .../components/lib/auth/build.gradle | 1 + .../components/lib/crash/build.gradle | 1 + .../lib/crash/src/main/res/values-ast/strings.xml | 6 +- .../crash/src/main/res/values-zh-rCN/strings.xml | 2 +- .../components/lib/push-firebase/build.gradle | 1 + .../push/firebase/AbstractFirebasePushService.kt | 2 +- .../components/service/fxa/AccountStorage.kt | 19 +- .../components/service/fxa/FirefoxAccount.kt | 11 + .../service/fxa/FxaDeviceConstellation.kt | 3 + .../java/mozilla/components/service/fxa/Types.kt | 25 + .../service/fxa/manager/FxaAccountManager.kt | 84 +- .../components/service/fxa/manager/State.kt | 31 +- .../components/service/fxa/store/SyncAction.kt | 6 + .../components/service/fxa/store/SyncState.kt | 5 +- .../components/service/fxa/store/SyncStore.kt | 1 + .../service/fxa/store/SyncStoreSupport.kt | 32 + .../service/fxa/FxaAccountManagerTest.kt | 192 +- .../components/service/fxa/manager/StateKtTest.kt | 4 +- .../service/fxa/store/SyncStoreSupportTest.kt | 106 +- .../java/mozilla/components/service/glean/Glean.kt | 4 +- .../service/glean/config/Configuration.kt | 4 + .../service/glean/private/MetricAliases.kt | 87 +- .../components/service/nimbus/messaging.fml.yaml | 29 +- .../pocket/stories_recommendations_response.json | 84 +- .../pocket/story_recommendation_response.json | 20 +- .../sync/logins/GeckoLoginStorageDelegate.kt | 9 + .../components/support/base/build.gradle | 1 + .../support/base/android/BuildVersionProvider.kt | 31 + .../base/android/PowerManagerInfoProvider.kt | 39 + .../support/base/android/ProcessInfoProvider.kt | 32 + .../support/base/android/StartForegroundService.kt | 41 + .../support/base/feature/UserInteractionHandler.kt | 7 + .../base/feature/ViewBoundFeatureWrapper.kt | 18 + .../base/android/StartForegroundServiceTest.kt | 98 + .../base/feature/ViewBoundFeatureWrapperTest.kt | 50 + .../components/support/ktx/android/view/View.kt | 5 +- .../components/support/ktx/kotlin/String.kt | 20 +- .../components/support/ktx/kotlin/StringTest.kt | 88 +- .../support/webextensions/WebExtensionSupport.kt | 4 + .../webextensions/WebExtensionSupportTest.kt | 19 + .../ui/autocomplete/InlineAutocompleteEditText.kt | 4 +- .../autocomplete/InlineAutocompleteEditTextTest.kt | 15 +- .../mozac_ic_private_mode_circle_fill_20.xml | 10 +- .../mozac_ic_private_mode_circle_fill_24.xml | 10 +- .../ui/icons/src/main/res/values/attrs.xml | 6 +- .../behavior/EngineViewScrollingBehaviorTest.kt | 2 +- .../android-components/config/detekt-baseline.xml | 1 + .../android-components/docs/_includes/header.html | 34 +- .../docs/_includes/post_detail.html | 22 +- .../android-components/docs/_layouts/post.html | 61 +- .../android-components/docs/assets/js/icon-js.js | 145 +- .../android/android-components/docs/changelog.md | 27 +- .../gradle/wrapper/gradle-wrapper.properties | 4 + .../src/main/java/ApplicationServices.kt | 2 +- .../src/main/java/DependenciesPlugin.kt | 31 +- .../plugins/dependencies/src/main/java/moz.yaml | 4 +- .../browser/src/androidTest/assets/index.html | 6 +- .../main/assets/extensions/borderify/borderify.js | 2 +- .../src/main/assets/extensions/test/background.js | 11 +- .../assets/extensions/test/manifest.template.json | 4 +- .../mozilla/samples/browser/BaseBrowserFragment.kt | 1 + .../android-components/samples/crash/build.gradle | 1 + .../samples/firefox-accounts/build.gradle | 1 + .../java/org/mozilla/samples/sync/MainActivity.kt | 9 +- .../samples/toolbar/build.gradle | 1 + mobile/android/android-components/settings.gradle | 69 +- mobile/android/app/geckoview-prefs.js | 3 - mobile/android/autopublish-settings.gradle | 114 + mobile/android/chrome/geckoview/geckoview.js | 2 +- .../android/components/extensions/ext-android.js | 18 +- .../components/geckoview/GeckoViewStartup.sys.mjs | 4 + .../geckoview/SessionStoreFunctions.sys.mjs | 61 + .../android/components/geckoview/components.conf | 6 + mobile/android/components/geckoview/moz.build | 1 + .../android-arm-gradle-dependencies/base | 2 +- mobile/android/config/mozconfigs/common | 2 +- mobile/android/docs/fenix.rst | 95 +- .../geckoview/contributor/contributing-to-mc.rst | 1 + .../contributor/geckoview-quick-start.rst | 136 +- mobile/android/docs/index.rst | 176 +- mobile/android/docs/overview.rst | 26 - .../android/docs/shared/android/device_testing.md | 2 +- mobile/android/fenix/app/build.gradle | 11 +- mobile/android/fenix/app/lint-baseline.xml | 49 +- mobile/android/fenix/app/messaging-fenix.fml.yaml | 7 + mobile/android/fenix/app/metrics.yaml | 49 +- mobile/android/fenix/app/nimbus.fml.yaml | 27 +- .../src/androidTest/assets/pages/addressForm.html | 24 +- .../androidTest/assets/pages/audioMediaPage.html | 20 +- .../androidTest/assets/pages/creditCardForm.html | 38 +- .../assets/pages/cross-site-cookies.html | 20 +- .../androidTest/assets/pages/externalLinks.html | 34 +- .../app/src/androidTest/assets/pages/generic1.html | 14 +- .../app/src/androidTest/assets/pages/generic2.html | 14 +- .../app/src/androidTest/assets/pages/generic3.html | 27 +- .../app/src/androidTest/assets/pages/generic4.html | 16 +- .../assets/pages/global_privacy_control.html | 23 +- .../src/androidTest/assets/pages/lorem-ipsum.html | 60 +- .../androidTest/assets/pages/mutedVideoPage.html | 68 +- .../app/src/androidTest/assets/pages/password.html | 37 +- .../androidTest/assets/pages/passwordsubmit.html | 12 +- .../app/src/androidTest/assets/pages/refresh.html | 61 +- .../androidTest/assets/pages/storage_check.html | 34 +- .../androidTest/assets/pages/storage_write.html | 42 +- .../androidTest/assets/pages/videoMediaPage.html | 68 +- .../mozilla/fenix/helpers/AppAndSystemHelper.kt | 10 + .../mozilla/fenix/syncintegration/test_bookmark.js | 18 +- .../fenix/syncintegration/test_bookmark_desktop.js | 17 +- .../mozilla/fenix/syncintegration/test_history.js | 26 +- .../fenix/syncintegration/test_history_desktop.js | 15 +- .../mozilla/fenix/syncintegration/test_logins.js | 15 +- .../java/org/mozilla/fenix/ui/BookmarksTest.kt | 13 - .../org/mozilla/fenix/ui/ComposeBookmarksTest.kt | 10 - .../mozilla/fenix/ui/ComposeCrashReportingTest.kt | 2 - .../org/mozilla/fenix/ui/ComposeHistoryTest.kt | 2 - .../java/org/mozilla/fenix/ui/ComposeSearchTest.kt | 11 +- .../org/mozilla/fenix/ui/CrashReportingTest.kt | 2 - .../org/mozilla/fenix/ui/FirefoxSuggestTest.kt | 2 +- .../java/org/mozilla/fenix/ui/LoginsTest.kt | 13 +- .../fenix/ui/NimbusMessagingHomescreenTest.kt | 2 +- .../java/org/mozilla/fenix/ui/OnboardingTest.kt | 49 +- .../java/org/mozilla/fenix/ui/PocketTest.kt | 2 + .../java/org/mozilla/fenix/ui/SearchTest.kt | 9 +- .../java/org/mozilla/fenix/ui/SettingsAboutTest.kt | 6 +- .../org/mozilla/fenix/ui/SettingsHomepageTest.kt | 4 +- .../org/mozilla/fenix/ui/SettingsSearchTest.kt | 20 +- .../fenix/ui/SettingsSitePermissionsTest.kt | 3 - .../org/mozilla/fenix/ui/robots/HomeScreenRobot.kt | 8 +- .../ui/robots/SettingsSubMenuHomepageRobot.kt | 2 +- .../fenix/ui/robots/ThreeDotMenuMainRobot.kt | 15 +- .../fenix/app/src/beta/res/xml/shortcuts.xml | 9 +- .../fenix/app/src/debug/res/xml/shortcuts.xml | 9 +- .../app/src/main/assets/highRiskErrorPages.js | 48 +- .../app/src/main/assets/high_risk_error_pages.html | 5 +- .../app/src/main/assets/lowMediumErrorPages.js | 212 +- .../assets/low_and_medium_risk_error_pages.html | 18 +- .../app/src/main/assets/shared_error_style.css | 1 - .../main/java/org/mozilla/fenix/FeatureFlags.kt | 7 +- .../java/org/mozilla/fenix/FenixApplication.kt | 7 +- .../main/java/org/mozilla/fenix/HomeActivity.kt | 39 +- .../org/mozilla/fenix/OnBackLongPressedListener.kt | 21 - .../org/mozilla/fenix/OnLongPressedListener.kt | 28 + .../fenix/addons/AddonsManagementFragment.kt | 8 +- .../mozilla/fenix/browser/BaseBrowserFragment.kt | 96 +- .../org/mozilla/fenix/browser/BrowserFragment.kt | 236 +- .../fenix/browser/StandardSnackbarErrorBinding.kt | 2 +- .../mozilla/fenix/browser/SwipeGestureLayout.kt | 3 +- .../java/org/mozilla/fenix/browser/TabPreview.kt | 4 +- .../fenix/browser/tabstrip/TabStripFeatureFlag.kt | 31 + .../mozilla/fenix/components/BackgroundServices.kt | 14 +- .../org/mozilla/fenix/components/Components.kt | 2 +- .../fenix/components/NotificationManager.kt | 74 + .../mozilla/fenix/components/appstate/AppAction.kt | 16 +- .../mozilla/fenix/components/appstate/AppState.kt | 6 +- .../fenix/components/appstate/AppStoreReducer.kt | 8 +- .../fenix/components/bookmarks/BookmarksUseCase.kt | 37 +- .../components/menu/BrowserNavigationParams.kt | 18 + .../fenix/components/menu/MenuAccessPoint.kt | 38 + .../fenix/components/menu/MenuDialogFragment.kt | 253 +- .../components/menu/compose/ExtensionsSubmenu.kt | 101 + .../fenix/components/menu/compose/MainMenu.kt | 365 + .../fenix/components/menu/compose/MenuDialog.kt | 203 - .../fenix/components/menu/compose/MenuGroup.kt | 5 +- .../fenix/components/menu/compose/MenuItem.kt | 202 + .../fenix/components/menu/compose/SaveSubmenu.kt | 163 + .../fenix/components/menu/compose/ToolsSubmenu.kt | 184 + .../components/menu/compose/header/MenuHeader.kt | 16 +- .../compose/header/MozillaAccountMenuButton.kt | 82 +- .../menu/compose/header/SubmenuHeader.kt | 94 + .../menu/middleware/MenuDialogMiddleware.kt | 93 + .../menu/middleware/MenuNavigationMiddleware.kt | 134 +- .../fenix/components/menu/store/MenuAction.kt | 97 +- .../fenix/components/menu/store/MenuState.kt | 27 +- .../fenix/components/menu/store/MenuStore.kt | 33 +- .../components/toolbar/BrowserToolbarController.kt | 24 +- .../toolbar/BrowserToolbarMenuController.kt | 51 +- .../fenix/components/toolbar/BrowserToolbarView.kt | 22 +- .../fenix/components/toolbar/DefaultToolbarMenu.kt | 9 + .../fenix/components/toolbar/ToolbarMenu.kt | 5 + .../fenix/compose/DismissibleItemBackground.kt | 2 +- .../org/mozilla/fenix/compose/button/Button.kt | 4 +- .../org/mozilla/fenix/compose/button/TextButton.kt | 16 +- .../org/mozilla/fenix/compose/list/ListItem.kt | 196 +- .../compose/tabstray/DismissedTabBackground.kt | 4 +- .../fenix/customtabs/ExternalAppBrowserFragment.kt | 6 +- .../mozilla/fenix/debugsettings/tabs/TabTools.kt | 11 +- .../mozilla/fenix/debugsettings/ui/DebugOverlay.kt | 2 + .../mozilla/fenix/downloads/StartDownloadDialog.kt | 11 +- .../main/java/org/mozilla/fenix/ext/Activity.kt | 21 + .../main/java/org/mozilla/fenix/ext/AppState.kt | 2 +- .../src/main/java/org/mozilla/fenix/ext/Bitmap.kt | 18 +- .../src/main/java/org/mozilla/fenix/ext/Context.kt | 6 + .../main/java/org/mozilla/fenix/ext/Fragment.kt | 55 + .../src/main/java/org/mozilla/fenix/ext/View.kt | 109 +- .../java/org/mozilla/fenix/gecko/GeckoProvider.kt | 5 +- .../java/org/mozilla/fenix/home/HomeFragment.kt | 74 +- .../main/java/org/mozilla/fenix/home/HomeMenu.kt | 14 + .../java/org/mozilla/fenix/home/HomeMenuView.kt | 17 + .../java/org/mozilla/fenix/home/ToolbarView.kt | 3 +- .../fenix/home/blocklist/BlocklistHandler.kt | 4 +- .../fenix/home/blocklist/BlocklistMiddleware.kt | 12 +- .../fenix/home/bookmarks/BookmarksFeature.kt | 59 + .../bookmarks/controller/BookmarksController.kt | 86 + .../bookmarks/interactor/BookmarksInteractor.kt | 36 + .../mozilla/fenix/home/bookmarks/view/Bookmarks.kt | 253 + .../bookmarks/view/BookmarksHeaderViewHolder.kt | 61 + .../fenix/home/bookmarks/view/BookmarksMenuItem.kt | 18 + .../home/bookmarks/view/BookmarksViewHolder.kt | 56 + .../fenix/home/collections/CollectionViewHolder.kt | 2 +- .../intent/OpenRecentlyClosedIntentProcessor.kt | 30 + .../home/recentbookmarks/RecentBookmarksFeature.kt | 60 - .../controller/RecentBookmarksController.kt | 86 - .../interactor/RecentBookmarksInteractor.kt | 36 - .../home/recentbookmarks/view/RecentBookmarks.kt | 253 - .../view/RecentBookmarksHeaderViewHolder.kt | 61 - .../view/RecentBookmarksMenuItem.kt | 18 - .../view/RecentBookmarksViewHolder.kt | 53 - .../home/sessioncontrol/SessionControlAdapter.kt | 25 +- .../sessioncontrol/SessionControlController.kt | 4 +- .../sessioncontrol/SessionControlInteractor.kt | 20 +- .../home/sessioncontrol/SessionControlView.kt | 12 +- .../mozilla/fenix/home/topsites/PagerIndicator.kt | 4 +- .../fenix/library/bookmarks/BookmarkFragment.kt | 9 +- .../fenix/library/bookmarks/BookmarkItemMenu.kt | 2 +- .../library/bookmarks/edit/EditBookmarkFragment.kt | 2 +- .../fenix/library/downloads/DownloadFragment.kt | 2 +- .../fenix/library/downloads/DownloadItemMenu.kt | 2 +- .../fenix/library/history/HistoryFragment.kt | 2 +- .../HistoryMetadataGroupFragment.kt | 2 +- .../recentlyclosed/RecentlyClosedFragment.kt | 2 +- .../fenix/messaging/FenixMessageSurfaceId.kt | 5 + .../mozilla/fenix/messaging/MessagingFeature.kt | 7 +- .../fenix/microsurvey/ui/MicroSurveyContent.kt | 123 + .../fenix/microsurvey/ui/MicroSurveyFooter.kt | 105 + .../fenix/microsurvey/ui/MicroSurveyHeader.kt | 86 + .../fenix/microsurvey/ui/MicroSurveyScaffold.kt | 67 + .../fenix/microsurvey/ui/MicrosurveyBottomSheet.kt | 117 + .../ui/MicrosurveyBottomSheetFragment.kt | 66 + .../microsurvey/ui/MicrosurveyRequestPrompt.kt | 96 + .../mozilla/fenix/onboarding/OnboardingFragment.kt | 11 +- .../fenix/perf/ProfilerStopDialogFragment.kt | 13 +- .../java/org/mozilla/fenix/perf/ProfilerUtils.kt | 6 +- .../mozilla/fenix/search/toolbar/ToolbarView.kt | 3 +- .../fenix/settings/CustomizationFragment.kt | 3 +- .../mozilla/fenix/settings/HomeSettingsFragment.kt | 6 +- .../org/mozilla/fenix/settings/PairFragment.kt | 4 + .../fenix/settings/SecretSettingsFragment.kt | 3 +- .../org/mozilla/fenix/settings/SettingsFragment.kt | 10 + .../org/mozilla/fenix/settings/SupportUtils.kt | 5 + .../mozilla/fenix/settings/SyncDebugFragment.kt | 19 + .../settings/account/AccountProblemFragment.kt | 8 +- .../fenix/settings/account/TurnOnSyncFragment.kt | 3 + .../fenix/settings/biometric/BiometricUtils.kt | 110 + .../creditcards/view/CreditCardEditorView.kt | 8 +- .../settings/logins/fragment/AddLoginFragment.kt | 8 +- .../settings/logins/fragment/EditLoginFragment.kt | 4 +- .../logins/fragment/SavedLoginsAuthFragment.kt | 121 +- .../fenix/settings/search/SearchEngineMenu.kt | 2 +- .../fenix/settings/search/SearchEngineShortcuts.kt | 19 +- .../org/mozilla/fenix/share/SaveToPDFMiddleware.kt | 4 + .../shopping/ui/ReviewQualityCheckInfoCard.kt | 50 +- .../mozilla/fenix/tabstray/SyncedTabsController.kt | 5 + .../mozilla/fenix/tabstray/SyncedTabsInteractor.kt | 5 + .../java/org/mozilla/fenix/tabstray/TabsTray.kt | 43 +- .../mozilla/fenix/tabstray/TabsTrayController.kt | 11 +- .../org/mozilla/fenix/tabstray/TabsTrayFragment.kt | 20 + .../mozilla/fenix/tabstray/TabsTrayInteractor.kt | 4 + .../tabstray/browser/InactiveTabViewHolder.kt | 4 + .../fenix/tabstray/browser/TabsTouchHelper.kt | 2 +- .../mozilla/fenix/tabstray/ext/SyncedDeviceTabs.kt | 41 +- .../fenix/tabstray/inactivetabs/InactiveTabs.kt | 112 +- .../fenix/tabstray/syncedtabs/SyncedTabs.kt | 314 - .../tabstray/syncedtabs/SyncedTabsIntegration.kt | 9 +- .../fenix/tabstray/syncedtabs/SyncedTabsList.kt | 348 + .../tabstray/syncedtabs/SyncedTabsListItem.kt | 18 +- .../syncedtabs/SyncedTabsListSupportedFeature.kt | 12 + .../viewholders/SyncedTabsPageViewHolder.kt | 1 + .../java/org/mozilla/fenix/theme/FirefoxTheme.kt | 167 +- .../fenix/translations/DownloadIndicator.kt | 12 +- .../fenix/translations/TranslationSettings.kt | 5 +- .../translations/TranslationSettingsFragment.kt | 38 +- .../fenix/translations/TranslationsBottomSheet.kt | 6 +- .../translations/TranslationsDialogBinding.kt | 5 +- .../translations/TranslationsDialogBottomSheet.kt | 8 +- .../translations/TranslationsDialogFragment.kt | 35 +- .../translations/TranslationsDialogMiddleware.kt | 13 +- .../AutomaticTranslationItemPreference.kt | 27 +- .../AutomaticTranslationOptionsPreference.kt | 4 + ...utomaticTranslationOptionsPreferenceFragment.kt | 14 +- .../automatic/AutomaticTranslationPreference.kt | 31 +- .../AutomaticTranslationPreferenceFragment.kt | 44 +- .../NeverTranslateSiteDialogPreferenceFragment.kt | 19 +- .../NeverTranslateSiteListItemPreference.kt | 12 - .../NeverTranslateSitePreference.kt | 99 - .../NeverTranslateSitePreferenceFragment.kt | 48 - .../NeverTranslateSitesPreference.kt | 97 + .../NeverTranslateSitesPreferenceFragment.kt | 60 + .../main/java/org/mozilla/fenix/utils/Settings.kt | 23 +- .../src/main/res/drawable/microsurvey_success.xml | 81 + .../main/res/layout/fragment_address_editor.xml | 2 +- .../app/src/main/res/layout/fragment_browser.xml | 4 +- .../res/layout/fragment_credit_card_editor.xml | 2 +- .../app/src/main/res/navigation/nav_graph.xml | 88 +- .../app/src/main/res/raw/initial_experiments.json | 109 +- .../fenix/app/src/main/res/values-azb/strings.xml | 69 +- .../fenix/app/src/main/res/values-be/strings.xml | 234 +- .../fenix/app/src/main/res/values-bg/strings.xml | 174 +- .../fenix/app/src/main/res/values-br/strings.xml | 267 +- .../fenix/app/src/main/res/values-bs/strings.xml | 225 +- .../fenix/app/src/main/res/values-cak/strings.xml | 94 +- .../fenix/app/src/main/res/values-co/strings.xml | 6 +- .../fenix/app/src/main/res/values-cs/strings.xml | 177 +- .../fenix/app/src/main/res/values-cy/strings.xml | 176 +- .../fenix/app/src/main/res/values-da/strings.xml | 193 +- .../fenix/app/src/main/res/values-de/strings.xml | 177 +- .../fenix/app/src/main/res/values-dsb/strings.xml | 176 +- .../fenix/app/src/main/res/values-el/strings.xml | 178 +- .../app/src/main/res/values-en-rCA/strings.xml | 175 +- .../app/src/main/res/values-en-rGB/strings.xml | 177 +- .../fenix/app/src/main/res/values-eo/strings.xml | 176 +- .../app/src/main/res/values-es-rAR/strings.xml | 176 +- .../app/src/main/res/values-es-rCL/strings.xml | 195 +- .../app/src/main/res/values-es-rES/strings.xml | 196 +- .../fenix/app/src/main/res/values-es/strings.xml | 196 +- .../fenix/app/src/main/res/values-eu/strings.xml | 221 +- .../fenix/app/src/main/res/values-fi/strings.xml | 178 +- .../fenix/app/src/main/res/values-fr/strings.xml | 175 +- .../fenix/app/src/main/res/values-fur/strings.xml | 175 +- .../app/src/main/res/values-fy-rNL/strings.xml | 178 +- .../fenix/app/src/main/res/values-gl/strings.xml | 173 +- .../fenix/app/src/main/res/values-gn/strings.xml | 178 +- .../fenix/app/src/main/res/values-hr/strings.xml | 147 +- .../fenix/app/src/main/res/values-hsb/strings.xml | 177 +- .../fenix/app/src/main/res/values-hu/strings.xml | 67 +- .../app/src/main/res/values-hy-rAM/strings.xml | 197 +- .../fenix/app/src/main/res/values-ia/strings.xml | 179 +- .../fenix/app/src/main/res/values-is/strings.xml | 173 +- .../fenix/app/src/main/res/values-it/strings.xml | 176 +- .../fenix/app/src/main/res/values-iw/strings.xml | 177 +- .../fenix/app/src/main/res/values-ja/strings.xml | 279 +- .../fenix/app/src/main/res/values-kab/strings.xml | 223 +- .../fenix/app/src/main/res/values-kk/strings.xml | 178 +- .../fenix/app/src/main/res/values-ko/strings.xml | 176 +- .../app/src/main/res/values-nb-rNO/strings.xml | 228 +- .../fenix/app/src/main/res/values-night/colors.xml | 28 +- .../fenix/app/src/main/res/values-nl/strings.xml | 178 +- .../app/src/main/res/values-nn-rNO/strings.xml | 346 +- .../fenix/app/src/main/res/values-oc/strings.xml | 202 +- .../app/src/main/res/values-pa-rIN/strings.xml | 177 +- .../fenix/app/src/main/res/values-pl/strings.xml | 119 +- .../app/src/main/res/values-pt-rBR/strings.xml | 176 +- .../app/src/main/res/values-pt-rPT/strings.xml | 188 +- .../fenix/app/src/main/res/values-rm/strings.xml | 2 + .../fenix/app/src/main/res/values-ru/strings.xml | 177 +- .../fenix/app/src/main/res/values-sat/strings.xml | 317 +- .../fenix/app/src/main/res/values-sc/strings.xml | 313 +- .../fenix/app/src/main/res/values-si/strings.xml | 60 +- .../fenix/app/src/main/res/values-sk/strings.xml | 175 +- .../fenix/app/src/main/res/values-skr/strings.xml | 107 +- .../fenix/app/src/main/res/values-sl/strings.xml | 304 +- .../fenix/app/src/main/res/values-sq/strings.xml | 67 +- .../fenix/app/src/main/res/values-su/strings.xml | 631 +- .../app/src/main/res/values-sv-rSE/strings.xml | 176 +- .../fenix/app/src/main/res/values-tg/strings.xml | 179 +- .../fenix/app/src/main/res/values-tr/strings.xml | 175 +- .../fenix/app/src/main/res/values-ug/strings.xml | 173 +- .../fenix/app/src/main/res/values-uk/strings.xml | 177 +- .../fenix/app/src/main/res/values-vi/strings.xml | 177 +- .../app/src/main/res/values-zh-rCN/strings.xml | 177 +- .../app/src/main/res/values-zh-rTW/strings.xml | 178 +- .../fenix/app/src/main/res/values/attrs.xml | 2 +- .../fenix/app/src/main/res/values/colors.xml | 46 +- .../app/src/main/res/values/preference_keys.xml | 6 +- .../app/src/main/res/values/static_strings.xml | 6 + .../fenix/app/src/main/res/values/strings.xml | 103 +- .../fenix/app/src/main/res/values/styles.xml | 16 +- .../app/src/main/res/xml/home_preferences.xml | 4 +- .../fenix/app/src/main/res/xml/preferences.xml | 6 + .../fenix/app/src/main/res/xml/shortcuts.xml | 9 +- .../src/main/res/xml/sync_debug_preferences.xml | 9 + .../fenix/app/src/nightly/res/xml/shortcuts.xml | 9 +- .../fenix/app/src/release/res/xml/shortcuts.xml | 9 +- .../java/org/mozilla/fenix/FenixApplicationTest.kt | 23 +- .../mozilla/fenix/browser/BrowserFragmentTest.kt | 256 +- .../browser/StandardSnackbarErrorBindingTest.kt | 2 +- .../fenix/browser/TranslationsBindingTest.kt | 1 + .../org/mozilla/fenix/components/AppStoreTest.kt | 10 +- .../components/bookmarks/BookmarksUseCaseTest.kt | 34 +- .../fenix/components/menu/MenuAccessPointTest.kt | 22 + .../components/menu/MenuDialogMiddlewareTest.kt | 206 + .../menu/MenuNavigationMiddlewareTest.kt | 324 +- .../mozilla/fenix/components/menu/MenuStoreTest.kt | 123 +- .../components/menu/fake/FakeBookmarksStorage.kt | 104 + .../toolbar/DefaultBrowserToolbarControllerTest.kt | 4 +- .../DefaultBrowserToolbarMenuControllerTest.kt | 13 +- .../test/java/org/mozilla/fenix/ext/BitmapTest.kt | 33 + .../test/java/org/mozilla/fenix/ext/ViewTest.kt | 22 + .../home/DefaultSessionControlControllerTest.kt | 29 +- .../org/mozilla/fenix/home/HomeMenuViewTest.kt | 5 + .../fenix/home/SessionControlInteractorTest.kt | 22 +- .../fenix/home/blocklist/BlocklistHandlerTest.kt | 6 +- .../home/blocklist/BlocklistMiddlewareTest.kt | 66 +- .../fenix/home/bookmarks/BookmarksFeatureTest.kt | 73 + .../bookmarks/DefaultBookmarksControllerTest.kt | 153 + .../OpenRecentlyClosedIntentProcessorTest.kt | 74 + .../DefaultRecentBookmarksControllerTest.kt | 153 - .../recentbookmarks/RecentBookmarksFeatureTest.kt | 73 - .../interactor/RecentVisitsInteractorTest.kt | 6 +- .../home/sessioncontrol/SessionControlViewTest.kt | 52 +- .../library/bookmarks/BookmarkItemMenuTest.kt | 2 +- .../fenix/messaging/MessagingFeatureTest.kt | 5 +- .../fenix/search/toolbar/ToolbarViewTest.kt | 5 +- .../settings/PreferenceBackedRadioButtonTest.kt | 1 + .../creditcards/CreditCardEditorViewTest.kt | 6 +- .../mozilla/fenix/share/SaveToPDFMiddlewareTest.kt | 26 +- .../mozilla/fenix/sync/ext/SyncedDeviceTabsTest.kt | 60 +- .../tabstray/DefaultTabsTrayControllerTest.kt | 16 + .../translations/TranslationsDialogBindingTest.kt | 33 +- .../TranslationsDialogMiddlewareTest.kt | 23 +- .../app/src/test/resources/robolectric.properties | 1 - .../androidTest/flank-arm-robo-test.yml | 1 + .../androidTest/flank-arm-start-test-robo.yml | 1 + .../robo-scripts/default-browser-dismissal.json | 11 + .../fenix/benchmark/src/main/AndroidManifest.xml | 3 + mobile/android/fenix/docs/UI-Tests.md | 73 + mobile/android/fenix/docs/index.rst | 3 +- .../fenix/gradle/wrapper/gradle-wrapper.properties | 4 + .../src/main/java/FenixDependenciesPlugin.kt | 6 +- mobile/android/fenix/settings.gradle | 89 +- .../android/fenix/tools/setup-startup-profiling.py | 2 +- mobile/android/focus-android/app/build.gradle | 8 +- mobile/android/focus-android/app/lint-baseline.xml | 3 + .../app/src/androidTest/assets/audioPage.html | 53 +- .../src/androidTest/assets/cross-site-cookies.html | 18 +- .../androidTest/assets/etpPages/adsTrackers.html | 31 +- .../assets/etpPages/analyticsTrackers.html | 31 +- .../androidTest/assets/etpPages/otherTrackers.html | 33 +- .../assets/etpPages/socialTrackers.html | 31 +- .../app/src/androidTest/assets/genericPage.html | 19 +- .../androidTest/assets/global_privacy_control.html | 23 +- .../app/src/androidTest/assets/htmlControls.html | 54 +- .../app/src/androidTest/assets/image_test.html | 37 +- .../app/src/androidTest/assets/mutedVideoPage.html | 77 +- .../src/androidTest/assets/same-site-cookies.html | 214 +- .../app/src/androidTest/assets/storage_check.html | 34 +- .../app/src/androidTest/assets/storage_start.html | 42 +- .../app/src/androidTest/assets/tab1.html | 35 +- .../app/src/androidTest/assets/tab2.html | 12 +- .../app/src/androidTest/assets/tab3.html | 16 +- .../app/src/androidTest/assets/test.html | 66 +- .../app/src/androidTest/assets/videoPage.html | 77 +- .../java/org/mozilla/focus/activity/SearchTest.kt | 2 +- .../java/org/mozilla/focus/FocusApplication.kt | 21 +- .../org/mozilla/focus/fragment/BrowserFragment.kt | 1 + .../focus-android/app/src/main/res/raw/about.html | 67 +- .../focus-android/app/src/main/res/raw/gpl.html | 1531 +- .../app/src/main/res/raw/licenses.html | 2243 ++- .../focus-android/app/src/main/res/raw/rights.html | 60 +- .../app/src/main/res/values-quc/strings.xml | 56 +- .../app/src/main/res/values-su/strings.xml | 4 +- .../java/org/mozilla/focus/TestFocusApplication.kt | 11 + .../integration/FullScreenIntegrationTest.kt | 134 +- .../app/src/test/resources/robolectric.properties | 2 - .../gradle/wrapper/gradle-wrapper.properties | 4 + .../src/main/java/FocusDependenciesPlugin.kt | 2 +- mobile/android/focus-android/settings.gradle | 89 +- mobile/android/geckoview/api.txt | 31 +- mobile/android/geckoview/build.gradle | 16 +- .../optional-permission-all-urls/manifest.json | 13 + .../geckoview/test/GeckoSessionTestRuleTest.kt | 3 - .../org/mozilla/geckoview/test/GeckoViewTest.kt | 3 +- .../org/mozilla/geckoview/test/ScreenshotTest.kt | 26 +- .../org/mozilla/geckoview/test/TelemetryTest.kt | 131 - .../org/mozilla/geckoview/test/TranslationsTest.kt | 115 +- .../org/mozilla/geckoview/test/WebExtensionTest.kt | 102 +- .../geckoview/test/rule/GeckoSessionTestRule.java | 6 - .../geckoview/test/util/RuntimeCreator.java | 50 - .../java/org/mozilla/geckoview/GeckoDisplay.java | 6 +- .../java/org/mozilla/geckoview/GeckoEditable.java | 6 +- .../mozilla/geckoview/GeckoRuntimeSettings.java | 26 - .../java/org/mozilla/geckoview/GeckoSession.java | 4 + .../mozilla/geckoview/GeckoSessionSettings.java | 18 +- .../main/java/org/mozilla/geckoview/GeckoView.java | 10 + .../org/mozilla/geckoview/RuntimeTelemetry.java | 171 - .../java/org/mozilla/geckoview/ScreenLength.java | 4 +- .../java/org/mozilla/geckoview/SessionFinder.java | 4 +- .../mozilla/geckoview/TranslationsController.java | 38 +- .../java/org/mozilla/geckoview/WebExtension.java | 2 +- .../mozilla/geckoview/WebExtensionController.java | 26 +- .../org/mozilla/geckoview/doc-files/CHANGELOG.md | 13 +- mobile/android/geckoview_example/build.gradle | 1 + .../geckoview_example/GeckoViewActivity.java | 24 - mobile/android/gradle.py | 13 +- .../gradle/dotgradle-offline/gradle.properties | 4 + .../gradle/dotgradle-online/gradle.properties | 4 + mobile/android/mach_commands.py | 9 +- .../modules/geckoview/GeckoViewNavigation.sys.mjs | 107 +- .../geckoview/GeckoViewSessionStore.sys.mjs | 95 +- .../modules/geckoview/GeckoViewSettings.sys.mjs | 16 + .../modules/geckoview/GeckoViewTestUtils.sys.mjs | 7 +- .../geckoview/GeckoViewTranslations.sys.mjs | 13 +- .../geckoview/GeckoViewWebExtension.sys.mjs | 54 +- .../geckoview/test_runner/TestRunnerActivity.java | 6 +- mobile/android/version.txt | 2 +- 812 files changed, 46606 insertions(+), 38213 deletions(-) create mode 100644 mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt create mode 100644 mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/ext/TabSessionState.kt create mode 100644 mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/ext/TabSessionStateTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/README.md create mode 100644 mobile/android/android-components/components/browser/toolbar2/build.gradle create mode 100644 mobile/android/android-components/components/browser/toolbar2/proguard-rules.pro create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/AndroidManifest.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/BrowserToolbar.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbar.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbarView.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/HighlightView.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/MenuButton.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/OriginView.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/SiteSecurityIconView.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconView.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/edit/EditToolbar.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/facts/ToolbarFacts.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionContainer.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionWrapper.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_browser_toolbar_icons_vertical_separator.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_dot_notification.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_site_security.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_off_for_a_site.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_no_trackers_blocked.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_trackers_blocked.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_displaytoolbar.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_edittoolbar.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-am/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-an/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ar/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ast/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-az/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-azb/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ban/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-be/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bg/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bn/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-br/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bs/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ca/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cak/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ceb/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ckb/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-co/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cs/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cy/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-da/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-de/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-dsb/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-el/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rCA/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rGB/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eo/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rAR/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rCL/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rES/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rMX/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-et/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eu/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fa/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ff/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fi/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fur/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fy-rNL/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ga-rIE/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gd/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gn/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gu-rIN/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hi-rIN/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hil/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hsb/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hu/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hy-rAM/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ia/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-in/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-is/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-it/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-iw/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ja/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ka/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kaa/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kab/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kk/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kmr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kn/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ko/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ldrtl/dimens.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lij/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lo/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lt/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mix/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ml/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-my/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nb-rNO/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ne-rNP/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nn-rNO/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-oc/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-or/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rIN/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rPK/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rBR/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rPT/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-rm/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ro/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ru/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sat/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sc/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-si/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sk/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-skr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sq/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-su/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sv-rSE/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-szl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ta/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-te/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tg/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-th/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tl/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tok/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tr/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-trs/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tt/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tzm/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ug/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uk/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ur/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uz/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vec/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vi/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-yo/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rCN/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rTW/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values/attrs_browser_toolbar.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values/dimens.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values/ids.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/main/res/values/strings.xml create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/AsyncFilterListenerTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/BrowserToolbarTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/DisplayToolbarTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/HighlightViewTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/MenuButtonTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconViewTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/edit/EditToolbarTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/internal/ActionContainerTest.kt create mode 100644 mobile/android/android-components/components/browser/toolbar2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 mobile/android/android-components/components/compose/cfr/src/main/java/mozilla/components/compose/cfr/CFRPopupLayout.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsFeature.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsUseCases.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsFeatureTest.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsUseCasesTest.kt delete mode 100644 mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/EventsObserverTest.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabReceivedEventsObserverTest.kt create mode 100644 mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabsClosedEventsObserverTest.kt delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-au.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-ca.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-co-uk.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-de.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-es.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-fr.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-in.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-it.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-nl.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazon-se.xml delete mode 100644 mobile/android/android-components/components/feature/search/src/main/assets/searchplugins/amazondotcom.xml create mode 100644 mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/BuildVersionProvider.kt create mode 100644 mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/PowerManagerInfoProvider.kt create mode 100644 mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/ProcessInfoProvider.kt create mode 100644 mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/StartForegroundService.kt create mode 100644 mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/android/StartForegroundServiceTest.kt create mode 100644 mobile/android/autopublish-settings.gradle create mode 100644 mobile/android/components/geckoview/SessionStoreFunctions.sys.mjs delete mode 100644 mobile/android/docs/overview.rst delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnLongPressedListener.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripFeatureFlag.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/BrowserNavigationParams.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuAccessPoint.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ExtensionsSubmenu.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MainMenu.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuDialog.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuItem.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/SaveSubmenu.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ToolsSubmenu.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/SubmenuHeader.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuDialogMiddleware.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/BookmarksFeature.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/controller/BookmarksController.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/interactor/BookmarksInteractor.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/Bookmarks.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksHeaderViewHolder.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksMenuItem.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksViewHolder.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/intent/OpenRecentlyClosedIntentProcessor.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/RecentBookmarksFeature.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/controller/RecentBookmarksController.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/interactor/RecentBookmarksInteractor.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarks.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksHeaderViewHolder.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksMenuItem.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksViewHolder.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyContent.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyFooter.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyHeader.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyScaffold.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheet.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheetFragment.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyRequestPrompt.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabs.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsList.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListSupportedFeature.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteListItemPreference.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreference.kt delete mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreferenceFragment.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreference.kt create mode 100644 mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreferenceFragment.kt create mode 100644 mobile/android/fenix/app/src/main/res/drawable/microsurvey_success.xml create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuAccessPointTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuDialogMiddlewareTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/fake/FakeBookmarksStorage.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/ext/BitmapTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/bookmarks/BookmarksFeatureTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/bookmarks/DefaultBookmarksControllerTest.kt create mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/intent/OpenRecentlyClosedIntentProcessorTest.kt delete mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/recentbookmarks/DefaultRecentBookmarksControllerTest.kt delete mode 100644 mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/recentbookmarks/RecentBookmarksFeatureTest.kt create mode 100644 mobile/android/fenix/automation/taskcluster/androidTest/robo-scripts/default-browser-dismissal.json create mode 100644 mobile/android/fenix/docs/UI-Tests.md create mode 100644 mobile/android/geckoview/src/androidTest/assets/web_extensions/optional-permission-all-urls/manifest.json delete mode 100644 mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt delete mode 100644 mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java (limited to 'mobile/android') diff --git a/mobile/android/actors/GeckoViewContentParent.sys.mjs b/mobile/android/actors/GeckoViewContentParent.sys.mjs index e666040b5e..082c68b3c0 100644 --- a/mobile/android/actors/GeckoViewContentParent.sys.mjs +++ b/mobile/android/actors/GeckoViewContentParent.sys.mjs @@ -9,6 +9,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.sys.mjs", + SessionStoreHelper: + "resource://gre/modules/sessionstore/SessionStoreHelper.sys.mjs", }); const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewContentParent"); @@ -33,7 +35,7 @@ export class GeckoViewContentParent extends GeckoViewActorParent { // TODO Bug 1648158 this should include scroll, form history, etc return SessionStoreUtils.initializeRestore( browsingContext, - SessionStoreUtils.constructSessionStoreRestoreData() + lazy.SessionStoreHelper.buildRestoreData(formdata, scrolldata) ); } diff --git a/mobile/android/actors/GeckoViewSettingsChild.sys.mjs b/mobile/android/actors/GeckoViewSettingsChild.sys.mjs index d5e6c01e27..625905caab 100644 --- a/mobile/android/actors/GeckoViewSettingsChild.sys.mjs +++ b/mobile/android/actors/GeckoViewSettingsChild.sys.mjs @@ -14,7 +14,7 @@ export class GeckoViewSettingsChild extends GeckoViewActorChild { case "SettingsUpdate": { const settings = message.data; - if (settings.isPopup) { + if (settings.isExtensionPopup) { // Allow web extensions to close their own action popups (bz1612363) this.contentWindow.windowUtils.allowScriptsToClose(); } diff --git a/mobile/android/android-components/.buildconfig.yml b/mobile/android/android-components/.buildconfig.yml index c8a03c3ab5..5930361a20 100644 --- a/mobile/android/android-components/.buildconfig.yml +++ b/mobile/android/android-components/.buildconfig.yml @@ -270,6 +270,33 @@ projects: - ui-colors - ui-icons - ui-widgets + browser-toolbar2: + description: A customizable toolbar for browsers. + path: components/browser/toolbar2 + publish: true + upstream_dependencies: + - browser-errorpages + - browser-menu + - browser-menu2 + - browser-state + - concept-awesomebar + - concept-base + - concept-engine + - concept-fetch + - concept-menu + - concept-storage + - concept-toolbar + - lib-publicsuffixlist + - lib-state + - support-base + - support-ktx + - support-test + - support-utils + - tooling-lint + - ui-autocomplete + - ui-colors + - ui-icons + - ui-widgets compose-awesomebar: description: An awesomebar component showing search results matching text entered into the toolbar. @@ -533,7 +560,12 @@ projects: path: components/feature/accounts-push publish: true upstream_dependencies: + - browser-errorpages + - browser-state + - concept-awesomebar - concept-base + - concept-engine + - concept-fetch - concept-push - concept-storage - concept-sync @@ -545,8 +577,10 @@ projects: - support-base - support-ktx - support-test + - support-test-libstate - support-utils - tooling-lint + - ui-icons feature-addons: description: A feature that provides for managing add-ons. path: components/feature/addons @@ -560,6 +594,7 @@ projects: - concept-fetch - concept-menu - concept-storage + - concept-toolbar - lib-publicsuffixlist - lib-state - support-base @@ -607,6 +642,7 @@ projects: - concept-engine - concept-fetch - concept-storage + - concept-toolbar - lib-fetch-okhttp - lib-publicsuffixlist - service-digitalassetlinks @@ -773,6 +809,7 @@ projects: - concept-engine - concept-fetch - concept-storage + - concept-toolbar - lib-publicsuffixlist - lib-state - support-android-test @@ -2272,6 +2309,7 @@ projects: - concept-engine - concept-fetch - concept-storage + - concept-toolbar - lib-publicsuffixlist - support-base - support-ktx diff --git a/mobile/android/android-components/README.md b/mobile/android/android-components/README.md index 7076f5e2ac..ed7199de63 100644 --- a/mobile/android/android-components/README.md +++ b/mobile/android/android-components/README.md @@ -83,6 +83,8 @@ High-level components for building browser(-like) apps. * 🔵 [**Toolbar**](components/browser/toolbar/README.md) - A customizable toolbar for browsers. +* 🔴 [**Toolbar2**](components/browser/toolbar2/README.md) - A customizable toolbar for browsers. + ## Concept _API contracts and abstraction layers for browser components._ diff --git a/mobile/android/android-components/build.gradle b/mobile/android/android-components/build.gradle index f1b46775c1..5907f00712 100644 --- a/mobile/android/android-components/build.gradle +++ b/mobile/android/android-components/build.gradle @@ -89,28 +89,6 @@ subprojects { } } - - // Allow local appservices substitution in each subproject. - def appServicesSrcDir = null - if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { - appServicesSrcDir = gradle.getProperty('localProperties.autoPublish.application-services.dir') - } else if (gradle.hasProperty('localProperties.branchBuild.application-services.dir')) { - appServicesSrcDir = gradle.getProperty('localProperties.branchBuild.application-services.dir') - } - if (appServicesSrcDir) { - if (appServicesSrcDir.startsWith("/")) { - apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle" - } else { - apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle" - } - } - - // Allow local Glean substitution in each subproject. - if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) { - ext.gleanSrcDir = gradle."localProperties.autoPublish.glean.dir" - apply from: "${rootProject.projectDir}/${gleanSrcDir}/build-scripts/substitute-local-glean.gradle" - } - if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) { if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) { ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir" diff --git a/mobile/android/android-components/components/browser/engine-gecko/build.gradle b/mobile/android/android-components/components/browser/engine-gecko/build.gradle index 4e5c2fda65..776c083ee9 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/build.gradle +++ b/mobile/android/android-components/components/browser/engine-gecko/build.gradle @@ -76,8 +76,9 @@ dependencies { api getGeckoViewDependency() } + implementation ComponentsDependencies.androidx_core_ktx implementation ComponentsDependencies.androidx_paging - implementation ComponentsDependencies.androidx_data_store_preferences + implementation ComponentsDependencies.androidx_datastore_preferences implementation ComponentsDependencies.androidx_lifecycle_livedata testImplementation ComponentsDependencies.androidx_test_core diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt index 92e6074a61..f5f1012448 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -426,6 +426,10 @@ class GeckoEngine( exception as WebExtensionInstallException, ) } + + override fun onOptionalPermissionsChanged(extension: org.mozilla.geckoview.WebExtension) { + webExtensionDelegate.onOptionalPermissionsChanged(GeckoWebExtension(extension, runtime)) + } } val extensionProcessDelegate = object : WebExtensionController.ExtensionProcessDelegate { diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt index e6907c6dde..dbf7b2d54f 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt @@ -4,7 +4,6 @@ package mozilla.components.browser.engine.gecko -import android.annotation.SuppressLint import android.net.Uri import android.os.Build import android.view.WindowManager @@ -58,6 +57,7 @@ import mozilla.components.support.base.facts.Action import mozilla.components.support.base.facts.Fact import mozilla.components.support.base.facts.collect import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.kotlin.decode import mozilla.components.support.ktx.kotlin.isEmail import mozilla.components.support.ktx.kotlin.isExtensionUrl import mozilla.components.support.ktx.kotlin.isGeoLocation @@ -557,9 +557,8 @@ class GeckoEngineSession( /** * See [EngineSession.findNext] */ - @SuppressLint("WrongConstant") // FinderFindFlags annotation doesn't include a 0 value. override fun findNext(forward: Boolean) { - val findFlags = if (forward) 0 else GeckoSession.FINDER_FIND_BACKWARDS + val findFlags = if (forward) GeckoSession.FINDER_FIND_FORWARD else GeckoSession.FINDER_FIND_BACKWARDS geckoSession.finder.find(null, findFlags).then { result: GeckoSession.FinderResult? -> result?.let { val activeMatchOrdinal = if (it.current > 0) it.current - 1 else it.current @@ -1159,9 +1158,9 @@ class GeckoEngineSession( return when { maybeInterceptRequest(request, false) != null -> - GeckoResult.fromValue(AllowOrDeny.DENY) + GeckoResult.deny() request.target == NavigationDelegate.TARGET_WINDOW_NEW -> - GeckoResult.fromValue(AllowOrDeny.ALLOW) + GeckoResult.allow() else -> { notifyObservers { onLoadRequest( @@ -1171,7 +1170,7 @@ class GeckoEngineSession( ) } - GeckoResult.fromValue(AllowOrDeny.ALLOW) + GeckoResult.allow() } } } @@ -1181,15 +1180,15 @@ class GeckoEngineSession( request: NavigationDelegate.LoadRequest, ): GeckoResult { if (request.target == NavigationDelegate.TARGET_WINDOW_NEW) { - return GeckoResult.fromValue(AllowOrDeny.ALLOW) + return GeckoResult.allow() } return if (maybeInterceptRequest(request, true) != null) { - GeckoResult.fromValue(AllowOrDeny.DENY) + GeckoResult.deny() } else { // Not notifying session observer because of performance concern and currently there // is no use case. - GeckoResult.fromValue(AllowOrDeny.ALLOW) + GeckoResult.allow() } } @@ -1526,7 +1525,7 @@ class GeckoEngineSession( url = url, contentLength = contentLength, contentType = DownloadUtils.sanitizeMimeType(contentType), - fileName = fileName.sanitizeFileName(), + fileName = fileName.sanitizeFileName().decode(), response = response, isPrivate = privateMode, openInApp = webResponse.requestExternalApp, diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt index d5d77b3073..39a4c2d6cc 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt @@ -9,6 +9,7 @@ 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 @@ -63,7 +64,7 @@ class GeckoEngineView @JvmOverloads constructor( // 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, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + ViewCompat.setImportantForAutofill(this, View.IMPORTANT_FOR_ACCESSIBILITY_YES) } internal fun setColorScheme(preferredColorScheme: PreferredColorScheme) { @@ -205,29 +206,18 @@ class GeckoEngineView @JvmOverloads constructor( geckoView.activityContextDelegate = GeckoViewActivityContextDelegate(WeakReference(context)) } - @Suppress("TooGenericExceptionCaught") override fun captureThumbnail(onFinish: (Bitmap?) -> Unit) { - try { - val geckoResult = geckoView.capturePixels() - geckoResult.then( - { bitmap -> - onFinish(bitmap) - GeckoResult() - }, - { - onFinish(null) - GeckoResult() - }, - ) - } catch (e: Exception) { - // There's currently no reliable way for consumers of GeckoView to - // know whether or not the compositor is ready. So we have to add - // a catch-all here. In the future, GeckoView will invoke our error - // callback instead and this block can be removed: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1645114 - // https://github.com/mozilla-mobile/android-components/issues/6680 - onFinish(null) - } + val geckoResult = geckoView.capturePixels() + geckoResult.then( + { bitmap -> + onFinish(bitmap) + GeckoResult() + }, + { + onFinish(null) + GeckoResult() + }, + ) } override fun clearSelection() { diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index d4276e675a..8fb2e1e0fb 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -282,17 +282,16 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe return geckoResult } + @Suppress("MaxLineLength") override fun onLoginSelect( session: GeckoSession, prompt: AutocompleteRequest, ): GeckoResult? { val promptOptions = prompt.options + val generatedPassword = - if (promptOptions.isNotEmpty() && promptOptions.first().hint == Autocomplete.SelectOption.Hint.GENERATED) { - promptOptions.first().value.password - } else { - null - } + promptOptions.firstOrNull { option -> option.hint == Autocomplete.SelectOption.Hint.GENERATED }?.value?.password + val geckoResult = GeckoResult() val onConfirmSelect: (Login) -> Unit = { login -> if (!prompt.isComplete) { diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegate.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegate.kt index 3266ba8538..884db33824 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegate.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegate.kt @@ -66,10 +66,11 @@ internal class GeckoTranslateSessionDelegate( state?.requestedTranslationPair?.toLanguage, ) val translationsState = TranslationEngineState( - detectedLanguages, - state?.error, - state?.isEngineReady, - pair, + detectedLanguages = detectedLanguages, + error = state?.error, + isEngineReady = state?.isEngineReady, + hasVisibleChange = state?.hasVisibleChange, + requestedTranslationPair = pair, ) engineSession.notifyObservers { diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt index 87849440b6..871c46a33d 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt @@ -349,7 +349,7 @@ class GeckoEngineSessionTest { val observer: EngineSession.Observer = mock() engineSession.register(observer) - val response = WebResponse.Builder("https://download.mozilla.org/image.png") + val response = WebResponse.Builder("https://download.mozilla.org/image%20name.png") .addHeader(Headers.Names.CONTENT_TYPE, "image/png") .addHeader(Headers.Names.CONTENT_LENGTH, "42") .skipConfirmation(true) @@ -362,8 +362,8 @@ class GeckoEngineSessionTest { contentDelegate.value.onExternalResponse(mock(), response) verify(observer).onExternalResource( - url = eq("https://download.mozilla.org/image.png"), - fileName = eq("image.png"), + url = eq("https://download.mozilla.org/image%20name.png"), + fileName = eq("image name.png"), contentLength = eq(42), contentType = eq("image/png"), cookie = eq(null), @@ -4361,7 +4361,7 @@ class GeckoEngineSessionTest { mockLoadRequest("sample:about", triggeredByRedirect = true), ) - assertEquals(geckoResult!!, GeckoResult.fromValue(AllowOrDeny.ALLOW)) + assertEquals(geckoResult!!, GeckoResult.allow()) } @Test diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt index 6a8ed3c330..a7b6c59954 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt @@ -2739,6 +2739,28 @@ class GeckoEngineTest { assertEquals(extension, capturedExtension.nativeExtension) } + @Test + fun `web extension delegate handles add-on onOptionalPermissionsChanged event`() { + val runtime: GeckoRuntime = mock() + val webExtensionController: WebExtensionController = mock() + whenever(runtime.webExtensionController).thenReturn(webExtensionController) + + val extension = mockNativeWebExtension("test", "uri") + val webExtensionsDelegate: WebExtensionDelegate = mock() + val engine = GeckoEngine(context, runtime = runtime) + engine.registerWebExtensionDelegate(webExtensionsDelegate) + + val geckoDelegateCaptor = argumentCaptor() + verify(webExtensionController).setAddonManagerDelegate(geckoDelegateCaptor.capture()) + + assertEquals(Unit, geckoDelegateCaptor.value.onOptionalPermissionsChanged(extension)) + val extensionCaptor = argumentCaptor() + verify(webExtensionsDelegate).onOptionalPermissionsChanged(extensionCaptor.capture()) + val capturedExtension = + extensionCaptor.value as mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension + assertEquals(extension, capturedExtension.nativeExtension) + } + @Test fun `web extension delegate handles add-on onInstallationFailed event`() { val runtime: GeckoRuntime = mock() diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt index 7056187e09..13fd983483 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt @@ -90,15 +90,6 @@ class GeckoEngineViewTest { shadowOf(getMainLooper()).idle() assertNull(thumbnail) - - // Test GeckoView throwing an exception - whenever(mockGeckoView.capturePixels()).thenThrow(IllegalStateException("Compositor not ready")) - - thumbnail = mock() - engineView.captureThumbnail { - thumbnail = it - } - assertNull(thumbnail) } @Test diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegateTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegateTest.kt index 65a1c7d8f9..f0c9762fa8 100644 --- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegateTest.kt +++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/translate/GeckoTranslateSessionDelegateTest.kt @@ -69,13 +69,14 @@ class GeckoTranslateSessionDelegateTest { val gecko = GeckoTranslateSessionDelegate(mockSession) // Mock state parameters to check Gecko to AC mapping is correctly occurring - var userLangTag = "en" - var isDocLangTagSupported = true - var docLangTag = "es" - var fromLanguage = "de" - var toLanguage = "bg" - var error = "Error!" - var isEngineReady = false + val userLangTag = "en" + val isDocLangTagSupported = true + val docLangTag = "es" + val fromLanguage = "de" + val toLanguage = "bg" + val error = "Error!" + val isEngineReady = false + val hasVisibleChange = true mockSession.register( object : EngineSession.Observer { @@ -88,14 +89,15 @@ class GeckoTranslateSessionDelegateTest { assertTrue(state.requestedTranslationPair?.toLanguage == toLanguage) assertTrue(state.error == error) assertTrue(state.isEngineReady == isEngineReady) + assertTrue(state.hasVisibleChange == hasVisibleChange) } }, ) // Mock states - var mockDetectedLanguages = TranslationsController.SessionTranslation.DetectedLanguages(userLangTag, isDocLangTagSupported, docLangTag) - var mockTranslationsPair = TranslationsController.SessionTranslation.TranslationPair(fromLanguage, toLanguage) - var mockGeckoState = TranslationsController.SessionTranslation.TranslationState(mockTranslationsPair, error, mockDetectedLanguages, isEngineReady) + val mockDetectedLanguages = TranslationsController.SessionTranslation.DetectedLanguages(userLangTag, isDocLangTagSupported, docLangTag) + val mockTranslationsPair = TranslationsController.SessionTranslation.TranslationPair(fromLanguage, toLanguage) + val mockGeckoState = TranslationsController.SessionTranslation.TranslationState(mockTranslationsPair, error, mockDetectedLanguages, isEngineReady, hasVisibleChange) gecko.onTranslationStateChange(mock(), mockGeckoState) assertTrue(onTranslateStateChangeWasCalled) diff --git a/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_blocklist.json b/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_blocklist.json index 845571cfa0..05571a483f 100644 --- a/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_blocklist.json +++ b/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_blocklist.json @@ -1,11046 +1,8340 @@ { - "license": "Copyright 2010-2019 Disconnect, Inc. / This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. / This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. / You should have received a copy of the GNU General Public License along with this program. If not, see .", - "categories": { - "Advertising": [ - { - "2leep.com": { - "http://2leep.com/": [ - "2leep.com" - ] - } - }, - { - "33Across": { - "http://33across.com/": [ - "33across.com" - ] - } - }, - { - "365Media": { - "http://365media.com/": [ - "365media.com" - ] - } - }, - { - "4INFO": { - "http://www.4info.com/": [ - "4info.com", - "adhaven.com" - ] - } - }, - { - "4mads": { - "http://4mads.com/": [ - "4mads.com" - ] - } - }, - { - "Abax Interactive": { - "http://abaxinteractive.com/": [ - "abaxinteractive.com" - ] - } - }, - { - "Accelia": { - "http://www.accelia.net/": [ - "accelia.net", - "durasite.net" - ] - } - }, - { - "Accordant Media": { - "http://www.accordantmedia.com/": [ - "accordantmedia.com" - ] - } - }, - { - "Acquisio": { - "http://www.acquisio.com/": [ - "acquisio.com", - "clickequations.net" - ] - } - }, - { - "Actisens": { - "http://www.actisens.com/": [ - "actisens.com", - "gestionpub.com" - ] - } - }, - { - "ActiveConversion": { - "http://www.activeconversion.com/": [ - "activeconversion.com", - "activemeter.com" - ] - } - }, - { - "Act-On": { - "http://www.act-on.com/": [ - "act-on.com", - "actonsoftware.com" - ] - } - }, - { - "Acuity": { - "http://www.acuity.com/": [ - "acuity.com", - "acuityads.com", - "acuityplatform.com" - ] - } - }, - { - "AD2ONE": { - "http://www.ad2onegroup.com/": [ - "ad2onegroup.com" - ] - } - }, - { - "Ad4Game": { - "http://ad4game.com/": [ - "ad4game.com" - ] - } - }, - { - "ad6media": { - "http://www.ad6media.fr/": [ - "ad6media.fr" - ] - } - }, - { - "Adabra": { - "https://www.adabra.com/": [ - "adabra.com" - ] - } - }, - { - "Adality": { - "http://adality.de/": [ - "adality.de", - "adrtx.net" - ] - } - }, - { - "AdaptiveAds": { - "http://www.adaptiveads.com/": [ - "adaptiveads.com" - ] - } - }, - { - "Adaptly": { - "http://adaptly.com/": [ - "adaptly.com" - ] - } - }, - { - "Adara Media": { - "http://www.adaramedia.com/": [ - "adaramedia.com", - "opinmind.com", - "yieldoptimizer.com" - ] - } - }, - { - "Adatus": { - "http://www.adatus.com/": [ - "adatus.com" - ] - } - }, - { - "Adbot": { - "https://adbot.tw/": [ - "adbot.tw" - ] - } - }, - { - "Adbrain": { - "http://www.adbrain.com/": [ - "adbrain.com", - "adbrn.com" - ] - } - }, - { - "adBrite": { - "http://www.adbrite.com/": [ - "adbrite.com" - ] - } - }, - { - "Adbroker.de": { - "http://adbroker.de/": [ - "adbroker.de" - ] - } - }, - { - "Adchemy": { - "http://www.adchemy.com/": [ - "adchemy.com" - ] - } - }, - { - "AdCirrus": { - "http://adcirrus.com/": [ - "adcirrus.com" - ] - } - }, - { - "Ad Decisive": { - "http://www.addecisive.com/": [ - "a2dfp.net", - "addecisive.com" - ] - } - }, - { - "addGloo": { - "http://www.addgloo.com/": [ - "addgloo.com" - ] - } - }, - { - "Addvantage Media": { - "http://www.addvantagemedia.com/": [ - "addvantagemedia.com" - ] - } - }, - { - "Ad Dynamo": { - "http://www.addynamo.com/": [ - "addynamo.com", - "addynamo.net" - ] - } - }, - { - "Adelphic": { - "https://adelphic.com/": [ - "adelphic.com", - "ipredictive.com" - ] - } - }, - { - "AdEngage": { - "http://adengage.com/": [ - "adengage.com" - ] - } - }, - { - "AD Europe": { - "http://www.adeurope.com/": [ - "adeurope.com" - ] - } - }, - { - "AdExtent": { - "http://www.adextent.com/": [ - "adextent.com" - ] - } - }, - { - "AdF.ly": { - "http://adf.ly/": [ - "adf.ly" - ] - } - }, - { - "Adfonic": { - "http://adfonic.com/": [ - "adfonic.com" - ] - } - }, - { - "Adforge": { - "http://adforgeinc.com/": [ - "adforgeinc.com" - ] - } - }, - { - "Adform": { - "http://www.adform.com/": [ - "adform.com", - "adform.net", - "adformdsp.net" - ] - } - }, - { - "AdFox": { - "http://adfox.ru/": [ - "adfox.ru" - ] - } - }, - { - "AdFrontiers": { - "http://www.adfrontiers.com/": [ - "adfrontiers.com" - ] - } - }, - { - "Adfunky": { - "http://www.adfunky.com/": [ - "adfunky.com", - "adfunkyserver.com" - ] - } - }, - { - "Adfusion": { - "http://www.adfusion.com/": [ - "adfusion.com" - ] - } - }, - { - "AdGainerSolutions": { - "http://adgainersolutions.com/adgainer/": [ - "adgainersolutions.com" - ] - } - }, - { - "AdGent Digital": { - "http://www.adgentdigital.com/": [ - "adgentdigital.com", - "shorttailmedia.com" - ] - } - }, - { - "AdGibbon": { - "http://www.adgibbon.com/": [ - "adgibbon.com" - ] - } - }, - { - "Adglare": { - "https://www.adglare.com/": [ - "adglare.com", - "adglare.net" - ] - } - }, - { - "adhood": { - "http://www.adhood.com/": [ - "adhood.com" - ] - } - }, - { - "Adiant": { - "http://www.adiant.com/": [ - "adblade.com", - "adiant.com" - ] - } - }, - { - "AdInsight": { - "http://www.adinsight.com/": [ - "adinsight.com", - "adinsight.eu" - ] - } - }, - { - "AdIQuity": { - "http://adiquity.com/": [ - "adiquity.com" - ] - } - }, - { - "ADITION": { - "http://www.adition.com/": [ - "adition.com" - ] - } - }, - { - "AdJug": { - "http://www.adjug.com/": [ - "adjug.com" - ] - } - }, - { - "AdJuggler": { - "http://www.adjuggler.com/": [ - "adjuggler.com", - "adjuggler.net" - ] - } - }, - { - "Adjust": { - "https://adjust.com": [ - "adjust.com" - ] - } - }, - { - "AdKeeper": { - "http://www.adkeeper.com/": [ - "adkeeper.com", - "akncdn.com" - ] - } - }, - { - "AdKernel": { - "http://adkernel.com": [ - "adkernel.com" - ] - } - }, - { - "Ad Knife": { - "http://static.adknife.com/": [ - "adknife.com" - ] - } - }, - { - "Adknowledge": { - "http://www.adknowledge.com/": [ - "adknowledge.com", - "adparlor.com", - "bidsystem.com", - "cubics.com", - "lookery.com" - ] - } - }, - { - "AdLantis": { - "http://www.adlantis.jp/": [ - "adimg.net", - "adlantis.jp" - ] - } - }, - { - "AdLeave": { - "http://www.adleave.com/": [ - "adleave.com" - ] - } - }, - { - "Adlibrium": { - "http://www.adlibrium.com/": [ - "adlibrium.com" - ] - } - }, - { - "Adlucent": { - "http://adlucent.com": [ - "adlucent.com" - ] - } - }, - { - "Ad Magnet": { - "http://www.admagnet.com/": [ - "admagnet.com", - "admagnet.net" - ] - } - }, - { - "Admarketplace": { - "http://www.admarketplace.com/": [ - "admarketplace.com", - "admarketplace.net", - "ampxchange.com" - ] - } - }, - { - "AdMarvel": { - "http://www.admarvel.com/": [ - "admarvel.com" - ] - } - }, - { - "AdMatrix": { - "http://www.admatrix.jp/": [ - "admatrix.jp" - ] - } - }, - { - "AdMaven": { - "https://ad-maven.com/": [ - "ad-maven.com", - "agreensdistra.info", - "boudja.com", - "rensovetors.info", - "wrethicap.info" - ] - } - }, - { - "AdMaximizer Network": { - "http://admaximizer.com/": [ - "admaximizer.com" - ] - } - }, - { - "AdMedia": { - "http://www.admedia.com/": [ - "admedia.com" - ] - } - }, - { - "Admeta": { - "http://www.admeta.com/": [ - "admeta.com", - "atemda.com" - ] - } - }, - { - "Admicro": { - "http://www.admicro.vn/": [ - "admicro.vn", - "vcmedia.vn" - ] - } - }, - { - "Admixer": { - "https://admixer.co.kr/main": [ - "admixer.co.kr" - ] - } - }, - { - "Admized": { - "http://www.admized.com/": [ - "admized.com" - ] - } - }, - { - "Admobile": { - "http://admobile.com/": [ - "admobile.com" - ] - } - }, - { - "Admotion": { - "http://www.admotion.com/": [ - "admotion.com", - "nspmotion.com" - ] - } - }, - { - "Adnetik": { - "http://adnetik.com/": [ - "adnetik.com", - "wtp101.com" - ] - } - }, - { - "AdNetwork.net": { - "http://www.adnetwork.net/": [ - "adnetwork.net" - ] - } - }, - { - "Adnium": { - "https://adnium.com": [ - "adnium.com" - ] - } - }, - { - "adnologies": { - "http://www.adnologies.com/": [ - "adnologies.com", - "heias.com" - ] - } - }, - { - "Adobe": { - "http://www.adobe.com/": [ - "2o7.net", - "auditude.com", - "demdex.com", - "demdex.net", - "dmtracker.com", - "efrontier.com", - "everestads.net", - "everestjs.net", - "everesttech.net", - "hitbox.com", - "omniture.com", - "omtrdc.net", - "touchclarity.com" - ] - } - }, - { - "AdOcean": { - "http://www.adocean-global.com/": [ - "adocean-global.com", - "adocean.pl" - ] - } - }, - { - "Adometry": { - "http://www.adometry.com/": [ - "adometry.com", - "dmtry.com" - ] - } - }, - { - "Adomik": { - "http://www.adomik.com/": [ - "adomik.com" - ] - } - }, - { - "AdOnion": { - "http://www.adonion.com/": [ - "adonion.com" - ] - } - }, - { - "Adorika": { - "http://www.clickotmedia.com/": [ - "clickotmedia.com" - ] - } - }, - { - "Adotmob": { - "https://adotmob.com/": [ - "adotmob.com" - ] - } - }, - { - "ADP Dealer Services": { - "http://www.adpdealerservices.com/": [ - "admission.net", - "adpdealerservices.com", - "cobalt.com" - ] - } - }, - { - "ad pepper media": { - "http://www.adpepper.us/": [ - "adpepper.com", - "adpepper.us" - ] - } - }, - { - "AdPerfect": { - "http://www.adperfect.com/": [ - "adperfect.com" - ] - } - }, - { - "Adperium": { - "http://www.adperium.com/": [ - "adperium.com" - ] - } - }, - { - "Adpersia": { - "http://www.adpersia.com/": [ - "adpersia.com" - ] - } - }, - { - "adPrecision": { - "http://adprecision.net/": [ - "adprs.net", - "aprecision.net" - ] - } - }, - { - "AdPredictive": { - "http://www.adpredictive.com/": [ - "adpredictive.com" - ] - } - }, - { - "AdReactor": { - "http://www.adreactor.com/": [ - "adreactor.com" - ] - } - }, - { - "AdReady": { - "http://www.adready.com/": [ - "adready.com", - "adreadytractions.com" - ] - } - }, - { - "AdRevolution": { - "http://adrevolution.com/": [ - "adrevolution.com" - ] - } - }, - { - "AdRiver": { - "http://adriver.ru/": [ - "adriver.ru" - ] - } - }, - { - "adrolays": { - "http://adrolays.com/": [ - "adrolays.com", - "adrolays.de" - ] - } - }, - { - "AdRoll": { - "http://www.adroll.com/": [ - "adroll.com" - ] - } - }, - { - "adscale": { - "http://www.adscale.de/": [ - "adscale.de" - ] - } - }, - { - "Adscience": { - "https://www.adscience.nl/": [ - "adscience.nl" - ] - } - }, - { - "AdServerPub": { - "http://www.adserverpub.com/": [ - "adserverpub.com" - ] - } - }, - { - "AdShuffle": { - "http://www.adshuffle.com/": [ - "adshuffle.com" - ] - } - }, - { - "AdSide": { - "http://www.adside.com/": [ - "adside.com", - "doclix.com" - ] - } - }, - { - "AdSpeed": { - "http://www.adspeed.com/": [ - "adspeed.com", - "adspeed.net" - ] - } - }, - { - "Adsperity": { - "https://www.adsperity.com/": [ - "adsperity.com" - ] - } - }, - { - "AdSpirit": { - "http://www.adspirit.de/": [ - "adspirit.com", - "adspirit.de", - "adspirit.net" - ] - } - }, - { - "Adsrevenue.net": { - "http://adsrevenue.net/": [ - "adsrevenue.net" - ] - } - }, - { - "AdStir": { - "https://en.ad-stir.com/": [ - "ad-stir.com" - ] - } - }, - { - "AdsTours": { - "http://www.adstours.com/": [ - "adstours.com", - "clickintext.net" - ] - } - }, - { - "Adsty": { - "http://adsty.com/": [ - "adsty.com", - "adx1.com" - ] - } - }, - { - "Adsupply": { - "http://www.adsupply.com/": [ - "4dsply.com", - "adsupply.com" - ] - } - }, - { - "Adswizz": { - "http://adswizz.com": [ - "adswizz.com" - ] - } - }, - { - "ADTECH": { - "http://www.adtech.com/": [ - "adtech.com", - "adtech.de", - "adtechus.com" - ] - } - }, - { - "Adtegrity.com": { - "http://www.adtegrity.com/": [ - "adtegrity.com", - "adtegrity.net" - ] - } - }, - { - "ADTELLIGENCE": { - "http://www.adtelligence.de/": [ - "adtelligence.de" - ] - } - }, - { - "Adthink": { - "https://adthink.com/": [ - "adthink.com", - "audienceinsights.net" - ] - } - }, - { - "AdTiger": { - "http://www.adtiger.de/": [ - "adtiger.de" - ] - } - }, - { - "AdTruth": { - "http://adtruth.com/": [ - "adtruth.com" - ] - } - }, - { - "Adult AdWorld": { - "http://adultadworld.com/": [ - "adultadworld.com" - ] - } - }, - { - "Adultmoda": { - "http://www.adultmoda.com/": [ - "adultmoda.com" - ] - } - }, - { - "Adventive": { - "http://adventive.com/": [ - "adventive.com" - ] - } - }, - { - "Adverline": { - "http://www.adverline.com/": [ - "adnext.fr", - "adverline.com" - ] - } - }, - { - "Adversal.com": { - "http://www.adversal.com/": [ - "adv-adserver.com", - "adversal.com" - ] - } - }, - { - "Adverticum": { - "http://www.adverticum.com/": [ - "adsmart.com", - "adverticum.com", - "adverticum.net" - ] - } - }, - { - "Advertise.com": { - "http://www.advertise.com/": [ - "advertise.com" - ] - } - }, - { - "AdvertiseSpace": { - "http://www.advertisespace.com/": [ - "advertisespace.com" - ] - } - }, - { - "Advert Stream": { - "http://www.advertstream.com/": [ - "advertstream.com" - ] - } - }, - { - "Advisor Media": { - "http://advisormedia.cz/": [ - "advisormedia.cz" - ] - } - }, - { - "Adworx": { - "http://adworx.at/": [ - "adworx.at", - "adworx.be", - "adworx.nl" - ] - } - }, - { - "AdXpansion": { - "http://www.adxpansion.com/": [ - "adxpansion.com" - ] - } - }, - { - "Adxvalue": { - "http://adxvalue.com/": [ - "adxvalue.com", - "adxvalue.de" - ] - } - }, - { - "adyard": { - "http://adyard.de/": [ - "adyard.de" - ] - } - }, - { - "AdYield": { - "http://www.adyield.com/": [ - "adxyield.com", - "adyield.com" - ] - } - }, - { - "AdYouLike": { - "https://www.adyoulike.com/": [ - "adyoulike.com", - "omnitagjs.com", - "pulpix.com" - ] - } - }, - { - "ADZ": { - "http://www.adzcentral.com/": [ - "adzcentral.com" - ] - } - }, - { - "Adzerk": { - "http://www.adzerk.com/": [ - "adzerk.com", - "adzerk.net" - ] - } - }, - { - "adzly": { - "http://www.adzly.com/": [ - "adzly.com" - ] - } - }, - { - "Aegis Group": { - "http://www.aemedia.com/": [ - "aemedia.com", - "bluestreak.com" - ] - } - }, - { - "AERIFY MEDIA": { - "http://aerifymedia.com/": [ - "aerifymedia.com", - "anonymous-media.com" - ] - } - }, - { - "Affectv": { - "http://affectv.co.uk/": [ - "affectv.co.uk" - ] - } - }, - { - "affilinet": { - "http://www.affili.net/": [ - "affili.net", - "affilinet-inside.de", - "banner-rotation.com", - "successfultogether.co.uk" - ] - } - }, - { - "Affine": { - "http://www.affine.tv/": [ - "affine.tv", - "affinesystems.com" - ] - } - }, - { - "Affinity": { - "http://www.affinity.com/": [ - "affinity.com" - ] - } - }, - { - "AfterDownload": { - "http://www.afterdownload.com/": [ - "afdads.com", - "afterdownload.com" - ] - } - }, - { - "Aim4Media": { - "http://aim4media.com/": [ - "aim4media.com" - ] - } - }, - { - "Airpush": { - "http://www.airpush.com/": [ - "airpush.com" - ] - } - }, - { - "AK": { - "http://www.aggregateknowledge.com/": [ - "aggregateknowledge.com", - "agkn.com" - ] - } - }, - { - "Akamai": { - "http://www.akamai.com/": [ - "imiclk.com" - ] - } - }, - { - "Albacross": { - "https://albacross.com": [ - "albacross.com" - ] - } - }, - { - "AllStarMediaGroup": { - "http://allstarmediagroup.com/": [ - "allstarmediagroup.com" - ] - } - }, - { - "Aloodo": { - "https://aloodo.com/": [ - "aloodo.com" - ] - } - }, - { - "AlterGeo": { - "http://altergeo.ru/": [ - "altergeo.ru" - ] - } - }, - { - "Amazon.com": { - "http://www.amazon.com/": [ - "amazon-adsystem.com", - "amazon.ca", - "amazon.co.jp", - "amazon.co.uk", - "amazon.de", - "amazon.es", - "amazon.fr", - "amazon.it", - "assoc-amazon.com" - ] - } - }, - { - "Ambient Digital": { - "http://ambientdigital.com.vn/": [ - "adnetwork.vn", - "ambientdigital.com.vn" - ] - } - }, - { - "Amobee": { - "http://amobee.com/": [ - "adconion.com", - "amgdgt.com", - "amobee.com", - "euroclick.com", - "smartclip.com", - "turn.com" - ] - } - }, - { - "AndBeyond": { - "http://andbeyond.media/": [ - "andbeyond.media" - ] - } - }, - { - "Answers.com": { - "http://www.answers.com/": [ - "dsply.com" - ] - } - }, - { - "AOL": { - "http://www.aol.com/": [ - "adsonar.com", - "adtechjp.com", - "advertising.com", - "aolcloud.net", - "atwola.com", - "leadback.com", - "tacoda.net", - "vidible.tv" - ] - } - }, - { - "AppCast": { - "https://appcast.io/": [ - "appcast.io" - ] - } - }, - { - "Appenda": { - "http://www.appenda.com/": [ - "appenda.com" - ] - } - }, - { - "AppFlood": { - "http://appflood.com/": [ - "appflood.com" - ] - } - }, - { - "Appier": { - "http://appier.com/": [ - "appier.com" - ] - } - }, - { - "Applifier": { - "http://www.applifier.com/": [ - "applifier.com" - ] - } - }, - { - "Applovin": { - "http://www.applovin.com/": [ - "applovin.com" - ] - } - }, - { - "AppNexus": { - "http://www.appnexus.com/": [ - "adlantic.nl", - "adnxs.com", - "adrdgt.com", - "alenty.com", - "appnexus.com" - ] - } - }, - { - "AppsFlyer": { - "http://appsflyer.com/": [ - "appsflyer.com" - ] - } - }, - { - "appssavvy": { - "http://appssavvy.com/": [ - "appssavvy.com" - ] - } - }, - { - "Arkwrights Homebrew": { - "http://www.arkwrightshomebrew.com/": [ - "arkwrightshomebrew.com", - "ctasnet.com" - ] - } - }, - { - "AT Internet": { - "http://www.atinternet.com/": [ - "hit-parade.com" - ] - } - }, - { - "ATN": { - "http://affiliatetracking.com/": [ - "affiliatetracking.com" - ] - } - }, - { - "Atoomic.com": { - "http://www.atoomic.com/": [ - "atoomic.com" - ] - } - }, - { - "Atrinsic": { - "http://atrinsic.com/": [ - "atrinsic.com" - ] - } - }, - { - "AT&T": { - "http://www.att.com/": [ - "att.com", - "yp.com" - ] - } - }, - { - "Audience2Media": { - "http://www.audience2media.com/": [ - "audience2media.com" - ] - } - }, - { - "Audience Ad Network": { - "http://audienceadnetwork.com/": [ - "audienceadnetwork.com" - ] - } - }, - { - "AudienceScience": { - "http://www.audiencescience.com/": [ - "audiencescience.com", - "revsci.net", - "targetingmarketplace.com", - "wunderloop.net" - ] - } - }, - { - "Augme": { - "http://www.augme.com/": [ - "augme.com", - "hipcricket.com" - ] - } - }, - { - "Augur": { - "http://www.augur.io/": [ - "augur.io" - ] - } - }, - { - "AUTOCENTRE.UA": { - "http://www.autocentre.ua/": [ - "am.ua", - "autocentre.ua" - ] - } - }, - { - "Automattic": { - "http://automattic.com/": [ - "pubmine.com" - ] - } - }, - { - "Avalanchers": { - "http://www.avalanchers.com/": [ - "avalanchers.com" - ] - } - }, - { - "AvantLink": { - "http://www.avantlink.com/": [ - "avantlink.com" - ] - } - }, - { - "Avocet": { - "https://avocet.io/": [ - "avocet.io" - ] - } - }, - { - "Avsads": { - "http://avsads.com/": [ - "avsads.com" - ] - } - }, - { - "AWeber": { - "http://www.aweber.com/": [ - "aweber.com" - ] - } - }, - { - "Awin": { - "http://www.awin.com/": [ - "digitalwindow.com", - "dwin1.com", - "perfiliate.com" - ] - } - }, - { - "Azet": { - "http://mediaimpact.sk/": [ - "azetklik.sk", - "rsz.sk" - ] - } - }, - { - "BackBeat Media": { - "http://www.backbeatmedia.com/": [ - "backbeatmedia.com" - ] - } - }, - { - "Bannerconnect": { - "http://www.bannerconnect.net/": [ - "bannerconnect.net" - ] - } - }, - { - "Barilliance": { - "http://www.barilliance.com/": [ - "barilliance.com" - ] - } - }, - { - "BaronsNetworks": { - "http://baronsoffers.com/": [ - "baronsoffers.com" - ] - } - }, - { - "Batanga Network": { - "http://www.batanganetwork.com/": [ - "batanga.com", - "batanganetwork.com" - ] - } - }, - { - "BeachFront": { - "http://beachfront.com/": [ - "beachfront.com" - ] - } - }, - { - "Beanstock Media": { - "http://www.beanstockmedia.com/": [ - "beanstockmedia.com" - ] - } - }, - { - "beencounter": { - "http://www.beencounter.com/": [ - "beencounter.com" - ] - } - }, - { - "Begun": { - "http://www.begun.ru/": [ - "begun.ru" - ] - } - }, - { - "belboon": { - "http://www.belboon.com/": [ - "adbutler.de", - "belboon.com" - ] - } - }, - { - "Betgenius": { - "http://www.betgenius.com/": [ - "betgenius.com", - "connextra.com" - ] - } - }, - { - "BetweenDigital": { - "http://betweendigital.com": [ - "betweendigital.com" - ] - } - }, - { - "Bidfluence": { - "https://www.bidfluence.com/": [ - "bidfluence.com" - ] - } - }, - { - "Bidr": { - "http://bidr.io": [ - "bidr.io" - ] - } - }, - { - "BidSwitch": { - "https://www.bidswitch.com/": [ - "bidswitch.net", - "mfadsrvr.com" - ] - } - }, - { - "Bidtellect": { - "https://www.bidtellect.com/": [ - "bidtellect.com", - "bttrack.com" - ] - } - }, - { - "BidVertiser": { - "http://www.bidvertiser.com/": [ - "bidvertiser.com" - ] - } - }, - { - "BigClick": { - "http://bigclick.me/": [ - "bgclck.me", - "xcvgdf.party" - ] - } - }, - { - "bigmirnet": { - "http://www.bigmir.net/": [ - "bigmir.net" - ] - } - }, - { - "BinLayer": { - "http://binlayer.com/": [ - "binlayer.com" - ] - } - }, - { - "Bitcoin Plus": { - "http://www.bitcoinplus.com/": [ - "bitcoinplus.com" - ] - } - }, - { - "BitMedia": { - "https://bitmedia.io/": [ - "bitmedia.io" - ] - } - }, - { - "BittAds": { - "http://www.bittads.com/": [ - "bittads.com" - ] - } - }, - { - "Bizo": { - "http://www.bizo.com/": [ - "bizo.com", - "bizographics.com" - ] - } - }, - { - "Black Label Ads": { - "http://www.blacklabelads.com/": [ - "blacklabelads.com" - ] - } - }, - { - "BlogCatalog": { - "http://www.blogcatalog.com/": [ - "blogcatalog.com" - ] - } - }, - { - "BlogFrog": { - "http://theblogfrog.com/": [ - "theblogfrog.com" - ] - } - }, - { - "BlogHer": { - "http://www.blogher.com/": [ - "blogher.com", - "blogherads.com" - ] - } - }, - { - "BlogRollr": { - "http://blogrollr.com/": [ - "blogrollr.com" - ] - } - }, - { - "BLOOM Digital Platforms": { - "http://bloom-hq.com/": [ - "adgear.com", - "adgrx.com", - "bloom-hq.com" - ] - } - }, - { - "BlueKai": { - "http://www.bluekai.com/": [ - "bkrtx.com", - "bluekai.com", - "tracksimple.com" - ] - } - }, - { - "Blu Trumpet": { - "http://www.blutrumpet.com/": [ - "blutrumpet.com" - ] - } - }, - { - "Boo-Box": { - "http://boo-box.com/": [ - "boo-box.com" - ] - } - }, - { - "BoostBox": { - "https://www.boostbox.com.br/": [ - "boostbox.com.br" - ] - } - }, - { - "Bouncex": { - "https://www.bouncex.com/": [ - "bounceexchange.com", - "bouncex.com", - "bouncex.net" - ] - } - }, - { - "Brainient": { - "http://brainient.com/": [ - "brainient.com" - ] - } - }, - { - "Brand Affinity Technologies": { - "http://www.brandaffinity.net/": [ - "brandaffinity.net" - ] - } - }, - { - "Brandcrumb": { - "http://www.brandcrumb.com": [ - "brandcrumb.com" - ] - } - }, - { - "Brand.net": { - "http://www.brand.net/": [ - "brand.net" - ] - } - }, - { - "Brandscreen": { - "http://www.brandscreen.com/": [ - "brandscreen.com", - "rtbidder.net" - ] - } - }, - { - "BreakTime": { - "https://www.breaktime.com.tw/": [ - "breaktime.com.tw" - ] - } - }, - { - "BrightRoll": { - "http://www.brightroll.com/": [ - "brightroll.com", - "btrll.com" - ] - } - }, - { - "BrightTag": { - "http://www.brighttag.com/": [ - "brighttag.com", - "btstatic.com", - "thebrighttag.com" - ] - } - }, - { - "Brilig": { - "http://www.brilig.com/": [ - "brilig.com" - ] - } - }, - { - "BuckSense": { - "http://www.bucksense.com": [ - "bucksense.com" - ] - } - }, - { - "Burstly": { - "http://www.burstly.com/": [ - "burstly.com" - ] - } - }, - { - "Burst Media": { - "http://www.burstmedia.com/": [ - "burstbeacon.com", - "burstdirectads.com", - "burstmedia.com", - "burstnet.com", - "giantrealm.com" - ] - } - }, - { - "BusinessOnline": { - "http://www.businessol.com/": [ - "businessol.com" - ] - } - }, - { - "Button": { - "https://www.usebutton.com": [ - "usebutton.com" - ] - } - }, - { - "BuySellAds": { - "http://buysellads.com/": [ - "beaconads.com", - "buysellads.com" - ] - } - }, - { - "Buysight": { - "http://www.buysight.com/": [ - "buysight.com", - "permuto.com", - "pulsemgr.com" - ] - } - }, - { - "BuzzParadise": { - "http://www.buzzparadise.com/": [ - "buzzparadise.com" - ] - } - }, - { - "BV! MEDIA": { - "http://www.bvmedia.ca/": [ - "bvmedia.ca", - "networldmedia.com", - "networldmedia.net" - ] - } - }, - { - "c1exchange": { - "https://c1exchange.com/": [ - "c1exchange.com" - ] - } - }, - { - "C3 Metrics": { - "http://c3metrics.com/": [ - "attributionmodel.com", - "c3metrics.com", - "c3tag.com" - ] - } - }, - { - "Cadreon": { - "http://www.cadreon.com/": [ - "cadreon.com" - ] - } - }, - { - "CampaignGrid": { - "http://www.campaigngrid.com/": [ - "campaigngrid.com" - ] - } - }, - { - "CAPITALDATA": { - "http://www.capitaldata.fr/": [ - "capitaldata.fr" - ] - } - }, - { - "Carambola": { - "https://www.carambola.com/": [ - "carambo.la" - ] - } - }, - { - "Caraytech": { - "http://www.caraytech.com.ar/": [ - "caraytech.com.ar", - "e-planning.net" - ] - } - }, - { - "Cart.ro": { - "http://www.cart.ro/": [ - "cart.ro", - "statistics.ro" - ] - } - }, - { - "CartsGuru": { - "https://carts.guru/": [ - "carts.guru" - ] - } - }, - { - "Casale Media": { - "http://www.casalemedia.com/": [ - "casalemedia.com", - "medianet.com" - ] - } - }, - { - "CBproADS": { - "http://www.cbproads.com/": [ - "cbproads.com" - ] - } - }, - { - "Cedato": { - "https://www.cedato.com/": [ - "cedato.com" - ] - } - }, - { - "Chango": { - "http://www.chango.com/": [ - "chango.ca", - "chango.com" - ] - } - }, - { - "ChannelAdvisor": { - "http://www.channeladvisor.com/": [ - "channeladvisor.com", - "searchmarketing.com" - ] - } - }, - { - "Channel Intelligence": { - "http://www.channelintelligence.com/": [ - "channelintelligence.com" - ] - } - }, - { - "Chartboost": { - "https://www.chartboost.com/": [ - "chartboost.com" - ] - } - }, - { - "CheckM8": { - "http://www.checkm8.com/": [ - "checkm8.com" - ] - } - }, - { - "Chitika": { - "http://chitika.com/": [ - "chitika.com", - "chitika.net" - ] - } - }, - { - "ChoiceStream": { - "http://www.choicestream.com/": [ - "choicestream.com" - ] - } - }, - { - "ClearLink": { - "https://www.clearlink.com/": [ - "clearlink.com" - ] - } - }, - { - "ClearSaleing": { - "http://www.clearsaleing.com/": [ - "clearsaleing.com", - "csdata1.com", - "csdata2.com", - "csdata3.com" - ] - } - }, - { - "Clearsearch Media": { - "http://www.clearsearchmedia.com/": [ - "clearsearchmedia.com", - "csm-secure.com" - ] - } - }, - { - "ClearSight Interactive": { - "http://www.clearsightinteractive.com/": [ - "clearsightinteractive.com", - "csi-tracking.com" - ] - } - }, - { - "ClickAider": { - "http://clickaider.com/": [ - "clickaider.com" - ] - } - }, - { - "Clickayab": { - "http://www.clickyab.com": [ - "clickyab.com" - ] - } - }, - { - "Clickbooth": { - "http://www.clickbooth.com/": [ - "adtoll.com", - "clickbooth.com" - ] - } - }, - { - "ClickDimensions": { - "http://www.clickdimensions.com/": [ - "clickdimensions.com" - ] - } - }, - { - "ClickDistrict": { - "http://www.clickdistrict.com/": [ - "clickdistrict.com", - "creative-serving.com" - ] - } - }, - { - "ClickFrog": { - "https://clickfrog.ru/": [ - "bashirian.biz", - "buckridge.link", - "clickfrog.ru", - "franecki.net", - "quitzon.net", - "reichelcormier.bid", - "wisokykulas.bid" - ] - } - }, - { - "ClickFuel": { - "http://clickfuel.com/": [ - "conversiondashboard.com" - ] - } - }, - { - "ClickInc": { - "http://www.clickinc.com/": [ - "clickinc.com" - ] - } - }, - { - "Clicksor": { - "http://www.clicksor.com/": [ - "clicksor.com", - "clicksor.net" - ] - } - }, - { - "Clickwinks": { - "http://www.clickwinks.com/": [ - "clickwinks.com" - ] - } - }, - { - "ClicManager": { - "http://www.clicmanager.fr/": [ - "clicmanager.fr" - ] - } - }, - { - "Clixtell": { - "https://www.clixtell.com/": [ - "clixtell.com" - ] - } - }, - { - "Clove Network": { - "http://www.clovenetwork.com/": [ - "clovenetwork.com" - ] - } - }, - { - "Cognitive Match": { - "http://www.cognitivematch.com/": [ - "cmads.com.tw", - "cmadsasia.com", - "cmadseu.com", - "cmmeglobal.com", - "cognitivematch.com" - ] - } - }, - { - "Collective": { - "http://collective.com/": [ - "collective-media.net", - "collective.com", - "oggifinogi.com", - "tumri.com", - "tumri.net", - "yt1187.net" - ] - } - }, - { - "Commission Junction": { - "http://www.cj.com/": [ - "apmebf.com", - "awltovhc.com", - "cj.com", - "ftjcfx.com", - "kcdwa.com", - "qksz.com", - "qksz.net", - "tqlkg.com", - "yceml.net" - ] - } - }, - { - "Communicator Corp": { - "http://www.communicatorcorp.com/": [ - "communicatorcorp.com" - ] - } - }, - { - "Compass Labs": { - "http://compasslabs.com/": [ - "compasslabs.com" - ] - } - }, - { - "Complex Media": { - "http://www.complexmedianetwork.com/": [ - "complex.com", - "complexmedianetwork.com" - ] - } - }, - { - "comScore": { - "http://www.comscore.com/": [ - "adxpose.com", - "proxilinks.com", - "proximic.com", - "proximic.net" - ] - } - }, - { - "Connatix.com": { - "https://connatix.com/": [ - "connatix.com" - ] - } - }, - { - "Connexity": { - "http://www.connexity.com/": [ - "pricegrabber.com" - ] - } - }, - { - "Consilium Media": { - "http://www.consiliummedia.com/": [ - "consiliummedia.com" - ] - } - }, - { - "Consumable": { - "http://consumable.com/": [ - "consumable.com" - ] - } - }, - { - "CONTAXE": { - "http://www.contaxe.com/": [ - "contaxe.com" - ] - } - }, - { - "ContentABC": { - "http://contentabc.com/": [ - "contentabc.com" - ] - } - }, - { - "CONTEXTin": { - "http://www.contextin.com/": [ - "admailtiser.com", - "contextin.com" - ] - } - }, - { - "ContextuAds": { - "http://www.contextuads.com/": [ - "agencytradingdesk.net", - "contextuads.com" - ] - } - }, - { - "CONTEXTWEB": { - "http://www.contextweb.com/": [ - "contextweb.com" - ] - } - }, - { - "ConvergeDirect": { - "http://www.convergedirect.com/": [ - "convergedirect.com", - "convergetrack.com" - ] - } - }, - { - "ConversantMedia": { - "http://conversantmedia.com": [ - "adserver.com", - "conversantmedia.com", - "dotomi.com", - "dtmpub.com", - "emjcd.com", - "fastclick.com", - "fastclick.net", - "greystripe.com", - "lduhtrp.net", - "mediaplex.com", - "valueclick.com", - "valueclick.net", - "valueclickmedia.com" - ] - } - }, - { - "ConversionRuler": { - "http://www.conversionruler.com/": [ - "conversionruler.com" - ] - } - }, - { - "Conversive": { - "http://www.conversive.nl/": [ - "conversive.nl" - ] - } - }, - { - "CoreMotives": { - "http://coremotives.com/": [ - "coremotives.com" - ] - } - }, - { - "Cox Digital Solutions": { - "http://www.coxdigitalsolutions.com/": [ - "adify.com", - "afy11.net", - "coxdigitalsolutions.com" - ] - } - }, - { - "CPMStar": { - "http://www.cpmstar.com/": [ - "cpmstar.com" - ] - } - }, - { - "CPX Interactive": { - "http://www.cpxinteractive.com/": [ - "adreadypixels.com", - "cpxadroit.com", - "cpxinteractive.com" - ] - } - }, - { - "Creafi": { - "http://www.creafi.com/": [ - "creafi.com" - ] - } - }, - { - "Crimtan": { - "http://www.crimtan.com/": [ - "crimtan.com" - ] - } - }, - { - "Crisp Media": { - "http://www.crispmedia.com/": [ - "crispmedia.com" - ] - } - }, - { - "Criteo": { - "http://www.criteo.com/": [ - "criteo.com", - "criteo.net", - "hlserve.com", - "hooklogic.com", - "storetail.io" - ] - } - }, - { - "Cross Pixel": { - "http://crosspixel.net/": [ - "crosspixel.net", - "crosspixelmedia.com", - "crsspxl.com" - ] - } - }, - { - "cXense": { - "http://www.cxense.com/": [ - "cxense.com", - "emediate.biz", - "emediate.com", - "emediate.dk", - "emediate.eu" - ] - } - }, - { - "Cyberplex": { - "http://www.cyberplex.com/": [ - "cyberplex.com" - ] - } - }, - { - "Dada": { - "http://dada.pro/": [ - "dada.pro", - "simply.com" - ] - } - }, - { - "Datalogix": { - "http://www.datalogix.com/": [ - "nexac.com", - "nextaction.net" - ] - } - }, - { - "DataXu": { - "http://www.dataxu.com/": [ - "dataxu.com", - "dataxu.net", - "mexad.com", - "w55c.net" - ] - } - }, - { - "Datonics": { - "http://datonics.com/": [ - "datonics.com", - "pro-market.net" - ] - } - }, - { - "Datran Media": { - "http://www.datranmedia.com/": [ - "datranmedia.com", - "displaymarketplace.com" - ] - } - }, - { - "Datvantage": { - "http://datvantage.com/": [ - "datvantage.com" - ] - } - }, - { - "DC Storm": { - "http://www.dc-storm.com/": [ - "dc-storm.com", - "stormiq.com" - ] - } - }, - { - "Dedicated Media": { - "http://www.dedicatedmedia.com/": [ - "dedicatedmedia.com", - "dedicatednetworks.com" - ] - } - }, - { - "Delivr": { - "http://delivr.com/": [ - "delivr.com", - "percentmobile.com" - ] - } - }, - { - "Delta Projects": { - "http://www.deltaprojects.se/": [ - "adaction.se", - "de17a.com", - "deltaprojects.se" - ] - } - }, - { - "Demand Media": { - "http://www.demandmedia.com/": [ - "demandmedia.com", - "indieclick.com" - ] - } - }, - { - "Deutsche Post DHL": { - "http://www.dp-dhl.com/": [ - "adcloud.com", - "adcloud.net", - "dp-dhl.com" - ] - } - }, - { - "Developer Media": { - "http://developermedia.com/": [ - "developermedia.com", - "lqcdn.com" - ] - } - }, - { - "DG": { - "http://www.dgit.com/": [ - "dgit.com", - "eyeblaster.com", - "eyewonder.com", - "mdadx.com", - "serving-sys.com", - "unicast.com" - ] - } - }, - { - "dianomi": { - "http://www.dianomi.com/": [ - "dianomi.com" - ] - } - }, - { - "Didit": { - "http://www.didit.com/": [ - "did-it.com", - "didit.com" - ] - } - }, - { - "DigitalAdConsortium": { - "https://www.dac.co.jp/": [ - "impact-ad.jp" - ] - } - }, - { - "Digital River": { - "http://www.digitalriver.com/": [ - "digitalriver.com", - "keywordmax.com", - "netflame.cc" - ] - } - }, - { - "Digital Target": { - "http://digitaltarget.ru": [ - "digitaltarget.ru" - ] - } - }, - { - "Digitize": { - "http://www.digitize.ie/": [ - "digitize.ie" - ] - } - }, - { - "DirectAdvert": { - "http://www.directadvert.ru/": [ - "directadvert.ru" - ] - } - }, - { - "Direct Response Group": { - "http://www.directresponsegroup.com/": [ - "directresponsegroup.com", - "ppctracking.net" - ] - } - }, - { - "Directtrack": { - "http://directtrack.com/": [ - "directtrack.com" - ] - } - }, - { - "Disqus": { - "http://disqus.com/": [ - "disqusads.com" - ] - } - }, - { - "DistrictM": { - "https://districtm.net": [ - "districtm.io" - ] - } - }, - { - "dmpxs": { - "http://bob.dmpxs.com": [ - "dmpxs.com" - ] - } - }, - { - "DoublePimp": { - "http://doublepimp.com/": [ - "doublepimp.com" - ] - } - }, - { - "DoublePositive": { - "http://www.doublepositive.com/": [ - "bid-tag.com", - "doublepositive.com" - ] - } - }, - { - "Drawbridge": { - "http://drawbrid.ge/": [ - "adsymptotic.com", - "drawbrid.ge" - ] - } - }, - { - "DS-IQ": { - "http://www.ds-iq.com/": [ - "ds-iq.com" - ] - } - }, - { - "DSNR Group": { - "http://www.dsnrmg.com/": [ - "dsnrgroup.com", - "dsnrmg.com", - "traffiliate.com", - "z5x.com", - "z5x.net" - ] - } - }, - { - "DynAdmic": { - "https://dynadmic.com/": [ - "dynadmic.com", - "dyntrk.com" - ] - } - }, - { - "DynamicOxygen": { - "http://www.dynamicoxygen.com/": [ - "dynamicoxygen.com", - "exitjunction.com" - ] - } - }, - { - "DynamicYield": { - "https://www.dynamicyield.com/": [ - "px-eu.dynamicyield.com", - "px.dynamicyield.com" - ] - } - }, - { - "Earnify": { - "http://earnify.com/": [ - "earnify.com" - ] - } - }, - { - "eBay": { - "http://www.ebay.com/": [ - "ebay.com" - ] - } - }, - { - "Effective Measure": { - "http://www.effectivemeasure.com/": [ - "effectivemeasure.com", - "effectivemeasure.net" - ] - } - }, - { - "ekolay": { - "http://www.ekolay.net/": [ - "e-kolay.net", - "ekolay.net" - ] - } - }, - { - "Eleavers": { - "http://eleavers.com/": [ - "eleavers.com" - ] - } - }, - { - "Emego": { - "http://www.usemax.de/": [ - "usemax.de" - ] - } - }, - { - "Emerse": { - "https://www.emerse.com": [ - "emerse.com" - ] - } - }, - { - "EMX": { - "https://emxdigital.com/": [ - "brealtime.com", - "clearstream.tv", - "emxdgt.com", - "emxdigital.com" - ] - } - }, - { - "Enecto": { - "http://www.enecto.com/": [ - "enecto.com" - ] - } - }, - { - "engage:BDR": { - "http://engagebdr.com/": [ - "bnmla.com", - "engagebdr.com" - ] - } - }, - { - "Engago Technology": { - "http://www.engago.com/": [ - "appmetrx.com", - "engago.com" - ] - } - }, - { - "Engine Network": { - "http://enginenetwork.com/": [ - "enginenetwork.com" - ] - } - }, - { - "Ensighten": { - "http://www.ensighten.com/": [ - "ensighten.com" - ] - } - }, - { - "Entireweb": { - "http://www.entireweb.com/": [ - "entireweb.com" - ] - } - }, - { - "Epic Media Group": { - "http://www.theepicmediagroup.com/": [ - "epicadvertising.com", - "epicmarketplace.com", - "epicmobileads.com", - "theepicmediagroup.com", - "trafficmp.com" - ] - } - }, - { - "Epsilon": { - "http://www.epsilon.com/": [ - "epsilon.com" - ] - } - }, - { - "EQ Ads": { - "http://www.eqads.com/": [ - "eqads.com" - ] - } - }, - { - "EroAdvertising": { - "http://www.ero-advertising.com/": [ - "ero-advertising.com" - ] - } - }, - { - "Etarget": { - "http://etargetnet.com/": [ - "etarget.eu", - "etargetnet.com" - ] - } - }, - { - "Etineria": { - "http://www.etineria.com/": [ - "adwitserver.com", - "etineria.com" - ] - } - }, - { - "eTrigue": { - "http://www.etrigue.com/": [ - "etrigue.com" - ] - } - }, - { - "Evergage": { - "http://www.evergage.com": [ - "mybuys.com", - "veruta.com" - ] - } - }, - { - "Everyday Health": { - "http://www.everydayhealth.com/": [ - "everydayhealth.com", - "waterfrontmedia.com" - ] - } - }, - { - "Evisions Marketing": { - "http://www.evisionsmarketing.com/": [ - "engineseeker.com", - "evisionsmarketing.com" - ] - } - }, - { - "Evolve": { - "http://www.evolvemediacorp.com/": [ - "evolvemediacorp.com", - "evolvemediametrics.com", - "gorillanation.com" - ] - } - }, - { - "eWayDirect": { - "http://www.ewaydirect.com/": [ - "ewaydirect.com", - "ixs1.net" - ] - } - }, - { - "ewebse": { - "http://ewebse.com/": [ - "777seo.com", - "ewebse.com" - ] - } - }, - { - "excitad": { - "http://excitad.com/": [ - "excitad.com" - ] - } - }, - { - "eXelate": { - "http://exelate.com/": [ - "exelate.com", - "exelator.com" - ] - } - }, - { - "ExoClick": { - "http://www.exoclick.com/": [ - "exoclick.com" - ] - } - }, - { - "Exosrv": { - "http://main.exosrv.com/": [ - "exosrv.com" - ] - } - }, - { - "Experian": { - "http://www.experian.com/": [ - "audienceiq.com", - "experian.com" - ] - } - }, - { - "expo-MAX": { - "http://expo-max.com/": [ - "expo-max.com" - ] - } - }, - { - "Exponential Interactive": { - "http://www.exponential.com/": [ - "adotube.com", - "exponential.com", - "fulltango.com", - "tribalfusion.com" - ] - } - }, - { - "Extension Factory": { - "http://www.extensionfactory.com/": [ - "extensionfactory.com" - ] - } - }, - { - "EXTENSIONS.RU": { - "http://extensions.ru/": [ - "extensions.ru" - ] - } - }, - { - "Eyeconomy": { - "http://www.eyeconomy.co.uk/": [ - "eyeconomy.co.uk", - "eyeconomy.com", - "sublimemedia.net" - ] - } - }, - { - "EyeNewton": { - "http://eyenewton.ru/": [ - "eyenewton.ru" - ] - } - }, - { - "eyeReturn Marketing": { - "http://www.eyereturnmarketing.com/": [ - "eyereturn.com", - "eyereturnmarketing.com" - ] - } - }, - { - "Eyeviewdigital": { - "http://www.eyeviewdigital.com/": [ - "eyeviewdigital.com" - ] - } - }, - { - "Facebook": { - "http://www.facebook.com/": [ - "atlassolutions.com" - ] - } - }, - { - "Facilitate Digital": { - "http://www.facilitatedigital.com/": [ - "adsfac.eu", - "adsfac.info", - "adsfac.net", - "adsfac.sg", - "adsfac.us", - "facilitatedigital.com" - ] - } - }, - { - "Fairfax Media": { - "http://www.fxj.com.au/": [ - "fairfax.com.au", - "fxj.com.au" - ] - } - }, - { - "faithadnet": { - "http://www.faithadnet.com/": [ - "faithadnet.com" - ] - } - }, - { - "Fanplayr": { - "https://fanplayr.com/": [ - "fanplayr.com" - ] - } - }, - { - "Fathom": { - "http://www.fathomdelivers.com/": [ - "fathomdelivers.com", - "fathomseo.com" - ] - } - }, - { - "Federated Media": { - "http://www.federatedmedia.net/": [ - "federatedmedia.net", - "fmpub.net", - "lijit.com" - ] - } - }, - { - "FetchBack": { - "http://www.fetchback.com/": [ - "fetchback.com" - ] - } - }, - { - "Fiksu": { - "http://www.fiksu.com/": [ - "fiksu.com" - ] - } - }, - { - "FinancialContent": { - "http://www.financialcontent.com/": [ - "financialcontent.com" - ] - } - }, - { - "Fizz-Buzz Media": { - "http://www.fizzbuzzmedia.com/": [ - "fizzbuzzmedia.com", - "fizzbuzzmedia.net" - ] - } - }, - { - "Flashtalking": { - "http://www.flashtalking.com/": [ - "flashtalking.com" - ] - } - }, - { - "Flite": { - "http://www.flite.com/": [ - "flite.com", - "widgetserver.com" - ] - } - }, - { - "Fluct": { - "https://corp.fluct.jp/": [ - "adingo.jp", - "fluct.jp" - ] - } - }, - { - "Flytxt": { - "http://www.flytxt.com/": [ - "flytxt.com" - ] - } - }, - { - "Forbes": { - "http://www.forbes.com/": [ - "brandsideplatform.com", - "forbes.com" - ] - } - }, - { - "Fox One Stop Media": { - "http://www.foxonestop.com/": [ - "fimserve.com", - "foxnetworks.com", - "foxonestop.com", - "mobsmith.com", - "myads.com", - "othersonline.com" - ] - } - }, - { - "FreakOut": { - "http://fout.jp/": [ - "fout.jp" - ] - } - }, - { - "Freedom Communications": { - "http://www.freedom.com/": [ - "freedom.com" - ] - } - }, - { - "FreeWheel": { - "http://www.freewheel.tv/": [ - "stickyadstv.com" - ] - } - }, - { - "FriendFinder Networks": { - "http://ffn.com/": [ - "adultfriendfinder.com", - "ffn.com", - "pop6.com" - ] - } - }, - { - "Friends2Follow": { - "https://friends2follow.com/": [ - "tracking.friends2follow.com" - ] - } - }, - { - "Frog Sex": { - "http://www.frogsex.com/": [ - "double-check.com", - "frogsex.com" - ] - } - }, - { - "FuelX": { - "https://fuelx.com/": [ - "fuel451.com", - "fuelx.com" - ] - } - }, - { - "Future Ads": { - "https://www.futureads.com/": [ - "futureads.com", - "resultlinks.com" - ] - } - }, - { - "Fyber": { - "https://www.fyber.com/": [ - "fyber.com" - ] - } - }, - { - "Game Advertising Online": { - "http://www.game-advertising-online.com/": [ - "game-advertising-online.com" - ] - } - }, - { - "Games2win": { - "http://www.games2win.com/": [ - "games2win.com", - "inviziads.com" - ] - } - }, - { - "Gamned": { - "http://www.gamned.com/": [ - "gamned.com" - ] - } - }, - { - "Gannett": { - "http://www.gannett.com/": [ - "gannett.com", - "pointroll.com" - ] - } - }, - { - "GB-World": { - "http://www.gb-world.net/": [ - "gb-world.net" - ] - } - }, - { - "Gemius": { - "http://www.gemius.com/": [ - "gemius.com", - "gemius.pl" - ] - } - }, - { - "Genesis Media": { - "http://www.genesismedia.com/": [ - "genesismedia.com", - "genesismediaus.com" - ] - } - }, - { - "GENIEE": { - "https://geniee.co.jp/": [ - "geniee.co.jp", - "gssprt.jp" - ] - } - }, - { - "GENIE GROUP": { - "http://www.geniegroupltd.co.uk/": [ - "geniegroupltd.co.uk" - ] - } - }, - { - "GeoAds": { - "http://www.geoads.com/": [ - "geoads.com" - ] - } - }, - { - "GetGlue": { - "http://getglue.com/": [ - "getglue.com", - "smrtlnks.com" - ] - } - }, - { - "GetIntent": { - "http://getintent.com/": [ - "adhigh.net", - "getintent.com" - ] - } - }, - { - "GISMAds": { - "http://www.gismads.jp/": [ - "gismads.jp" - ] - } - }, - { - "Glam Media": { - "http://www.glammedia.com/": [ - "glam.com", - "glammedia.com" - ] - } - }, - { - "Gleam": { - "https://gleam.io/": [ - "fraudjs.io", - "gleam.io" - ] - } - }, - { - "Globe7": { - "http://www.globe7.com/": [ - "globe7.com" - ] - } - }, - { - "GoDataFeed": { - "http://godatafeed.com/": [ - "godatafeed.com" - ] - } - }, - { - "Goldbach": { - "http://www.goldbachgroup.com/": [ - "goldbach.com", - "goldbachgroup.com" - ] - } - }, - { - "GoldSpot Media": { - "http://www.goldspotmedia.com/": [ - "goldspotmedia.com" - ] - } - }, - { - "Google": { - "http://www.google.com/": [ - "2mdn.net", - "admeld.com", - "admob.com", - "adservice.google.ca", - "adservice.google.com", - "adwords.google.com", - "cc-dt.com", - "destinationurl.com", - "doubleclick.net", - "googleadservices.com", - "googlesyndication.com", - "googletagservices.com", - "invitemedia.com", - "smtad.net", - "teracent.com", - "teracent.net", - "ytsa.net" - ] - } - }, - { - "Grapeshot": { - "http://www.grapeshot.co.uk/": [ - "grapeshot.co.uk" - ] - } - }, - { - "Graphnium": { - "https://www.graphinium.com/": [ - "crm4d.com" - ] - } - }, - { - "Grocery Shopping Network": { - "http://www.groceryshopping.net/": [ - "groceryshopping.net" - ] - } - }, - { - "GroovinAds": { - "http://www.groovinads.com/": [ - "groovinads.com" - ] - } - }, - { - "Gruner + Jahr": { - "http://www.guj.de/": [ - "guj.de", - "ligatus.com" - ] - } - }, - { - "GumGum": { - "http://gumgum.com/": [ - "gumgum.com" - ] - } - }, - { - "Gunggo": { - "http://www.gunggo.com/": [ - "gunggo.com" - ] - } - }, - { - "Hands Mobile": { - "http://www.hands.com.br/": [ - "hands.com.br" - ] - } - }, - { - "Harrenmedia": { - "http://www.harrenmedia.com/": [ - "harrenmedia.com", - "harrenmedianetwork.com" - ] - } - }, - { - "HealthPricer": { - "http://www.healthpricer.com/": [ - "adacado.com", - "healthpricer.com" - ] - } - }, - { - "Hearst": { - "http://www.hearst.com/": [ - "hearst.com", - "ic-live.com", - "iclive.com", - "icrossing.com", - "sptag.com", - "sptag1.com", - "sptag2.com", - "sptag3.com" - ] - } - }, - { - "HilltopAds": { - "https://hilltopads.com/": [ - "hilltopads.com", - "hilltopads.net", - "shoporielder.pro" - ] - } - }, - { - "Hi-media": { - "http://www.hi-media.com/": [ - "comclick.com", - "hi-media.com" - ] - } - }, - { - "Horyzon Media": { - "http://www.horyzon-media.com/": [ - "horyzon-media.com" - ] - } - }, - { - "HotMart": { - "https://www.hotmart.com/en/": [ - "hotmart.com" - ] - } - }, - { - "HOTWords": { - "http://www.hotwords.com/": [ - "hotwords.com", - "hotwords.es" - ] - } - }, - { - "HP": { - "http://www.hp.com/": [ - "hp.com", - "optimost.com" - ] - } - }, - { - "Httpool": { - "http://www.httpool.com/": [ - "httpool.com" - ] - } - }, - { - "HUNT Mobile Ads": { - "http://www.huntmads.com/": [ - "huntmads.com" - ] - } - }, - { - "Hurra.com": { - "http://www.hurra.com/": [ - "hurra.com" - ] - } - }, - { - "IAB": { - "https://iabtechlab.com/": [ - "digitru.st" - ] - } - }, - { - "IAC": { - "http://www.iac.com/": [ - "iac.com", - "iacadvertising.com" - ] - } - }, - { - "iBehavior": { - "http://www.i-behavior.com/": [ - "i-behavior.com", - "ib-ibi.com" - ] - } - }, - { - "IBM": { - "http://www.ibm.com/": [ - "unica.com" - ] - } - }, - { - "ID5": { - "http://id5.io/": [ - "id5-sync.com" - ] - } - }, - { - "IDG": { - "http://www.idg.com/": [ - "idg.com", - "idgtechnetwork.com" - ] - } - }, - { - "iEntry": { - "http://www.ientry.com/": [ - "600z.com", - "ientry.com" - ] - } - }, - { - "IgnitAd": { - "http://www.ignitad.com/": [ - "ignitad.com" - ] - } - }, - { - "IgnitionOne": { - "http://www.ignitionone.com/": [ - "ignitionone.com", - "ignitionone.net", - "searchignite.com" - ] - } - }, - { - "Improve Digital": { - "www.improvedigital.com/": [ - "360yield.com", - "improvedigital.com" - ] - } - }, - { - "Inadco": { - "http://www.inadco.com/": [ - "anadcoads.com", - "inadco.com", - "inadcoads.com" - ] - } - }, - { - "IndexExchange": { - "https://www.indexexchange.com": [ - "indexexchange.com" - ] - } - }, - { - "Infectious Media": { - "http://www.infectiousmedia.com/": [ - "impressiondesk.com", - "infectiousmedia.com" - ] - } - }, - { - "Inflection Point Media": { - "http://www.inflectionpointmedia.com/": [ - "inflectionpointmedia.com" - ] - } - }, - { - "Infogroup": { - "http://www.infogroup.com/": [ - "infogroup.com" - ] - } - }, - { - "Infolinks": { - "http://www.infolinks.com/": [ - "infolinks.com" - ] - } - }, - { - "Infra-Ad": { - "http://www.infra-ad.com/": [ - "infra-ad.com" - ] - } - }, - { - "InMobi": { - "http://www.inmobi.com/": [ - "aerserv.com", - "inmobi.com", - "sproutinc.com" - ] - } - }, - { - "inneractive": { - "http://inner-active.com/": [ - "inner-active.com" - ] - } - }, - { - "Innity": { - "http://innity.com/": [ - "innity.com" - ] - } - }, - { - "InsightExpress": { - "http://www.insightexpress.com/": [ - "insightexpress.com", - "insightexpressai.com" - ] - } - }, - { - "InSkin Media": { - "http://inskinmedia.com/": [ - "inskinmedia.com" - ] - } - }, - { - "Instinctive": { - "https://instinctive.io/": [ - "instinctive.io", - "instinctiveads.com" - ] - } - }, - { - "Integral Ad Science": { - "https://integralads.com/": [ - "adsafemedia.com", - "adsafeprotected.com", - "iasds01.com", - "integralads.com" - ] - } - }, - { - "Intent Media": { - "http://www.intentmedia.com/": [ - "intentmedia.com", - "intentmedia.net" - ] - } - }, - { - "Intergi": { - "http://intergi.com/": [ - "intergi.com" - ] - } - }, - { - "Intermarkets": { - "http://www.intermarkets.net/": [ - "intermarkets.net" - ] - } - }, - { - "Intermundo Media": { - "http://intermundomedia.com/": [ - "intermundomedia.com" - ] - } - }, - { - "Internet Brands": { - "http://www.internetbrands.com/": [ - "ibpxl.com", - "internetbrands.com" - ] - } - }, - { - "Interpolls": { - "http://www.interpolls.com/": [ - "interpolls.com" - ] - } - }, - { - "Inuvo": { - "http://inuvo.com/": [ - "inuvo.com" - ] - } - }, - { - "InvestingChannel": { - "http://investingchannel.com/": [ - "investingchannel.com" - ] - } - }, - { - "IponWeb": { - "https://www.iponweb.com/": [ - "iponweb.com", - "iponweb.net" - ] - } - }, - { - "iPROM": { - "http://www.iprom.si/": [ - "centraliprom.com", - "iprom.net", - "iprom.si", - "mediaiprom.com" - ] - } - }, - { - "iPromote": { - "http://www.ipromote.com/": [ - "ipromote.com" - ] - } - }, - { - "iProspect": { - "http://www.iprospect.com/": [ - "clickmanage.com", - "iprospect.com" - ] - } - }, - { - "ISI Technologies": { - "http://digbro.com/": [ - "adversalservers.com", - "digbro.com" - ] - } - }, - { - "ismatlab.com": { - "http://ismatlab.com": [ - "ismatlab.com" - ] - } - }, - { - "I.UA": { - "http://www.i.ua/": [ - "i.ua" - ] - } - }, - { - "Jaroop": { - "http://www.jaroop.com/": [ - "jaroop.com" - ] - } - }, - { - "JasperLabs": { - "http://www.jasperlabs.com/": [ - "jasperlabs.com" - ] - } - }, - { - "Jemm": { - "http://jemmgroup.com/": [ - "jemmgroup.com" - ] - } - }, - { - "Jink": { - "http://www.jink.de/": [ - "jink.de", - "jinkads.com" - ] - } - }, - { - "Jirbo": { - "http://jirbo.com/": [ - "adcolony.com", - "jirbo.com" - ] - } - }, - { - "Jivox": { - "http://www.jivox.com/": [ - "jivox.com" - ] - } - }, - { - "JobThread": { - "http://www.jobthread.com/": [ - "jobthread.com" - ] - } - }, - { - "JuicyAds": { - "http://www.juicyads.com/": [ - "juicyads.com" - ] - } - }, - { - "Jumptap": { - "http://www.jumptap.com/": [ - "jumptap.com" - ] - } - }, - { - "justuno": { - "https://www.justuno.com/": [ - "justuno.com" - ] - } - }, - { - "Kargo": { - "https://kargo.com/": [ - "kargo.com" - ] - } - }, - { - "Kenshoo": { - "http://www.kenshoo.com/": [ - "kenshoo.com", - "xg4ken.com" - ] - } - }, - { - "Keyade": { - "http://www.keyade.com/": [ - "keyade.com" - ] - } - }, - { - "Keywee": { - "https://keywee.co": [ - "keywee.co" - ] - } - }, - { - "KissMyAds": { - "http://kissmyads.com/": [ - "kissmyads.com" - ] - } - }, - { - "Kitara Media": { - "http://www.kitaramedia.com/": [ - "103092804.com", - "kitaramedia.com" - ] - } - }, - { - "KIT digital": { - "http://kitd.com/": [ - "keewurd.com", - "kitd.com", - "peerset.com" - ] - } - }, - { - "Kokteyl": { - "http://www.kokteyl.com/": [ - "admost.com", - "kokteyl.com" - ] - } - }, - { - "Komli": { - "http://www.komli.com/": [ - "komli.com" - ] - } - }, - { - "Kontera": { - "http://www.kontera.com/": [ - "kontera.com" - ] - } - }, - { - "Korrelate": { - "http://korrelate.com/": [ - "adsummos.com", - "adsummos.net", - "korrelate.com" - ] - } - }, - { - "Krux": { - "http://www.krux.com/": [ - "krux.com", - "kruxdigital.com", - "krxd.net" - ] - } - }, - { - "Lakana": { - "http://www.lakana.com/": [ - "ibsys.com", - "lakana.com" - ] - } - }, - { - "Layer-Ad.org": { - "http://layer-ad.org/": [ - "layer-ad.org" - ] - } - }, - { - "Layer Ads": { - "http://layer-ads.net/": [ - "layer-ads.net" - ] - } - }, - { - "LeadBolt": { - "http://www.leadbolt.com/": [ - "leadbolt.com" - ] - } - }, - { - "LeadFormix": { - "http://www.leadformix.com/": [ - "leadforce1.com", - "leadformix.com" - ] - } - }, - { - "LeanPlum": { - "https://www.leanplum.com/": [ - "leanplum.com" - ] - } - }, - { - "Legolas Media": { - "http://www.legolas-media.com/": [ - "legolas-media.com" - ] - } - }, - { - "Levexis": { - "http://www.levexis.com/": [ - "levexis.com" - ] - } - }, - { - "Lexos Media": { - "http://www.lexosmedia.com/": [ - "adbull.com", - "lexosmedia.com" - ] - } - }, - { - "LifeStreet": { - "http://lifestreetmedia.com/": [ - "lfstmedia.com", - "lifestreetmedia.com" - ] - } - }, - { - "LinkConnector": { - "http://www.linkconnector.com/": [ - "linkconnector.com" - ] - } - }, - { - "LinkShare": { - "http://www.linkshare.com/": [ - "linkshare.com", - "linksynergy.com" - ] - } - }, - { - "Linkz": { - "http://www.linkz.net/": [ - "linkz.net" - ] - } - }, - { - "Listrak": { - "http://www.listrak.com/": [ - "listrak.com", - "listrakbi.com" - ] - } - }, - { - "LiveIntent": { - "http://www.liveintent.com/": [ - "liadm.com", - "liveintent.com" - ] - } - }, - { - "LiveInternet": { - "http://www.liveinternet.ru": [ - "liveinternet.ru", - "yadro.ru" - ] - } - }, - { - "LiveRamp": { - "https://liveramp.com/": [ - "liveramp.com", - "tvpixel.com" - ] - } - }, - { - "LKQD": { - "http://lkqd.com": [ - "lkqd.com", - "lkqd.net" - ] - } - }, - { - "Local Yokel Media": { - "http://www.localyokelmedia.com/": [ - "localyokelmedia.com" - ] - } - }, - { - "Localytics": { - "https://www.localytics.com/": [ - "localytics.com" - ] - } - }, - { - "LockerDome": { - "https://lockerdome.com/": [ - "lockerdome.com" - ] - } - }, - { - "Longboard Media": { - "http://longboardmedia.com/": [ - "longboardmedia.com" - ] - } - }, - { - "Loomia": { - "http://www.loomia.com/": [ - "loomia.com" - ] - } - }, - { - "LoopFuse": { - "https://www.loopfuse.net/": [ - "lfov.net", - "loopfuse.net" - ] - } - }, - { - "LoopMe": { - "https://loopme.com/": [ - "loopme.com" - ] - } - }, - { - "LotLinx": { - "https://www.lotlinx.com": [ - "lotlinx.com" - ] - } - }, - { - "Lower My Bills": { - "http://lowermybills.com": [ - "lowermybills.com" - ] - } - }, - { - "lptracker": { - "https://lptracker.io/": [ - "lptracker.io" - ] - } - }, - { - "LucidMedia": { - "http://www.lucidmedia.com/": [ - "lucidmedia.com" - ] - } - }, - { - "m6d": { - "http://m6d.com/": [ - "m6d.com", - "media6degrees.com" - ] - } - }, - { - "Madhouse": { - "http://www.madhouse.cn/": [ - "madhouse.cn" - ] - } - }, - { - "Madison Logic": { - "http://www.madisonlogic.com/": [ - "dinclinx.com", - "madisonlogic.com" - ] - } - }, - { - "madvertise": { - "http://madvertise.com/": [ - "madvertise.com" - ] - } - }, - { - "Magnetic": { - "http://www.magnetic.com/": [ - "domdex.com", - "domdex.net", - "magnetic.com", - "qjex.net" - ] - } - }, - { - "Magnify360": { - "http://www.magnify360.com/": [ - "dialogmgr.com", - "magnify360.com" - ] - } - }, - { - "MailChimp": { - "http://mailchimp.com/": [ - "campaign-archive1.com", - "list-manage.com", - "mailchimp.com" - ] - } - }, - { - "Manifest": { - "http://www.manifest.ru/": [ - "bannerbank.ru", - "manifest.ru" - ] - } - }, - { - "Marchex": { - "http://www.marchex.com/": [ - "industrybrains.com", - "marchex.com" - ] - } - }, - { - "Marimedia": { - "http://www.marimedia.net/": [ - "marimedia.net" - ] - } - }, - { - "MarketGid": { - "http://www.marketgid.com/": [ - "dt00.net", - "dt07.net", - "marketgid.com" - ] - } - }, - { - "Marketo": { - "http://www.marketo.com/": [ - "marketo.com", - "marketo.net" - ] - } - }, - { - "Martini Media": { - "http://martinimedianetwork.com/": [ - "martiniadnetwork.com", - "martinimedianetwork.com" - ] - } - }, - { - "mashero": { - "http://www.mashero.com/": [ - "mashero.com" - ] - } - }, - { - "Match.com": { - "http://www.match.com/": [ - "chemistry.com", - "match.com", - "meetic-partners.com" - ] - } - }, - { - "Matomy": { - "http://www.matomy.com/": [ - "adnetinteractive.com", - "adsmarket.com", - "matomy.com", - "matomymarket.com", - "matomymedia.com", - "mediawhiz.com", - "optimatic.com", - "xtendmedia.com" - ] - } - }, - { - "MaxBounty": { - "http://www.maxbounty.com/": [ - "maxbounty.com", - "mb01.com" - ] - } - }, - { - "MaxPoint": { - "http://maxpointinteractive.com/": [ - "maxpointinteractive.com", - "maxusglobal.com", - "mxptint.net" - ] - } - }, - { - "MdotM": { - "http://mdotm.com/": [ - "mdotm.com" - ] - } - }, - { - "MediaBrix": { - "http://www.mediabrix.com/": [ - "mediabrix.com" - ] - } - }, - { - "MediaCom": { - "http://www.mediacom.com/": [ - "mediacom.com" - ] - } - }, - { - "mediaFORGE": { - "http://www.mediaforge.com/": [ - "mediaforge.com" - ] - } - }, - { - "Medialets": { - "http://www.medialets.com/": [ - "medialets.com" - ] - } - }, - { - "MediaMath": { - "http://www.mediamath.com/": [ - "adroitinteractive.com", - "designbloxlive.com", - "mathtag.com", - "mediamath.com" - ] - } - }, - { - "media.net": { - "http://www.media.net/": [ - "media.net" - ] - } - }, - { - "Mediaocean": { - "http://www.mediaocean.com/": [ - "adbuyer.com", - "mediaocean.com" - ] - } - }, - { - "MediaShakers": { - "http://www.mediashakers.com/": [ - "media-servers.net", - "mediashakers.com" - ] - } - }, - { - "MediaTrust": { - "http://www.mediatrust.com/": [ - "mediatrust.com" - ] - } - }, - { - "Medicx Media Solutions": { - "http://www.medicxmedia.com/": [ - "medicxmedia.com" - ] - } - }, - { - "MegaIndex": { - "http://www.megaindex.ru/": [ - "megaindex.ru" - ] - } - }, - { - "Mercent": { - "http://www.mercent.com/": [ - "mercent.com" - ] - } - }, - { - "MerchantAdvantage": { - "http://www.merchantadvantage.com/": [ - "merchantadvantage.com" - ] - } - }, - { - "Merchenta": { - "http://www.merchenta.com/": [ - "merchenta.com" - ] - } - }, - { - "Merkle": { - "https://www.merkleinc.com/": [ - "rimmkaufman.com", - "rkdms.com" - ] - } - }, - { - "Meta Network": { - "http://www.metanetwork.com/": [ - "metanetwork.com" - ] - } - }, - { - "Meteor": { - "http://www.meteorsolutions.com/": [ - "meteorsolutions.com" - ] - } - }, - { - "MetrixLab": { - "https://www.metrixlab.com": [ - "adoftheyear.com", - "crm-metrix.com", - "customerconversio.com", - "metrixlab.com", - "opinionbar.com" - ] - } - }, - { - "MicroAd": { - "http://www.microad.jp/": [ - "microad.jp" - ] - } - }, - { - "Microsoft": { - "http://www.microsoft.com/": [ - "adbureau.net", - "adecn.com", - "aquantive.com", - "msads.net", - "netconversions.com", - "roiservice.com" - ] - } - }, - { - "Millennial Media": { - "http://www.millennialmedia.com/": [ - "decktrade.com", - "millennialmedia.com", - "mydas.mobi" - ] - } - }, - { - "Mindset Media": { - "http://www.mindset-media.com/": [ - "mindset-media.com", - "mmismm.com" - ] - } - }, - { - "Mirando": { - "http://www.mirando.de/": [ - "mirando.de" - ] - } - }, - { - "Mixpo": { - "http://www.mixpo.com/": [ - "mixpo.com" - ] - } - }, - { - "Moat": { - "http://www.moat.com/": [ - "moat.com", - "moatads.com" - ] - } - }, - { - "MobFox": { - "http://www.mobfox.com/": [ - "mobfox.com" - ] - } - }, - { - "Mobials": { - "http://mobials.com": [ - "mobials.com" - ] - } - }, - { - "MobileAdTrading": { - "https://mobileadtrading.com/": [ - "mobileadtrading.com" - ] - } - }, - { - "Mobile Meteor": { - "http://mobilemeteor.com/": [ - "mobilemeteor.com", - "showmeinn.com" - ] - } - }, - { - "Mobile Storm": { - "http://mobilestorm.com/": [ - "mobilestorm.com" - ] - } - }, - { - "MobVision": { - "http://www.mobvision.com/": [ - "admoda.com", - "mobvision.com" - ] - } - }, - { - "Mocean Mobile": { - "http://www.moceanmobile.com/": [ - "moceanmobile.com" - ] - } - }, - { - "Mochila": { - "http://www.mochila.com/": [ - "mochila.com" - ] - } - }, - { - "Mojiva": { - "http://www.mojiva.com/": [ - "mojiva.com" - ] - } - }, - { - "Monetate": { - "http://monetate.com/": [ - "monetate.com", - "monetate.net" - ] - } - }, - { - "MONETIZEdigital": { - "https://www.cpalead.com/": [ - "cpalead.com" - ] - } - }, - { - "Monetize More": { - "http://monetizemore.com/": [ - "monetizemore.com" - ] - } - }, - { - "Monoloop": { - "http://www.monoloop.com/": [ - "monoloop.com" - ] - } - }, - { - "Monster": { - "http://www.monster.com/": [ - "monster.com" - ] - } - }, - { - "Moolah Media": { - "http://www.moolahmedia.com/": [ - "moolah-media.com", - "moolahmedia.com" - ] - } - }, - { - "MoPub": { - "http://www.mopub.com/": [ - "mopub.com" - ] - } - }, - { - "MovieLush.com": { - "https://www.movielush.com/": [ - "affbuzzads.com", - "movielush.com" - ] - } - }, - { - "Multiple Stream Media": { - "http://www.multiplestreammktg.com/": [ - "adclickmedia.com", - "multiplestreammktg.com" - ] - } - }, - { - "MUNDO Media": { - "http://www.mundomedia.com/": [ - "mundomedia.com", - "silver-path.com" - ] - } - }, - { - "MyCounter": { - "http://mycounter.com.ua/": [ - "mycounter.com.ua" - ] - } - }, - { - "MyPressPlus": { - "http://www.mypressplus.com/": [ - "mypressplus.com", - "ppjol.net" - ] - } - }, - { - "myThings": { - "http://www.mythings.com/": [ - "mythings.com", - "mythingsmedia.com" - ] - } - }, - { - "MyWebGrocer": { - "http://www.mywebgrocer.com/": [ - "mywebgrocer.com" - ] - } - }, - { - "Nanigans": { - "http://www.nanigans.com/": [ - "nanigans.com" - ] - } - }, - { - "NativeAds": { - "https://nativeads.com/": [ - "nativeads.com" - ] - } - }, - { - "Nativo": { - "http://www.nativo.net/": [ - "postrelease.com" - ] - } - }, - { - "Navegg": { - "http://www.navegg.com/": [ - "navdmp.com", - "navegg.com" - ] - } - }, - { - "NetAffiliation": { - "http://www.netaffiliation.com/": [ - "netaffiliation.com" - ] - } - }, - { - "NetBina": { - "http://www.netbina.com/": [ - "netbina.com" - ] - } - }, - { - "NetElixir": { - "http://www.netelixir.com/": [ - "adelixir.com", - "netelixir.com" - ] - } - }, - { - "Netmining": { - "http://www.netmining.com/": [ - "netmining.com", - "netmng.com" - ] - } - }, - { - "Net-Results": { - "http://www.net-results.com/": [ - "cdnma.com", - "net-results.com", - "nr7.us" - ] - } - }, - { - "NetSeer": { - "http://www.netseer.com/": [ - "netseer.com" - ] - } - }, - { - "NetShelter": { - "http://netshelter.com/": [ - "netshelter.com", - "netshelter.net" - ] - } - }, - { - "Neustar": { - "http://www.neustar.biz/": [ - "adadvisor.net", - "neustar.biz" - ] - } - }, - { - "newtention": { - "http://newtention.de/": [ - "newtention.de", - "newtention.net", - "newtentionassets.net" - ] - } - }, - { - "Nexage": { - "http://nexage.com/": [ - "nexage.com" - ] - } - }, - { - "Nextag": { - "http://www.nextag.com/": [ - "nextag.com" - ] - } - }, - { - "NextPerformance": { - "http://www.nextperformance.com/": [ - "nextperformance.com", - "nxtck.com" - ] - } - }, - { - "Nielsen": { - "http://www.nielsen.com/": [ - "imrworldwide.com", - "imrworldwide.net" - ] - } - }, - { - "Ninua": { - "http://www.ninua.com/": [ - "networkedblogs.com", - "ninua.com" - ] - } - }, - { - "Nokta": { - "http://www.noktamedya.com/": [ - "noktamedya.com", - "virgul.com" - ] - } - }, - { - "NowSpots": { - "http://nowspots.com/": [ - "nowspots.com" - ] - } - }, - { - "nrelate": { - "http://nrelate.com/": [ - "nrelate.com" - ] - } - }, - { - "Nuffnang": { - "http://www.nuffnang.com.my/": [ - "nuffnang.com", - "nuffnang.com.my" - ] - } - }, - { - "nugg.ad": { - "http://www.nugg.ad/": [ - "nugg.ad", - "nuggad.net" - ] - } - }, - { - "Ohana Media": { - "http://www.ohana-media.com/": [ - "adohana.com", - "ohana-media.com", - "ohanaqb.com" - ] - } - }, - { - "Omnicom Group": { - "http://www.omnicomgroup.com/": [ - "accuenmedia.com", - "omnicomgroup.com", - "p-td.com" - ] - } - }, - { - "onAd": { - "http://www.onad.eu/": [ - "onad.eu" - ] - } - }, - { - "Onclusive": { - "https://onclusive.com/": [ - "airpr.com" - ] - } - }, - { - "OneAd": { - "https://www.onead.com.tw/": [ - "guoshipartners.com", - "onevision.com.tw" - ] - } - }, - { - "One iota": { - "http://www.itsoneiota.com/": [ - "itsoneiota.com", - "oneiota.co.uk" - ] - } - }, - { - "Oneupweb": { - "http://www.oneupweb.com/": [ - "oneupweb.com", - "sodoit.com" - ] - } - }, - { - "OnlineMetrix": { - "http://h.online-metrix.net": [ - "online-metrix.net" - ] - } - }, - { - "Open New Media": { - "http://www.onm.de/": [ - "onm.de" - ] - } - }, - { - "OpenX": { - "http://openx.com/": [ - "liftdna.com", - "openx.com", - "openx.net", - "openx.org", - "openxenterprise.com", - "servedbyopenx.com" - ] - } - }, - { - "Opera": { - "http://www.opera.com/": [ - "mobiletheory.com", - "opera.com", - "operamediaworks.com", - "operasoftware.com" - ] - } - }, - { - "OPT": { - "http://www.opt.ne.jp/": [ - "advg.jp", - "opt.ne.jp", - "p-advg.com" - ] - } - }, - { - "Optify": { - "http://www.optify.net/": [ - "optify.net" - ] - } - }, - { - "Optimal": { - "http://optim.al/": [ - "cpmadvisors.com", - "cpmatic.com", - "nprove.com", - "optim.al", - "orbengine.com", - "xa.net" - ] - } - }, - { - "OptimumResponse": { - "http://www.optimumresponse.com/": [ - "optimumresponse.com" - ] - } - }, - { - "OptinMonster": { - "https://optinmonster.com/": [ - "optinmonster.com", - "optnmstr.com" - ] - } - }, - { - "OptMD": { - "http://optmd.com/": [ - "optmd.com" - ] - } - }, - { - "Oracle": { - "http://www.oracle.com/": [ - "estara.com" - ] - } - }, - { - "OrangeSoda": { - "http://www.orangesoda.com/": [ - "orangesoda.com", - "otracking.com" - ] - } - }, - { - "Outbrain": { - "http://www.outbrain.com/": [ - "outbrain.com", - "sphere.com", - "visualrevenue.com" - ] - } - }, - { - "Out There Media": { - "http://www.out-there-media.com/": [ - "out-there-media.com" - ] - } - }, - { - "Oversee.net": { - "http://www.oversee.net/": [ - "dsnextgen.com", - "oversee.net" - ] - } - }, - { - "OwnerIQ": { - "http://www.owneriq.com/": [ - "owneriq.com", - "owneriq.net" - ] - } - }, - { - "OxaMedia": { - "http://www.oxamedia.com/": [ - "adconnexa.com", - "adsbwm.com", - "oxamedia.com" - ] - } - }, - { - "PageFair": { - "https://pagefair.com/": [ - "pagefair.com", - "pagefair.net" - ] - } - }, - { - "Paid-To-Promote.net": { - "http://www.paid-to-promote.net/": [ - "paid-to-promote.net" - ] - } - }, - { - "Pardot": { - "http://www.pardot.com/": [ - "pardot.com" - ] - } - }, - { - "PayHit": { - "http://www.payhit.com/": [ - "payhit.com" - ] - } - }, - { - "Paypopup.com": { - "http://www.paypopup.com/": [ - "lzjl.com", - "paypopup.com" - ] - } - }, - { - "PebblePost": { - "https://www.pebblepost.com/": [ - "pbbl.co" - ] - } - }, - { - "Peer39": { - "http://www.peer39.com/": [ - "peer39.com", - "peer39.net" - ] - } - }, - { - "PeerFly": { - "http://peerfly.com/": [ - "peerfly.com" - ] - } - }, - { - "Performancing": { - "http://performancing.com/": [ - "performancing.com" - ] - } - }, - { - "PerimeterX": { - "https://www.perimeterx.com": [ - "perimeterx.net" - ] - } - }, - { - "Pheedo": { - "http://site.pheedo.com/": [ - "pheedo.com" - ] - } - }, - { - "Pictela": { - "http://www.pictela.com/": [ - "pictela.com", - "pictela.net" - ] - } - }, - { - "PinPoll": { - "https://pinpoll.com/": [ - "pinpoll.com" - ] - } - }, - { - "Pixel.sg": { - "http://www.pixel.sg/": [ - "pixel.sg" - ] - } - }, - { - "Piximedia": { - "http://www.piximedia.com/": [ - "piximedia.com" - ] - } - }, - { - "Pixlee": { - "https://www.pixlee.com/": [ - "pixlee.com" - ] - } - }, - { - "PLATFORM ONE": { - "http://www.platform-one.co.jp/": [ - "platform-one.co.jp" - ] - } - }, - { - "plista": { - "http://www.plista.com/": [ - "plista.com" - ] - } - }, - { - "PocketCents": { - "http://pocketcents.com/": [ - "pocketcents.com" - ] - } - }, - { - "Polar Mobile": { - "http://polarmobile.com": [ - "mediavoice.com", - "polarmobile.com" - ] - } - }, - { - "Politads": { - "http://politads.com/": [ - "politads.com" - ] - } - }, - { - "Polymorph": { - "http://getpolymorph.com/": [ - "adsnative.com", - "getpolymorph.com" - ] - } - }, - { - "Pontiflex": { - "http://www.pontiflex.com/": [ - "pontiflex.com" - ] - } - }, - { - "PopAds": { - "https://www.popads.net/": [ - "popads.net", - "popadscdn.net" - ] - } - }, - { - "PopRule": { - "http://poprule.com/": [ - "gocampaignlive.com", - "poprule.com" - ] - } - }, - { - "Popunder.ru": { - "http://popunder.ru/": [ - "popunder.ru" - ] - } - }, - { - "Po.st": { - "http://www.po.st/": [ - "po.st" - ] - } - }, - { - "Powerlinks": { - "https://www.powerlinks.com/": [ - "powerlinks.com" - ] - } - }, - { - "PPCProtect": { - "https://ppcprotect.com": [ - "ppcprotect.com" - ] - } - }, - { - "PrecisionClick": { - "http://www.precisionclick.com/": [ - "precisionclick.com" - ] - } - }, - { - "PredictAd": { - "http://www.predictad.com/": [ - "predictad.com" - ] - } - }, - { - "Pressflex": { - "http://www.pressflex.com/": [ - "blogads.com", - "pressflex.com" - ] - } - }, - { - "Prime Visibility": { - "http://www.primevisibility.com/": [ - "adcde.com", - "addlvr.com", - "adonnetwork.com", - "adonnetwork.net", - "adtrgt.com", - "bannertgt.com", - "cptgt.com", - "cpvfeed.com", - "cpvtgt.com", - "dashboardad.net", - "popcde.com", - "primevisibility.com", - "sdfje.com", - "urtbk.com" - ] - } - }, - { - "Primis": { - "https://www.primis.tech": [ - "sekindo.com" - ] - } - }, - { - "PrismApp": { - "https://www.prismapp.io/": [ - "prismapp.io" - ] - } - }, - { - "Proclivity": { - "http://www.proclivitymedia.com/": [ - "proclivitymedia.com", - "proclivitysystems.com", - "pswec.com" - ] - } - }, - { - "Project Wonderful": { - "http://www.projectwonderful.com/": [ - "projectwonderful.com" - ] - } - }, - { - "PrometheusIntelligenceTechnology": { - "https://prometheusintelligencetechnology.com/": [ - "prometheusintelligencetechnology.com" - ] - } - }, - { - "Propeller Ads": { - "http://propellerads.com/": [ - "propellerads.com" - ] - } - }, - { - "Prosperent": { - "http://prosperent.com/": [ - "prosperent.com" - ] - } - }, - { - "Protected Media": { - "http://www.protected.media/": [ - "ad-score.com", - "protected.media" - ] - } - }, - { - "Provers": { - "http://provers.pro": [ - "provers.pro" - ] - } - }, - { - "Psonstrentie": { - "http://psonstrentie.info": [ - "psonstrentie.info" - ] - } - }, - { - "Public-Idées": { - "http://www.publicidees.com/": [ - "publicidees.com" - ] - } - }, - { - "Publishers Clearing House": { - "http://www.pch.com/": [ - "pch.com" - ] - } - }, - { - "PubMatic": { - "http://www.pubmatic.com/": [ - "pubmatic.com", - "revinet.com" - ] - } - }, - { - "PulsePoint": { - "https://www.pulsepoint.com/": [ - "pulsepoint.com" - ] - } - }, - { - "quadrantOne": { - "http://www.quadrantone.com/": [ - "quadrantone.com" - ] - } - }, - { - "Quake Marketing": { - "http://quakemarketing.com/": [ - "quakemarketing.com" - ] - } - }, - { - "Quantcast": { - "http://www.quantcast.com/": [ - "quantcast.com", - "quantcount.com", - "quantserve.com" - ] - } - }, - { - "QuantumAdvertising": { - "http://quantum-advertising.com": [ - "quantum-advertising.com" - ] - } - }, - { - "QuinStreet": { - "http://quinstreet.com/": [ - "qnsr.com", - "qsstats.com", - "quinstreet.com" - ] - } - }, - { - "QUISMA": { - "https://quisma.com/": [ - "iaded.com", - "quisma.com", - "quismatch.com", - "xaded.com", - "xmladed.com" - ] - } - }, - { - "Radial": { - "https://www.radial.com": [ - "gsicommerce.com", - "gsimedia.net" - ] - } - }, - { - "Radiate Media": { - "http://www.radiatemedia.com/": [ - "matchbin.com", - "radiatemedia.com" - ] - } - }, - { - "RadiumOne": { - "http://www.radiumone.com/": [ - "gwallet.com", - "radiumone.com" - ] - } - }, - { - "Radius Marketing": { - "http://www.radiusmarketing.com/": [ - "radiusmarketing.com" - ] - } - }, - { - "Rambler": { - "http://www.rambler.ru/": [ - "rambler.ru" - ] - } - }, - { - "Rapleaf": { - "http://www.rapleaf.com/": [ - "rapleaf.com", - "rlcdn.com" - ] - } - }, - { - "ReachLocal": { - "http://www.reachlocal.com/": [ - "reachlocal.com", - "rlcdn.net" - ] - } - }, - { - "React2Media": { - "http://www.react2media.com/": [ - "react2media.com" - ] - } - }, - { - "Redux Media": { - "http://reduxmedia.com/": [ - "reduxmedia.com" - ] - } - }, - { - "Rekko": { - "http://rekko.com/": [ - "convertglobal.com", - "rekko.com" - ] - } - }, - { - "Reklamport": { - "http://www.reklamport.com/": [ - "reklamport.com" - ] - } - }, - { - "Reklam Store": { - "http://reklamstore.com/": [ - "reklamstore.com" - ] - } - }, - { - "Reklamz": { - "http://www.reklamz.com/": [ - "reklamz.com" - ] - } - }, - { - "Relevad": { - "http://www.relevad.com/": [ - "relestar.com", - "relevad.com" - ] - } - }, - { - "Renegade Internet": { - "http://www.renegadeinternet.com/": [ - "advertserve.com", - "renegadeinternet.com" - ] - } - }, - { - "Reporo": { - "http://www.reporo.com/": [ - "buzzcity.com" - ] - } - }, - { - "ResolutionMedia": { - "https://nonstoppartner.net/": [ - "nonstoppartner.net" - ] - } - }, - { - "Resolution Media": { - "http://resolutionmedia.com/": [ - "resolutionmedia.com" - ] - } - }, - { - "Resonate": { - "http://www.resonateinsights.com/": [ - "reson8.com", - "resonateinsights.com", - "resonatenetworks.com" - ] - } - }, - { - "Responsys": { - "http://www.responsys.com/": [ - "responsys.com" - ] - } - }, - { - "ReTargeter": { - "http://www.retargeter.com/": [ - "retargeter.com" - ] - } - }, - { - "Retirement Living": { - "www.retirement-living.com/": [ - "blvdstatus.com", - "retirement-living.com" - ] - } - }, - { - "RevContent": { - "http://revcontent.com/": [ - "revcontent.com" - ] - } - }, - { - "RevenueMax": { - "http://revenuemax.de/": [ - "revenuemax.de" - ] - } - }, - { - "Rhythm": { - "http://rhythmnewmedia.com/": [ - "1rx.io", - "rhythmnewmedia.com", - "rhythmone.com", - "rhythmxchange.com", - "rnmd.net" - ] - } - }, - { - "RichAudience": { - "https://richaudience.com/": [ - "richaudience.com" - ] - } - }, - { - "RichRelevance": { - "http://www.richrelevance.com/": [ - "richrelevance.com" - ] - } - }, - { - "RightAction": { - "http://rightaction.com/": [ - "rightaction.com" - ] - } - }, - { - "RMBN": { - "http://rmbn.net/": [ - "rmbn.net", - "rmbn.ru" - ] - } - }, - { - "RMM": { - "http://www.rmmonline.com/": [ - "rmmonline.com" - ] - } - }, - { - "Rocket Fuel": { - "http://rocketfuel.com/": [ - "rfihub.com", - "rfihub.net", - "rocketfuel.com", - "ru4.com", - "xplusone.com" - ] - } - }, - { - "Rovion": { - "http://www.rovion.com/": [ - "rovion.com" - ] - } - }, - { - "rtk": { - "http://rtk.io/": [ - "rtk.io" - ] - } - }, - { - "RubiconProject": { - "http://rubiconproject.com/": [ - "adsbyisocket.com", - "isocket.com", - "rubiconproject.com" - ] - } - }, - { - "RunAds": { - "http://www.runads.com/": [ - "runads.com", - "rundsp.com" - ] - } - }, - { - "RuTarget": { - "http://www.rutarget.ru/": [ - "rutarget.ru" - ] - } - }, - { - "Sabavision": { - "http://www.sabavision.com": [ - "sabavision.com" - ] - } - }, - { - "Sabre": { - "http://www.sabre.com/": [ - "reztrack.com", - "sabre.com", - "sabrehospitality.com" - ] - } - }, - { - "Salesforce.com": { - "http://www.salesforce.com/": [ - "salesforce.com" - ] - } - }, - { - "Samurai Factory": { - "http://www.samurai-factory.jp/": [ - "samurai-factory.jp", - "shinobi.jp" - ] - } - }, - { - "SAP": { - "https://www.sap.com": [ - "seewhy.com" - ] - } - }, - { - "Sapient": { - "http://www.sapient.com/": [ - "bridgetrack.com", - "sapient.com" - ] - } - }, - { - "SAS": { - "http://www.sas.com/": [ - "aimatch.com", - "sas.com" - ] - } - }, - { - "Scandinavian AdNetworks": { - "http://www.scandinavianadnetworks.com/": [ - "scandinavianadnetworks.com" - ] - } - }, - { - "Scribol": { - "http://scribol.com/": [ - "scribol.com" - ] - } - }, - { - "SearchForce": { - "http://www.searchforce.com/": [ - "searchforce.com", - "searchforce.net" - ] - } - }, - { - "Seevast": { - "http://www.seevast.com/": [ - "kanoodle.com", - "pulse360.com", - "seevast.com", - "syndigonetworks.com" - ] - } - }, - { - "Selectable Media": { - "http://selectablemedia.com/": [ - "nabbr.com", - "selectablemedia.com" - ] - } - }, - { - "Semantiqo": { - "http://semantiqo.com/": [ - "semantiqo.com" - ] - } - }, - { - "Semasio": { - "http://www.semasio.com/": [ - "semasio.com", - "semasio.net" - ] - } - }, - { - "SevenAds": { - "http://www.sevenads.net/": [ - "sevenads.net" - ] - } - }, - { - "SexInYourCity": { - "http://www.sexinyourcity.com/": [ - "sexinyourcity.com" - ] - } - }, - { - "ShaftTraffic": { - "https://shafttraffic.com": [ - "libertystmedia.com" - ] - } - }, - { - "ShareASale": { - "http://www.shareasale.com/": [ - "shareasale.com" - ] - } - }, - { - "Sharethrough": { - "http://sharethrough.com/": [ - "sharethrough.com" - ] - } - }, - { - "Shopzilla": { - "http://www.shopzilla.com/": [ - "shopzilla.com" - ] - } - }, - { - "Shortest": { - "http://shorte.st/": [ - "shorte.st" - ] - } - }, - { - "Silverpop": { - "http://www.silverpop.com/": [ - "mkt51.net", - "pages05.net", - "silverpop.com", - "vtrenz.net" - ] - } - }, - { - "Simpli.fi": { - "http://www.simpli.fi/": [ - "simpli.fi" - ] - } - }, - { - "SiteScout": { - "http://www.sitescout.com/": [ - "sitescout.com" - ] - } - }, - { - "Skimlinks": { - "http://skimlinks.com/": [ - "skimlinks.com", - "skimresources.com" - ] - } - }, - { - "Skupe Net": { - "http://www.skupenet.com/": [ - "adcentriconline.com", - "skupenet.com" - ] - } - }, - { - "Smaato": { - "http://www.smaato.com/": [ - "smaato.com" - ] - } - }, - { - "SmartAdServer": { - "http://smartadserver.com/": [ - "smartadserver.com" - ] - } - }, - { - "SmartyAds": { - "https://smartyads.com/": [ - "smartyads.com" - ] - } - }, - { - "Smiley Media": { - "http://www.smileymedia.com/": [ - "smileymedia.com" - ] - } - }, - { - "Smowtion": { - "http://smowtion.com/": [ - "smowtion.com" - ] - } - }, - { - "Snap": { - "http://www.snap.com/": [ - "snap.com" - ] - } - }, - { - "SocialChorus": { - "http://www.socialchorus.com/": [ - "halogenmediagroup.com", - "halogennetwork.com", - "socialchorus.com" - ] - } - }, - { - "SocialInterface": { - "http://socialinterface.com/": [ - "ratevoice.com", - "socialinterface.com" - ] - } - }, - { - "SocialTwist": { - "http://tellafriend.socialtwist.com/": [ - "socialtwist.com" - ] - } - }, - { - "sociomantic labs": { - "http://www.sociomantic.com/": [ - "sociomantic.com" - ] - } - }, - { - "Socital": { - "https://www.socital.com": [ - "socital.com" - ] - } - }, - { - "Sojern": { - "https://www.sojern.com": [ - "sojern.com" - ] - } - }, - { - "SomoAudience": { - "https://somoaudience.com/": [ - "somoaudience.com" - ] - } - }, - { - "Sonobi": { - "http://sonobi.com/": [ - "sonobi.com" - ] - } - }, - { - "sophus3": { - "http://www.sophus3.com/": [ - "sophus3.co.uk", - "sophus3.com" - ] - } - }, - { - "Sortable": { - "https://www.sortable.com/": [ - "deployads.com" - ] - } - }, - { - "Sovrn": { - "https://www.sovrn.com/": [ - "sovrn.com" - ] - } - }, - { - "Space Chimp Media": { - "http://spacechimpmedia.com/": [ - "spacechimpmedia.com" - ] - } - }, - { - "Sparklit": { - "http://www.sparklit.com/": [ - "adbutler.com", - "sparklit.com" - ] - } - }, - { - "Spark Studios": { - "http://www.sparkstudios.com/": [ - "sparkstudios.com" - ] - } - }, - { - "Specific Media": { - "http://www.specificmedia.com/": [ - "adviva.co.uk", - "adviva.net", - "sitemeter.com", - "specificclick.net", - "specificmedia.co.uk", - "specificmedia.com" - ] - } - }, - { - "Spectate": { - "http://spectate.com/": [ - "spectate.com" - ] - } - }, - { - "Sponge": { - "http://spongegroup.com/": [ - "spongegroup.com" - ] - } - }, - { - "Spongecell": { - "http://www.spongecell.com/": [ - "spongecell.com" - ] - } - }, - { - "SponsorAds": { - "http://www.sponsorads.de/": [ - "sponsorads.de" - ] - } - }, - { - "Spot200": { - "http://spot200.com/": [ - "spot200.com" - ] - } - }, - { - "SpotX": { - "https://www.spotx.tv": [ - "spotx.tv" - ] - } - }, - { - "SpotXchange": { - "http://www.spotxchange.com/": [ - "spotxchange.com" - ] - } - }, - { - "SpringServe": { - "https://springserve.com/": [ - "springserve.com" - ] - } - }, - { - "StackAdapt": { - "https://www.stackadapt.com/": [ - "stackadapt.com" - ] - } - }, - { - "StarGames": { - "https://www.stargames.net/": [ - "stargamesaffiliate.com" - ] - } - }, - { - "SteelHouse": { - "http://www.steelhouse.com/": [ - "steelhouse.com", - "steelhousemedia.com" - ] - } - }, - { - "Storygize": { - "http://www.storygize.com/": [ - "storygize.com", - "storygize.net" - ] - } - }, - { - "Streamray": { - "http://streamray.com/": [ - "cams.com", - "streamray.com" - ] - } - }, - { - "StrikeAd": { - "http://www.strikead.com/": [ - "strikead.com" - ] - } - }, - { - "StrongMail": { - "http://www.strongmail.com/": [ - "popularmedia.com" - ] - } - }, - { - "Struq": { - "http://struq.com/": [ - "struq.com" - ] - } - }, - { - "Sublime Skinz": { - "http://sublime.xyz/": [ - "ayads.co", - "sublime.xyz" - ] - } - }, - { - "Suite 66": { - "http://www.suite66.com/": [ - "suite66.com" - ] - } - }, - { - "Summit": { - "http://www.summit.co.uk/": [ - "summitmedia.co.uk" - ] - } - }, - { - "Superfish": { - "http://www.superfish.com/": [ - "superfish.com" - ] - } - }, - { - "SupersonicAds": { - "http://www.supersonicads.com/": [ - "supersonicads.com" - ] - } - }, - { - "Survata": { - "https://www.survata.com/": [ - "survata.com" - ] - } - }, - { - "Switch": { - "http://www.switchconcepts.com/": [ - "ethicalads.net", - "switchadhub.com", - "switchconcepts.co.uk", - "switchconcepts.com" - ] - } - }, - { - "Swoop": { - "http://swoop.com/": [ - "swoop.com" - ] - } - }, - { - "SymphonyAM": { - "http://www.factortg.com/": [ - "factortg.com" - ] - } - }, - { - "Syncapse": { - "http://www.syncapse.com/": [ - "clickable.net", - "syncapse.com" - ] - } - }, - { - "Syrup Ad": { - "http://adotsolution.com/": [ - "adotsolution.com" - ] - } - }, - { - "Taboola": { - "https://www.taboola.com/": [ - "perfectmarket.com", - "taboola.com" - ] - } - }, - { - "Tailsweep": { - "http://www.tailsweep.com/": [ - "tailsweep.com" - ] - } - }, - { - "Taleria": { - "https://outstream.telaria.com/": [ - "freeskreen.com" - ] - } - }, - { - "Tapad": { - "http://www.tapad.com/": [ - "tapad.com" - ] - } - }, - { - "Tapgage": { - "http://www.tapgage.com/": [ - "bizmey.com", - "tapgage.com" - ] - } - }, - { - "TapIt!": { - "http://tapit.com/": [ - "tapit.com" - ] - } - }, - { - "Tap.me": { - "http://tap.me/": [ - "tap.me" - ] - } - }, - { - "Targetix": { - "http://targetix.net/": [ - "targetix.net" - ] - } - }, - { - "Tatto Media": { - "http://tattomedia.com/": [ - "quicknoodles.com", - "tattomedia.com" - ] - } - }, - { - "Teadma": { - "http://www.teadma.com/": [ - "teadma.com" - ] - } - }, - { - "Teads.tv": { - "http://teads.tv/": [ - "ebuzzing.com", - "teads.tv" - ] - } - }, - { - "Technorati": { - "http://technorati.com/": [ - "technorati.com", - "technoratimedia.com" - ] - } - }, - { - "TellApart": { - "http://tellapart.com/": [ - "tellapart.com", - "tellapt.com" - ] - } - }, - { - "Telstra": { - "http://www.telstra.com.au/": [ - "sensis.com.au", - "sensisdata.com.au", - "sensisdigitalmedia.com.au", - "telstra.com.au" - ] - } - }, - { - "Terra": { - "http://www.terra.com.br/": [ - "eztargetmedia.com", - "terra.com.br" - ] - } - }, - { - "The Numa Group": { - "http://www.thenumagroup.com/": [ - "hittail.com", - "thenumagroup.com" - ] - } - }, - { - "The Search Agency": { - "http://www.thesearchagency.com/": [ - "thesearchagency.com", - "thesearchagency.net" - ] - } - }, - { - "The Trade Desk": { - "http://thetradedesk.com/": [ - "adsrvr.org", - "thetradedesk.com" - ] - } - }, - { - "Think Realtime": { - "http://www.thinkrealtime.com/": [ - "echosearch.com", - "esm1.net", - "thinkrealtime.com" - ] - } - }, - { - "Tinder": { - "http://tinder.com/": [ - "carbonads.com", - "tinder.com" - ] - } - }, - { - "TiqIQ": { - "http://www.tiqiq.com/": [ - "tiqiq.com" - ] - } - }, - { - "Tisoomi": { - "http://www.tisoomi.com/": [ - "adternal.com", - "tisoomi.com" - ] - } - }, - { - "TLVMedia": { - "http://tlvmedia.com/": [ - "tlvmedia.com" - ] - } - }, - { - "Todacell": { - "http://www.todacell.com/": [ - "todacell.com" - ] - } - }, - { - "ToneFuse": { - "http://tonefuse.com/": [ - "tonefuse.com" - ] - } - }, - { - "ToneMedia": { - "http://tonemedia.com/": [ - "clickfuse.com", - "tonemedia.com" - ] - } - }, - { - "TouchCommerce": { - "http://www.touchcommerce.com/": [ - "inq.com", - "touchcommerce.com" - ] - } - }, - { - "TrackingSoft": { - "http://trackingsoft.com/": [ - "trackingsoft.com" - ] - } - }, - { - "Tradedoubler": { - "http://www.tradedoubler.com/": [ - "tradedoubler.com" - ] - } - }, - { - "TradeTracker": { - "http://www.tradetracker.com/": [ - "tradetracker.com", - "tradetracker.net" - ] - } - }, - { - "TrafficHaus": { - "http://www.traffichaus.com/": [ - "traffichaus.com", - "traffichouse.com" - ] - } - }, - { - "TrafficRevenue": { - "http://www.trafficrevenue.net/": [ - "trafficrevenue.net" - ] - } - }, - { - "Traffiq": { - "http://www.traffiq.com/": [ - "traffiq.com" - ] - } - }, - { - "Trafmag": { - "http://trafmag.com/": [ - "trafmag.com" - ] - } - }, - { - "Traverse": { - "http://www.traversedata.com/": [ - "traversedlp.com" - ] - } - }, - { - "Travora Media": { - "http://www.travoramedia.com/": [ - "traveladnetwork.com", - "traveladvertising.com", - "travoramedia.com" - ] - } - }, - { - "Tremor Video": { - "http://www.tremorvideo.com/": [ - "scanscout.com", - "tmnetads.com", - "tremorhub.com", - "tremormedia.com", - "tremorvideo.com" - ] - } - }, - { - "Triggit": { - "http://triggit.com/": [ - "triggit.com" - ] - } - }, - { - "TripleLift": { - "http://triplelift.com/": [ - "3lift.com", - "triplelift.com" - ] - } - }, - { - "TruEffect": { - "http://www.trueffect.com/": [ - "adlegend.com", - "trueffect.com" - ] - } - }, - { - "TrustX": { - "https://trustx.org/": [ - "trustx.org" - ] - } - }, - { - "TubeMogul": { - "http://www.tubemogul.com/": [ - "tmogul.com", - "tubemogul.com" - ] - } - }, - { - "Twelvefold": { - "http://www.twelvefold.com/": [ - "buzzlogic.com", - "twelvefold.com" - ] - } - }, - { - "Twitter": { - "https://twitter.com/": [ - "ads-twitter.com" - ] - } - }, - { - "Twyn Group": { - "http://www.twyn.com/": [ - "twyn-group.com", - "twyn.com" - ] - } - }, - { - "Tyroo": { - "http://www.tyroo.com/": [ - "tyroo.com" - ] - } - }, - { - "ucfunnel": { - "https://www.ucfunnel.com/": [ - "aralego.com", - "ucfunnel.com" - ] - } - }, - { - "uCoz": { - "http://www.ucoz.com/": [ - "ucoz.ae", - "ucoz.br", - "ucoz.com", - "ucoz.du", - "ucoz.fr", - "ucoz.net", - "ucoz.ru" - ] - } - }, - { - "Unanimis": { - "http://www.unanimis.co.uk/": [ - "unanimis.co.uk" - ] - } - }, - { - "Underdog Media": { - "http://www.underdogmedia.com/": [ - "udmserve.net", - "underdogmedia.com" - ] - } - }, - { - "Undertone": { - "http://www.undertone.com/": [ - "undertone.com", - "undertonenetworks.com", - "undertonevideo.com" - ] - } - }, - { - "UniQlick": { - "http://www.uniqlick.com/": [ - "51network.com", - "uniqlick.com", - "wanmo.com" - ] - } - }, - { - "Unruly": { - "https://unruly.co/": [ - "unrulymedia.com" - ] - } - }, - { - "Upland": { - "https://uplandsoftware.com/": [ - "leadlander.com", - "trackalyzer.com" - ] - } - }, - { - "up-value": { - "http://www.up-value.de/": [ - "up-value.de" - ] - } - }, - { - "Value Ad": { - "http://valuead.com/": [ - "valuead.com" - ] - } - }, - { - "Various": { - "http://www.various.com/": [ - "amigos.com", - "getiton.com", - "medley.com", - "nostringsattached.com", - "various.com" - ] - } - }, - { - "Vdopia": { - "http://www.vdopia.com/": [ - "ivdopia.com", - "vdopia.com" - ] - } - }, - { - "Veeseo": { - "http://veeseo.com": [ - "veeseo.com" - ] - } - }, - { - "Velocity Media": { - "http://adsvelocity.com/": [ - "adsvelocity.com" - ] - } - }, - { - "Velti": { - "http://www.velti.com/": [ - "mobclix.com", - "velti.com" - ] - } - }, - { - "Vemba": { - "https://www.vemba.com/": [ - "vemba.com" - ] - } - }, - { - "Venatus Media": { - "http://venatusmedia.com": [ - "venatusmedia.com" - ] - } - }, - { - "Vendemore": { - "https://vendemore.com/": [ - "vendemore.com" - ] - } - }, - { - "Vendio": { - "http://www.vendio.com/": [ - "singlefeed.com", - "vendio.com" - ] - } - }, - { - "Veoxa": { - "http://www.veoxa.com/": [ - "veoxa.com" - ] - } - }, - { - "Veremedia": { - "http://www.veremedia.com/": [ - "veremedia.com" - ] - } - }, - { - "VerticalHealth": { - "https://www.verticalhealth.com/": [ - "verticalhealth.net" - ] - } - }, - { - "VerticalResponse": { - "http://www.verticalresponse.com/": [ - "verticalresponse.com", - "vresp.com" - ] - } - }, - { - "Vibrant Media": { - "http://www.vibrantmedia.com/": [ - "intellitxt.com", - "picadmedia.com", - "vibrantmedia.com" - ] - } - }, - { - "VideoIntelligence": { - "https://www.vi.ai/": [ - "vi.ai" - ] - } - }, - { - "VigLink": { - "http://www.viglink.com/": [ - "viglink.com" - ] - } - }, - { - "VisibleBrands": { - "http://www.visbrands.com/": [ - "visbrands.com" - ] - } - }, - { - "Visible Measures": { - "http://www.visiblemeasures.com/": [ - "viewablemedia.net", - "visiblemeasures.com" - ] - } - }, - { - "VisualDNA": { - "http://www.visualdna.com/": [ - "vdna-assets.com", - "visualdna-stats.com", - "visualdna.com" - ] - } - }, - { - "Vizu": { - "http://www.vizu.com/": [ - "vizu.com" - ] - } - }, - { - "Vizury": { - "http://www.vizury.com/": [ - "vizury.com" - ] - } - }, - { - "Vserv": { - "http://www.vserv.com/": [ - "vserv.com", - "vserv.mobi" - ] - } - }, - { - "Vuble": { - "https://vuble.tv/us/": [ - "mediabong.com" - ] - } - }, - { - "Wahoha": { - "http://wahoha.com/": [ - "contentwidgets.net", - "wahoha.com" - ] - } - }, - { - "Wayfair": { - "https://www.wayfair.com/": [ - "wayfair.com" - ] - } - }, - { - "WebAds": { - "http://www.webads.co.uk/": [ - "webads.co.uk" - ] - } - }, - { - "Web.com": { - "http://www.web.com/": [ - "feedperfect.com", - "web.com" - ] - } - }, - { - "WebGozar.com": { - "http://www.webgozar.com/": [ - "webgozar.com", - "webgozar.ir" - ] - } - }, - { - "Webmecanik": { - "https://www.webmecanik.com/": [ - "webmecanik.com" - ] - } - }, - { - "WebMetro": { - "http://www.webmetro.com/": [ - "dsmmadvantage.com", - "webmetro.com" - ] - } - }, - { - "Weborama": { - "http://weborama.com/": [ - "weborama.com", - "weborama.fr" - ] - } - }, - { - "Webtraffic": { - "http://www.webtraffic.se/": [ - "webtraffic.no", - "webtraffic.se" - ] - } - }, - { - "WideOrbit": { - "https://www.wideorbit.com/": [ - "dep-x.com" - ] - } - }, - { - "WiredMinds": { - "http://www.wiredminds.com/": [ - "wiredminds.com", - "wiredminds.de" - ] - } - }, - { - "Wishabi": { - "http://wishabi.com": [ - "wishabi.com", - "wishabi.net" - ] - } - }, - { - "WordStream": { - "http://www.wordstream.com/": [ - "wordstream.com" - ] - } - }, - { - "WPP": { - "http://www.wpp.com/": [ - "247realmedia.com", - "accelerator-media.com", - "acceleratorusa.com", - "decdna.net", - "decideinteractive.com", - "gmads.net", - "groupm.com", - "kantarmedia.com", - "mecglobal.com", - "mindshare.nl", - "mookie1.com", - "pm14.com", - "realmedia.com", - "targ.ad", - "themig.com", - "wpp.com", - "xaxis.com" - ] - } - }, - { - "xAd": { - "http://www.xad.com/": [ - "xad.com" - ] - } - }, - { - "Xertive Media": { - "http://www.xertivemedia.com/": [ - "admanager-xertive.com", - "xertivemedia.com" - ] - } - }, - { - "xplosion interactive": { - "http://www.xplosion.de/": [ - "xplosion.de" - ] - } - }, - { - "Xrost DS": { - "http://www.adplan-ds.com/": [ - "adplan-ds.com" - ] - } - }, - { - "Yabuka": { - "http://www.yabuka.com/": [ - "yabuka.com" - ] - } - }, - { - "Yahoo!": { - "http://www.yahoo.com/": [ - "adinterax.com", - "adrevolver.com", - "ads.yahoo.com", - "adserver.yahoo.com", - "advertising.yahoo.com", - "bluelithium.com", - "dapper.net", - "flurry.com", - "interclick.com", - "marketingsolutions.yahoo.com", - "overture.com", - "rightmedia.com", - "rmxads.com", - "secure-adserver.com", - "thewheelof.com", - "yieldmanager.com", - "yieldmanager.net", - "yldmgrimg.net" - ] - } - }, - { - "Yandex": { - "http://www.yandex.com/": [ - "adfox.yandex.ru", - "an.yandex.ru", - "awaps.yandex.ru", - "mc.yandex.ru", - "moikrug.ru", - "web-visor.com", - "yandex.ru/clck/click", - "yandex.ru/clck/counter", - "yandex.ru/cycounter", - "yandex.ru/portal/set/any", - "yandex.ru/set/s/rsya-tag-users/data" - ] - } - }, - { - "Ybrant Digital": { - "http://www.ybrantdigital.com/": [ - "addynamix.com", - "adserverplus.com", - "oridian.com", - "ybrantdigital.com" - ] - } - }, - { - "YD": { - "http://www.ydworld.com/": [ - "ydworld.com", - "yieldivision.com" - ] - } - }, - { - "YellowHammer": { - "http://www.yhmg.com/": [ - "attracto.com", - "clickhype.com", - "yellowhammermg.com", - "yhmg.com" - ] - } - }, - { - "Yes Ads": { - "http://yesads.com/": [ - "yesads.com" - ] - } - }, - { - "YieldAds": { - "http://yieldads.com/": [ - "yieldads.com" - ] - } - }, - { - "YieldBids": { - "http://ybx.io/": [ - "ybx.io" - ] - } - }, - { - "YieldBot": { - "http://yieldbot.com/": [ - "yldbt.com" - ] - } - }, - { - "YieldBuild": { - "http://yieldbuild.com/": [ - "yieldbuild.com" - ] - } - }, - { - "Yieldify": { - "https://www.yieldify.com/": [ - "yieldify.com" - ] - } - }, - { - "Yieldlab": { - "http://www.yieldlab.de/": [ - "yieldlab.de", - "yieldlab.net" - ] - } - }, - { - "Yieldmo": { - "https://yieldmo.com": [ - "yieldmo.com" - ] - } - }, - { - "YieldNexus": { - "https://www.yieldnexus.com/": [ - "ynxs.io" - ] - } - }, - { - "YOC": { - "http://group.yoc.com/": [ - "yoc-performance.com", - "yoc.com" - ] - } - }, - { - "Yoggrt": { - "http://www.yoggrt.com/": [ - "yoggrt.com" - ] - } - }, - { - "youknowbest": { - "http://www.youknowbest.com/": [ - "youknowbest.com" - ] - } - }, - { - "YuMe": { - "http://www.yume.com/": [ - "yume.com", - "yumenetworks.com" - ] - } - }, - { - "ZafulAffiliate": { - "https://affiliate.zaful.com/": [ - "affasi.com", - "gw-ec.com", - "zaful.com" - ] - } - }, - { - "Zango": { - "http://www.zango.com/": [ - "metricsdirect.com", - "zango.com" - ] - } - }, - { - "zanox": { - "http://www.zanox.com/": [ - "buy.at", - "zanox-affiliate.de", - "zanox.com" - ] - } - }, - { - "zapunited": { - "http://www.zapunited.com/": [ - "zaparena.com", - "zapunited.com" - ] - } - }, - { - "ZEDO": { - "http://www.zedo.com/": [ - "zedo.com", - "zincx.com" - ] - } - }, - { - "Zefir": { - "https://ze-fir.com/": [ - "ze-fir.com" - ] - } - }, - { - "Zemanta": { - "http://www.zemanta.com/": [ - "zemanta.com" - ] - } - }, - { - "ZestAd": { - "http://www.zestad.com/": [ - "zestad.com" - ] - } - }, - { - "Zeta Email Solutions": { - "http://www.zetaemailsolutions.com/": [ - "insightgrit.com", - "zetaemailsolutions.com" - ] - } - }, - { - "Zumobi": { - "http://www.zumobi.com/": [ - "zumobi.com" - ] - } - }, - { - "ZypMedia": { - "http://www.zypmedia.com/": [ - "extend.tv", - "zypmedia.com" - ] - } - } - ], - "Content": [ - { - "33Across": { - "http://33across.com/": [ - "tynt.com" - ] - } - }, - { - "ActivEngage": { - "http://www.activengage.com/": [ - "activengage.com" - ] - } - }, - { - "Adap.tv": { - "http://adap.tv/": [ - "adap.tv" - ] - } - }, - { - "Adobe": { - "http://www.adobe.com/": [ - "adobe.com", - "fyre.co", - "livefyre.com", - "typekit.com" - ] - } - }, - { - "Akamai": { - "http://www.akamai.com/": [ - "abmr.net", - "akamai.com", - "edgesuite.net" - ] - } - }, - { - "AKQA": { - "http://www.akqa.com/": [ - "akqa.com", - "srtk.net" - ] - } - }, - { - "Amazon.com": { - "http://www.amazon.com/": [ - "alexa.com", - "amazon.com", - "cloudfront.net" - ] - } - }, - { - "AOL": { - "http://www.aol.com/": [ - "5min.com", - "aim.com", - "aol.com", - "aolanswers.com", - "aolcdn.com", - "aoltechguru.com", - "autoblog.com", - "cambio.com", - "dailyfinance.com", - "editions.com", - "engadget.com", - "games.com", - "homesessive.com", - "huffingtonpost.com", - "joystiq.com", - "kitchendaily.com", - "makers.com", - "mandatory.com", - "mapquest.com", - "moviefone.com", - "noisecreep.com", - "patch.com", - "pawnation.com", - "shortcuts.com", - "shoutcast.com", - "spinner.com", - "stylelist.com", - "stylemepretty.com", - "surphace.com", - "techcrunch.com", - "theboombox.com", - "theboot.com", - "tuaw.com", - "userplane.com", - "winamp.com" - ] - } - }, - { - "Automattic": { - "http://automattic.com/": [ - "automattic.com", - "gravatar.com", - "intensedebate.com" - ] - } - }, - { - "Baynote": { - "http://www.baynote.com/": [ - "baynote.com", - "baynote.net" - ] - } - }, - { - "Bazaarvoice": { - "http://www.bazaarvoice.com/": [ - "bazaarvoice.com" - ] - } - }, - { - "BigDoor": { - "http://www.bigdoor.com/": [ - "bigdoor.com", - "onetruefan.com" - ] - } - }, - { - "Brightcove": { - "http://www.brightcove.com/": [ - "brightcove.com" - ] - } - }, - { - "Browser-Update.org": { - "www.browser-update.org/": [ - "browser-update.org" - ] - } - }, - { - "BTBuckets": { - "http://btbuckets.com/": [ - "btbuckets.com" - ] - } - }, - { - "Buffer": { - "http://bufferapp.com/": [ - "bufferapp.com" - ] - } - }, - { - "Bunchball": { - "http://www.bunchball.com/": [ - "bunchball.com" - ] - } - }, - { - "buySAFE": { - "http://www.buysafe.com/": [ - "buysafe.com" - ] - } - }, - { - "BuzzFeed": { - "http://www.buzzfeed.com/": [ - "buzzfed.com", - "buzzfeed.com" - ] - } - }, - { - "Cbox": { - "http://www.cbox.ws/": [ - "cbox.ws" - ] - } - }, - { - "CBS Interactive": { - "http://www.cbsinteractive.com/": [ - "cbsinteractive.com", - "com.com" - ] - } - }, - { - "Cedexis": { - "http://www.cedexis.com/": [ - "cedexis.com", - "cedexis.net" - ] - } - }, - { - "Certona": { - "http://www.certona.com/": [ - "certona.com", - "res-x.com" - ] - } - }, - { - "ClipSyndicate": { - "http://www.clipsyndicate.com/": [ - "clipsyndicate.com" - ] - } - }, - { - "Collarity": { - "http://www.collarity.com/": [ - "collarity.com" - ] - } - }, - { - "Conduit": { - "http://www.conduit.com/": [ - "conduit-banners.com", - "conduit-services.com", - "conduit.com", - "wibiya.com" - ] - } - }, - { - "Congoo": { - "http://www.congoo.com/": [ - "congoo.com" - ] - } - }, - { - "Contact At Once!": { - "http://www.contactatonce.com/": [ - "contactatonce.com" - ] - } - }, - { - "Conviva": { - "http://www.conviva.com/": [ - "conviva.com" - ] - } - }, - { - "DailyMe": { - "http://dailyme.com/": [ - "dailyme.com", - "newstogram.com" - ] - } - }, - { - "DataSift": { - "http://datasift.com/": [ - "datasift.com", - "tweetmeme.com" - ] - } - }, - { - "Disqus": { - "http://disqus.com/": [ - "disqus.com" - ] - } - }, - { - "Echo": { - "http://aboutecho.com/": [ - "aboutecho.com", - "haloscan.com", - "js-kit.com" - ] - } - }, - { - "Facebook": { - "http://www.facebook.com/": [ - "fbcdn.net", - "instagram.com", - "messenger.com" - ] - } - }, - { - "Flattr": { - "http://flattr.com/": [ - "flattr.com" - ] - } - }, - { - "FreeWheel": { - "http://www.freewheel.tv/": [ - "freewheel.tv", - "fwmrm.net" - ] - } - }, - { - "Genius.com": { - "http://www.genius.com/": [ - "genius.com" - ] - } - }, - { - "Get Satisfaction": { - "https://getsatisfaction.com/": [ - "getsatisfaction.com" - ] - } - }, - { - "Gigya": { - "http://www.gigya.com/": [ - "gigcount.com", - "gigya.com" - ] - } - }, - { - "Global Takeoff": { - "http://www.globaltakeoff.com/": [ - "globaltakeoff.com", - "globaltakeoff.net" - ] - } - }, - { - "GoGrid": { - "http://www.gogrid.com/": [ - "formalyzer.com", - "gogrid.com", - "komli.net" - ] - } - }, - { - "Google": { - "http://www.google.com/": [ - "accounts.google.com", - "apis.google.com", - "appengine.google.com", - "apture.com", - "blogger.com", - "books.google.com", - "checkout.google.com", - "chrome.google.com", - "code.google.com", - "codesearch.google.com", - "docs.google.com", - "drive.google.com", - "earth.google.com", - "encrypted.google.com", - "feedburner.com", - "feedburner.google.com", - "feedproxy.google.com", - "finance.google.com", - "ggpht.com", - "gmodules.com", - "google-melange.com", - "google.ad", - "google.ae", - "google.al", - "google.am", - "google.as", - "google.at", - "google.az", - "google.ba", - "google.be", - "google.bf", - "google.bg", - "google.bi", - "google.bj", - "google.bs", - "google.bt", - "google.by", - "google.ca", - "google.cat", - "google.cd", - "google.cf", - "google.cg", - "google.ch", - "google.ci", - "google.cl", - "google.cm", - "google.cn", - "google.co.ao", - "google.co.bw", - "google.co.ck", - "google.co.cr", - "google.co.id", - "google.co.il", - "google.co.in", - "google.co.jp", - "google.co.ke", - "google.co.kr", - "google.co.ls", - "google.co.ma", - "google.co.mz", - "google.co.nz", - "google.co.th", - "google.co.tz", - "google.co.ug", - "google.co.uk", - "google.co.uz", - "google.co.ve", - "google.co.vi", - "google.co.za", - "google.co.zm", - "google.co.zw", - "google.com", - "google.com.af", - "google.com.ag", - "google.com.ai", - "google.com.ar", - "google.com.au", - "google.com.bd", - "google.com.bh", - "google.com.bn", - "google.com.bo", - "google.com.br", - "google.com.bz", - "google.com.co", - "google.com.cu", - "google.com.cy", - "google.com.do", - "google.com.ec", - "google.com.eg", - "google.com.et", - "google.com.fj", - "google.com.gh", - "google.com.gi", - "google.com.gt", - "google.com.hk", - "google.com.jm", - "google.com.kh", - "google.com.kw", - "google.com.lb", - "google.com.ly", - "google.com.mm", - "google.com.mt", - "google.com.mx", - "google.com.my", - "google.com.na", - "google.com.nf", - "google.com.ng", - "google.com.ni", - "google.com.np", - "google.com.om", - "google.com.pa", - "google.com.pe", - "google.com.pg", - "google.com.ph", - "google.com.pk", - "google.com.pr", - "google.com.py", - "google.com.qa", - "google.com.sa", - "google.com.sb", - "google.com.sg", - "google.com.sl", - "google.com.sv", - "google.com.tj", - "google.com.tr", - "google.com.tw", - "google.com.ua", - "google.com.uy", - "google.com.vc", - "google.com.vn", - "google.cv", - "google.cz", - "google.de", - "google.dj", - "google.dk", - "google.dm", - "google.dz", - "google.ee", - "google.es", - "google.fi", - "google.fm", - "google.fr", - "google.ga", - "google.ge", - "google.gg", - "google.gl", - "google.gm", - "google.gp", - "google.gr", - "google.gy", - "google.hn", - "google.hr", - "google.ht", - "google.hu", - "google.ie", - "google.im", - "google.iq", - "google.is", - "google.it", - "google.je", - "google.jo", - "google.kg", - "google.ki", - "google.kz", - "google.la", - "google.li", - "google.lk", - "google.lt", - "google.lu", - "google.lv", - "google.md", - "google.me", - "google.mg", - "google.mk", - "google.ml", - "google.mn", - "google.ms", - "google.mu", - "google.mv", - "google.mw", - "google.ne", - "google.nl", - "google.no", - "google.nr", - "google.nu", - "google.pl", - "google.pn", - "google.ps", - "google.pt", - "google.ro", - "google.rs", - "google.ru", - "google.rw", - "google.sc", - "google.se", - "google.sh", - "google.si", - "google.sk", - "google.sm", - "google.sn", - "google.so", - "google.st", - "google.td", - "google.tg", - "google.tk", - "google.tl", - "google.tm", - "google.tn", - "google.to", - "google.tt", - "google.vg", - "google.vu", - "google.ws", - "googleapis.com", - "googleartproject.com", - "googleusercontent.com", - "groups.google.com", - "gstatic.com", - "health.google.com", - "images.google.com", - "investor.google.com", - "knol.google.com", - "maps.google.com", - "music.google.com", - "news.google.com", - "panoramio.com", - "picasa.google.com", - "picasaweb.google.com", - "play.google.com", - "postini.com", - "recaptcha.net", - "script.google.com", - "shopping.google.com", - "sites.google.com", - "sketchup.google.com", - "support.google.com", - "talk.google.com", - "talkgadget.google.com", - "toolbar.google.com", - "translate.google.com", - "trends.google.com", - "video.google.com", - "videos.google.com", - "wallet.google.com", - "youtube.com" - ] - } - }, - { - "Gravity": { - "http://www.gravity.com/": [ - "gravity.com", - "grvcdn.com" - ] - } - }, - { - "Heyzap": { - "http://www.heyzap.com/": [ - "heyzap.com" - ] - } - }, - { - "HubSpot": { - "http://www.hubspot.com/": [ - "hubspot.com" - ] - } - }, - { - "IBM": { - "http://www.ibm.com/": [ - "xtify.com" - ] - } - }, - { - "iovation": { - "http://www.iovation.com/": [ - "iesnare.com", - "iovation.com" - ] - } - }, - { - "Kaltura": { - "http://corp.kaltura.com/": [ - "kaltura.com" - ] - } - }, - { - "kikin": { - "http://www.kikin.com/": [ - "kikin.com" - ] - } - }, - { - "Limelight Networks": { - "http://www.limelight.com/": [ - "clickability.com", - "limelight.com", - "llnwd.net" - ] - } - }, - { - "LivePerson": { - "http://www.liveperson.net/": [ - "liveperson.net" - ] - } - }, - { - "LiveRail": { - "http://liverail.com/": [ - "liverail.com" - ] - } - }, - { - "LongTail Video": { - "http://www.longtailvideo.com/": [ - "longtailvideo.com", - "ltassrv.com" - ] - } - }, - { - "Markit": { - "http://www.markit.com/": [ - "markit.com", - "wsod.com" - ] - } - }, - { - "MashLogic": { - "http://www.mashlogic.com/": [ - "mashlogic.com" - ] - } - }, - { - "McAfee": { - "http://www.mcafee.com/": [ - "mcafee.com", - "scanalert.com" - ] - } - }, - { - "Microsoft": { - "http://www.microsoft.com/": [ - "bing.com", - "gamesforwindows.com", - "getgamesmart.com", - "healthvault.com", - "ieaddons.com", - "iegallery.com", - "live.com", - "microsoft.com", - "microsoftalumni.com", - "microsoftalumni.org", - "microsoftstore.com", - "msn.com", - "msndirect.com", - "office.com", - "officelive.com", - "outlook.com", - "s-msn.com", - "skype.com", - "windowsphone.com", - "worldwidetelescope.org", - "xbox.com", - "zune.com", - "zune.net" - ] - } - }, - { - "NDN": { - "http://www.newsinc.com/": [ - "newsinc.com" - ] - } - }, - { - "Oberon Media": { - "http://www.oberon-media.com/": [ - "blaze.com", - "oberon-media.com" - ] - } - }, - { - "Ooyala": { - "http://www.ooyala.com/": [ - "oo4.com", - "ooyala.com" - ] - } - }, - { - "Oracle": { - "http://www.oracle.com/": [ - "atgsvcs.com", - "instantservice.com", - "istrack.com", - "oracle.com" - ] - } - }, - { - "Peerius": { - "http://www.peerius.com/": [ - "peerius.com" - ] - } - }, - { - "Pinterest": { - "http://pinterest.com/": [ - "pinimg.com", - "pinterest.com" - ] - } - }, - { - "PunchTab": { - "http://www.punchtab.com/": [ - "punchtab.com" - ] - } - }, - { - "RIM": { - "http://www.rim.com/": [ - "rim.com", - "scoreloop.com" - ] - } - }, - { - "Salesforce.com": { - "http://www.salesforce.com/": [ - "salesforceliveagent.com" - ] - } - }, - { - "SAY": { - "http://saymedia.com/": [ - "saymedia.com", - "typepad.com", - "videoegg.com" - ] - } - }, - { - "ScribeFire": { - "http://www.scribefire.com/": [ - "scribefire.com" - ] - } - }, - { - "Six Apart": { - "http://www.sixapart.com/": [ - "sixapart.com" - ] - } - }, - { - "Skribit": { - "http://skribit.com/": [ - "skribit.com" - ] - } - }, - { - "SnapEngage": { - "http://www.snapengage.com/": [ - "snapengage.com" - ] - } - }, - { - "Spring Metrics": { - "http://www.springmetrics.com/": [ - "springmetrics.com" - ] - } - }, - { - "Synacor": { - "http://www.synacor.com/": [ - "synacor.com" - ] - } - }, - { - "ThingLink": { - "http://www.thinglink.com/": [ - "thinglink.com" - ] - } - }, - { - "Thismoment": { - "http://www.thismoment.com/": [ - "thismoment.com" - ] - } - }, - { - "Thummit": { - "http://www.thummit.com/": [ - "thummit.com" - ] - } - }, - { - "Topsy": { - "http://topsy.com/": [ - "topsy.com" - ] - } - }, - { - "TraceMyIP.org": { - "http://www.tracemyip.org/": [ - "tracemyip.org" - ] - } - }, - { - "Trackset": { - "http://www.trackset.com/": [ - "trackset.com" - ] - } - }, - { - "Trovus": { - "http://www.trovus.co.uk/": [ - "trovus.co.uk" - ] - } - }, - { - "Trumba": { - "http://www.trumba.com/": [ - "trumba.com" - ] - } - }, - { - "TRUSTe": { - "http://www.truste.com/": [ - "truste.com" - ] - } - }, - { - "TurnTo": { - "http://www.turntonetworks.com/": [ - "turnto.com", - "turntonetworks.com" - ] - } - }, - { - "Tweetboard": { - "http://tweetboard.com/": [ - "tweetboard.com" - ] - } - }, - { - "Twitter Counter": { - "http://twittercounter.com/": [ - "twittercounter.com" - ] - } - }, - { - "UberMedia": { - "http://ubermedia.com/": [ - "tweetup.com", - "ubermedia.com" - ] - } - }, - { - "UberTags": { - "http://ubertags.com/": [ - "ubertags.com" - ] - } - }, - { - "Unbounce": { - "http://unbounce.com/": [ - "unbounce.com" - ] - } - }, - { - "Uptrends": { - "http://www.uptrends.com/": [ - "uptrends.com" - ] - } - }, - { - "Usability Sciences": { - "http://www.usabilitysciences.com/": [ - "usabilitysciences.com", - "webiqonline.com" - ] - } - }, - { - "UserVoice": { - "http://www.uservoice.com/": [ - "uservoice.com" - ] - } - }, - { - "Vertical Acuity": { - "http://www.verticalacuity.com/": [ - "verticalacuity.com" - ] - } - }, - { - "VG WORT": { - "http://www.vgwort.de/": [ - "vgwort.de" - ] - } - }, - { - "Videology": { - "http://www.videologygroup.com/": [ - "tidaltv.com", - "videologygroup.com" - ] - } - }, - { - "Viewbix": { - "http://www.viewbix.com/": [ - "qoof.com", - "viewbix.com" - ] - } - }, - { - "Vimeo": { - "http://vimeo.com/": [ - "vimeo.com", - "vimeocdn.com" - ] - } - }, - { - "VINDICO": { - "http://vindicogroup.com/": [ - "vindicogroup.com", - "vindicosuite.com" - ] - } - }, - { - "Voice2Page": { - "http://www.voice2page.com/": [ - "voice2page.com" - ] - } - }, - { - "WebsiteAlive": { - "http://www.websitealive.com/": [ - "websitealive.com", - "websitealive0.com", - "websitealive1.com", - "websitealive2.com", - "websitealive3.com", - "websitealive4.com", - "websitealive5.com", - "websitealive6.com", - "websitealive7.com", - "websitealive8.com", - "websitealive9.com" - ] - } - }, - { - "Yahoo!": { - "http://www.yahoo.com/": [ - "answers.yahoo.com", - "apps.yahoo.com", - "autos.yahoo.com", - "biz.yahoo.com", - "developer.yahoo.com", - "everything.yahoo.com", - "finance.yahoo.com", - "flickr.com", - "games.yahoo.com", - "groups.yahoo.com", - "help.yahoo.com", - "hotjobs.yahoo.com", - "info.yahoo.com", - "local.yahoo.com", - "luminate.com", - "messages.yahoo.com", - "movies.yahoo.com", - "msg.yahoo.com", - "news.yahoo.com", - "omg.yahoo.com", - "pipes.yahoo.com", - "pixazza.com", - "realestate.yahoo.com", - "search.yahoo.com", - "shine.yahoo.com", - "smallbusiness.yahoo.com", - "sports.yahoo.com", - "staticflickr.com", - "suggestions.yahoo.com", - "travel.yahoo.com", - "tumblr.com", - "upcoming.yahoo.com", - "webhosting.yahoo.com", - "widgets.yahoo.com", - "www.yahoo.com", - "yahooapis.com", - "yahoofs.com", - "yimg.com", - "ypolicyblog.com", - "yuilibrary.com", - "zenfs.com" - ] - } - }, - { - "Yandex": { - "http://www.yandex.com/": [ - "kinopoisk.ru", - "yandex.by", - "yandex.com", - "yandex.com.tr", - "yandex.ru", - "yandex.st", - "yandex.ua" - ] - } - }, - { - "Zendesk": { - "http://www.zendesk.com/": [ - "zendesk.com" - ] - } - }, - { - "Zopim": { - "https://www.zopim.com/": [ - "zopim.com" - ] - } - } - ], - "Analytics": [ - { - "63 Squares": { - "http://63squares.com/": [ - "63squares.com", - "i-stats.com" - ] - } - }, - { - "Acxiom": { - "http://www.acxiom.com/": [ - "acxiom.com", - "acxiomapac.com", - "mm7.net", - "pippio.com" - ] - } - }, - { - "AddFreeStats": { - "http://www.addfreestats.com/": [ - "3dstats.com", - "addfreestats.com" - ] - } - }, - { - "Adloox": { - "http://www.adloox.com/": [ - "adloox.com", - "adlooxtracking.com" - ] - } - }, - { - "Adventori": { - "https://adventori.com": [ - "adventori.com" - ] - } - }, - { - "AIData": { - "http://www.aidata.me/": [ - "advombat.ru", - "aidata.me" - ] - } - }, - { - "AivaLabs": { - "https://aivalabs.com": [ - "aivalabs.com" - ] - } - }, - { - "Akamai": { - "http://www.akamai.com/": [ - "go-mpulse.net" - ] - } - }, - { - "Amadesa": { - "http://www.amadesa.com/": [ - "amadesa.com" - ] - } - }, - { - "Amazing Counters": { - "http://amazingcounters.com/": [ - "amazingcounters.com" - ] - } - }, - { - "Amazon.com": { - "http://www.amazon.com/": [ - "alexametrics.com" - ] - } - }, - { - "Amplitude": { - "https://amplitude.com/": [ - "amplitude.com" - ] - } - }, - { - "anormal-media.de": { - "http://anormal-media.de/": [ - "anormal-media.de", - "anormal-tracker.de" - ] - } - }, - { - "AT Internet": { - "http://www.atinternet.com/": [ - "at-o.net", - "atinternet.com", - "xiti.com" - ] - } - }, - { - "Attracta": { - "https://www.attracta.com/": [ - "attracta.com" - ] - } - }, - { - "Automattic": { - "http://automattic.com/": [ - "polldaddy.com" - ] - } - }, - { - "AvantLink": { - "http://www.avantlink.com/": [ - "avmws.com" - ] - } - }, - { - "Awio": { - "http://www.awio.com/": [ - "awio.com", - "w3counter.com", - "w3roi.com" - ] - } - }, - { - "Belstat": { - "http://www.belstat.com/": [ - "belstat.be", - "belstat.com", - "belstat.de", - "belstat.fr", - "belstat.nl" - ] - } - }, - { - "BetssonPalantir": { - "https://betssonpalantir.com/": [ - "betssonpalantir.com" - ] - } - }, - { - "BlogCounter.com": { - "http://www.blogcounter.de/": [ - "blogcounter.de" - ] - } - }, - { - "BloomReach": { - "http://www.bloomreach.com/": [ - "p.brsrvr.com" - ] - } - }, - { - "BlueCava": { - "http://www.bluecava.com/": [ - "bluecava.com" - ] - } - }, - { - "Bluemetrix": { - "http://www.bluemetrix.com/": [ - "bluemetrix.com", - "bmmetrix.com" - ] - } - }, - { - "Bombora": { - "https://bombora.com/": [ - "ml314.com" - ] - } - }, - { - "Branch": { - "https://branch.io/": [ - "branch.io" - ] - } - }, - { - "Branica": { - "http://www.branica.com/": [ - "branica.com" - ] - } - }, - { - "BrightEdge": { - "http://www.brightedge.com/": [ - "b0e8.com", - "brightedge.com" - ] - } - }, - { - "Bubblestat": { - "http://www.bubblestat.com/": [ - "bubblestat.com" - ] - } - }, - { - "Cardlytics": { - "http://www.cardlytics.com/": [ - "cardlytics.com" - ] - } - }, - { - "Chartbeat": { - "http://chartbeat.com/": [ - "chartbeat.com", - "chartbeat.net" - ] - } - }, - { - "Clickdensity": { - "http://www.clickdensity.com/": [ - "clickdensity.com" - ] - } - }, - { - "ClickGuard": { - "https://www.clickguard.com/": [ - "clickguard.com" - ] - } - }, - { - "ClickTale": { - "http://www.clicktale.com/": [ - "clicktale.com", - "clicktale.net", - "pantherssl.com" - ], - "session-replay": "true" - } - }, - { - "ClixMetrix": { - "http://www.clixmetrix.com/": [ - "clixmetrix.com" - ] - } - }, - { - "Clixpy": { - "http://clixpy.com/": [ - "clixpy.com" - ] - } - }, - { - "ClustrMaps": { - "http://www.clustrmaps.com/": [ - "clustrmaps.com" - ] - } - }, - { - "CNZZ": { - "http://www.cnzz.com/": [ - "cnzz.com" - ] - } - }, - { - "Compuware": { - "http://www.compuware.com/": [ - "axf8.net", - "compuware.com", - "gomez.com" - ] - } - }, - { - "comScore": { - "http://www.comscore.com/": [ - "certifica.com", - "comscore.com", - "mdotlabs.com", - "scorecardresearch.com", - "sitestat.com", - "voicefive.com" - ] - } - }, - { - "Connexity": { - "http://www.connexity.com/": [ - "connexity.com", - "connexity.net" - ] - } - }, - { - "Convert Insights": { - "http://www.convert.com/": [ - "convert.com", - "reedge.com" - ] - } - }, - { - "Convertro": { - "http://www.convertro.com/": [ - "convertro.com" - ] - } - }, - { - "Crazy Egg": { - "http://www.crazyegg.com/": [ - "cetrk.com", - "crazyegg.com" - ] - } - }, - { - "Crowd Science": { - "http://crowdscience.com/": [ - "crowdscience.com" - ] - } - }, - { - "Cya2": { - "http://cya2.net/": [ - "cya2.net" - ] - } - }, - { - "Dataium": { - "http://www.dataium.com/": [ - "collserve.com", - "dataium.com" - ] - } - }, - { - "Deep Intent": { - "https://www.deepintent.com/": [ - "deepintent.com" - ] - } - }, - { - "Demandbase": { - "http://www.demandbase.com/": [ - "company-target.com", - "demandbase.com" - ] - } - }, - { - "DirectCORP": { - "http://www.directcorp.de/": [ - "ipcounter.de" - ] - } - }, - { - "DistilNetworks": { - "https://www.distilnetworks.com/": [ - "distiltag.com" - ] - } - }, - { - "DoubleVerify": { - "http://www.doubleverify.com/": [ - "doubleverify.com" - ] - } - }, - { - "dwstat.com": { - "http://www.dwstat.cn/": [ - "dwstat.cn" - ] - } - }, - { - "ECSAnalytics": { - "https://www.theecsinc.com/": [ - "ecsanalytics.com" - ] - } - }, - { - "EFF": { - "https://www.eff.org/": [ - "do-not-tracker.org", - "eviltracker.net", - "trackersimulator.org" - ] - } - }, - { - "eProof.com": { - "http://www.eproof.com/": [ - "eproof.com" - ] - } - }, - { - "etracker": { - "http://www.etracker.com/": [ - "etracker.com", - "etracker.de", - "sedotracker.com", - "sedotracker.de" - ] - } - }, - { - "Eulerian Technologies": { - "http://www.eulerian.com/": [ - "eulerian.com", - "eulerian.net" - ] - } - }, - { - "eXTReMe digital": { - "http://extremetracking.com/": [ - "extreme-dm.com", - "extremetracking.com" - ] - } - }, - { - "Eyeota": { - "http://eyeota.net/": [ - "eyeota.net" - ] - } - }, - { - "Feedjit": { - "http://feedjit.com/": [ - "feedjit.com" - ] - } - }, - { - "Flashtalking": { - "http://www.flashtalking.com/": [ - "encoremetrics.com", - "sitecompass.com" - ] - } - }, - { - "Footprint": { - "http://www.footprintlive.com/": [ - "footprintlive.com" - ] - } - }, - { - "Free Online Users": { - "http://www.freeonlineusers.com/": [ - "freeonlineusers.com" - ] - } - }, - { - "Free-PageRank.com": { - "http://www.free-pagerank.com/": [ - "free-pagerank.com" - ] - } - }, - { - "Friends2Follow": { - "https://friends2follow.com/": [ - "antifraudjs.friends2follow.com" - ] - } - }, - { - "Fullstory": { - "https://www.fullstory.com/": [ - "fullstory.com" - ], - "session-replay": "true" - } - }, - { - "GetSiteControl": { - "https://getsitecontrol.com/": [ - "getsitecontrol.com" - ] - } - }, - { - "GfK Group": { - "http://www.gfk.com/": [ - "daphnecm.com", - "gfk.com", - "gfkdaphne.com" - ] - } - }, - { - "GitHub": { - "https://github.com/": [ - "gaug.es" - ] - } - }, - { - "Go Daddy": { - "http://www.godaddy.com/": [ - "godaddy.com", - "trafficfacts.com" - ] - } - }, - { - "Google": { - "http://www.google.com/": [ - "google-analytics.com", - "postrank.com" - ] - } - }, - { - "GoSquared": { - "https://www.gosquared.com/": [ - "gosquared.com" - ] - } - }, - { - "GoStats": { - "http://gostats.com/": [ - "gostats.com" - ] - } - }, - { - "GrapheneMedia": { - "http://graphenemedia.in/": [ - "graphenedigitalanalytics.in" - ] - } - }, - { - "GTop": { - "http://www.gtop.ro/": [ - "gtop.ro", - "gtopstats.com" - ] - } - }, - { - "Hearst": { - "http://www.hearst.com/": [ - "raasnet.com", - "redaril.com" - ] - } - }, - { - "Histats": { - "http://www.histats.com/": [ - "histats.com" - ] - } - }, - { - "HitsLink": { - "http://www.hitslink.com/": [ - "hitslink.com" - ] - } - }, - { - "Hit Sniffer": { - "http://www.hitsniffer.com/": [ - "hitsniffer.com" - ] - } - }, - { - "Hotjar": { - "https://www.hotjar.com": [ - "hotjar.com" - ] - } - }, - { - "HubSpot": { - "http://www.hubspot.com/": [ - "hs-analytics.net" - ] - } - }, - { - "IBM": { - "http://www.ibm.com/": [ - "cmcore.com", - "coremetrics.com", - "ibm.com" - ] - } - }, - { - "InboundWriter": { - "http://www.inboundwriter.com/": [ - "enquisite.com", - "inboundwriter.com" - ] - } - }, - { - "Infernotions": { - "https://infernotions.com/": [ - "infernotions.com" - ] - } - }, - { - "INFOnline": { - "https://www.infonline.de/": [ - "infonline.de", - "ioam.de", - "ivwbox.de" - ] - } - }, - { - "InfoStars": { - "http://infostars.ru/": [ - "hotlog.ru", - "infostars.ru" - ] - } - }, - { - "Inspectlet": { - "http://www.inspectlet.com/": [ - "inspectlet.com" - ] - } - }, - { - "IntelligenceFocus": { - "http://www.intelligencefocus.com/": [ - "domodomain.com", - "intelligencefocus.com" - ] - } - }, - { - "iPerceptions": { - "http://www.iperceptions.com/": [ - "iperceptions.com" - ] - } - }, - { - "IslayTech": { - "http://islay.tech": [ - "islay.tech" - ] - } - }, - { - "ItIsATracker": { - "https://itisatracker.com/": [ - "itisatracker.com" - ], - "dnt": "eff" - } - }, - { - "KeyMetric": { - "http://www.keymetric.net/": [ - "keymetric.net" - ] - } - }, - { - "KISSmetrics": { - "http://kissmetrics.com/": [ - "kissmetrics.com" - ] - } - }, - { - "Kitcode": { - "http://src.kitcode.net/": [ - "src.kitcode.net" - ] - } - }, - { - "LeadForensics": { - "https://www.leadforensics.com": [ - "leadforensics.com" - ] - } - }, - { - "LineZing": { - "http://www.linezing.com/": [ - "linezing.com" - ] - } - }, - { - "LivePerson": { - "http://www.liveperson.net/": [ - "liveperson.com", - "nuconomy.com" - ] - } - }, - { - "Logdy": { - "http://logdy.com/": [ - "logdy.com" - ] - } - }, - { - "Lotame": { - "http://www.lotame.com/": [ - "crwdcntrl.net", - "lotame.com" - ] - } - }, - { - "LuckyOrange": { - "https://www.luckyorange.com": [ - "luckyorange.com", - "luckyorange.net" - ], - "session-replay": "true" - } - }, - { - "Lynchpin": { - "http://www.lynchpin.com/": [ - "lynchpin.com", - "lypn.com" - ] - } - }, - { - "Lyris": { - "http://www.lyris.com/": [ - "clicktracks.com", - "lyris.com" - ] - } - }, - { - "Lytiks": { - "http://www.lytiks.com/": [ - "lytiks.com" - ] - } - }, - { - "MarkMonitor": { - "https://www.markmonitor.com": [ - "9c9media.ca", - "markmonitor.com" - ] - } - }, - { - "Marktest": { - "http://www.marktest.com/": [ - "marktest.com", - "marktest.pt" - ] - } - }, - { - "MaxMind": { - "https://www.maxmind.com/en/home": [ - "maxmind.com", - "mmapiws.com" - ] - } - }, - { - "Médiamétrie-eStat": { - "http://www.mediametrie-estat.com/": [ - "estat.com", - "mediametrie-estat.com" - ] - } - }, - { - "Merkle": { - "https://www.merkleinc.com/": [ - "merkleinc.com", - "rkdms.com" - ] - } - }, - { - "Mixpanel": { - "https://mixpanel.com/": [ - "mixpanel.com", - "mxpnl.com" - ] - } - }, - { - "Mongoose Metrics": { - "http://www.mongoosemetrics.com/": [ - "mongoosemetrics.com" - ] - } - }, - { - "Monitus": { - "http://www.monitus.net/": [ - "monitus.net" - ] - } - }, - { - "motigo": { - "http://motigo.com/": [ - "motigo.com", - "nedstatbasic.net" - ] - } - }, - { - "Mouseflow": { - "http://mouseflow.com/": [ - "mouseflow.com" - ] - } - }, - { - "MyPagerank.Net": { - "http://www.mypagerank.net/": [ - "mypagerank.net" - ] - } - }, - { - "Mystighty": { - "http://mystighty.info/": [ - "mystighty.info", - "sweeterge.info" - ] - } - }, - { - "Narrative": { - "http://narrative.io/2/": [ - "narrative.io" - ] - } - }, - { - "Net Applications": { - "http://www.netapplications.com/": [ - "hitsprocessor.com", - "netapplications.com" - ] - } - }, - { - "New Relic": { - "http://newrelic.com/": [ - "newrelic.com", - "nr-data.net" - ] - } - }, - { - "NewsRight": { - "http://www.newsright.com/": [ - "apnewsregistry.com" - ] - } - }, - { - "NextSTAT": { - "http://www.nextstat.com/": [ - "nextstat.com" - ] - } - }, - { - "Nielsen": { - "http://www.nielsen.com/": [ - "glanceguide.com", - "nielsen.com" - ] - } - }, - { - "NuDataSecurity": { - "https://nudatasecurity.com/": [ - "nudatasecurity.com" - ] - } - }, - { - "nurago": { - "http://www.nurago.com/": [ - "nurago.com", - "nurago.de", - "sensic.net" - ] - } - }, - { - "Observer": { - "http://observerapp.com/": [ - "observerapp.com" - ] - } - }, - { - "OnAudience": { - "http://www.onaudience.com/": [ - "behavioralengine.com", - "onaudience.com" - ] - } - }, - { - "OneStat": { - "http://www.onestat.com/": [ - "onestat.com" - ] - } - }, - { - "Openstat": { - "https://www.openstat.ru/": [ - "openstat.ru", - "spylog.com" - ] - } - }, - { - "Opentracker": { - "http://www.opentracker.net/": [ - "opentracker.net" - ] - } - }, - { - "Opolen": { - "https://opolen.com.br": [ - "opolen.com.br" - ] - } - }, - { - "Optimizely": { - "https://www.optimizely.com/": [ - "optimizely.com" - ] - } - }, - { - "Oracle": { - "http://www.oracle.com/": [ - "eloqua.com", - "maxymiser.com" - ] - } - }, - { - "ÖWA": { - "http://www.oewa.at/": [ - "oewa.at", - "oewabox.at" - ] - } - }, - { - "Parse.ly": { - "http://parsely.com/": [ - "parsely.com" - ] - } - }, - { - "PersianStat.com": { - "http://www.persianstat.com/": [ - "persianstat.com" - ] - } - }, - { - "Phonalytics": { - "http://www.phonalytics.com/": [ - "phonalytics.com" - ] - } - }, - { - "phpMyVisites": { - "http://www.phpmyvisites.us/": [ - "phpmyvisites.us" - ] - } - }, - { - "Piwik": { - "http://piwik.org/": [ - "piwik.org" - ] - } - }, - { - "PixAnalytics": { - "https://pixanalytics.com/": [ - "pixanalytics.com" - ] - } - }, - { - "Poool": { - "http://poool.fr/": [ - "poool.fr" - ] - } - }, - { - "Pronunciator": { - "http://www.pronunciator.com/": [ - "pronunciator.com", - "visitorville.com" - ] - } - }, - { - "Qualaroo": { - "http://qualaroo.com/": [ - "kissinsights.com", - "qualaroo.com" - ] - } - }, - { - "QuinStreet": { - "http://quinstreet.com/": [ - "thecounter.com" - ] - } - }, - { - "Quintelligence": { - "http://www.quintelligence.com/": [ - "quintelligence.com" - ] - } - }, - { - "RadarURL": { - "http://radarurl.com/": [ - "radarurl.com" - ] - } - }, - { - "Research Now": { - "http://www.researchnow.com/": [ - "researchnow.com", - "valuedopinions.co.uk" - ] - } - }, - { - "Retail Automata": { - "https://retailautomata.com": [ - "retailautomata.com" - ] - } - }, - { - "Revtracks": { - "http://revtrax.com/": [ - "revtrax.com" - ] - } - }, - { - "Ringier": { - "http://ringier.cz/": [ - "ringier.cz" - ] - } - }, - { - "Rollick": { - "https://gorollick.com": [ - "rollick.io" - ] - } - }, - { - "Roxr": { - "http://roxr.net/": [ - "getclicky.com", - "roxr.net", - "staticstuff.net" - ] - } - }, - { - "Safecount": { - "http://www.safecount.net/": [ - "dl-rms.com", - "dlqm.net", - "questionmarket.com", - "safecount.net" - ] - } - }, - { - "SageMetrics": { - "http://www.sagemetrics.com/": [ - "sageanalyst.net", - "sagemetrics.com" - ] - } - }, - { - "Salesintelligence": { - "https://salesintelligence.pl/": [ - "plugin.management" - ] - } - }, - { - "SeeVolution": { - "https://www.seevolution.com/": [ - "seevolution.com", - "svlu.net" - ] - } - }, - { - "Segment.io": { - "https://segment.io/": [ - "segment.io" - ] - } - }, - { - "SendPulse": { - "https://sendpulse.com/": [ - "sendpulse.com" - ] - } - }, - { - "SessionCam": { - "https://sessioncam.com/": [ - "sessioncam.com" - ], - "session-replay": "true" - } - }, - { - "ShinyStat": { - "http://www.shinystat.com/": [ - "shinystat.com" - ] - } - }, - { - "Smartlook": { - "https://www.smartlook.com/": [ - "smartlook.com" - ], - "session-replay": "true" - } - }, - { - "Snoobi": { - "http://www.snoobi.com/": [ - "snoobi.com" - ] - } - }, - { - "Sourcepoint": { - "https://www.sourcepoint.com/": [ - "summerhamster.com" - ] - } - }, - { - "Sputnik.ru": { - "http://sputnik.ru": [ - "sputnik.ru" - ] - } - }, - { - "StackTrack": { - "http://stat-track.com": [ - "stat-track.com" - ] - } - }, - { - "stat4u": { - "http://stat.4u.pl/": [ - "4u.pl" - ] - } - }, - { - "StatCounter": { - "http://statcounter.com/": [ - "statcounter.com" - ] - } - }, - { - "Statisfy": { - "http://statisfy.net": [ - "statisfy.net" - ] - } - }, - { - "STATSIT": { - "http://www.statsit.com/": [ - "statsit.com" - ] - } - }, - { - "Storeland": { - "https://storeland.ru/": [ - "storeland.ru" - ] - } - }, - { - "Stratigent": { - "http://www.stratigent.com/": [ - "stratigent.com" - ] - } - }, - { - "Tealium": { - "https://tealium.com": [ - "tealiumiq.com" - ] - } - }, - { - "TechSolutions": { - "https://www.techsolutions.com.tw/": [ - "techsolutions.com.tw" - ] - } - }, - { - "TENSQUARE": { - "http://www.tensquare.com/": [ - "tensquare.com" - ] - } - }, - { - "The Heron Partnership": { - "http://www.heronpartners.com.au/": [ - "heronpartners.com.au", - "marinsm.com" - ] - } - }, - { - "TNS": { - "http://www.tnsglobal.com/": [ - "sesamestats.com", - "statistik-gallup.net", - "tns-counter.ru", - "tns-cs.net", - "tnsglobal.com" - ] - } - }, - { - "TrackingSoft": { - "http://trackingsoft.com/": [ - "roia.biz", - "trackingsoft.com" - ] - } - }, - { - "TrafficScore": { - "https://trafficscore.com/": [ - "trafficscore.com" - ] - } - }, - { - "Twitter": { - "https://twitter.com/": [ - "crashlytics.com", - "tweetdeck.com" - ] - } - }, - { - "Umbel": { - "https://www.umbel.com/": [ - "umbel.com" - ] - } - }, - { - "User Local": { - "http://nakanohito.jp/": [ - "nakanohito.jp" - ] - } - }, - { - "V12 Data": { - "https://www.v12data.com/": [ - "v12data.com", - "v12group.com" - ] - } - }, - { - "Vertster": { - "http://www.vertster.com/": [ - "vertster.com" - ] - } - }, - { - "VisiStat": { - "http://www.visistat.com/": [ - "sa-as.com", - "visistat.com" - ] - } - }, - { - "Visit Streamer": { - "http://www.visitstreamer.com/": [ - "visitstreamer.com" - ] - } - }, - { - "vistrac": { - "http://vistrac.com/": [ - "vistrac.com" - ] - } - }, - { - "ViziSense": { - "http://www.vizisense.com/": [ - "vizisense.com", - "vizisense.net" - ] - } - }, - { - "Webclicktracker": { - "http://www.webclicktracker.com/": [ - "webclicktracker.com" - ] - } - }, - { - "Web Stats": { - "http://www.onlinewebstats.com/": [ - "onlinewebstats.com" - ] - } - }, - { - "Web Tracking Services": { - "http://www.webtrackingservices.com/": [ - "web-stat.com", - "webtrackingservices.com" - ] - } - }, - { - "Web Traxs": { - "http://www.webtraxs.com/": [ - "webtraxs.com" - ] - } - }, - { - "Webtrekk": { - "http://www.webtrekk.com/": [ - "webtrekk.com", - "webtrekk.net" - ] - } - }, - { - "Webtrends": { - "http://webtrends.com/": [ - "reinvigorate.net", - "webtrends.com", - "webtrendslive.com" - ] - } - }, - { - "White Ops": { - "https://www.whiteops.com/": [ - "adzmath.com", - "whiteops.com" - ] - } - }, - { - "whos.amung.us": { - "http://whos.amung.us/": [ - "amung.us" - ] - } - }, - { - "Wingify": { - "http://wingify.com/": [ - "visualwebsiteoptimizer.com", - "wingify.com" - ] - } - }, - { - "Woopra": { - "http://www.woopra.com/": [ - "woopra-ns.com", - "woopra.com" - ] - } - }, - { - "WOW Analytics": { - "http://www.wowanalytics.co.uk/": [ - "wowanalytics.co.uk" - ] - } - }, - { - "WPP": { - "http://www.wpp.com/": [ - "compete.com" - ] - } - }, - { - "Wysistat": { - "http://www.wysistat.com/": [ - "wysistat.com" - ] - } - }, - { - "Yahoo!": { - "http://www.yahoo.com/": [ - "analytics.yahoo.com" - ] - } - }, - { - "YellowTracker": { - "http://www.yellowtracker.com/": [ - "yellowtracker.com" - ] - } - }, - { - "YSance": { - "https://www.ysance.com/data-services/fr/home/": [ - "y-track.com" - ] - } - } - ], - "Fingerprinting": [ - { - "Adabra": { - "https://www.adabra.com/": [ - "adabra.com" - ] - } - }, - { - "Adbot": { - "https://adbot.tw/": [ - "adbot.tw" - ] - } - }, - { - "AdGainerSolutions": { - "http://adgainersolutions.com/adgainer/": [ - "adgainersolutions.com" - ] - } - }, - { - "AdMaven": { - "https://ad-maven.com/": [ - "ad-maven.com", - "agreensdistra.info", - "boudja.com", - "rensovetors.info", - "wrethicap.info" - ] - } - }, - { - "Admicro": { - "http://www.admicro.vn/": [ - "admicro.vn", - "vcmedia.vn" - ] - } - }, - { - "Adnium": { - "https://adnium.com": [ - "adnium.com", - "montwam.top" - ] - } - }, - { - "AdScore": { - "http://www.adscoremarketing.com/": [ - "adsco.re" - ] - } - }, - { - "AdYouLike": { - "https://www.adyoulike.com/": [ - "pulpix.com" - ] - } - }, - { - "AivaLabs": { - "https://aivalabs.com": [ - "aivalabs.com" - ] - } - }, - { - "Albacross": { - "https://albacross.com": [ - "albacross.com" - ] - } - }, - { - "AppCast": { - "https://appcast.io/": [ - "appcast.io" - ] - } - }, - { - "AuditedMedia": { - "https://auditedmedia.com/": [ - "aamapi.com", - "aamsitecertifier.com", - "auditedmedia.com" - ] - } - }, - { - "Augur": { - "http://www.augur.io/": [ - "augur.io" - ] - } - }, - { - "Azet": { - "http://mediaimpact.sk/": [ - "azetklik.sk", - "rsz.sk" - ] - } - }, - { - "BetssonPalantir": { - "https://betssonpalantir.com/": [ - "betssonpalantir.com" - ] - } - }, - { - "BigClick": { - "http://bigclick.me/": [ - "bgclck.me", - "xcvgdf.party" - ] - } - }, - { - "BitMedia": { - "https://bitmedia.io/": [ - "bitmedia.io" - ] - } - }, - { - "BlueCava": { - "http://www.bluecava.com/": [ - "bluecava.com" - ] - } - }, - { - "BoostBox": { - "https://www.boostbox.com.br/": [ - "boostbox.com.br" - ] - } - }, - { - "Brandcrumb": { - "http://www.brandcrumb.com": [ - "brandcrumb.com" - ] - } - }, - { - "BreakTime": { - "https://www.breaktime.com.tw/": [ - "breaktime.com.tw" - ] - } - }, - { - "BrightEdge": { - "http://www.brightedge.com/": [ - "b0e8.com" - ] - } - }, - { - "C3 Metrics": { - "http://c3metrics.com/": [ - "attributionmodel.com", - "c3metrics.com", - "c3tag.com" - ] - } - }, - { - "CallSource": { - "https://www.callsource.com/": [ - "leadtrackingdata.com" - ] - } - }, - { - "CartsGuru": { - "https://carts.guru/": [ - "carts.guru" - ] - } - }, - { - "ClearLink": { - "https://www.clearlink.com/": [ - "clearlink.com" - ] - } - }, - { - "Clickayab": { - "http://www.clickyab.com": [ - "clickyab.com" - ] - } - }, - { - "ClickFrog": { - "https://clickfrog.ru/": [ - "bashirian.biz", - "buckridge.link", - "franecki.net", - "quitzon.net", - "reichelcormier.bid", - "wisokykulas.bid" - ] - } - }, - { - "ClickGuard": { - "https://www.clickguard.com/": [ - "clickguard.com" - ] - } - }, - { - "Clixtell": { - "https://www.clixtell.com/": [ - "clixtell.com" - ] - } - }, - { - "Consumable": { - "http://consumable.com/": [ - "consumable.com" - ] - } - }, - { - "dmpxs": { - "http://bob.dmpxs.com": [ - "dmpxs.com" - ] - } - }, - { - "ECSAnalytics": { - "https://www.theecsinc.com/": [ - "ecsanalytics.com" - ] - } - }, - { - "EroAdvertising": { - "http://www.ero-advertising.com/": [ - "ero-advertising.com" - ] - } - }, - { - "eyeReturn Marketing": { - "http://www.eyereturnmarketing.com/": [ - "eyereturn.com", - "eyereturnmarketing.com" - ] - } - }, - { - "Fanplayr": { - "https://fanplayr.com/": [ - "fanplayr.com" - ] - } - }, - { - "Foresee": { - "https://www.foresee.com": [ - "answerscloud.com", - "foresee.com" - ] - } - }, - { - "Friends2Follow": { - "https://friends2follow.com/": [ - "antifraudjs.friends2follow.com" - ] - } - }, - { - "FuelX": { - "https://fuelx.com/": [ - "fuel451.com", - "fuelx.com" - ] - } - }, - { - "Gleam": { - "https://gleam.io/": [ - "fraudjs.io" - ] - } - }, - { - "GrapheneMedia": { - "http://graphenemedia.in/": [ - "graphenedigitalanalytics.in" - ] - } - }, - { - "Gruner + Jahr": { - "http://www.guj.de/": [ - "ligatus.com" - ] - } - }, - { - "HilltopAds": { - "https://hilltopads.com/": [ - "hilltopads.net", - "shoporielder.pro" - ] - } - }, - { - "HotelChamp": { - "https://www.hotelchamp.com": [ - "hotelchamp.com" - ] - } - }, - { - "iMedia": { - "http://www.imedia.cz": [ - "imedia.cz" - ] - } - }, - { - "IslayTech": { - "http://islay.tech": [ - "islay.tech" - ] - } - }, - { - "ismatlab.com": { - "http://ismatlab.com": [ - "ismatlab.com" - ] - } - }, - { - "Itch": { - "https://itch.io/": [ - "itch.io" - ] - } - }, - { - "justuno": { - "https://www.justuno.com/": [ - "justuno.com" - ] - } - }, - { - "Konduto": { - "http://konduto.com": [ - "k-analytix.com", - "konduto.com" - ] - } - }, - { - "LeadsHub": { - "https://ztsrv.com/": [ - "ztsrv.com" - ] - } - }, - { - "lptracker": { - "https://lptracker.io/": [ - "lptracker.io" - ] - } - }, - { - "MaxMind": { - "https://www.maxmind.com/en/home": [ - "maxmind.com", - "mmapiws.com" - ] - } - }, - { - "Mercadopago": { - "https://www.mercadopago.com/": [ - "mercadopago.com" - ] - } - }, - { - "Mobials": { - "http://mobials.com": [ - "mobials.com" - ] - } - }, - { - "Mystighty": { - "http://mystighty.info/": [ - "mystighty.info", - "sweeterge.info" - ] - } - }, - { - "Negishim": { - "http://www.negishim.org": [ - "negishim.org" - ] - } - }, - { - "NuDataSecurity": { - "https://nudatasecurity.com/": [ - "nudatasecurity.com" - ] - } - }, - { - "OneAd": { - "https://www.onead.com.tw/": [ - "guoshipartners.com", - "onevision.com.tw" - ] - } - }, - { - "OnlineMetrix": { - "http://h.online-metrix.net": [ - "online-metrix.net" - ] - } - }, - { - "Opolen": { - "https://opolen.com.br": [ - "opolen.com.br" - ] - } - }, - { - "PaymentsMB": { - "https://paymentsmb.com": [ - "paymentsmb.com" - ] - } - }, - { - "Paypal": { - "https://www.paypal.com": [ - "simility.com" - ] - } - }, - { - "PerimeterX": { - "https://www.perimeterx.com": [ - "perimeterx.net" - ] - } - }, - { - "PixAnalytics": { - "https://pixanalytics.com/": [ - "pixanalytics.com" - ] - } - }, - { - "Pixlee": { - "https://www.pixlee.com/": [ - "pixlee.com" - ] - } - }, - { - "Poool": { - "http://poool.fr/": [ - "poool.fr" - ] - } - }, - { - "PPCProtect": { - "https://ppcprotect.com": [ - "ppcprotect.com" - ] - } - }, - { - "PrismApp": { - "https://www.prismapp.io/": [ - "prismapp.io" - ] - } - }, - { - "PrometheusIntelligenceTechnology": { - "https://prometheusintelligencetechnology.com/": [ - "prometheusintelligencetechnology.com" - ] - } - }, - { - "Provers": { - "http://provers.pro": [ - "provers.pro" - ] - } - }, - { - "Psonstrentie": { - "http://psonstrentie.info": [ - "psonstrentie.info" - ] - } - }, - { - "Rollick": { - "https://gorollick.com": [ - "rollick.io" - ] - } - }, - { - "SAP": { - "https://www.sap.com": [ - "seewhy.com" - ] - } - }, - { - "Selectable Media": { - "http://selectablemedia.com/": [ - "nabbr.com", - "selectablemedia.com" - ] - } - }, - { - "Semantiqo": { - "http://semantiqo.com/": [ - "semantiqo.com" - ] - } - }, - { - "SendPulse": { - "https://sendpulse.com/": [ - "sendpulse.com" - ] - } - }, - { - "ShaftTraffic": { - "https://shafttraffic.com": [ - "libertystmedia.com" - ] - } - }, - { - "Shortest": { - "http://shorte.st/": [ - "shorte.st" - ] - } - }, - { - "SiftScience": { - "https://sift.com/": [ - "siftscience.com" - ] - } - }, - { - "Signifyd": { - "https://www.signifyd.com/": [ - "signifyd.com" - ] - } - }, - { - "Smi": { - "http://24smi.net": [ - "24smi.net" - ] - } - }, - { - "Socital": { - "https://www.socital.com": [ - "socital.com" - ] - } - }, - { - "Storeland": { - "https://storeland.ru/": [ - "storeland.ru" - ] - } - }, - { - "Stripe": { - "https://stripe.com": [ - "stripe.network" - ] - } - }, - { - "TechSolutions": { - "https://www.techsolutions.com.tw/": [ - "techsolutions.com.tw" - ] - } - }, - { - "tongdun.cn": { - "https://www.tongdun.cn/?lan=EN": [ - "fraudmetrix.cn", - "tongdun.net" - ] - } - }, - { - "Upland": { - "https://uplandsoftware.com/": [ - "leadlander.com", - "sf14g.com" - ] - } - }, - { - "Vendemore": { - "https://vendemore.com/": [ - "vendemore.com" - ] - } - }, - { - "VerticalHealth": { - "https://www.verticalhealth.com/": [ - "verticalhealth.net" - ] - } - }, - { - "Webmecanik": { - "https://www.webmecanik.com/": [ - "webmecanik.com" - ] - } - }, - { - "WideOrbit": { - "https://www.wideorbit.com/": [ - "dep-x.com" - ] - } - }, - { - "YSance": { - "https://www.ysance.com/data-services/fr/home/": [ - "y-track.com" - ] - } - }, - { - "ZafulAffiliate": { - "https://affiliate.zaful.com/": [ - "affasi.com", - "gw-ec.com", - "zaful.com" - ] - } - }, - { - "Zefir": { - "https://ze-fir.com/": [ - "ze-fir.com" - ] - } - } - ], - "Social": [ - { - "AddThis": { - "http://www.addthis.com/": [ - "addthis.com", - "addthiscdn.com", - "addthisedge.com", - "clearspring.com", - "connectedads.net", - "xgraph.com", - "xgraph.net" - ] - } - }, - { - "Causes": { - "http://www.causes.com/": [ - "causes.com" - ] - } - }, - { - "Digg": { - "http://digg.com/": [ - "digg.com" - ] - } - }, - { - "Facebook": { - "http://www.facebook.com/": [ - "apps.fbsbx.com", - "atdmt.com", - "facebook.com", - "facebook.de", - "facebook.fr", - "facebook.net", - "fb.com", - "fbsbx.com", - "friendfeed.com" - ] - } - }, - { - "Google": { - "http://www.google.com/": [ - "developers.google.com", - "gmail.com", - "googlemail.com", - "inbox.google.com", - "mail.google.com", - "orkut.com", - "plus.google.com", - "plusone.google.com", - "smartlock.google.com", - "voice.google.com", - "wave.google.com" - ] - } - }, - { - "LinkedIn": { - "http://www.linkedin.com/": [ - "licdn.com", - "linkedin.com" - ] - } - }, - { - "Lockerz": { - "http://lockerz.com/": [ - "lockerz.com" - ] - } - }, - { - "Mail.Ru": { - "http://mail.ru/": [ - "list.ru", - "mail.ru" - ] - } - }, - { - "Meebo": { - "https://www.meebo.com/": [ - "meebo.com", - "meebocdn.net" - ] - } - }, - { - "Papaya": { - "http://papayamobile.com/": [ - "papayamobile.com" - ] - } - }, - { - "reddit": { - "http://www.reddit.com/": [ - "reddit.com" - ] - } - }, - { - "Shareaholic": { - "http://www.shareaholic.com/": [ - "shareaholic.com" - ] - } - }, - { - "ShareThis": { - "http://sharethis.com/": [ - "sharethis.com" - ] - } - }, - { - "StumbleUpon": { - "http://www.stumbleupon.com/": [ - "stumble-upon.com", - "stumbleupon.com" - ] - } - }, - { - "Twitter": { - "https://twitter.com/": [ - "twimg.com", - "twitter.com", - "twitter.jp" - ] - } - }, - { - "VKontakte": { - "http://vk.com/": [ - "userapi.com", - "vk.com", - "vkontakte.ru" - ] - } - }, - { - "Yahoo!": { - "http://www.yahoo.com/": [ - "address.yahoo.com", - "alerts.yahoo.com", - "avatars.yahoo.com", - "buzz.yahoo.com", - "calendar.yahoo.com", - "edit.yahoo.com", - "legalredirect.yahoo.com", - "login.yahoo.com", - "mail.yahoo.com", - "my.yahoo.com", - "mybloglog.com", - "notepad.yahoo.com", - "pulse.yahoo.com", - "rocketmail.com", - "webmessenger.yahoo.com", - "ymail.com" - ] - } - } - ], - "Cryptomining": [ - { - "a.js": { - "http://zymerget.bid": [ - "alflying.date", - "alflying.win", - "anybest.site", - "flightsy.bid", - "flightsy.win", - "flightzy.bid", - "flightzy.date", - "flightzy.win", - "zymerget.bid", - "zymerget.faith" - ], - "performance": "true" - } - }, - { - "CashBeet": { - "http://cashbeet.com": [ - "cashbeet.com", - "serv1swork.com" - ] - } - }, - { - "CoinHive": { - "https://coinhive.com": [ - "ad-miner.com", - "authedmine.com", - "bmst.pw", - "cnhv.co", - "coin-hive.com", - "coinhive.com", - "wsservices.org" - ], - "performance": "true" - } - }, - { - "CoinPot": { - "http://coinpot.co": [ - "coinpot.co" - ], - "performance": "true" - } - }, - { - "CryptoLoot": { - "https://crypto-loot.com": [ - "cryptaloot.pro", - "crypto-loot.com", - "cryptolootminer.com", - "flashx.pw", - "gitgrub.pro", - "reauthenticator.com", - "statdynamic.com", - "webmine.pro" - ], - "performance": "true" - } - }, - { - "CryptoWebMiner": { - "https://www.crypto-webminer.com": [ - "bitcoin-pay.eu", - "crypto-webminer.com", - "ethpocket.de", - "ethtrader.de" - ] - } - }, - { - "Gridcash": { - "https://www.gridcash.net/": [ - "adless.io", - "gridcash.net" - ], - "performance": "true" - } - }, - { - "JSE": { - "http://jsecoin.com": [ - "freecontent.bid", - "freecontent.date", - "freecontent.stream", - "hashing.win", - "hostingcloud.racing", - "hostingcloud.science", - "jsecoin.com" - ], - "performance": "true" - } - }, - { - "MinerAlt": { - "http://mineralt.io": [ - "1q2w3.website", - "analytics.blue", - "aster18cdn.nl", - "belicimo.pw", - "besstahete.info", - "dinorslick.icu", - "feesocrald.com", - "gramombird.com", - "istlandoll.com", - "mepirtedic.com", - "mineralt.io", - "pampopholf.com", - "tercabilis.info", - "tulip18.com", - "vidzi.tv", - "yololike.space" - ], - "performance": "true" - } - }, - { - "Minescripts": { - "http://minescripts.info": [ - "minescripts.info", - "sslverify.info" - ], - "performance": "true" - } - }, - { - "MineXMR": { - "http://minexmr.stream": [ - "minexmr.stream" - ], - "performance": "true" - } - }, - { - "NeroHut": { - "https://nerohut.com": [ - "nerohut.com", - "nhsrv.cf" - ], - "performance": "true" - } - }, - { - "Service4refresh": { - "https://service4refresh.info": [ - "service4refresh.info" - ] - } - }, - { - "SpareChange": { - "http://sparechange.io": [ - "sparechange.io" - ], - "performance": "true" - } - }, - { - "SwiftMining": { - "https://swiftmining.win/": [ - "swiftmining.win" - ] - } - }, - { - "Webmine": { - "https://webmine.cz/": [ - "authedwebmine.cz", - "webmine.cz" - ] - } - }, - { - "WebminePool": { - "http://webminepool.com": [ - "webminepool.com" - ], - "performance": "true" - } - }, - { - "Webmining": { - "https://webmining.co/": [ - "webmining.co" - ] - } - } - ] - } + "license": "Copyright 2010-2019 Disconnect, Inc. / This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. / This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. / You should have received a copy of the GNU General Public License along with this program. If not, see .", + "categories": { + "Advertising": [ + { + "2leep.com": { + "http://2leep.com/": ["2leep.com"] + } + }, + { + "33Across": { + "http://33across.com/": ["33across.com"] + } + }, + { + "365Media": { + "http://365media.com/": ["365media.com"] + } + }, + { + "4INFO": { + "http://www.4info.com/": ["4info.com", "adhaven.com"] + } + }, + { + "4mads": { + "http://4mads.com/": ["4mads.com"] + } + }, + { + "Abax Interactive": { + "http://abaxinteractive.com/": ["abaxinteractive.com"] + } + }, + { + "Accelia": { + "http://www.accelia.net/": ["accelia.net", "durasite.net"] + } + }, + { + "Accordant Media": { + "http://www.accordantmedia.com/": ["accordantmedia.com"] + } + }, + { + "Acquisio": { + "http://www.acquisio.com/": ["acquisio.com", "clickequations.net"] + } + }, + { + "Actisens": { + "http://www.actisens.com/": ["actisens.com", "gestionpub.com"] + } + }, + { + "ActiveConversion": { + "http://www.activeconversion.com/": [ + "activeconversion.com", + "activemeter.com" + ] + } + }, + { + "Act-On": { + "http://www.act-on.com/": ["act-on.com", "actonsoftware.com"] + } + }, + { + "Acuity": { + "http://www.acuity.com/": [ + "acuity.com", + "acuityads.com", + "acuityplatform.com" + ] + } + }, + { + "AD2ONE": { + "http://www.ad2onegroup.com/": ["ad2onegroup.com"] + } + }, + { + "Ad4Game": { + "http://ad4game.com/": ["ad4game.com"] + } + }, + { + "ad6media": { + "http://www.ad6media.fr/": ["ad6media.fr"] + } + }, + { + "Adabra": { + "https://www.adabra.com/": ["adabra.com"] + } + }, + { + "Adality": { + "http://adality.de/": ["adality.de", "adrtx.net"] + } + }, + { + "AdaptiveAds": { + "http://www.adaptiveads.com/": ["adaptiveads.com"] + } + }, + { + "Adaptly": { + "http://adaptly.com/": ["adaptly.com"] + } + }, + { + "Adara Media": { + "http://www.adaramedia.com/": [ + "adaramedia.com", + "opinmind.com", + "yieldoptimizer.com" + ] + } + }, + { + "Adatus": { + "http://www.adatus.com/": ["adatus.com"] + } + }, + { + "Adbot": { + "https://adbot.tw/": ["adbot.tw"] + } + }, + { + "Adbrain": { + "http://www.adbrain.com/": ["adbrain.com", "adbrn.com"] + } + }, + { + "adBrite": { + "http://www.adbrite.com/": ["adbrite.com"] + } + }, + { + "Adbroker.de": { + "http://adbroker.de/": ["adbroker.de"] + } + }, + { + "Adchemy": { + "http://www.adchemy.com/": ["adchemy.com"] + } + }, + { + "AdCirrus": { + "http://adcirrus.com/": ["adcirrus.com"] + } + }, + { + "Ad Decisive": { + "http://www.addecisive.com/": ["a2dfp.net", "addecisive.com"] + } + }, + { + "addGloo": { + "http://www.addgloo.com/": ["addgloo.com"] + } + }, + { + "Addvantage Media": { + "http://www.addvantagemedia.com/": ["addvantagemedia.com"] + } + }, + { + "Ad Dynamo": { + "http://www.addynamo.com/": ["addynamo.com", "addynamo.net"] + } + }, + { + "Adelphic": { + "https://adelphic.com/": ["adelphic.com", "ipredictive.com"] + } + }, + { + "AdEngage": { + "http://adengage.com/": ["adengage.com"] + } + }, + { + "AD Europe": { + "http://www.adeurope.com/": ["adeurope.com"] + } + }, + { + "AdExtent": { + "http://www.adextent.com/": ["adextent.com"] + } + }, + { + "AdF.ly": { + "http://adf.ly/": ["adf.ly"] + } + }, + { + "Adfonic": { + "http://adfonic.com/": ["adfonic.com"] + } + }, + { + "Adforge": { + "http://adforgeinc.com/": ["adforgeinc.com"] + } + }, + { + "Adform": { + "http://www.adform.com/": [ + "adform.com", + "adform.net", + "adformdsp.net" + ] + } + }, + { + "AdFox": { + "http://adfox.ru/": ["adfox.ru"] + } + }, + { + "AdFrontiers": { + "http://www.adfrontiers.com/": ["adfrontiers.com"] + } + }, + { + "Adfunky": { + "http://www.adfunky.com/": ["adfunky.com", "adfunkyserver.com"] + } + }, + { + "Adfusion": { + "http://www.adfusion.com/": ["adfusion.com"] + } + }, + { + "AdGainerSolutions": { + "http://adgainersolutions.com/adgainer/": ["adgainersolutions.com"] + } + }, + { + "AdGent Digital": { + "http://www.adgentdigital.com/": [ + "adgentdigital.com", + "shorttailmedia.com" + ] + } + }, + { + "AdGibbon": { + "http://www.adgibbon.com/": ["adgibbon.com"] + } + }, + { + "Adglare": { + "https://www.adglare.com/": ["adglare.com", "adglare.net"] + } + }, + { + "adhood": { + "http://www.adhood.com/": ["adhood.com"] + } + }, + { + "Adiant": { + "http://www.adiant.com/": ["adblade.com", "adiant.com"] + } + }, + { + "AdInsight": { + "http://www.adinsight.com/": ["adinsight.com", "adinsight.eu"] + } + }, + { + "AdIQuity": { + "http://adiquity.com/": ["adiquity.com"] + } + }, + { + "ADITION": { + "http://www.adition.com/": ["adition.com"] + } + }, + { + "AdJug": { + "http://www.adjug.com/": ["adjug.com"] + } + }, + { + "AdJuggler": { + "http://www.adjuggler.com/": ["adjuggler.com", "adjuggler.net"] + } + }, + { + "Adjust": { + "https://adjust.com": ["adjust.com"] + } + }, + { + "AdKeeper": { + "http://www.adkeeper.com/": ["adkeeper.com", "akncdn.com"] + } + }, + { + "AdKernel": { + "http://adkernel.com": ["adkernel.com"] + } + }, + { + "Ad Knife": { + "http://static.adknife.com/": ["adknife.com"] + } + }, + { + "Adknowledge": { + "http://www.adknowledge.com/": [ + "adknowledge.com", + "adparlor.com", + "bidsystem.com", + "cubics.com", + "lookery.com" + ] + } + }, + { + "AdLantis": { + "http://www.adlantis.jp/": ["adimg.net", "adlantis.jp"] + } + }, + { + "AdLeave": { + "http://www.adleave.com/": ["adleave.com"] + } + }, + { + "Adlibrium": { + "http://www.adlibrium.com/": ["adlibrium.com"] + } + }, + { + "Adlucent": { + "http://adlucent.com": ["adlucent.com"] + } + }, + { + "Ad Magnet": { + "http://www.admagnet.com/": ["admagnet.com", "admagnet.net"] + } + }, + { + "Admarketplace": { + "http://www.admarketplace.com/": [ + "admarketplace.com", + "admarketplace.net", + "ampxchange.com" + ] + } + }, + { + "AdMarvel": { + "http://www.admarvel.com/": ["admarvel.com"] + } + }, + { + "AdMatrix": { + "http://www.admatrix.jp/": ["admatrix.jp"] + } + }, + { + "AdMaven": { + "https://ad-maven.com/": [ + "ad-maven.com", + "agreensdistra.info", + "boudja.com", + "rensovetors.info", + "wrethicap.info" + ] + } + }, + { + "AdMaximizer Network": { + "http://admaximizer.com/": ["admaximizer.com"] + } + }, + { + "AdMedia": { + "http://www.admedia.com/": ["admedia.com"] + } + }, + { + "Admeta": { + "http://www.admeta.com/": ["admeta.com", "atemda.com"] + } + }, + { + "Admicro": { + "http://www.admicro.vn/": ["admicro.vn", "vcmedia.vn"] + } + }, + { + "Admixer": { + "https://admixer.co.kr/main": ["admixer.co.kr"] + } + }, + { + "Admized": { + "http://www.admized.com/": ["admized.com"] + } + }, + { + "Admobile": { + "http://admobile.com/": ["admobile.com"] + } + }, + { + "Admotion": { + "http://www.admotion.com/": ["admotion.com", "nspmotion.com"] + } + }, + { + "Adnetik": { + "http://adnetik.com/": ["adnetik.com", "wtp101.com"] + } + }, + { + "AdNetwork.net": { + "http://www.adnetwork.net/": ["adnetwork.net"] + } + }, + { + "Adnium": { + "https://adnium.com": ["adnium.com"] + } + }, + { + "adnologies": { + "http://www.adnologies.com/": ["adnologies.com", "heias.com"] + } + }, + { + "Adobe": { + "http://www.adobe.com/": [ + "2o7.net", + "auditude.com", + "demdex.com", + "demdex.net", + "dmtracker.com", + "efrontier.com", + "everestads.net", + "everestjs.net", + "everesttech.net", + "hitbox.com", + "omniture.com", + "omtrdc.net", + "touchclarity.com" + ] + } + }, + { + "AdOcean": { + "http://www.adocean-global.com/": ["adocean-global.com", "adocean.pl"] + } + }, + { + "Adometry": { + "http://www.adometry.com/": ["adometry.com", "dmtry.com"] + } + }, + { + "Adomik": { + "http://www.adomik.com/": ["adomik.com"] + } + }, + { + "AdOnion": { + "http://www.adonion.com/": ["adonion.com"] + } + }, + { + "Adorika": { + "http://www.clickotmedia.com/": ["clickotmedia.com"] + } + }, + { + "Adotmob": { + "https://adotmob.com/": ["adotmob.com"] + } + }, + { + "ADP Dealer Services": { + "http://www.adpdealerservices.com/": [ + "admission.net", + "adpdealerservices.com", + "cobalt.com" + ] + } + }, + { + "ad pepper media": { + "http://www.adpepper.us/": ["adpepper.com", "adpepper.us"] + } + }, + { + "AdPerfect": { + "http://www.adperfect.com/": ["adperfect.com"] + } + }, + { + "Adperium": { + "http://www.adperium.com/": ["adperium.com"] + } + }, + { + "Adpersia": { + "http://www.adpersia.com/": ["adpersia.com"] + } + }, + { + "adPrecision": { + "http://adprecision.net/": ["adprs.net", "aprecision.net"] + } + }, + { + "AdPredictive": { + "http://www.adpredictive.com/": ["adpredictive.com"] + } + }, + { + "AdReactor": { + "http://www.adreactor.com/": ["adreactor.com"] + } + }, + { + "AdReady": { + "http://www.adready.com/": ["adready.com", "adreadytractions.com"] + } + }, + { + "AdRevolution": { + "http://adrevolution.com/": ["adrevolution.com"] + } + }, + { + "AdRiver": { + "http://adriver.ru/": ["adriver.ru"] + } + }, + { + "adrolays": { + "http://adrolays.com/": ["adrolays.com", "adrolays.de"] + } + }, + { + "AdRoll": { + "http://www.adroll.com/": ["adroll.com"] + } + }, + { + "adscale": { + "http://www.adscale.de/": ["adscale.de"] + } + }, + { + "Adscience": { + "https://www.adscience.nl/": ["adscience.nl"] + } + }, + { + "AdServerPub": { + "http://www.adserverpub.com/": ["adserverpub.com"] + } + }, + { + "AdShuffle": { + "http://www.adshuffle.com/": ["adshuffle.com"] + } + }, + { + "AdSide": { + "http://www.adside.com/": ["adside.com", "doclix.com"] + } + }, + { + "AdSpeed": { + "http://www.adspeed.com/": ["adspeed.com", "adspeed.net"] + } + }, + { + "Adsperity": { + "https://www.adsperity.com/": ["adsperity.com"] + } + }, + { + "AdSpirit": { + "http://www.adspirit.de/": [ + "adspirit.com", + "adspirit.de", + "adspirit.net" + ] + } + }, + { + "Adsrevenue.net": { + "http://adsrevenue.net/": ["adsrevenue.net"] + } + }, + { + "AdStir": { + "https://en.ad-stir.com/": ["ad-stir.com"] + } + }, + { + "AdsTours": { + "http://www.adstours.com/": ["adstours.com", "clickintext.net"] + } + }, + { + "Adsty": { + "http://adsty.com/": ["adsty.com", "adx1.com"] + } + }, + { + "Adsupply": { + "http://www.adsupply.com/": ["4dsply.com", "adsupply.com"] + } + }, + { + "Adswizz": { + "http://adswizz.com": ["adswizz.com"] + } + }, + { + "ADTECH": { + "http://www.adtech.com/": ["adtech.com", "adtech.de", "adtechus.com"] + } + }, + { + "Adtegrity.com": { + "http://www.adtegrity.com/": ["adtegrity.com", "adtegrity.net"] + } + }, + { + "ADTELLIGENCE": { + "http://www.adtelligence.de/": ["adtelligence.de"] + } + }, + { + "Adthink": { + "https://adthink.com/": ["adthink.com", "audienceinsights.net"] + } + }, + { + "AdTiger": { + "http://www.adtiger.de/": ["adtiger.de"] + } + }, + { + "AdTruth": { + "http://adtruth.com/": ["adtruth.com"] + } + }, + { + "Adult AdWorld": { + "http://adultadworld.com/": ["adultadworld.com"] + } + }, + { + "Adultmoda": { + "http://www.adultmoda.com/": ["adultmoda.com"] + } + }, + { + "Adventive": { + "http://adventive.com/": ["adventive.com"] + } + }, + { + "Adverline": { + "http://www.adverline.com/": ["adnext.fr", "adverline.com"] + } + }, + { + "Adversal.com": { + "http://www.adversal.com/": ["adv-adserver.com", "adversal.com"] + } + }, + { + "Adverticum": { + "http://www.adverticum.com/": [ + "adsmart.com", + "adverticum.com", + "adverticum.net" + ] + } + }, + { + "Advertise.com": { + "http://www.advertise.com/": ["advertise.com"] + } + }, + { + "AdvertiseSpace": { + "http://www.advertisespace.com/": ["advertisespace.com"] + } + }, + { + "Advert Stream": { + "http://www.advertstream.com/": ["advertstream.com"] + } + }, + { + "Advisor Media": { + "http://advisormedia.cz/": ["advisormedia.cz"] + } + }, + { + "Adworx": { + "http://adworx.at/": ["adworx.at", "adworx.be", "adworx.nl"] + } + }, + { + "AdXpansion": { + "http://www.adxpansion.com/": ["adxpansion.com"] + } + }, + { + "Adxvalue": { + "http://adxvalue.com/": ["adxvalue.com", "adxvalue.de"] + } + }, + { + "adyard": { + "http://adyard.de/": ["adyard.de"] + } + }, + { + "AdYield": { + "http://www.adyield.com/": ["adxyield.com", "adyield.com"] + } + }, + { + "AdYouLike": { + "https://www.adyoulike.com/": [ + "adyoulike.com", + "omnitagjs.com", + "pulpix.com" + ] + } + }, + { + "ADZ": { + "http://www.adzcentral.com/": ["adzcentral.com"] + } + }, + { + "Adzerk": { + "http://www.adzerk.com/": ["adzerk.com", "adzerk.net"] + } + }, + { + "adzly": { + "http://www.adzly.com/": ["adzly.com"] + } + }, + { + "Aegis Group": { + "http://www.aemedia.com/": ["aemedia.com", "bluestreak.com"] + } + }, + { + "AERIFY MEDIA": { + "http://aerifymedia.com/": ["aerifymedia.com", "anonymous-media.com"] + } + }, + { + "Affectv": { + "http://affectv.co.uk/": ["affectv.co.uk"] + } + }, + { + "affilinet": { + "http://www.affili.net/": [ + "affili.net", + "affilinet-inside.de", + "banner-rotation.com", + "successfultogether.co.uk" + ] + } + }, + { + "Affine": { + "http://www.affine.tv/": ["affine.tv", "affinesystems.com"] + } + }, + { + "Affinity": { + "http://www.affinity.com/": ["affinity.com"] + } + }, + { + "AfterDownload": { + "http://www.afterdownload.com/": ["afdads.com", "afterdownload.com"] + } + }, + { + "Aim4Media": { + "http://aim4media.com/": ["aim4media.com"] + } + }, + { + "Airpush": { + "http://www.airpush.com/": ["airpush.com"] + } + }, + { + "AK": { + "http://www.aggregateknowledge.com/": [ + "aggregateknowledge.com", + "agkn.com" + ] + } + }, + { + "Akamai": { + "http://www.akamai.com/": ["imiclk.com"] + } + }, + { + "Albacross": { + "https://albacross.com": ["albacross.com"] + } + }, + { + "AllStarMediaGroup": { + "http://allstarmediagroup.com/": ["allstarmediagroup.com"] + } + }, + { + "Aloodo": { + "https://aloodo.com/": ["aloodo.com"] + } + }, + { + "AlterGeo": { + "http://altergeo.ru/": ["altergeo.ru"] + } + }, + { + "Amazon.com": { + "http://www.amazon.com/": [ + "amazon-adsystem.com", + "amazon.ca", + "amazon.co.jp", + "amazon.co.uk", + "amazon.de", + "amazon.es", + "amazon.fr", + "amazon.it", + "assoc-amazon.com" + ] + } + }, + { + "Ambient Digital": { + "http://ambientdigital.com.vn/": [ + "adnetwork.vn", + "ambientdigital.com.vn" + ] + } + }, + { + "Amobee": { + "http://amobee.com/": [ + "adconion.com", + "amgdgt.com", + "amobee.com", + "euroclick.com", + "smartclip.com", + "turn.com" + ] + } + }, + { + "AndBeyond": { + "http://andbeyond.media/": ["andbeyond.media"] + } + }, + { + "Answers.com": { + "http://www.answers.com/": ["dsply.com"] + } + }, + { + "AOL": { + "http://www.aol.com/": [ + "adsonar.com", + "adtechjp.com", + "advertising.com", + "aolcloud.net", + "atwola.com", + "leadback.com", + "tacoda.net", + "vidible.tv" + ] + } + }, + { + "AppCast": { + "https://appcast.io/": ["appcast.io"] + } + }, + { + "Appenda": { + "http://www.appenda.com/": ["appenda.com"] + } + }, + { + "AppFlood": { + "http://appflood.com/": ["appflood.com"] + } + }, + { + "Appier": { + "http://appier.com/": ["appier.com"] + } + }, + { + "Applifier": { + "http://www.applifier.com/": ["applifier.com"] + } + }, + { + "Applovin": { + "http://www.applovin.com/": ["applovin.com"] + } + }, + { + "AppNexus": { + "http://www.appnexus.com/": [ + "adlantic.nl", + "adnxs.com", + "adrdgt.com", + "alenty.com", + "appnexus.com" + ] + } + }, + { + "AppsFlyer": { + "http://appsflyer.com/": ["appsflyer.com"] + } + }, + { + "appssavvy": { + "http://appssavvy.com/": ["appssavvy.com"] + } + }, + { + "Arkwrights Homebrew": { + "http://www.arkwrightshomebrew.com/": [ + "arkwrightshomebrew.com", + "ctasnet.com" + ] + } + }, + { + "AT Internet": { + "http://www.atinternet.com/": ["hit-parade.com"] + } + }, + { + "ATN": { + "http://affiliatetracking.com/": ["affiliatetracking.com"] + } + }, + { + "Atoomic.com": { + "http://www.atoomic.com/": ["atoomic.com"] + } + }, + { + "Atrinsic": { + "http://atrinsic.com/": ["atrinsic.com"] + } + }, + { + "AT&T": { + "http://www.att.com/": ["att.com", "yp.com"] + } + }, + { + "Audience2Media": { + "http://www.audience2media.com/": ["audience2media.com"] + } + }, + { + "Audience Ad Network": { + "http://audienceadnetwork.com/": ["audienceadnetwork.com"] + } + }, + { + "AudienceScience": { + "http://www.audiencescience.com/": [ + "audiencescience.com", + "revsci.net", + "targetingmarketplace.com", + "wunderloop.net" + ] + } + }, + { + "Augme": { + "http://www.augme.com/": ["augme.com", "hipcricket.com"] + } + }, + { + "Augur": { + "http://www.augur.io/": ["augur.io"] + } + }, + { + "AUTOCENTRE.UA": { + "http://www.autocentre.ua/": ["am.ua", "autocentre.ua"] + } + }, + { + "Automattic": { + "http://automattic.com/": ["pubmine.com"] + } + }, + { + "Avalanchers": { + "http://www.avalanchers.com/": ["avalanchers.com"] + } + }, + { + "AvantLink": { + "http://www.avantlink.com/": ["avantlink.com"] + } + }, + { + "Avocet": { + "https://avocet.io/": ["avocet.io"] + } + }, + { + "Avsads": { + "http://avsads.com/": ["avsads.com"] + } + }, + { + "AWeber": { + "http://www.aweber.com/": ["aweber.com"] + } + }, + { + "Awin": { + "http://www.awin.com/": [ + "digitalwindow.com", + "dwin1.com", + "perfiliate.com" + ] + } + }, + { + "Azet": { + "http://mediaimpact.sk/": ["azetklik.sk", "rsz.sk"] + } + }, + { + "BackBeat Media": { + "http://www.backbeatmedia.com/": ["backbeatmedia.com"] + } + }, + { + "Bannerconnect": { + "http://www.bannerconnect.net/": ["bannerconnect.net"] + } + }, + { + "Barilliance": { + "http://www.barilliance.com/": ["barilliance.com"] + } + }, + { + "BaronsNetworks": { + "http://baronsoffers.com/": ["baronsoffers.com"] + } + }, + { + "Batanga Network": { + "http://www.batanganetwork.com/": [ + "batanga.com", + "batanganetwork.com" + ] + } + }, + { + "BeachFront": { + "http://beachfront.com/": ["beachfront.com"] + } + }, + { + "Beanstock Media": { + "http://www.beanstockmedia.com/": ["beanstockmedia.com"] + } + }, + { + "beencounter": { + "http://www.beencounter.com/": ["beencounter.com"] + } + }, + { + "Begun": { + "http://www.begun.ru/": ["begun.ru"] + } + }, + { + "belboon": { + "http://www.belboon.com/": ["adbutler.de", "belboon.com"] + } + }, + { + "Betgenius": { + "http://www.betgenius.com/": ["betgenius.com", "connextra.com"] + } + }, + { + "BetweenDigital": { + "http://betweendigital.com": ["betweendigital.com"] + } + }, + { + "Bidfluence": { + "https://www.bidfluence.com/": ["bidfluence.com"] + } + }, + { + "Bidr": { + "http://bidr.io": ["bidr.io"] + } + }, + { + "BidSwitch": { + "https://www.bidswitch.com/": ["bidswitch.net", "mfadsrvr.com"] + } + }, + { + "Bidtellect": { + "https://www.bidtellect.com/": ["bidtellect.com", "bttrack.com"] + } + }, + { + "BidVertiser": { + "http://www.bidvertiser.com/": ["bidvertiser.com"] + } + }, + { + "BigClick": { + "http://bigclick.me/": ["bgclck.me", "xcvgdf.party"] + } + }, + { + "bigmirnet": { + "http://www.bigmir.net/": ["bigmir.net"] + } + }, + { + "BinLayer": { + "http://binlayer.com/": ["binlayer.com"] + } + }, + { + "Bitcoin Plus": { + "http://www.bitcoinplus.com/": ["bitcoinplus.com"] + } + }, + { + "BitMedia": { + "https://bitmedia.io/": ["bitmedia.io"] + } + }, + { + "BittAds": { + "http://www.bittads.com/": ["bittads.com"] + } + }, + { + "Bizo": { + "http://www.bizo.com/": ["bizo.com", "bizographics.com"] + } + }, + { + "Black Label Ads": { + "http://www.blacklabelads.com/": ["blacklabelads.com"] + } + }, + { + "BlogCatalog": { + "http://www.blogcatalog.com/": ["blogcatalog.com"] + } + }, + { + "BlogFrog": { + "http://theblogfrog.com/": ["theblogfrog.com"] + } + }, + { + "BlogHer": { + "http://www.blogher.com/": ["blogher.com", "blogherads.com"] + } + }, + { + "BlogRollr": { + "http://blogrollr.com/": ["blogrollr.com"] + } + }, + { + "BLOOM Digital Platforms": { + "http://bloom-hq.com/": ["adgear.com", "adgrx.com", "bloom-hq.com"] + } + }, + { + "BlueKai": { + "http://www.bluekai.com/": [ + "bkrtx.com", + "bluekai.com", + "tracksimple.com" + ] + } + }, + { + "Blu Trumpet": { + "http://www.blutrumpet.com/": ["blutrumpet.com"] + } + }, + { + "Boo-Box": { + "http://boo-box.com/": ["boo-box.com"] + } + }, + { + "BoostBox": { + "https://www.boostbox.com.br/": ["boostbox.com.br"] + } + }, + { + "Bouncex": { + "https://www.bouncex.com/": [ + "bounceexchange.com", + "bouncex.com", + "bouncex.net" + ] + } + }, + { + "Brainient": { + "http://brainient.com/": ["brainient.com"] + } + }, + { + "Brand Affinity Technologies": { + "http://www.brandaffinity.net/": ["brandaffinity.net"] + } + }, + { + "Brandcrumb": { + "http://www.brandcrumb.com": ["brandcrumb.com"] + } + }, + { + "Brand.net": { + "http://www.brand.net/": ["brand.net"] + } + }, + { + "Brandscreen": { + "http://www.brandscreen.com/": ["brandscreen.com", "rtbidder.net"] + } + }, + { + "BreakTime": { + "https://www.breaktime.com.tw/": ["breaktime.com.tw"] + } + }, + { + "BrightRoll": { + "http://www.brightroll.com/": ["brightroll.com", "btrll.com"] + } + }, + { + "BrightTag": { + "http://www.brighttag.com/": [ + "brighttag.com", + "btstatic.com", + "thebrighttag.com" + ] + } + }, + { + "Brilig": { + "http://www.brilig.com/": ["brilig.com"] + } + }, + { + "BuckSense": { + "http://www.bucksense.com": ["bucksense.com"] + } + }, + { + "Burstly": { + "http://www.burstly.com/": ["burstly.com"] + } + }, + { + "Burst Media": { + "http://www.burstmedia.com/": [ + "burstbeacon.com", + "burstdirectads.com", + "burstmedia.com", + "burstnet.com", + "giantrealm.com" + ] + } + }, + { + "BusinessOnline": { + "http://www.businessol.com/": ["businessol.com"] + } + }, + { + "Button": { + "https://www.usebutton.com": ["usebutton.com"] + } + }, + { + "BuySellAds": { + "http://buysellads.com/": ["beaconads.com", "buysellads.com"] + } + }, + { + "Buysight": { + "http://www.buysight.com/": [ + "buysight.com", + "permuto.com", + "pulsemgr.com" + ] + } + }, + { + "BuzzParadise": { + "http://www.buzzparadise.com/": ["buzzparadise.com"] + } + }, + { + "BV! MEDIA": { + "http://www.bvmedia.ca/": [ + "bvmedia.ca", + "networldmedia.com", + "networldmedia.net" + ] + } + }, + { + "c1exchange": { + "https://c1exchange.com/": ["c1exchange.com"] + } + }, + { + "C3 Metrics": { + "http://c3metrics.com/": [ + "attributionmodel.com", + "c3metrics.com", + "c3tag.com" + ] + } + }, + { + "Cadreon": { + "http://www.cadreon.com/": ["cadreon.com"] + } + }, + { + "CampaignGrid": { + "http://www.campaigngrid.com/": ["campaigngrid.com"] + } + }, + { + "CAPITALDATA": { + "http://www.capitaldata.fr/": ["capitaldata.fr"] + } + }, + { + "Carambola": { + "https://www.carambola.com/": ["carambo.la"] + } + }, + { + "Caraytech": { + "http://www.caraytech.com.ar/": ["caraytech.com.ar", "e-planning.net"] + } + }, + { + "Cart.ro": { + "http://www.cart.ro/": ["cart.ro", "statistics.ro"] + } + }, + { + "CartsGuru": { + "https://carts.guru/": ["carts.guru"] + } + }, + { + "Casale Media": { + "http://www.casalemedia.com/": ["casalemedia.com", "medianet.com"] + } + }, + { + "CBproADS": { + "http://www.cbproads.com/": ["cbproads.com"] + } + }, + { + "Cedato": { + "https://www.cedato.com/": ["cedato.com"] + } + }, + { + "Chango": { + "http://www.chango.com/": ["chango.ca", "chango.com"] + } + }, + { + "ChannelAdvisor": { + "http://www.channeladvisor.com/": [ + "channeladvisor.com", + "searchmarketing.com" + ] + } + }, + { + "Channel Intelligence": { + "http://www.channelintelligence.com/": ["channelintelligence.com"] + } + }, + { + "Chartboost": { + "https://www.chartboost.com/": ["chartboost.com"] + } + }, + { + "CheckM8": { + "http://www.checkm8.com/": ["checkm8.com"] + } + }, + { + "Chitika": { + "http://chitika.com/": ["chitika.com", "chitika.net"] + } + }, + { + "ChoiceStream": { + "http://www.choicestream.com/": ["choicestream.com"] + } + }, + { + "ClearLink": { + "https://www.clearlink.com/": ["clearlink.com"] + } + }, + { + "ClearSaleing": { + "http://www.clearsaleing.com/": [ + "clearsaleing.com", + "csdata1.com", + "csdata2.com", + "csdata3.com" + ] + } + }, + { + "Clearsearch Media": { + "http://www.clearsearchmedia.com/": [ + "clearsearchmedia.com", + "csm-secure.com" + ] + } + }, + { + "ClearSight Interactive": { + "http://www.clearsightinteractive.com/": [ + "clearsightinteractive.com", + "csi-tracking.com" + ] + } + }, + { + "ClickAider": { + "http://clickaider.com/": ["clickaider.com"] + } + }, + { + "Clickayab": { + "http://www.clickyab.com": ["clickyab.com"] + } + }, + { + "Clickbooth": { + "http://www.clickbooth.com/": ["adtoll.com", "clickbooth.com"] + } + }, + { + "ClickDimensions": { + "http://www.clickdimensions.com/": ["clickdimensions.com"] + } + }, + { + "ClickDistrict": { + "http://www.clickdistrict.com/": [ + "clickdistrict.com", + "creative-serving.com" + ] + } + }, + { + "ClickFrog": { + "https://clickfrog.ru/": [ + "bashirian.biz", + "buckridge.link", + "clickfrog.ru", + "franecki.net", + "quitzon.net", + "reichelcormier.bid", + "wisokykulas.bid" + ] + } + }, + { + "ClickFuel": { + "http://clickfuel.com/": ["conversiondashboard.com"] + } + }, + { + "ClickInc": { + "http://www.clickinc.com/": ["clickinc.com"] + } + }, + { + "Clicksor": { + "http://www.clicksor.com/": ["clicksor.com", "clicksor.net"] + } + }, + { + "Clickwinks": { + "http://www.clickwinks.com/": ["clickwinks.com"] + } + }, + { + "ClicManager": { + "http://www.clicmanager.fr/": ["clicmanager.fr"] + } + }, + { + "Clixtell": { + "https://www.clixtell.com/": ["clixtell.com"] + } + }, + { + "Clove Network": { + "http://www.clovenetwork.com/": ["clovenetwork.com"] + } + }, + { + "Cognitive Match": { + "http://www.cognitivematch.com/": [ + "cmads.com.tw", + "cmadsasia.com", + "cmadseu.com", + "cmmeglobal.com", + "cognitivematch.com" + ] + } + }, + { + "Collective": { + "http://collective.com/": [ + "collective-media.net", + "collective.com", + "oggifinogi.com", + "tumri.com", + "tumri.net", + "yt1187.net" + ] + } + }, + { + "Commission Junction": { + "http://www.cj.com/": [ + "apmebf.com", + "awltovhc.com", + "cj.com", + "ftjcfx.com", + "kcdwa.com", + "qksz.com", + "qksz.net", + "tqlkg.com", + "yceml.net" + ] + } + }, + { + "Communicator Corp": { + "http://www.communicatorcorp.com/": ["communicatorcorp.com"] + } + }, + { + "Compass Labs": { + "http://compasslabs.com/": ["compasslabs.com"] + } + }, + { + "Complex Media": { + "http://www.complexmedianetwork.com/": [ + "complex.com", + "complexmedianetwork.com" + ] + } + }, + { + "comScore": { + "http://www.comscore.com/": [ + "adxpose.com", + "proxilinks.com", + "proximic.com", + "proximic.net" + ] + } + }, + { + "Connatix.com": { + "https://connatix.com/": ["connatix.com"] + } + }, + { + "Connexity": { + "http://www.connexity.com/": ["pricegrabber.com"] + } + }, + { + "Consilium Media": { + "http://www.consiliummedia.com/": ["consiliummedia.com"] + } + }, + { + "Consumable": { + "http://consumable.com/": ["consumable.com"] + } + }, + { + "CONTAXE": { + "http://www.contaxe.com/": ["contaxe.com"] + } + }, + { + "ContentABC": { + "http://contentabc.com/": ["contentabc.com"] + } + }, + { + "CONTEXTin": { + "http://www.contextin.com/": ["admailtiser.com", "contextin.com"] + } + }, + { + "ContextuAds": { + "http://www.contextuads.com/": [ + "agencytradingdesk.net", + "contextuads.com" + ] + } + }, + { + "CONTEXTWEB": { + "http://www.contextweb.com/": ["contextweb.com"] + } + }, + { + "ConvergeDirect": { + "http://www.convergedirect.com/": [ + "convergedirect.com", + "convergetrack.com" + ] + } + }, + { + "ConversantMedia": { + "http://conversantmedia.com": [ + "adserver.com", + "conversantmedia.com", + "dotomi.com", + "dtmpub.com", + "emjcd.com", + "fastclick.com", + "fastclick.net", + "greystripe.com", + "lduhtrp.net", + "mediaplex.com", + "valueclick.com", + "valueclick.net", + "valueclickmedia.com" + ] + } + }, + { + "ConversionRuler": { + "http://www.conversionruler.com/": ["conversionruler.com"] + } + }, + { + "Conversive": { + "http://www.conversive.nl/": ["conversive.nl"] + } + }, + { + "CoreMotives": { + "http://coremotives.com/": ["coremotives.com"] + } + }, + { + "Cox Digital Solutions": { + "http://www.coxdigitalsolutions.com/": [ + "adify.com", + "afy11.net", + "coxdigitalsolutions.com" + ] + } + }, + { + "CPMStar": { + "http://www.cpmstar.com/": ["cpmstar.com"] + } + }, + { + "CPX Interactive": { + "http://www.cpxinteractive.com/": [ + "adreadypixels.com", + "cpxadroit.com", + "cpxinteractive.com" + ] + } + }, + { + "Creafi": { + "http://www.creafi.com/": ["creafi.com"] + } + }, + { + "Crimtan": { + "http://www.crimtan.com/": ["crimtan.com"] + } + }, + { + "Crisp Media": { + "http://www.crispmedia.com/": ["crispmedia.com"] + } + }, + { + "Criteo": { + "http://www.criteo.com/": [ + "criteo.com", + "criteo.net", + "hlserve.com", + "hooklogic.com", + "storetail.io" + ] + } + }, + { + "Cross Pixel": { + "http://crosspixel.net/": [ + "crosspixel.net", + "crosspixelmedia.com", + "crsspxl.com" + ] + } + }, + { + "cXense": { + "http://www.cxense.com/": [ + "cxense.com", + "emediate.biz", + "emediate.com", + "emediate.dk", + "emediate.eu" + ] + } + }, + { + "Cyberplex": { + "http://www.cyberplex.com/": ["cyberplex.com"] + } + }, + { + "Dada": { + "http://dada.pro/": ["dada.pro", "simply.com"] + } + }, + { + "Datalogix": { + "http://www.datalogix.com/": ["nexac.com", "nextaction.net"] + } + }, + { + "DataXu": { + "http://www.dataxu.com/": [ + "dataxu.com", + "dataxu.net", + "mexad.com", + "w55c.net" + ] + } + }, + { + "Datonics": { + "http://datonics.com/": ["datonics.com", "pro-market.net"] + } + }, + { + "Datran Media": { + "http://www.datranmedia.com/": [ + "datranmedia.com", + "displaymarketplace.com" + ] + } + }, + { + "Datvantage": { + "http://datvantage.com/": ["datvantage.com"] + } + }, + { + "DC Storm": { + "http://www.dc-storm.com/": ["dc-storm.com", "stormiq.com"] + } + }, + { + "Dedicated Media": { + "http://www.dedicatedmedia.com/": [ + "dedicatedmedia.com", + "dedicatednetworks.com" + ] + } + }, + { + "Delivr": { + "http://delivr.com/": ["delivr.com", "percentmobile.com"] + } + }, + { + "Delta Projects": { + "http://www.deltaprojects.se/": [ + "adaction.se", + "de17a.com", + "deltaprojects.se" + ] + } + }, + { + "Demand Media": { + "http://www.demandmedia.com/": ["demandmedia.com", "indieclick.com"] + } + }, + { + "Deutsche Post DHL": { + "http://www.dp-dhl.com/": ["adcloud.com", "adcloud.net", "dp-dhl.com"] + } + }, + { + "Developer Media": { + "http://developermedia.com/": ["developermedia.com", "lqcdn.com"] + } + }, + { + "DG": { + "http://www.dgit.com/": [ + "dgit.com", + "eyeblaster.com", + "eyewonder.com", + "mdadx.com", + "serving-sys.com", + "unicast.com" + ] + } + }, + { + "dianomi": { + "http://www.dianomi.com/": ["dianomi.com"] + } + }, + { + "Didit": { + "http://www.didit.com/": ["did-it.com", "didit.com"] + } + }, + { + "DigitalAdConsortium": { + "https://www.dac.co.jp/": ["impact-ad.jp"] + } + }, + { + "Digital River": { + "http://www.digitalriver.com/": [ + "digitalriver.com", + "keywordmax.com", + "netflame.cc" + ] + } + }, + { + "Digital Target": { + "http://digitaltarget.ru": ["digitaltarget.ru"] + } + }, + { + "Digitize": { + "http://www.digitize.ie/": ["digitize.ie"] + } + }, + { + "DirectAdvert": { + "http://www.directadvert.ru/": ["directadvert.ru"] + } + }, + { + "Direct Response Group": { + "http://www.directresponsegroup.com/": [ + "directresponsegroup.com", + "ppctracking.net" + ] + } + }, + { + "Directtrack": { + "http://directtrack.com/": ["directtrack.com"] + } + }, + { + "Disqus": { + "http://disqus.com/": ["disqusads.com"] + } + }, + { + "DistrictM": { + "https://districtm.net": ["districtm.io"] + } + }, + { + "dmpxs": { + "http://bob.dmpxs.com": ["dmpxs.com"] + } + }, + { + "DoublePimp": { + "http://doublepimp.com/": ["doublepimp.com"] + } + }, + { + "DoublePositive": { + "http://www.doublepositive.com/": [ + "bid-tag.com", + "doublepositive.com" + ] + } + }, + { + "Drawbridge": { + "http://drawbrid.ge/": ["adsymptotic.com", "drawbrid.ge"] + } + }, + { + "DS-IQ": { + "http://www.ds-iq.com/": ["ds-iq.com"] + } + }, + { + "DSNR Group": { + "http://www.dsnrmg.com/": [ + "dsnrgroup.com", + "dsnrmg.com", + "traffiliate.com", + "z5x.com", + "z5x.net" + ] + } + }, + { + "DynAdmic": { + "https://dynadmic.com/": ["dynadmic.com", "dyntrk.com"] + } + }, + { + "DynamicOxygen": { + "http://www.dynamicoxygen.com/": [ + "dynamicoxygen.com", + "exitjunction.com" + ] + } + }, + { + "DynamicYield": { + "https://www.dynamicyield.com/": [ + "px-eu.dynamicyield.com", + "px.dynamicyield.com" + ] + } + }, + { + "Earnify": { + "http://earnify.com/": ["earnify.com"] + } + }, + { + "eBay": { + "http://www.ebay.com/": ["ebay.com"] + } + }, + { + "Effective Measure": { + "http://www.effectivemeasure.com/": [ + "effectivemeasure.com", + "effectivemeasure.net" + ] + } + }, + { + "ekolay": { + "http://www.ekolay.net/": ["e-kolay.net", "ekolay.net"] + } + }, + { + "Eleavers": { + "http://eleavers.com/": ["eleavers.com"] + } + }, + { + "Emego": { + "http://www.usemax.de/": ["usemax.de"] + } + }, + { + "Emerse": { + "https://www.emerse.com": ["emerse.com"] + } + }, + { + "EMX": { + "https://emxdigital.com/": [ + "brealtime.com", + "clearstream.tv", + "emxdgt.com", + "emxdigital.com" + ] + } + }, + { + "Enecto": { + "http://www.enecto.com/": ["enecto.com"] + } + }, + { + "engage:BDR": { + "http://engagebdr.com/": ["bnmla.com", "engagebdr.com"] + } + }, + { + "Engago Technology": { + "http://www.engago.com/": ["appmetrx.com", "engago.com"] + } + }, + { + "Engine Network": { + "http://enginenetwork.com/": ["enginenetwork.com"] + } + }, + { + "Ensighten": { + "http://www.ensighten.com/": ["ensighten.com"] + } + }, + { + "Entireweb": { + "http://www.entireweb.com/": ["entireweb.com"] + } + }, + { + "Epic Media Group": { + "http://www.theepicmediagroup.com/": [ + "epicadvertising.com", + "epicmarketplace.com", + "epicmobileads.com", + "theepicmediagroup.com", + "trafficmp.com" + ] + } + }, + { + "Epsilon": { + "http://www.epsilon.com/": ["epsilon.com"] + } + }, + { + "EQ Ads": { + "http://www.eqads.com/": ["eqads.com"] + } + }, + { + "EroAdvertising": { + "http://www.ero-advertising.com/": ["ero-advertising.com"] + } + }, + { + "Etarget": { + "http://etargetnet.com/": ["etarget.eu", "etargetnet.com"] + } + }, + { + "Etineria": { + "http://www.etineria.com/": ["adwitserver.com", "etineria.com"] + } + }, + { + "eTrigue": { + "http://www.etrigue.com/": ["etrigue.com"] + } + }, + { + "Evergage": { + "http://www.evergage.com": ["mybuys.com", "veruta.com"] + } + }, + { + "Everyday Health": { + "http://www.everydayhealth.com/": [ + "everydayhealth.com", + "waterfrontmedia.com" + ] + } + }, + { + "Evisions Marketing": { + "http://www.evisionsmarketing.com/": [ + "engineseeker.com", + "evisionsmarketing.com" + ] + } + }, + { + "Evolve": { + "http://www.evolvemediacorp.com/": [ + "evolvemediacorp.com", + "evolvemediametrics.com", + "gorillanation.com" + ] + } + }, + { + "eWayDirect": { + "http://www.ewaydirect.com/": ["ewaydirect.com", "ixs1.net"] + } + }, + { + "ewebse": { + "http://ewebse.com/": ["777seo.com", "ewebse.com"] + } + }, + { + "excitad": { + "http://excitad.com/": ["excitad.com"] + } + }, + { + "eXelate": { + "http://exelate.com/": ["exelate.com", "exelator.com"] + } + }, + { + "ExoClick": { + "http://www.exoclick.com/": ["exoclick.com"] + } + }, + { + "Exosrv": { + "http://main.exosrv.com/": ["exosrv.com"] + } + }, + { + "Experian": { + "http://www.experian.com/": ["audienceiq.com", "experian.com"] + } + }, + { + "expo-MAX": { + "http://expo-max.com/": ["expo-max.com"] + } + }, + { + "Exponential Interactive": { + "http://www.exponential.com/": [ + "adotube.com", + "exponential.com", + "fulltango.com", + "tribalfusion.com" + ] + } + }, + { + "Extension Factory": { + "http://www.extensionfactory.com/": ["extensionfactory.com"] + } + }, + { + "EXTENSIONS.RU": { + "http://extensions.ru/": ["extensions.ru"] + } + }, + { + "Eyeconomy": { + "http://www.eyeconomy.co.uk/": [ + "eyeconomy.co.uk", + "eyeconomy.com", + "sublimemedia.net" + ] + } + }, + { + "EyeNewton": { + "http://eyenewton.ru/": ["eyenewton.ru"] + } + }, + { + "eyeReturn Marketing": { + "http://www.eyereturnmarketing.com/": [ + "eyereturn.com", + "eyereturnmarketing.com" + ] + } + }, + { + "Eyeviewdigital": { + "http://www.eyeviewdigital.com/": ["eyeviewdigital.com"] + } + }, + { + "Facebook": { + "http://www.facebook.com/": ["atlassolutions.com"] + } + }, + { + "Facilitate Digital": { + "http://www.facilitatedigital.com/": [ + "adsfac.eu", + "adsfac.info", + "adsfac.net", + "adsfac.sg", + "adsfac.us", + "facilitatedigital.com" + ] + } + }, + { + "Fairfax Media": { + "http://www.fxj.com.au/": ["fairfax.com.au", "fxj.com.au"] + } + }, + { + "faithadnet": { + "http://www.faithadnet.com/": ["faithadnet.com"] + } + }, + { + "Fanplayr": { + "https://fanplayr.com/": ["fanplayr.com"] + } + }, + { + "Fathom": { + "http://www.fathomdelivers.com/": [ + "fathomdelivers.com", + "fathomseo.com" + ] + } + }, + { + "Federated Media": { + "http://www.federatedmedia.net/": [ + "federatedmedia.net", + "fmpub.net", + "lijit.com" + ] + } + }, + { + "FetchBack": { + "http://www.fetchback.com/": ["fetchback.com"] + } + }, + { + "Fiksu": { + "http://www.fiksu.com/": ["fiksu.com"] + } + }, + { + "FinancialContent": { + "http://www.financialcontent.com/": ["financialcontent.com"] + } + }, + { + "Fizz-Buzz Media": { + "http://www.fizzbuzzmedia.com/": [ + "fizzbuzzmedia.com", + "fizzbuzzmedia.net" + ] + } + }, + { + "Flashtalking": { + "http://www.flashtalking.com/": ["flashtalking.com"] + } + }, + { + "Flite": { + "http://www.flite.com/": ["flite.com", "widgetserver.com"] + } + }, + { + "Fluct": { + "https://corp.fluct.jp/": ["adingo.jp", "fluct.jp"] + } + }, + { + "Flytxt": { + "http://www.flytxt.com/": ["flytxt.com"] + } + }, + { + "Forbes": { + "http://www.forbes.com/": ["brandsideplatform.com", "forbes.com"] + } + }, + { + "Fox One Stop Media": { + "http://www.foxonestop.com/": [ + "fimserve.com", + "foxnetworks.com", + "foxonestop.com", + "mobsmith.com", + "myads.com", + "othersonline.com" + ] + } + }, + { + "FreakOut": { + "http://fout.jp/": ["fout.jp"] + } + }, + { + "Freedom Communications": { + "http://www.freedom.com/": ["freedom.com"] + } + }, + { + "FreeWheel": { + "http://www.freewheel.tv/": ["stickyadstv.com"] + } + }, + { + "FriendFinder Networks": { + "http://ffn.com/": ["adultfriendfinder.com", "ffn.com", "pop6.com"] + } + }, + { + "Friends2Follow": { + "https://friends2follow.com/": ["tracking.friends2follow.com"] + } + }, + { + "Frog Sex": { + "http://www.frogsex.com/": ["double-check.com", "frogsex.com"] + } + }, + { + "FuelX": { + "https://fuelx.com/": ["fuel451.com", "fuelx.com"] + } + }, + { + "Future Ads": { + "https://www.futureads.com/": ["futureads.com", "resultlinks.com"] + } + }, + { + "Fyber": { + "https://www.fyber.com/": ["fyber.com"] + } + }, + { + "Game Advertising Online": { + "http://www.game-advertising-online.com/": [ + "game-advertising-online.com" + ] + } + }, + { + "Games2win": { + "http://www.games2win.com/": ["games2win.com", "inviziads.com"] + } + }, + { + "Gamned": { + "http://www.gamned.com/": ["gamned.com"] + } + }, + { + "Gannett": { + "http://www.gannett.com/": ["gannett.com", "pointroll.com"] + } + }, + { + "GB-World": { + "http://www.gb-world.net/": ["gb-world.net"] + } + }, + { + "Gemius": { + "http://www.gemius.com/": ["gemius.com", "gemius.pl"] + } + }, + { + "Genesis Media": { + "http://www.genesismedia.com/": [ + "genesismedia.com", + "genesismediaus.com" + ] + } + }, + { + "GENIEE": { + "https://geniee.co.jp/": ["geniee.co.jp", "gssprt.jp"] + } + }, + { + "GENIE GROUP": { + "http://www.geniegroupltd.co.uk/": ["geniegroupltd.co.uk"] + } + }, + { + "GeoAds": { + "http://www.geoads.com/": ["geoads.com"] + } + }, + { + "GetGlue": { + "http://getglue.com/": ["getglue.com", "smrtlnks.com"] + } + }, + { + "GetIntent": { + "http://getintent.com/": ["adhigh.net", "getintent.com"] + } + }, + { + "GISMAds": { + "http://www.gismads.jp/": ["gismads.jp"] + } + }, + { + "Glam Media": { + "http://www.glammedia.com/": ["glam.com", "glammedia.com"] + } + }, + { + "Gleam": { + "https://gleam.io/": ["fraudjs.io", "gleam.io"] + } + }, + { + "Globe7": { + "http://www.globe7.com/": ["globe7.com"] + } + }, + { + "GoDataFeed": { + "http://godatafeed.com/": ["godatafeed.com"] + } + }, + { + "Goldbach": { + "http://www.goldbachgroup.com/": ["goldbach.com", "goldbachgroup.com"] + } + }, + { + "GoldSpot Media": { + "http://www.goldspotmedia.com/": ["goldspotmedia.com"] + } + }, + { + "Google": { + "http://www.google.com/": [ + "2mdn.net", + "admeld.com", + "admob.com", + "adservice.google.ca", + "adservice.google.com", + "adwords.google.com", + "cc-dt.com", + "destinationurl.com", + "doubleclick.net", + "googleadservices.com", + "googlesyndication.com", + "googletagservices.com", + "invitemedia.com", + "smtad.net", + "teracent.com", + "teracent.net", + "ytsa.net" + ] + } + }, + { + "Grapeshot": { + "http://www.grapeshot.co.uk/": ["grapeshot.co.uk"] + } + }, + { + "Graphnium": { + "https://www.graphinium.com/": ["crm4d.com"] + } + }, + { + "Grocery Shopping Network": { + "http://www.groceryshopping.net/": ["groceryshopping.net"] + } + }, + { + "GroovinAds": { + "http://www.groovinads.com/": ["groovinads.com"] + } + }, + { + "Gruner + Jahr": { + "http://www.guj.de/": ["guj.de", "ligatus.com"] + } + }, + { + "GumGum": { + "http://gumgum.com/": ["gumgum.com"] + } + }, + { + "Gunggo": { + "http://www.gunggo.com/": ["gunggo.com"] + } + }, + { + "Hands Mobile": { + "http://www.hands.com.br/": ["hands.com.br"] + } + }, + { + "Harrenmedia": { + "http://www.harrenmedia.com/": [ + "harrenmedia.com", + "harrenmedianetwork.com" + ] + } + }, + { + "HealthPricer": { + "http://www.healthpricer.com/": ["adacado.com", "healthpricer.com"] + } + }, + { + "Hearst": { + "http://www.hearst.com/": [ + "hearst.com", + "ic-live.com", + "iclive.com", + "icrossing.com", + "sptag.com", + "sptag1.com", + "sptag2.com", + "sptag3.com" + ] + } + }, + { + "HilltopAds": { + "https://hilltopads.com/": [ + "hilltopads.com", + "hilltopads.net", + "shoporielder.pro" + ] + } + }, + { + "Hi-media": { + "http://www.hi-media.com/": ["comclick.com", "hi-media.com"] + } + }, + { + "Horyzon Media": { + "http://www.horyzon-media.com/": ["horyzon-media.com"] + } + }, + { + "HotMart": { + "https://www.hotmart.com/en/": ["hotmart.com"] + } + }, + { + "HOTWords": { + "http://www.hotwords.com/": ["hotwords.com", "hotwords.es"] + } + }, + { + "HP": { + "http://www.hp.com/": ["hp.com", "optimost.com"] + } + }, + { + "Httpool": { + "http://www.httpool.com/": ["httpool.com"] + } + }, + { + "HUNT Mobile Ads": { + "http://www.huntmads.com/": ["huntmads.com"] + } + }, + { + "Hurra.com": { + "http://www.hurra.com/": ["hurra.com"] + } + }, + { + "IAB": { + "https://iabtechlab.com/": ["digitru.st"] + } + }, + { + "IAC": { + "http://www.iac.com/": ["iac.com", "iacadvertising.com"] + } + }, + { + "iBehavior": { + "http://www.i-behavior.com/": ["i-behavior.com", "ib-ibi.com"] + } + }, + { + "IBM": { + "http://www.ibm.com/": ["unica.com"] + } + }, + { + "ID5": { + "http://id5.io/": ["id5-sync.com"] + } + }, + { + "IDG": { + "http://www.idg.com/": ["idg.com", "idgtechnetwork.com"] + } + }, + { + "iEntry": { + "http://www.ientry.com/": ["600z.com", "ientry.com"] + } + }, + { + "IgnitAd": { + "http://www.ignitad.com/": ["ignitad.com"] + } + }, + { + "IgnitionOne": { + "http://www.ignitionone.com/": [ + "ignitionone.com", + "ignitionone.net", + "searchignite.com" + ] + } + }, + { + "Improve Digital": { + "www.improvedigital.com/": ["360yield.com", "improvedigital.com"] + } + }, + { + "Inadco": { + "http://www.inadco.com/": [ + "anadcoads.com", + "inadco.com", + "inadcoads.com" + ] + } + }, + { + "IndexExchange": { + "https://www.indexexchange.com": ["indexexchange.com"] + } + }, + { + "Infectious Media": { + "http://www.infectiousmedia.com/": [ + "impressiondesk.com", + "infectiousmedia.com" + ] + } + }, + { + "Inflection Point Media": { + "http://www.inflectionpointmedia.com/": ["inflectionpointmedia.com"] + } + }, + { + "Infogroup": { + "http://www.infogroup.com/": ["infogroup.com"] + } + }, + { + "Infolinks": { + "http://www.infolinks.com/": ["infolinks.com"] + } + }, + { + "Infra-Ad": { + "http://www.infra-ad.com/": ["infra-ad.com"] + } + }, + { + "InMobi": { + "http://www.inmobi.com/": [ + "aerserv.com", + "inmobi.com", + "sproutinc.com" + ] + } + }, + { + "inneractive": { + "http://inner-active.com/": ["inner-active.com"] + } + }, + { + "Innity": { + "http://innity.com/": ["innity.com"] + } + }, + { + "InsightExpress": { + "http://www.insightexpress.com/": [ + "insightexpress.com", + "insightexpressai.com" + ] + } + }, + { + "InSkin Media": { + "http://inskinmedia.com/": ["inskinmedia.com"] + } + }, + { + "Instinctive": { + "https://instinctive.io/": ["instinctive.io", "instinctiveads.com"] + } + }, + { + "Integral Ad Science": { + "https://integralads.com/": [ + "adsafemedia.com", + "adsafeprotected.com", + "iasds01.com", + "integralads.com" + ] + } + }, + { + "Intent Media": { + "http://www.intentmedia.com/": ["intentmedia.com", "intentmedia.net"] + } + }, + { + "Intergi": { + "http://intergi.com/": ["intergi.com"] + } + }, + { + "Intermarkets": { + "http://www.intermarkets.net/": ["intermarkets.net"] + } + }, + { + "Intermundo Media": { + "http://intermundomedia.com/": ["intermundomedia.com"] + } + }, + { + "Internet Brands": { + "http://www.internetbrands.com/": ["ibpxl.com", "internetbrands.com"] + } + }, + { + "Interpolls": { + "http://www.interpolls.com/": ["interpolls.com"] + } + }, + { + "Inuvo": { + "http://inuvo.com/": ["inuvo.com"] + } + }, + { + "InvestingChannel": { + "http://investingchannel.com/": ["investingchannel.com"] + } + }, + { + "IponWeb": { + "https://www.iponweb.com/": ["iponweb.com", "iponweb.net"] + } + }, + { + "iPROM": { + "http://www.iprom.si/": [ + "centraliprom.com", + "iprom.net", + "iprom.si", + "mediaiprom.com" + ] + } + }, + { + "iPromote": { + "http://www.ipromote.com/": ["ipromote.com"] + } + }, + { + "iProspect": { + "http://www.iprospect.com/": ["clickmanage.com", "iprospect.com"] + } + }, + { + "ISI Technologies": { + "http://digbro.com/": ["adversalservers.com", "digbro.com"] + } + }, + { + "ismatlab.com": { + "http://ismatlab.com": ["ismatlab.com"] + } + }, + { + "I.UA": { + "http://www.i.ua/": ["i.ua"] + } + }, + { + "Jaroop": { + "http://www.jaroop.com/": ["jaroop.com"] + } + }, + { + "JasperLabs": { + "http://www.jasperlabs.com/": ["jasperlabs.com"] + } + }, + { + "Jemm": { + "http://jemmgroup.com/": ["jemmgroup.com"] + } + }, + { + "Jink": { + "http://www.jink.de/": ["jink.de", "jinkads.com"] + } + }, + { + "Jirbo": { + "http://jirbo.com/": ["adcolony.com", "jirbo.com"] + } + }, + { + "Jivox": { + "http://www.jivox.com/": ["jivox.com"] + } + }, + { + "JobThread": { + "http://www.jobthread.com/": ["jobthread.com"] + } + }, + { + "JuicyAds": { + "http://www.juicyads.com/": ["juicyads.com"] + } + }, + { + "Jumptap": { + "http://www.jumptap.com/": ["jumptap.com"] + } + }, + { + "justuno": { + "https://www.justuno.com/": ["justuno.com"] + } + }, + { + "Kargo": { + "https://kargo.com/": ["kargo.com"] + } + }, + { + "Kenshoo": { + "http://www.kenshoo.com/": ["kenshoo.com", "xg4ken.com"] + } + }, + { + "Keyade": { + "http://www.keyade.com/": ["keyade.com"] + } + }, + { + "Keywee": { + "https://keywee.co": ["keywee.co"] + } + }, + { + "KissMyAds": { + "http://kissmyads.com/": ["kissmyads.com"] + } + }, + { + "Kitara Media": { + "http://www.kitaramedia.com/": ["103092804.com", "kitaramedia.com"] + } + }, + { + "KIT digital": { + "http://kitd.com/": ["keewurd.com", "kitd.com", "peerset.com"] + } + }, + { + "Kokteyl": { + "http://www.kokteyl.com/": ["admost.com", "kokteyl.com"] + } + }, + { + "Komli": { + "http://www.komli.com/": ["komli.com"] + } + }, + { + "Kontera": { + "http://www.kontera.com/": ["kontera.com"] + } + }, + { + "Korrelate": { + "http://korrelate.com/": [ + "adsummos.com", + "adsummos.net", + "korrelate.com" + ] + } + }, + { + "Krux": { + "http://www.krux.com/": ["krux.com", "kruxdigital.com", "krxd.net"] + } + }, + { + "Lakana": { + "http://www.lakana.com/": ["ibsys.com", "lakana.com"] + } + }, + { + "Layer-Ad.org": { + "http://layer-ad.org/": ["layer-ad.org"] + } + }, + { + "Layer Ads": { + "http://layer-ads.net/": ["layer-ads.net"] + } + }, + { + "LeadBolt": { + "http://www.leadbolt.com/": ["leadbolt.com"] + } + }, + { + "LeadFormix": { + "http://www.leadformix.com/": ["leadforce1.com", "leadformix.com"] + } + }, + { + "LeanPlum": { + "https://www.leanplum.com/": ["leanplum.com"] + } + }, + { + "Legolas Media": { + "http://www.legolas-media.com/": ["legolas-media.com"] + } + }, + { + "Levexis": { + "http://www.levexis.com/": ["levexis.com"] + } + }, + { + "Lexos Media": { + "http://www.lexosmedia.com/": ["adbull.com", "lexosmedia.com"] + } + }, + { + "LifeStreet": { + "http://lifestreetmedia.com/": [ + "lfstmedia.com", + "lifestreetmedia.com" + ] + } + }, + { + "LinkConnector": { + "http://www.linkconnector.com/": ["linkconnector.com"] + } + }, + { + "LinkShare": { + "http://www.linkshare.com/": ["linkshare.com", "linksynergy.com"] + } + }, + { + "Linkz": { + "http://www.linkz.net/": ["linkz.net"] + } + }, + { + "Listrak": { + "http://www.listrak.com/": ["listrak.com", "listrakbi.com"] + } + }, + { + "LiveIntent": { + "http://www.liveintent.com/": ["liadm.com", "liveintent.com"] + } + }, + { + "LiveInternet": { + "http://www.liveinternet.ru": ["liveinternet.ru", "yadro.ru"] + } + }, + { + "LiveRamp": { + "https://liveramp.com/": ["liveramp.com", "tvpixel.com"] + } + }, + { + "LKQD": { + "http://lkqd.com": ["lkqd.com", "lkqd.net"] + } + }, + { + "Local Yokel Media": { + "http://www.localyokelmedia.com/": ["localyokelmedia.com"] + } + }, + { + "Localytics": { + "https://www.localytics.com/": ["localytics.com"] + } + }, + { + "LockerDome": { + "https://lockerdome.com/": ["lockerdome.com"] + } + }, + { + "Longboard Media": { + "http://longboardmedia.com/": ["longboardmedia.com"] + } + }, + { + "Loomia": { + "http://www.loomia.com/": ["loomia.com"] + } + }, + { + "LoopFuse": { + "https://www.loopfuse.net/": ["lfov.net", "loopfuse.net"] + } + }, + { + "LoopMe": { + "https://loopme.com/": ["loopme.com"] + } + }, + { + "LotLinx": { + "https://www.lotlinx.com": ["lotlinx.com"] + } + }, + { + "Lower My Bills": { + "http://lowermybills.com": ["lowermybills.com"] + } + }, + { + "lptracker": { + "https://lptracker.io/": ["lptracker.io"] + } + }, + { + "LucidMedia": { + "http://www.lucidmedia.com/": ["lucidmedia.com"] + } + }, + { + "m6d": { + "http://m6d.com/": ["m6d.com", "media6degrees.com"] + } + }, + { + "Madhouse": { + "http://www.madhouse.cn/": ["madhouse.cn"] + } + }, + { + "Madison Logic": { + "http://www.madisonlogic.com/": ["dinclinx.com", "madisonlogic.com"] + } + }, + { + "madvertise": { + "http://madvertise.com/": ["madvertise.com"] + } + }, + { + "Magnetic": { + "http://www.magnetic.com/": [ + "domdex.com", + "domdex.net", + "magnetic.com", + "qjex.net" + ] + } + }, + { + "Magnify360": { + "http://www.magnify360.com/": ["dialogmgr.com", "magnify360.com"] + } + }, + { + "MailChimp": { + "http://mailchimp.com/": [ + "campaign-archive1.com", + "list-manage.com", + "mailchimp.com" + ] + } + }, + { + "Manifest": { + "http://www.manifest.ru/": ["bannerbank.ru", "manifest.ru"] + } + }, + { + "Marchex": { + "http://www.marchex.com/": ["industrybrains.com", "marchex.com"] + } + }, + { + "Marimedia": { + "http://www.marimedia.net/": ["marimedia.net"] + } + }, + { + "MarketGid": { + "http://www.marketgid.com/": ["dt00.net", "dt07.net", "marketgid.com"] + } + }, + { + "Marketo": { + "http://www.marketo.com/": ["marketo.com", "marketo.net"] + } + }, + { + "Martini Media": { + "http://martinimedianetwork.com/": [ + "martiniadnetwork.com", + "martinimedianetwork.com" + ] + } + }, + { + "mashero": { + "http://www.mashero.com/": ["mashero.com"] + } + }, + { + "Match.com": { + "http://www.match.com/": [ + "chemistry.com", + "match.com", + "meetic-partners.com" + ] + } + }, + { + "Matomy": { + "http://www.matomy.com/": [ + "adnetinteractive.com", + "adsmarket.com", + "matomy.com", + "matomymarket.com", + "matomymedia.com", + "mediawhiz.com", + "optimatic.com", + "xtendmedia.com" + ] + } + }, + { + "MaxBounty": { + "http://www.maxbounty.com/": ["maxbounty.com", "mb01.com"] + } + }, + { + "MaxPoint": { + "http://maxpointinteractive.com/": [ + "maxpointinteractive.com", + "maxusglobal.com", + "mxptint.net" + ] + } + }, + { + "MdotM": { + "http://mdotm.com/": ["mdotm.com"] + } + }, + { + "MediaBrix": { + "http://www.mediabrix.com/": ["mediabrix.com"] + } + }, + { + "MediaCom": { + "http://www.mediacom.com/": ["mediacom.com"] + } + }, + { + "mediaFORGE": { + "http://www.mediaforge.com/": ["mediaforge.com"] + } + }, + { + "Medialets": { + "http://www.medialets.com/": ["medialets.com"] + } + }, + { + "MediaMath": { + "http://www.mediamath.com/": [ + "adroitinteractive.com", + "designbloxlive.com", + "mathtag.com", + "mediamath.com" + ] + } + }, + { + "media.net": { + "http://www.media.net/": ["media.net"] + } + }, + { + "Mediaocean": { + "http://www.mediaocean.com/": ["adbuyer.com", "mediaocean.com"] + } + }, + { + "MediaShakers": { + "http://www.mediashakers.com/": [ + "media-servers.net", + "mediashakers.com" + ] + } + }, + { + "MediaTrust": { + "http://www.mediatrust.com/": ["mediatrust.com"] + } + }, + { + "Medicx Media Solutions": { + "http://www.medicxmedia.com/": ["medicxmedia.com"] + } + }, + { + "MegaIndex": { + "http://www.megaindex.ru/": ["megaindex.ru"] + } + }, + { + "Mercent": { + "http://www.mercent.com/": ["mercent.com"] + } + }, + { + "MerchantAdvantage": { + "http://www.merchantadvantage.com/": ["merchantadvantage.com"] + } + }, + { + "Merchenta": { + "http://www.merchenta.com/": ["merchenta.com"] + } + }, + { + "Merkle": { + "https://www.merkleinc.com/": ["rimmkaufman.com", "rkdms.com"] + } + }, + { + "Meta Network": { + "http://www.metanetwork.com/": ["metanetwork.com"] + } + }, + { + "Meteor": { + "http://www.meteorsolutions.com/": ["meteorsolutions.com"] + } + }, + { + "MetrixLab": { + "https://www.metrixlab.com": [ + "adoftheyear.com", + "crm-metrix.com", + "customerconversio.com", + "metrixlab.com", + "opinionbar.com" + ] + } + }, + { + "MicroAd": { + "http://www.microad.jp/": ["microad.jp"] + } + }, + { + "Microsoft": { + "http://www.microsoft.com/": [ + "adbureau.net", + "adecn.com", + "aquantive.com", + "msads.net", + "netconversions.com", + "roiservice.com" + ] + } + }, + { + "Millennial Media": { + "http://www.millennialmedia.com/": [ + "decktrade.com", + "millennialmedia.com", + "mydas.mobi" + ] + } + }, + { + "Mindset Media": { + "http://www.mindset-media.com/": ["mindset-media.com", "mmismm.com"] + } + }, + { + "Mirando": { + "http://www.mirando.de/": ["mirando.de"] + } + }, + { + "Mixpo": { + "http://www.mixpo.com/": ["mixpo.com"] + } + }, + { + "Moat": { + "http://www.moat.com/": ["moat.com", "moatads.com"] + } + }, + { + "MobFox": { + "http://www.mobfox.com/": ["mobfox.com"] + } + }, + { + "Mobials": { + "http://mobials.com": ["mobials.com"] + } + }, + { + "MobileAdTrading": { + "https://mobileadtrading.com/": ["mobileadtrading.com"] + } + }, + { + "Mobile Meteor": { + "http://mobilemeteor.com/": ["mobilemeteor.com", "showmeinn.com"] + } + }, + { + "Mobile Storm": { + "http://mobilestorm.com/": ["mobilestorm.com"] + } + }, + { + "MobVision": { + "http://www.mobvision.com/": ["admoda.com", "mobvision.com"] + } + }, + { + "Mocean Mobile": { + "http://www.moceanmobile.com/": ["moceanmobile.com"] + } + }, + { + "Mochila": { + "http://www.mochila.com/": ["mochila.com"] + } + }, + { + "Mojiva": { + "http://www.mojiva.com/": ["mojiva.com"] + } + }, + { + "Monetate": { + "http://monetate.com/": ["monetate.com", "monetate.net"] + } + }, + { + "MONETIZEdigital": { + "https://www.cpalead.com/": ["cpalead.com"] + } + }, + { + "Monetize More": { + "http://monetizemore.com/": ["monetizemore.com"] + } + }, + { + "Monoloop": { + "http://www.monoloop.com/": ["monoloop.com"] + } + }, + { + "Monster": { + "http://www.monster.com/": ["monster.com"] + } + }, + { + "Moolah Media": { + "http://www.moolahmedia.com/": ["moolah-media.com", "moolahmedia.com"] + } + }, + { + "MoPub": { + "http://www.mopub.com/": ["mopub.com"] + } + }, + { + "MovieLush.com": { + "https://www.movielush.com/": ["affbuzzads.com", "movielush.com"] + } + }, + { + "Multiple Stream Media": { + "http://www.multiplestreammktg.com/": [ + "adclickmedia.com", + "multiplestreammktg.com" + ] + } + }, + { + "MUNDO Media": { + "http://www.mundomedia.com/": ["mundomedia.com", "silver-path.com"] + } + }, + { + "MyCounter": { + "http://mycounter.com.ua/": ["mycounter.com.ua"] + } + }, + { + "MyPressPlus": { + "http://www.mypressplus.com/": ["mypressplus.com", "ppjol.net"] + } + }, + { + "myThings": { + "http://www.mythings.com/": ["mythings.com", "mythingsmedia.com"] + } + }, + { + "MyWebGrocer": { + "http://www.mywebgrocer.com/": ["mywebgrocer.com"] + } + }, + { + "Nanigans": { + "http://www.nanigans.com/": ["nanigans.com"] + } + }, + { + "NativeAds": { + "https://nativeads.com/": ["nativeads.com"] + } + }, + { + "Nativo": { + "http://www.nativo.net/": ["postrelease.com"] + } + }, + { + "Navegg": { + "http://www.navegg.com/": ["navdmp.com", "navegg.com"] + } + }, + { + "NetAffiliation": { + "http://www.netaffiliation.com/": ["netaffiliation.com"] + } + }, + { + "NetBina": { + "http://www.netbina.com/": ["netbina.com"] + } + }, + { + "NetElixir": { + "http://www.netelixir.com/": ["adelixir.com", "netelixir.com"] + } + }, + { + "Netmining": { + "http://www.netmining.com/": ["netmining.com", "netmng.com"] + } + }, + { + "Net-Results": { + "http://www.net-results.com/": [ + "cdnma.com", + "net-results.com", + "nr7.us" + ] + } + }, + { + "NetSeer": { + "http://www.netseer.com/": ["netseer.com"] + } + }, + { + "NetShelter": { + "http://netshelter.com/": ["netshelter.com", "netshelter.net"] + } + }, + { + "Neustar": { + "http://www.neustar.biz/": ["adadvisor.net", "neustar.biz"] + } + }, + { + "newtention": { + "http://newtention.de/": [ + "newtention.de", + "newtention.net", + "newtentionassets.net" + ] + } + }, + { + "Nexage": { + "http://nexage.com/": ["nexage.com"] + } + }, + { + "Nextag": { + "http://www.nextag.com/": ["nextag.com"] + } + }, + { + "NextPerformance": { + "http://www.nextperformance.com/": [ + "nextperformance.com", + "nxtck.com" + ] + } + }, + { + "Nielsen": { + "http://www.nielsen.com/": ["imrworldwide.com", "imrworldwide.net"] + } + }, + { + "Ninua": { + "http://www.ninua.com/": ["networkedblogs.com", "ninua.com"] + } + }, + { + "Nokta": { + "http://www.noktamedya.com/": ["noktamedya.com", "virgul.com"] + } + }, + { + "NowSpots": { + "http://nowspots.com/": ["nowspots.com"] + } + }, + { + "nrelate": { + "http://nrelate.com/": ["nrelate.com"] + } + }, + { + "Nuffnang": { + "http://www.nuffnang.com.my/": ["nuffnang.com", "nuffnang.com.my"] + } + }, + { + "nugg.ad": { + "http://www.nugg.ad/": ["nugg.ad", "nuggad.net"] + } + }, + { + "Ohana Media": { + "http://www.ohana-media.com/": [ + "adohana.com", + "ohana-media.com", + "ohanaqb.com" + ] + } + }, + { + "Omnicom Group": { + "http://www.omnicomgroup.com/": [ + "accuenmedia.com", + "omnicomgroup.com", + "p-td.com" + ] + } + }, + { + "onAd": { + "http://www.onad.eu/": ["onad.eu"] + } + }, + { + "Onclusive": { + "https://onclusive.com/": ["airpr.com"] + } + }, + { + "OneAd": { + "https://www.onead.com.tw/": [ + "guoshipartners.com", + "onevision.com.tw" + ] + } + }, + { + "One iota": { + "http://www.itsoneiota.com/": ["itsoneiota.com", "oneiota.co.uk"] + } + }, + { + "Oneupweb": { + "http://www.oneupweb.com/": ["oneupweb.com", "sodoit.com"] + } + }, + { + "OnlineMetrix": { + "http://h.online-metrix.net": ["online-metrix.net"] + } + }, + { + "Open New Media": { + "http://www.onm.de/": ["onm.de"] + } + }, + { + "OpenX": { + "http://openx.com/": [ + "liftdna.com", + "openx.com", + "openx.net", + "openx.org", + "openxenterprise.com", + "servedbyopenx.com" + ] + } + }, + { + "Opera": { + "http://www.opera.com/": [ + "mobiletheory.com", + "opera.com", + "operamediaworks.com", + "operasoftware.com" + ] + } + }, + { + "OPT": { + "http://www.opt.ne.jp/": ["advg.jp", "opt.ne.jp", "p-advg.com"] + } + }, + { + "Optify": { + "http://www.optify.net/": ["optify.net"] + } + }, + { + "Optimal": { + "http://optim.al/": [ + "cpmadvisors.com", + "cpmatic.com", + "nprove.com", + "optim.al", + "orbengine.com", + "xa.net" + ] + } + }, + { + "OptimumResponse": { + "http://www.optimumresponse.com/": ["optimumresponse.com"] + } + }, + { + "OptinMonster": { + "https://optinmonster.com/": ["optinmonster.com", "optnmstr.com"] + } + }, + { + "OptMD": { + "http://optmd.com/": ["optmd.com"] + } + }, + { + "Oracle": { + "http://www.oracle.com/": ["estara.com"] + } + }, + { + "OrangeSoda": { + "http://www.orangesoda.com/": ["orangesoda.com", "otracking.com"] + } + }, + { + "Outbrain": { + "http://www.outbrain.com/": [ + "outbrain.com", + "sphere.com", + "visualrevenue.com" + ] + } + }, + { + "Out There Media": { + "http://www.out-there-media.com/": ["out-there-media.com"] + } + }, + { + "Oversee.net": { + "http://www.oversee.net/": ["dsnextgen.com", "oversee.net"] + } + }, + { + "OwnerIQ": { + "http://www.owneriq.com/": ["owneriq.com", "owneriq.net"] + } + }, + { + "OxaMedia": { + "http://www.oxamedia.com/": [ + "adconnexa.com", + "adsbwm.com", + "oxamedia.com" + ] + } + }, + { + "PageFair": { + "https://pagefair.com/": ["pagefair.com", "pagefair.net"] + } + }, + { + "Paid-To-Promote.net": { + "http://www.paid-to-promote.net/": ["paid-to-promote.net"] + } + }, + { + "Pardot": { + "http://www.pardot.com/": ["pardot.com"] + } + }, + { + "PayHit": { + "http://www.payhit.com/": ["payhit.com"] + } + }, + { + "Paypopup.com": { + "http://www.paypopup.com/": ["lzjl.com", "paypopup.com"] + } + }, + { + "PebblePost": { + "https://www.pebblepost.com/": ["pbbl.co"] + } + }, + { + "Peer39": { + "http://www.peer39.com/": ["peer39.com", "peer39.net"] + } + }, + { + "PeerFly": { + "http://peerfly.com/": ["peerfly.com"] + } + }, + { + "Performancing": { + "http://performancing.com/": ["performancing.com"] + } + }, + { + "PerimeterX": { + "https://www.perimeterx.com": ["perimeterx.net"] + } + }, + { + "Pheedo": { + "http://site.pheedo.com/": ["pheedo.com"] + } + }, + { + "Pictela": { + "http://www.pictela.com/": ["pictela.com", "pictela.net"] + } + }, + { + "PinPoll": { + "https://pinpoll.com/": ["pinpoll.com"] + } + }, + { + "Pixel.sg": { + "http://www.pixel.sg/": ["pixel.sg"] + } + }, + { + "Piximedia": { + "http://www.piximedia.com/": ["piximedia.com"] + } + }, + { + "Pixlee": { + "https://www.pixlee.com/": ["pixlee.com"] + } + }, + { + "PLATFORM ONE": { + "http://www.platform-one.co.jp/": ["platform-one.co.jp"] + } + }, + { + "plista": { + "http://www.plista.com/": ["plista.com"] + } + }, + { + "PocketCents": { + "http://pocketcents.com/": ["pocketcents.com"] + } + }, + { + "Polar Mobile": { + "http://polarmobile.com": ["mediavoice.com", "polarmobile.com"] + } + }, + { + "Politads": { + "http://politads.com/": ["politads.com"] + } + }, + { + "Polymorph": { + "http://getpolymorph.com/": ["adsnative.com", "getpolymorph.com"] + } + }, + { + "Pontiflex": { + "http://www.pontiflex.com/": ["pontiflex.com"] + } + }, + { + "PopAds": { + "https://www.popads.net/": ["popads.net", "popadscdn.net"] + } + }, + { + "PopRule": { + "http://poprule.com/": ["gocampaignlive.com", "poprule.com"] + } + }, + { + "Popunder.ru": { + "http://popunder.ru/": ["popunder.ru"] + } + }, + { + "Po.st": { + "http://www.po.st/": ["po.st"] + } + }, + { + "Powerlinks": { + "https://www.powerlinks.com/": ["powerlinks.com"] + } + }, + { + "PPCProtect": { + "https://ppcprotect.com": ["ppcprotect.com"] + } + }, + { + "PrecisionClick": { + "http://www.precisionclick.com/": ["precisionclick.com"] + } + }, + { + "PredictAd": { + "http://www.predictad.com/": ["predictad.com"] + } + }, + { + "Pressflex": { + "http://www.pressflex.com/": ["blogads.com", "pressflex.com"] + } + }, + { + "Prime Visibility": { + "http://www.primevisibility.com/": [ + "adcde.com", + "addlvr.com", + "adonnetwork.com", + "adonnetwork.net", + "adtrgt.com", + "bannertgt.com", + "cptgt.com", + "cpvfeed.com", + "cpvtgt.com", + "dashboardad.net", + "popcde.com", + "primevisibility.com", + "sdfje.com", + "urtbk.com" + ] + } + }, + { + "Primis": { + "https://www.primis.tech": ["sekindo.com"] + } + }, + { + "PrismApp": { + "https://www.prismapp.io/": ["prismapp.io"] + } + }, + { + "Proclivity": { + "http://www.proclivitymedia.com/": [ + "proclivitymedia.com", + "proclivitysystems.com", + "pswec.com" + ] + } + }, + { + "Project Wonderful": { + "http://www.projectwonderful.com/": ["projectwonderful.com"] + } + }, + { + "PrometheusIntelligenceTechnology": { + "https://prometheusintelligencetechnology.com/": [ + "prometheusintelligencetechnology.com" + ] + } + }, + { + "Propeller Ads": { + "http://propellerads.com/": ["propellerads.com"] + } + }, + { + "Prosperent": { + "http://prosperent.com/": ["prosperent.com"] + } + }, + { + "Protected Media": { + "http://www.protected.media/": ["ad-score.com", "protected.media"] + } + }, + { + "Provers": { + "http://provers.pro": ["provers.pro"] + } + }, + { + "Psonstrentie": { + "http://psonstrentie.info": ["psonstrentie.info"] + } + }, + { + "Public-Idées": { + "http://www.publicidees.com/": ["publicidees.com"] + } + }, + { + "Publishers Clearing House": { + "http://www.pch.com/": ["pch.com"] + } + }, + { + "PubMatic": { + "http://www.pubmatic.com/": ["pubmatic.com", "revinet.com"] + } + }, + { + "PulsePoint": { + "https://www.pulsepoint.com/": ["pulsepoint.com"] + } + }, + { + "quadrantOne": { + "http://www.quadrantone.com/": ["quadrantone.com"] + } + }, + { + "Quake Marketing": { + "http://quakemarketing.com/": ["quakemarketing.com"] + } + }, + { + "Quantcast": { + "http://www.quantcast.com/": [ + "quantcast.com", + "quantcount.com", + "quantserve.com" + ] + } + }, + { + "QuantumAdvertising": { + "http://quantum-advertising.com": ["quantum-advertising.com"] + } + }, + { + "QuinStreet": { + "http://quinstreet.com/": [ + "qnsr.com", + "qsstats.com", + "quinstreet.com" + ] + } + }, + { + "QUISMA": { + "https://quisma.com/": [ + "iaded.com", + "quisma.com", + "quismatch.com", + "xaded.com", + "xmladed.com" + ] + } + }, + { + "Radial": { + "https://www.radial.com": ["gsicommerce.com", "gsimedia.net"] + } + }, + { + "Radiate Media": { + "http://www.radiatemedia.com/": ["matchbin.com", "radiatemedia.com"] + } + }, + { + "RadiumOne": { + "http://www.radiumone.com/": ["gwallet.com", "radiumone.com"] + } + }, + { + "Radius Marketing": { + "http://www.radiusmarketing.com/": ["radiusmarketing.com"] + } + }, + { + "Rambler": { + "http://www.rambler.ru/": ["rambler.ru"] + } + }, + { + "Rapleaf": { + "http://www.rapleaf.com/": ["rapleaf.com", "rlcdn.com"] + } + }, + { + "ReachLocal": { + "http://www.reachlocal.com/": ["reachlocal.com", "rlcdn.net"] + } + }, + { + "React2Media": { + "http://www.react2media.com/": ["react2media.com"] + } + }, + { + "Redux Media": { + "http://reduxmedia.com/": ["reduxmedia.com"] + } + }, + { + "Rekko": { + "http://rekko.com/": ["convertglobal.com", "rekko.com"] + } + }, + { + "Reklamport": { + "http://www.reklamport.com/": ["reklamport.com"] + } + }, + { + "Reklam Store": { + "http://reklamstore.com/": ["reklamstore.com"] + } + }, + { + "Reklamz": { + "http://www.reklamz.com/": ["reklamz.com"] + } + }, + { + "Relevad": { + "http://www.relevad.com/": ["relestar.com", "relevad.com"] + } + }, + { + "Renegade Internet": { + "http://www.renegadeinternet.com/": [ + "advertserve.com", + "renegadeinternet.com" + ] + } + }, + { + "Reporo": { + "http://www.reporo.com/": ["buzzcity.com"] + } + }, + { + "ResolutionMedia": { + "https://nonstoppartner.net/": ["nonstoppartner.net"] + } + }, + { + "Resolution Media": { + "http://resolutionmedia.com/": ["resolutionmedia.com"] + } + }, + { + "Resonate": { + "http://www.resonateinsights.com/": [ + "reson8.com", + "resonateinsights.com", + "resonatenetworks.com" + ] + } + }, + { + "Responsys": { + "http://www.responsys.com/": ["responsys.com"] + } + }, + { + "ReTargeter": { + "http://www.retargeter.com/": ["retargeter.com"] + } + }, + { + "Retirement Living": { + "www.retirement-living.com/": [ + "blvdstatus.com", + "retirement-living.com" + ] + } + }, + { + "RevContent": { + "http://revcontent.com/": ["revcontent.com"] + } + }, + { + "RevenueMax": { + "http://revenuemax.de/": ["revenuemax.de"] + } + }, + { + "Rhythm": { + "http://rhythmnewmedia.com/": [ + "1rx.io", + "rhythmnewmedia.com", + "rhythmone.com", + "rhythmxchange.com", + "rnmd.net" + ] + } + }, + { + "RichAudience": { + "https://richaudience.com/": ["richaudience.com"] + } + }, + { + "RichRelevance": { + "http://www.richrelevance.com/": ["richrelevance.com"] + } + }, + { + "RightAction": { + "http://rightaction.com/": ["rightaction.com"] + } + }, + { + "RMBN": { + "http://rmbn.net/": ["rmbn.net", "rmbn.ru"] + } + }, + { + "RMM": { + "http://www.rmmonline.com/": ["rmmonline.com"] + } + }, + { + "Rocket Fuel": { + "http://rocketfuel.com/": [ + "rfihub.com", + "rfihub.net", + "rocketfuel.com", + "ru4.com", + "xplusone.com" + ] + } + }, + { + "Rovion": { + "http://www.rovion.com/": ["rovion.com"] + } + }, + { + "rtk": { + "http://rtk.io/": ["rtk.io"] + } + }, + { + "RubiconProject": { + "http://rubiconproject.com/": [ + "adsbyisocket.com", + "isocket.com", + "rubiconproject.com" + ] + } + }, + { + "RunAds": { + "http://www.runads.com/": ["runads.com", "rundsp.com"] + } + }, + { + "RuTarget": { + "http://www.rutarget.ru/": ["rutarget.ru"] + } + }, + { + "Sabavision": { + "http://www.sabavision.com": ["sabavision.com"] + } + }, + { + "Sabre": { + "http://www.sabre.com/": [ + "reztrack.com", + "sabre.com", + "sabrehospitality.com" + ] + } + }, + { + "Salesforce.com": { + "http://www.salesforce.com/": ["salesforce.com"] + } + }, + { + "Samurai Factory": { + "http://www.samurai-factory.jp/": ["samurai-factory.jp", "shinobi.jp"] + } + }, + { + "SAP": { + "https://www.sap.com": ["seewhy.com"] + } + }, + { + "Sapient": { + "http://www.sapient.com/": ["bridgetrack.com", "sapient.com"] + } + }, + { + "SAS": { + "http://www.sas.com/": ["aimatch.com", "sas.com"] + } + }, + { + "Scandinavian AdNetworks": { + "http://www.scandinavianadnetworks.com/": [ + "scandinavianadnetworks.com" + ] + } + }, + { + "Scribol": { + "http://scribol.com/": ["scribol.com"] + } + }, + { + "SearchForce": { + "http://www.searchforce.com/": ["searchforce.com", "searchforce.net"] + } + }, + { + "Seevast": { + "http://www.seevast.com/": [ + "kanoodle.com", + "pulse360.com", + "seevast.com", + "syndigonetworks.com" + ] + } + }, + { + "Selectable Media": { + "http://selectablemedia.com/": ["nabbr.com", "selectablemedia.com"] + } + }, + { + "Semantiqo": { + "http://semantiqo.com/": ["semantiqo.com"] + } + }, + { + "Semasio": { + "http://www.semasio.com/": ["semasio.com", "semasio.net"] + } + }, + { + "SevenAds": { + "http://www.sevenads.net/": ["sevenads.net"] + } + }, + { + "SexInYourCity": { + "http://www.sexinyourcity.com/": ["sexinyourcity.com"] + } + }, + { + "ShaftTraffic": { + "https://shafttraffic.com": ["libertystmedia.com"] + } + }, + { + "ShareASale": { + "http://www.shareasale.com/": ["shareasale.com"] + } + }, + { + "Sharethrough": { + "http://sharethrough.com/": ["sharethrough.com"] + } + }, + { + "Shopzilla": { + "http://www.shopzilla.com/": ["shopzilla.com"] + } + }, + { + "Shortest": { + "http://shorte.st/": ["shorte.st"] + } + }, + { + "Silverpop": { + "http://www.silverpop.com/": [ + "mkt51.net", + "pages05.net", + "silverpop.com", + "vtrenz.net" + ] + } + }, + { + "Simpli.fi": { + "http://www.simpli.fi/": ["simpli.fi"] + } + }, + { + "SiteScout": { + "http://www.sitescout.com/": ["sitescout.com"] + } + }, + { + "Skimlinks": { + "http://skimlinks.com/": ["skimlinks.com", "skimresources.com"] + } + }, + { + "Skupe Net": { + "http://www.skupenet.com/": ["adcentriconline.com", "skupenet.com"] + } + }, + { + "Smaato": { + "http://www.smaato.com/": ["smaato.com"] + } + }, + { + "SmartAdServer": { + "http://smartadserver.com/": ["smartadserver.com"] + } + }, + { + "SmartyAds": { + "https://smartyads.com/": ["smartyads.com"] + } + }, + { + "Smiley Media": { + "http://www.smileymedia.com/": ["smileymedia.com"] + } + }, + { + "Smowtion": { + "http://smowtion.com/": ["smowtion.com"] + } + }, + { + "Snap": { + "http://www.snap.com/": ["snap.com"] + } + }, + { + "SocialChorus": { + "http://www.socialchorus.com/": [ + "halogenmediagroup.com", + "halogennetwork.com", + "socialchorus.com" + ] + } + }, + { + "SocialInterface": { + "http://socialinterface.com/": [ + "ratevoice.com", + "socialinterface.com" + ] + } + }, + { + "SocialTwist": { + "http://tellafriend.socialtwist.com/": ["socialtwist.com"] + } + }, + { + "sociomantic labs": { + "http://www.sociomantic.com/": ["sociomantic.com"] + } + }, + { + "Socital": { + "https://www.socital.com": ["socital.com"] + } + }, + { + "Sojern": { + "https://www.sojern.com": ["sojern.com"] + } + }, + { + "SomoAudience": { + "https://somoaudience.com/": ["somoaudience.com"] + } + }, + { + "Sonobi": { + "http://sonobi.com/": ["sonobi.com"] + } + }, + { + "sophus3": { + "http://www.sophus3.com/": ["sophus3.co.uk", "sophus3.com"] + } + }, + { + "Sortable": { + "https://www.sortable.com/": ["deployads.com"] + } + }, + { + "Sovrn": { + "https://www.sovrn.com/": ["sovrn.com"] + } + }, + { + "Space Chimp Media": { + "http://spacechimpmedia.com/": ["spacechimpmedia.com"] + } + }, + { + "Sparklit": { + "http://www.sparklit.com/": ["adbutler.com", "sparklit.com"] + } + }, + { + "Spark Studios": { + "http://www.sparkstudios.com/": ["sparkstudios.com"] + } + }, + { + "Specific Media": { + "http://www.specificmedia.com/": [ + "adviva.co.uk", + "adviva.net", + "sitemeter.com", + "specificclick.net", + "specificmedia.co.uk", + "specificmedia.com" + ] + } + }, + { + "Spectate": { + "http://spectate.com/": ["spectate.com"] + } + }, + { + "Sponge": { + "http://spongegroup.com/": ["spongegroup.com"] + } + }, + { + "Spongecell": { + "http://www.spongecell.com/": ["spongecell.com"] + } + }, + { + "SponsorAds": { + "http://www.sponsorads.de/": ["sponsorads.de"] + } + }, + { + "Spot200": { + "http://spot200.com/": ["spot200.com"] + } + }, + { + "SpotX": { + "https://www.spotx.tv": ["spotx.tv"] + } + }, + { + "SpotXchange": { + "http://www.spotxchange.com/": ["spotxchange.com"] + } + }, + { + "SpringServe": { + "https://springserve.com/": ["springserve.com"] + } + }, + { + "StackAdapt": { + "https://www.stackadapt.com/": ["stackadapt.com"] + } + }, + { + "StarGames": { + "https://www.stargames.net/": ["stargamesaffiliate.com"] + } + }, + { + "SteelHouse": { + "http://www.steelhouse.com/": [ + "steelhouse.com", + "steelhousemedia.com" + ] + } + }, + { + "Storygize": { + "http://www.storygize.com/": ["storygize.com", "storygize.net"] + } + }, + { + "Streamray": { + "http://streamray.com/": ["cams.com", "streamray.com"] + } + }, + { + "StrikeAd": { + "http://www.strikead.com/": ["strikead.com"] + } + }, + { + "StrongMail": { + "http://www.strongmail.com/": ["popularmedia.com"] + } + }, + { + "Struq": { + "http://struq.com/": ["struq.com"] + } + }, + { + "Sublime Skinz": { + "http://sublime.xyz/": ["ayads.co", "sublime.xyz"] + } + }, + { + "Suite 66": { + "http://www.suite66.com/": ["suite66.com"] + } + }, + { + "Summit": { + "http://www.summit.co.uk/": ["summitmedia.co.uk"] + } + }, + { + "Superfish": { + "http://www.superfish.com/": ["superfish.com"] + } + }, + { + "SupersonicAds": { + "http://www.supersonicads.com/": ["supersonicads.com"] + } + }, + { + "Survata": { + "https://www.survata.com/": ["survata.com"] + } + }, + { + "Switch": { + "http://www.switchconcepts.com/": [ + "ethicalads.net", + "switchadhub.com", + "switchconcepts.co.uk", + "switchconcepts.com" + ] + } + }, + { + "Swoop": { + "http://swoop.com/": ["swoop.com"] + } + }, + { + "SymphonyAM": { + "http://www.factortg.com/": ["factortg.com"] + } + }, + { + "Syncapse": { + "http://www.syncapse.com/": ["clickable.net", "syncapse.com"] + } + }, + { + "Syrup Ad": { + "http://adotsolution.com/": ["adotsolution.com"] + } + }, + { + "Taboola": { + "https://www.taboola.com/": ["perfectmarket.com", "taboola.com"] + } + }, + { + "Tailsweep": { + "http://www.tailsweep.com/": ["tailsweep.com"] + } + }, + { + "Taleria": { + "https://outstream.telaria.com/": ["freeskreen.com"] + } + }, + { + "Tapad": { + "http://www.tapad.com/": ["tapad.com"] + } + }, + { + "Tapgage": { + "http://www.tapgage.com/": ["bizmey.com", "tapgage.com"] + } + }, + { + "TapIt!": { + "http://tapit.com/": ["tapit.com"] + } + }, + { + "Tap.me": { + "http://tap.me/": ["tap.me"] + } + }, + { + "Targetix": { + "http://targetix.net/": ["targetix.net"] + } + }, + { + "Tatto Media": { + "http://tattomedia.com/": ["quicknoodles.com", "tattomedia.com"] + } + }, + { + "Teadma": { + "http://www.teadma.com/": ["teadma.com"] + } + }, + { + "Teads.tv": { + "http://teads.tv/": ["ebuzzing.com", "teads.tv"] + } + }, + { + "Technorati": { + "http://technorati.com/": ["technorati.com", "technoratimedia.com"] + } + }, + { + "TellApart": { + "http://tellapart.com/": ["tellapart.com", "tellapt.com"] + } + }, + { + "Telstra": { + "http://www.telstra.com.au/": [ + "sensis.com.au", + "sensisdata.com.au", + "sensisdigitalmedia.com.au", + "telstra.com.au" + ] + } + }, + { + "Terra": { + "http://www.terra.com.br/": ["eztargetmedia.com", "terra.com.br"] + } + }, + { + "The Numa Group": { + "http://www.thenumagroup.com/": ["hittail.com", "thenumagroup.com"] + } + }, + { + "The Search Agency": { + "http://www.thesearchagency.com/": [ + "thesearchagency.com", + "thesearchagency.net" + ] + } + }, + { + "The Trade Desk": { + "http://thetradedesk.com/": ["adsrvr.org", "thetradedesk.com"] + } + }, + { + "Think Realtime": { + "http://www.thinkrealtime.com/": [ + "echosearch.com", + "esm1.net", + "thinkrealtime.com" + ] + } + }, + { + "Tinder": { + "http://tinder.com/": ["carbonads.com", "tinder.com"] + } + }, + { + "TiqIQ": { + "http://www.tiqiq.com/": ["tiqiq.com"] + } + }, + { + "Tisoomi": { + "http://www.tisoomi.com/": ["adternal.com", "tisoomi.com"] + } + }, + { + "TLVMedia": { + "http://tlvmedia.com/": ["tlvmedia.com"] + } + }, + { + "Todacell": { + "http://www.todacell.com/": ["todacell.com"] + } + }, + { + "ToneFuse": { + "http://tonefuse.com/": ["tonefuse.com"] + } + }, + { + "ToneMedia": { + "http://tonemedia.com/": ["clickfuse.com", "tonemedia.com"] + } + }, + { + "TouchCommerce": { + "http://www.touchcommerce.com/": ["inq.com", "touchcommerce.com"] + } + }, + { + "TrackingSoft": { + "http://trackingsoft.com/": ["trackingsoft.com"] + } + }, + { + "Tradedoubler": { + "http://www.tradedoubler.com/": ["tradedoubler.com"] + } + }, + { + "TradeTracker": { + "http://www.tradetracker.com/": [ + "tradetracker.com", + "tradetracker.net" + ] + } + }, + { + "TrafficHaus": { + "http://www.traffichaus.com/": ["traffichaus.com", "traffichouse.com"] + } + }, + { + "TrafficRevenue": { + "http://www.trafficrevenue.net/": ["trafficrevenue.net"] + } + }, + { + "Traffiq": { + "http://www.traffiq.com/": ["traffiq.com"] + } + }, + { + "Trafmag": { + "http://trafmag.com/": ["trafmag.com"] + } + }, + { + "Traverse": { + "http://www.traversedata.com/": ["traversedlp.com"] + } + }, + { + "Travora Media": { + "http://www.travoramedia.com/": [ + "traveladnetwork.com", + "traveladvertising.com", + "travoramedia.com" + ] + } + }, + { + "Tremor Video": { + "http://www.tremorvideo.com/": [ + "scanscout.com", + "tmnetads.com", + "tremorhub.com", + "tremormedia.com", + "tremorvideo.com" + ] + } + }, + { + "Triggit": { + "http://triggit.com/": ["triggit.com"] + } + }, + { + "TripleLift": { + "http://triplelift.com/": ["3lift.com", "triplelift.com"] + } + }, + { + "TruEffect": { + "http://www.trueffect.com/": ["adlegend.com", "trueffect.com"] + } + }, + { + "TrustX": { + "https://trustx.org/": ["trustx.org"] + } + }, + { + "TubeMogul": { + "http://www.tubemogul.com/": ["tmogul.com", "tubemogul.com"] + } + }, + { + "Twelvefold": { + "http://www.twelvefold.com/": ["buzzlogic.com", "twelvefold.com"] + } + }, + { + "Twitter": { + "https://twitter.com/": ["ads-twitter.com"] + } + }, + { + "Twyn Group": { + "http://www.twyn.com/": ["twyn-group.com", "twyn.com"] + } + }, + { + "Tyroo": { + "http://www.tyroo.com/": ["tyroo.com"] + } + }, + { + "ucfunnel": { + "https://www.ucfunnel.com/": ["aralego.com", "ucfunnel.com"] + } + }, + { + "uCoz": { + "http://www.ucoz.com/": [ + "ucoz.ae", + "ucoz.br", + "ucoz.com", + "ucoz.du", + "ucoz.fr", + "ucoz.net", + "ucoz.ru" + ] + } + }, + { + "Unanimis": { + "http://www.unanimis.co.uk/": ["unanimis.co.uk"] + } + }, + { + "Underdog Media": { + "http://www.underdogmedia.com/": ["udmserve.net", "underdogmedia.com"] + } + }, + { + "Undertone": { + "http://www.undertone.com/": [ + "undertone.com", + "undertonenetworks.com", + "undertonevideo.com" + ] + } + }, + { + "UniQlick": { + "http://www.uniqlick.com/": [ + "51network.com", + "uniqlick.com", + "wanmo.com" + ] + } + }, + { + "Unruly": { + "https://unruly.co/": ["unrulymedia.com"] + } + }, + { + "Upland": { + "https://uplandsoftware.com/": ["leadlander.com", "trackalyzer.com"] + } + }, + { + "up-value": { + "http://www.up-value.de/": ["up-value.de"] + } + }, + { + "Value Ad": { + "http://valuead.com/": ["valuead.com"] + } + }, + { + "Various": { + "http://www.various.com/": [ + "amigos.com", + "getiton.com", + "medley.com", + "nostringsattached.com", + "various.com" + ] + } + }, + { + "Vdopia": { + "http://www.vdopia.com/": ["ivdopia.com", "vdopia.com"] + } + }, + { + "Veeseo": { + "http://veeseo.com": ["veeseo.com"] + } + }, + { + "Velocity Media": { + "http://adsvelocity.com/": ["adsvelocity.com"] + } + }, + { + "Velti": { + "http://www.velti.com/": ["mobclix.com", "velti.com"] + } + }, + { + "Vemba": { + "https://www.vemba.com/": ["vemba.com"] + } + }, + { + "Venatus Media": { + "http://venatusmedia.com": ["venatusmedia.com"] + } + }, + { + "Vendemore": { + "https://vendemore.com/": ["vendemore.com"] + } + }, + { + "Vendio": { + "http://www.vendio.com/": ["singlefeed.com", "vendio.com"] + } + }, + { + "Veoxa": { + "http://www.veoxa.com/": ["veoxa.com"] + } + }, + { + "Veremedia": { + "http://www.veremedia.com/": ["veremedia.com"] + } + }, + { + "VerticalHealth": { + "https://www.verticalhealth.com/": ["verticalhealth.net"] + } + }, + { + "VerticalResponse": { + "http://www.verticalresponse.com/": [ + "verticalresponse.com", + "vresp.com" + ] + } + }, + { + "Vibrant Media": { + "http://www.vibrantmedia.com/": [ + "intellitxt.com", + "picadmedia.com", + "vibrantmedia.com" + ] + } + }, + { + "VideoIntelligence": { + "https://www.vi.ai/": ["vi.ai"] + } + }, + { + "VigLink": { + "http://www.viglink.com/": ["viglink.com"] + } + }, + { + "VisibleBrands": { + "http://www.visbrands.com/": ["visbrands.com"] + } + }, + { + "Visible Measures": { + "http://www.visiblemeasures.com/": [ + "viewablemedia.net", + "visiblemeasures.com" + ] + } + }, + { + "VisualDNA": { + "http://www.visualdna.com/": [ + "vdna-assets.com", + "visualdna-stats.com", + "visualdna.com" + ] + } + }, + { + "Vizu": { + "http://www.vizu.com/": ["vizu.com"] + } + }, + { + "Vizury": { + "http://www.vizury.com/": ["vizury.com"] + } + }, + { + "Vserv": { + "http://www.vserv.com/": ["vserv.com", "vserv.mobi"] + } + }, + { + "Vuble": { + "https://vuble.tv/us/": ["mediabong.com"] + } + }, + { + "Wahoha": { + "http://wahoha.com/": ["contentwidgets.net", "wahoha.com"] + } + }, + { + "Wayfair": { + "https://www.wayfair.com/": ["wayfair.com"] + } + }, + { + "WebAds": { + "http://www.webads.co.uk/": ["webads.co.uk"] + } + }, + { + "Web.com": { + "http://www.web.com/": ["feedperfect.com", "web.com"] + } + }, + { + "WebGozar.com": { + "http://www.webgozar.com/": ["webgozar.com", "webgozar.ir"] + } + }, + { + "Webmecanik": { + "https://www.webmecanik.com/": ["webmecanik.com"] + } + }, + { + "WebMetro": { + "http://www.webmetro.com/": ["dsmmadvantage.com", "webmetro.com"] + } + }, + { + "Weborama": { + "http://weborama.com/": ["weborama.com", "weborama.fr"] + } + }, + { + "Webtraffic": { + "http://www.webtraffic.se/": ["webtraffic.no", "webtraffic.se"] + } + }, + { + "WideOrbit": { + "https://www.wideorbit.com/": ["dep-x.com"] + } + }, + { + "WiredMinds": { + "http://www.wiredminds.com/": ["wiredminds.com", "wiredminds.de"] + } + }, + { + "Wishabi": { + "http://wishabi.com": ["wishabi.com", "wishabi.net"] + } + }, + { + "WordStream": { + "http://www.wordstream.com/": ["wordstream.com"] + } + }, + { + "WPP": { + "http://www.wpp.com/": [ + "247realmedia.com", + "accelerator-media.com", + "acceleratorusa.com", + "decdna.net", + "decideinteractive.com", + "gmads.net", + "groupm.com", + "kantarmedia.com", + "mecglobal.com", + "mindshare.nl", + "mookie1.com", + "pm14.com", + "realmedia.com", + "targ.ad", + "themig.com", + "wpp.com", + "xaxis.com" + ] + } + }, + { + "xAd": { + "http://www.xad.com/": ["xad.com"] + } + }, + { + "Xertive Media": { + "http://www.xertivemedia.com/": [ + "admanager-xertive.com", + "xertivemedia.com" + ] + } + }, + { + "xplosion interactive": { + "http://www.xplosion.de/": ["xplosion.de"] + } + }, + { + "Xrost DS": { + "http://www.adplan-ds.com/": ["adplan-ds.com"] + } + }, + { + "Yabuka": { + "http://www.yabuka.com/": ["yabuka.com"] + } + }, + { + "Yahoo!": { + "http://www.yahoo.com/": [ + "adinterax.com", + "adrevolver.com", + "ads.yahoo.com", + "adserver.yahoo.com", + "advertising.yahoo.com", + "bluelithium.com", + "dapper.net", + "flurry.com", + "interclick.com", + "marketingsolutions.yahoo.com", + "overture.com", + "rightmedia.com", + "rmxads.com", + "secure-adserver.com", + "thewheelof.com", + "yieldmanager.com", + "yieldmanager.net", + "yldmgrimg.net" + ] + } + }, + { + "Yandex": { + "http://www.yandex.com/": [ + "adfox.yandex.ru", + "an.yandex.ru", + "awaps.yandex.ru", + "mc.yandex.ru", + "moikrug.ru", + "web-visor.com", + "yandex.ru/clck/click", + "yandex.ru/clck/counter", + "yandex.ru/cycounter", + "yandex.ru/portal/set/any", + "yandex.ru/set/s/rsya-tag-users/data" + ] + } + }, + { + "Ybrant Digital": { + "http://www.ybrantdigital.com/": [ + "addynamix.com", + "adserverplus.com", + "oridian.com", + "ybrantdigital.com" + ] + } + }, + { + "YD": { + "http://www.ydworld.com/": ["ydworld.com", "yieldivision.com"] + } + }, + { + "YellowHammer": { + "http://www.yhmg.com/": [ + "attracto.com", + "clickhype.com", + "yellowhammermg.com", + "yhmg.com" + ] + } + }, + { + "Yes Ads": { + "http://yesads.com/": ["yesads.com"] + } + }, + { + "YieldAds": { + "http://yieldads.com/": ["yieldads.com"] + } + }, + { + "YieldBids": { + "http://ybx.io/": ["ybx.io"] + } + }, + { + "YieldBot": { + "http://yieldbot.com/": ["yldbt.com"] + } + }, + { + "YieldBuild": { + "http://yieldbuild.com/": ["yieldbuild.com"] + } + }, + { + "Yieldify": { + "https://www.yieldify.com/": ["yieldify.com"] + } + }, + { + "Yieldlab": { + "http://www.yieldlab.de/": ["yieldlab.de", "yieldlab.net"] + } + }, + { + "Yieldmo": { + "https://yieldmo.com": ["yieldmo.com"] + } + }, + { + "YieldNexus": { + "https://www.yieldnexus.com/": ["ynxs.io"] + } + }, + { + "YOC": { + "http://group.yoc.com/": ["yoc-performance.com", "yoc.com"] + } + }, + { + "Yoggrt": { + "http://www.yoggrt.com/": ["yoggrt.com"] + } + }, + { + "youknowbest": { + "http://www.youknowbest.com/": ["youknowbest.com"] + } + }, + { + "YuMe": { + "http://www.yume.com/": ["yume.com", "yumenetworks.com"] + } + }, + { + "ZafulAffiliate": { + "https://affiliate.zaful.com/": [ + "affasi.com", + "gw-ec.com", + "zaful.com" + ] + } + }, + { + "Zango": { + "http://www.zango.com/": ["metricsdirect.com", "zango.com"] + } + }, + { + "zanox": { + "http://www.zanox.com/": ["buy.at", "zanox-affiliate.de", "zanox.com"] + } + }, + { + "zapunited": { + "http://www.zapunited.com/": ["zaparena.com", "zapunited.com"] + } + }, + { + "ZEDO": { + "http://www.zedo.com/": ["zedo.com", "zincx.com"] + } + }, + { + "Zefir": { + "https://ze-fir.com/": ["ze-fir.com"] + } + }, + { + "Zemanta": { + "http://www.zemanta.com/": ["zemanta.com"] + } + }, + { + "ZestAd": { + "http://www.zestad.com/": ["zestad.com"] + } + }, + { + "Zeta Email Solutions": { + "http://www.zetaemailsolutions.com/": [ + "insightgrit.com", + "zetaemailsolutions.com" + ] + } + }, + { + "Zumobi": { + "http://www.zumobi.com/": ["zumobi.com"] + } + }, + { + "ZypMedia": { + "http://www.zypmedia.com/": ["extend.tv", "zypmedia.com"] + } + } + ], + "Content": [ + { + "33Across": { + "http://33across.com/": ["tynt.com"] + } + }, + { + "ActivEngage": { + "http://www.activengage.com/": ["activengage.com"] + } + }, + { + "Adap.tv": { + "http://adap.tv/": ["adap.tv"] + } + }, + { + "Adobe": { + "http://www.adobe.com/": [ + "adobe.com", + "fyre.co", + "livefyre.com", + "typekit.com" + ] + } + }, + { + "Akamai": { + "http://www.akamai.com/": ["abmr.net", "akamai.com", "edgesuite.net"] + } + }, + { + "AKQA": { + "http://www.akqa.com/": ["akqa.com", "srtk.net"] + } + }, + { + "Amazon.com": { + "http://www.amazon.com/": [ + "alexa.com", + "amazon.com", + "cloudfront.net" + ] + } + }, + { + "AOL": { + "http://www.aol.com/": [ + "5min.com", + "aim.com", + "aol.com", + "aolanswers.com", + "aolcdn.com", + "aoltechguru.com", + "autoblog.com", + "cambio.com", + "dailyfinance.com", + "editions.com", + "engadget.com", + "games.com", + "homesessive.com", + "huffingtonpost.com", + "joystiq.com", + "kitchendaily.com", + "makers.com", + "mandatory.com", + "mapquest.com", + "moviefone.com", + "noisecreep.com", + "patch.com", + "pawnation.com", + "shortcuts.com", + "shoutcast.com", + "spinner.com", + "stylelist.com", + "stylemepretty.com", + "surphace.com", + "techcrunch.com", + "theboombox.com", + "theboot.com", + "tuaw.com", + "userplane.com", + "winamp.com" + ] + } + }, + { + "Automattic": { + "http://automattic.com/": [ + "automattic.com", + "gravatar.com", + "intensedebate.com" + ] + } + }, + { + "Baynote": { + "http://www.baynote.com/": ["baynote.com", "baynote.net"] + } + }, + { + "Bazaarvoice": { + "http://www.bazaarvoice.com/": ["bazaarvoice.com"] + } + }, + { + "BigDoor": { + "http://www.bigdoor.com/": ["bigdoor.com", "onetruefan.com"] + } + }, + { + "Brightcove": { + "http://www.brightcove.com/": ["brightcove.com"] + } + }, + { + "Browser-Update.org": { + "www.browser-update.org/": ["browser-update.org"] + } + }, + { + "BTBuckets": { + "http://btbuckets.com/": ["btbuckets.com"] + } + }, + { + "Buffer": { + "http://bufferapp.com/": ["bufferapp.com"] + } + }, + { + "Bunchball": { + "http://www.bunchball.com/": ["bunchball.com"] + } + }, + { + "buySAFE": { + "http://www.buysafe.com/": ["buysafe.com"] + } + }, + { + "BuzzFeed": { + "http://www.buzzfeed.com/": ["buzzfed.com", "buzzfeed.com"] + } + }, + { + "Cbox": { + "http://www.cbox.ws/": ["cbox.ws"] + } + }, + { + "CBS Interactive": { + "http://www.cbsinteractive.com/": ["cbsinteractive.com", "com.com"] + } + }, + { + "Cedexis": { + "http://www.cedexis.com/": ["cedexis.com", "cedexis.net"] + } + }, + { + "Certona": { + "http://www.certona.com/": ["certona.com", "res-x.com"] + } + }, + { + "ClipSyndicate": { + "http://www.clipsyndicate.com/": ["clipsyndicate.com"] + } + }, + { + "Collarity": { + "http://www.collarity.com/": ["collarity.com"] + } + }, + { + "Conduit": { + "http://www.conduit.com/": [ + "conduit-banners.com", + "conduit-services.com", + "conduit.com", + "wibiya.com" + ] + } + }, + { + "Congoo": { + "http://www.congoo.com/": ["congoo.com"] + } + }, + { + "Contact At Once!": { + "http://www.contactatonce.com/": ["contactatonce.com"] + } + }, + { + "Conviva": { + "http://www.conviva.com/": ["conviva.com"] + } + }, + { + "DailyMe": { + "http://dailyme.com/": ["dailyme.com", "newstogram.com"] + } + }, + { + "DataSift": { + "http://datasift.com/": ["datasift.com", "tweetmeme.com"] + } + }, + { + "Disqus": { + "http://disqus.com/": ["disqus.com"] + } + }, + { + "Echo": { + "http://aboutecho.com/": [ + "aboutecho.com", + "haloscan.com", + "js-kit.com" + ] + } + }, + { + "Facebook": { + "http://www.facebook.com/": [ + "fbcdn.net", + "instagram.com", + "messenger.com" + ] + } + }, + { + "Flattr": { + "http://flattr.com/": ["flattr.com"] + } + }, + { + "FreeWheel": { + "http://www.freewheel.tv/": ["freewheel.tv", "fwmrm.net"] + } + }, + { + "Genius.com": { + "http://www.genius.com/": ["genius.com"] + } + }, + { + "Get Satisfaction": { + "https://getsatisfaction.com/": ["getsatisfaction.com"] + } + }, + { + "Gigya": { + "http://www.gigya.com/": ["gigcount.com", "gigya.com"] + } + }, + { + "Global Takeoff": { + "http://www.globaltakeoff.com/": [ + "globaltakeoff.com", + "globaltakeoff.net" + ] + } + }, + { + "GoGrid": { + "http://www.gogrid.com/": [ + "formalyzer.com", + "gogrid.com", + "komli.net" + ] + } + }, + { + "Google": { + "http://www.google.com/": [ + "accounts.google.com", + "apis.google.com", + "appengine.google.com", + "apture.com", + "blogger.com", + "books.google.com", + "checkout.google.com", + "chrome.google.com", + "code.google.com", + "codesearch.google.com", + "docs.google.com", + "drive.google.com", + "earth.google.com", + "encrypted.google.com", + "feedburner.com", + "feedburner.google.com", + "feedproxy.google.com", + "finance.google.com", + "ggpht.com", + "gmodules.com", + "google-melange.com", + "google.ad", + "google.ae", + "google.al", + "google.am", + "google.as", + "google.at", + "google.az", + "google.ba", + "google.be", + "google.bf", + "google.bg", + "google.bi", + "google.bj", + "google.bs", + "google.bt", + "google.by", + "google.ca", + "google.cat", + "google.cd", + "google.cf", + "google.cg", + "google.ch", + "google.ci", + "google.cl", + "google.cm", + "google.cn", + "google.co.ao", + "google.co.bw", + "google.co.ck", + "google.co.cr", + "google.co.id", + "google.co.il", + "google.co.in", + "google.co.jp", + "google.co.ke", + "google.co.kr", + "google.co.ls", + "google.co.ma", + "google.co.mz", + "google.co.nz", + "google.co.th", + "google.co.tz", + "google.co.ug", + "google.co.uk", + "google.co.uz", + "google.co.ve", + "google.co.vi", + "google.co.za", + "google.co.zm", + "google.co.zw", + "google.com", + "google.com.af", + "google.com.ag", + "google.com.ai", + "google.com.ar", + "google.com.au", + "google.com.bd", + "google.com.bh", + "google.com.bn", + "google.com.bo", + "google.com.br", + "google.com.bz", + "google.com.co", + "google.com.cu", + "google.com.cy", + "google.com.do", + "google.com.ec", + "google.com.eg", + "google.com.et", + "google.com.fj", + "google.com.gh", + "google.com.gi", + "google.com.gt", + "google.com.hk", + "google.com.jm", + "google.com.kh", + "google.com.kw", + "google.com.lb", + "google.com.ly", + "google.com.mm", + "google.com.mt", + "google.com.mx", + "google.com.my", + "google.com.na", + "google.com.nf", + "google.com.ng", + "google.com.ni", + "google.com.np", + "google.com.om", + "google.com.pa", + "google.com.pe", + "google.com.pg", + "google.com.ph", + "google.com.pk", + "google.com.pr", + "google.com.py", + "google.com.qa", + "google.com.sa", + "google.com.sb", + "google.com.sg", + "google.com.sl", + "google.com.sv", + "google.com.tj", + "google.com.tr", + "google.com.tw", + "google.com.ua", + "google.com.uy", + "google.com.vc", + "google.com.vn", + "google.cv", + "google.cz", + "google.de", + "google.dj", + "google.dk", + "google.dm", + "google.dz", + "google.ee", + "google.es", + "google.fi", + "google.fm", + "google.fr", + "google.ga", + "google.ge", + "google.gg", + "google.gl", + "google.gm", + "google.gp", + "google.gr", + "google.gy", + "google.hn", + "google.hr", + "google.ht", + "google.hu", + "google.ie", + "google.im", + "google.iq", + "google.is", + "google.it", + "google.je", + "google.jo", + "google.kg", + "google.ki", + "google.kz", + "google.la", + "google.li", + "google.lk", + "google.lt", + "google.lu", + "google.lv", + "google.md", + "google.me", + "google.mg", + "google.mk", + "google.ml", + "google.mn", + "google.ms", + "google.mu", + "google.mv", + "google.mw", + "google.ne", + "google.nl", + "google.no", + "google.nr", + "google.nu", + "google.pl", + "google.pn", + "google.ps", + "google.pt", + "google.ro", + "google.rs", + "google.ru", + "google.rw", + "google.sc", + "google.se", + "google.sh", + "google.si", + "google.sk", + "google.sm", + "google.sn", + "google.so", + "google.st", + "google.td", + "google.tg", + "google.tk", + "google.tl", + "google.tm", + "google.tn", + "google.to", + "google.tt", + "google.vg", + "google.vu", + "google.ws", + "googleapis.com", + "googleartproject.com", + "googleusercontent.com", + "groups.google.com", + "gstatic.com", + "health.google.com", + "images.google.com", + "investor.google.com", + "knol.google.com", + "maps.google.com", + "music.google.com", + "news.google.com", + "panoramio.com", + "picasa.google.com", + "picasaweb.google.com", + "play.google.com", + "postini.com", + "recaptcha.net", + "script.google.com", + "shopping.google.com", + "sites.google.com", + "sketchup.google.com", + "support.google.com", + "talk.google.com", + "talkgadget.google.com", + "toolbar.google.com", + "translate.google.com", + "trends.google.com", + "video.google.com", + "videos.google.com", + "wallet.google.com", + "youtube.com" + ] + } + }, + { + "Gravity": { + "http://www.gravity.com/": ["gravity.com", "grvcdn.com"] + } + }, + { + "Heyzap": { + "http://www.heyzap.com/": ["heyzap.com"] + } + }, + { + "HubSpot": { + "http://www.hubspot.com/": ["hubspot.com"] + } + }, + { + "IBM": { + "http://www.ibm.com/": ["xtify.com"] + } + }, + { + "iovation": { + "http://www.iovation.com/": ["iesnare.com", "iovation.com"] + } + }, + { + "Kaltura": { + "http://corp.kaltura.com/": ["kaltura.com"] + } + }, + { + "kikin": { + "http://www.kikin.com/": ["kikin.com"] + } + }, + { + "Limelight Networks": { + "http://www.limelight.com/": [ + "clickability.com", + "limelight.com", + "llnwd.net" + ] + } + }, + { + "LivePerson": { + "http://www.liveperson.net/": ["liveperson.net"] + } + }, + { + "LiveRail": { + "http://liverail.com/": ["liverail.com"] + } + }, + { + "LongTail Video": { + "http://www.longtailvideo.com/": ["longtailvideo.com", "ltassrv.com"] + } + }, + { + "Markit": { + "http://www.markit.com/": ["markit.com", "wsod.com"] + } + }, + { + "MashLogic": { + "http://www.mashlogic.com/": ["mashlogic.com"] + } + }, + { + "McAfee": { + "http://www.mcafee.com/": ["mcafee.com", "scanalert.com"] + } + }, + { + "Microsoft": { + "http://www.microsoft.com/": [ + "bing.com", + "gamesforwindows.com", + "getgamesmart.com", + "healthvault.com", + "ieaddons.com", + "iegallery.com", + "live.com", + "microsoft.com", + "microsoftalumni.com", + "microsoftalumni.org", + "microsoftstore.com", + "msn.com", + "msndirect.com", + "office.com", + "officelive.com", + "outlook.com", + "s-msn.com", + "skype.com", + "windowsphone.com", + "worldwidetelescope.org", + "xbox.com", + "zune.com", + "zune.net" + ] + } + }, + { + "NDN": { + "http://www.newsinc.com/": ["newsinc.com"] + } + }, + { + "Oberon Media": { + "http://www.oberon-media.com/": ["blaze.com", "oberon-media.com"] + } + }, + { + "Ooyala": { + "http://www.ooyala.com/": ["oo4.com", "ooyala.com"] + } + }, + { + "Oracle": { + "http://www.oracle.com/": [ + "atgsvcs.com", + "instantservice.com", + "istrack.com", + "oracle.com" + ] + } + }, + { + "Peerius": { + "http://www.peerius.com/": ["peerius.com"] + } + }, + { + "Pinterest": { + "http://pinterest.com/": ["pinimg.com", "pinterest.com"] + } + }, + { + "PunchTab": { + "http://www.punchtab.com/": ["punchtab.com"] + } + }, + { + "RIM": { + "http://www.rim.com/": ["rim.com", "scoreloop.com"] + } + }, + { + "Salesforce.com": { + "http://www.salesforce.com/": ["salesforceliveagent.com"] + } + }, + { + "SAY": { + "http://saymedia.com/": [ + "saymedia.com", + "typepad.com", + "videoegg.com" + ] + } + }, + { + "ScribeFire": { + "http://www.scribefire.com/": ["scribefire.com"] + } + }, + { + "Six Apart": { + "http://www.sixapart.com/": ["sixapart.com"] + } + }, + { + "Skribit": { + "http://skribit.com/": ["skribit.com"] + } + }, + { + "SnapEngage": { + "http://www.snapengage.com/": ["snapengage.com"] + } + }, + { + "Spring Metrics": { + "http://www.springmetrics.com/": ["springmetrics.com"] + } + }, + { + "Synacor": { + "http://www.synacor.com/": ["synacor.com"] + } + }, + { + "ThingLink": { + "http://www.thinglink.com/": ["thinglink.com"] + } + }, + { + "Thismoment": { + "http://www.thismoment.com/": ["thismoment.com"] + } + }, + { + "Thummit": { + "http://www.thummit.com/": ["thummit.com"] + } + }, + { + "Topsy": { + "http://topsy.com/": ["topsy.com"] + } + }, + { + "TraceMyIP.org": { + "http://www.tracemyip.org/": ["tracemyip.org"] + } + }, + { + "Trackset": { + "http://www.trackset.com/": ["trackset.com"] + } + }, + { + "Trovus": { + "http://www.trovus.co.uk/": ["trovus.co.uk"] + } + }, + { + "Trumba": { + "http://www.trumba.com/": ["trumba.com"] + } + }, + { + "TRUSTe": { + "http://www.truste.com/": ["truste.com"] + } + }, + { + "TurnTo": { + "http://www.turntonetworks.com/": ["turnto.com", "turntonetworks.com"] + } + }, + { + "Tweetboard": { + "http://tweetboard.com/": ["tweetboard.com"] + } + }, + { + "Twitter Counter": { + "http://twittercounter.com/": ["twittercounter.com"] + } + }, + { + "UberMedia": { + "http://ubermedia.com/": ["tweetup.com", "ubermedia.com"] + } + }, + { + "UberTags": { + "http://ubertags.com/": ["ubertags.com"] + } + }, + { + "Unbounce": { + "http://unbounce.com/": ["unbounce.com"] + } + }, + { + "Uptrends": { + "http://www.uptrends.com/": ["uptrends.com"] + } + }, + { + "Usability Sciences": { + "http://www.usabilitysciences.com/": [ + "usabilitysciences.com", + "webiqonline.com" + ] + } + }, + { + "UserVoice": { + "http://www.uservoice.com/": ["uservoice.com"] + } + }, + { + "Vertical Acuity": { + "http://www.verticalacuity.com/": ["verticalacuity.com"] + } + }, + { + "VG WORT": { + "http://www.vgwort.de/": ["vgwort.de"] + } + }, + { + "Videology": { + "http://www.videologygroup.com/": [ + "tidaltv.com", + "videologygroup.com" + ] + } + }, + { + "Viewbix": { + "http://www.viewbix.com/": ["qoof.com", "viewbix.com"] + } + }, + { + "Vimeo": { + "http://vimeo.com/": ["vimeo.com", "vimeocdn.com"] + } + }, + { + "VINDICO": { + "http://vindicogroup.com/": ["vindicogroup.com", "vindicosuite.com"] + } + }, + { + "Voice2Page": { + "http://www.voice2page.com/": ["voice2page.com"] + } + }, + { + "WebsiteAlive": { + "http://www.websitealive.com/": [ + "websitealive.com", + "websitealive0.com", + "websitealive1.com", + "websitealive2.com", + "websitealive3.com", + "websitealive4.com", + "websitealive5.com", + "websitealive6.com", + "websitealive7.com", + "websitealive8.com", + "websitealive9.com" + ] + } + }, + { + "Yahoo!": { + "http://www.yahoo.com/": [ + "answers.yahoo.com", + "apps.yahoo.com", + "autos.yahoo.com", + "biz.yahoo.com", + "developer.yahoo.com", + "everything.yahoo.com", + "finance.yahoo.com", + "flickr.com", + "games.yahoo.com", + "groups.yahoo.com", + "help.yahoo.com", + "hotjobs.yahoo.com", + "info.yahoo.com", + "local.yahoo.com", + "luminate.com", + "messages.yahoo.com", + "movies.yahoo.com", + "msg.yahoo.com", + "news.yahoo.com", + "omg.yahoo.com", + "pipes.yahoo.com", + "pixazza.com", + "realestate.yahoo.com", + "search.yahoo.com", + "shine.yahoo.com", + "smallbusiness.yahoo.com", + "sports.yahoo.com", + "staticflickr.com", + "suggestions.yahoo.com", + "travel.yahoo.com", + "tumblr.com", + "upcoming.yahoo.com", + "webhosting.yahoo.com", + "widgets.yahoo.com", + "www.yahoo.com", + "yahooapis.com", + "yahoofs.com", + "yimg.com", + "ypolicyblog.com", + "yuilibrary.com", + "zenfs.com" + ] + } + }, + { + "Yandex": { + "http://www.yandex.com/": [ + "kinopoisk.ru", + "yandex.by", + "yandex.com", + "yandex.com.tr", + "yandex.ru", + "yandex.st", + "yandex.ua" + ] + } + }, + { + "Zendesk": { + "http://www.zendesk.com/": ["zendesk.com"] + } + }, + { + "Zopim": { + "https://www.zopim.com/": ["zopim.com"] + } + } + ], + "Analytics": [ + { + "63 Squares": { + "http://63squares.com/": ["63squares.com", "i-stats.com"] + } + }, + { + "Acxiom": { + "http://www.acxiom.com/": [ + "acxiom.com", + "acxiomapac.com", + "mm7.net", + "pippio.com" + ] + } + }, + { + "AddFreeStats": { + "http://www.addfreestats.com/": ["3dstats.com", "addfreestats.com"] + } + }, + { + "Adloox": { + "http://www.adloox.com/": ["adloox.com", "adlooxtracking.com"] + } + }, + { + "Adventori": { + "https://adventori.com": ["adventori.com"] + } + }, + { + "AIData": { + "http://www.aidata.me/": ["advombat.ru", "aidata.me"] + } + }, + { + "AivaLabs": { + "https://aivalabs.com": ["aivalabs.com"] + } + }, + { + "Akamai": { + "http://www.akamai.com/": ["go-mpulse.net"] + } + }, + { + "Amadesa": { + "http://www.amadesa.com/": ["amadesa.com"] + } + }, + { + "Amazing Counters": { + "http://amazingcounters.com/": ["amazingcounters.com"] + } + }, + { + "Amazon.com": { + "http://www.amazon.com/": ["alexametrics.com"] + } + }, + { + "Amplitude": { + "https://amplitude.com/": ["amplitude.com"] + } + }, + { + "anormal-media.de": { + "http://anormal-media.de/": ["anormal-media.de", "anormal-tracker.de"] + } + }, + { + "AT Internet": { + "http://www.atinternet.com/": [ + "at-o.net", + "atinternet.com", + "xiti.com" + ] + } + }, + { + "Attracta": { + "https://www.attracta.com/": ["attracta.com"] + } + }, + { + "Automattic": { + "http://automattic.com/": ["polldaddy.com"] + } + }, + { + "AvantLink": { + "http://www.avantlink.com/": ["avmws.com"] + } + }, + { + "Awio": { + "http://www.awio.com/": ["awio.com", "w3counter.com", "w3roi.com"] + } + }, + { + "Belstat": { + "http://www.belstat.com/": [ + "belstat.be", + "belstat.com", + "belstat.de", + "belstat.fr", + "belstat.nl" + ] + } + }, + { + "BetssonPalantir": { + "https://betssonpalantir.com/": ["betssonpalantir.com"] + } + }, + { + "BlogCounter.com": { + "http://www.blogcounter.de/": ["blogcounter.de"] + } + }, + { + "BloomReach": { + "http://www.bloomreach.com/": ["p.brsrvr.com"] + } + }, + { + "BlueCava": { + "http://www.bluecava.com/": ["bluecava.com"] + } + }, + { + "Bluemetrix": { + "http://www.bluemetrix.com/": ["bluemetrix.com", "bmmetrix.com"] + } + }, + { + "Bombora": { + "https://bombora.com/": ["ml314.com"] + } + }, + { + "Branch": { + "https://branch.io/": ["branch.io"] + } + }, + { + "Branica": { + "http://www.branica.com/": ["branica.com"] + } + }, + { + "BrightEdge": { + "http://www.brightedge.com/": ["b0e8.com", "brightedge.com"] + } + }, + { + "Bubblestat": { + "http://www.bubblestat.com/": ["bubblestat.com"] + } + }, + { + "Cardlytics": { + "http://www.cardlytics.com/": ["cardlytics.com"] + } + }, + { + "Chartbeat": { + "http://chartbeat.com/": ["chartbeat.com", "chartbeat.net"] + } + }, + { + "Clickdensity": { + "http://www.clickdensity.com/": ["clickdensity.com"] + } + }, + { + "ClickGuard": { + "https://www.clickguard.com/": ["clickguard.com"] + } + }, + { + "ClickTale": { + "http://www.clicktale.com/": [ + "clicktale.com", + "clicktale.net", + "pantherssl.com" + ], + "session-replay": "true" + } + }, + { + "ClixMetrix": { + "http://www.clixmetrix.com/": ["clixmetrix.com"] + } + }, + { + "Clixpy": { + "http://clixpy.com/": ["clixpy.com"] + } + }, + { + "ClustrMaps": { + "http://www.clustrmaps.com/": ["clustrmaps.com"] + } + }, + { + "CNZZ": { + "http://www.cnzz.com/": ["cnzz.com"] + } + }, + { + "Compuware": { + "http://www.compuware.com/": [ + "axf8.net", + "compuware.com", + "gomez.com" + ] + } + }, + { + "comScore": { + "http://www.comscore.com/": [ + "certifica.com", + "comscore.com", + "mdotlabs.com", + "scorecardresearch.com", + "sitestat.com", + "voicefive.com" + ] + } + }, + { + "Connexity": { + "http://www.connexity.com/": ["connexity.com", "connexity.net"] + } + }, + { + "Convert Insights": { + "http://www.convert.com/": ["convert.com", "reedge.com"] + } + }, + { + "Convertro": { + "http://www.convertro.com/": ["convertro.com"] + } + }, + { + "Crazy Egg": { + "http://www.crazyegg.com/": ["cetrk.com", "crazyegg.com"] + } + }, + { + "Crowd Science": { + "http://crowdscience.com/": ["crowdscience.com"] + } + }, + { + "Cya2": { + "http://cya2.net/": ["cya2.net"] + } + }, + { + "Dataium": { + "http://www.dataium.com/": ["collserve.com", "dataium.com"] + } + }, + { + "Deep Intent": { + "https://www.deepintent.com/": ["deepintent.com"] + } + }, + { + "Demandbase": { + "http://www.demandbase.com/": ["company-target.com", "demandbase.com"] + } + }, + { + "DirectCORP": { + "http://www.directcorp.de/": ["ipcounter.de"] + } + }, + { + "DistilNetworks": { + "https://www.distilnetworks.com/": ["distiltag.com"] + } + }, + { + "DoubleVerify": { + "http://www.doubleverify.com/": ["doubleverify.com"] + } + }, + { + "dwstat.com": { + "http://www.dwstat.cn/": ["dwstat.cn"] + } + }, + { + "ECSAnalytics": { + "https://www.theecsinc.com/": ["ecsanalytics.com"] + } + }, + { + "EFF": { + "https://www.eff.org/": [ + "do-not-tracker.org", + "eviltracker.net", + "trackersimulator.org" + ] + } + }, + { + "eProof.com": { + "http://www.eproof.com/": ["eproof.com"] + } + }, + { + "etracker": { + "http://www.etracker.com/": [ + "etracker.com", + "etracker.de", + "sedotracker.com", + "sedotracker.de" + ] + } + }, + { + "Eulerian Technologies": { + "http://www.eulerian.com/": ["eulerian.com", "eulerian.net"] + } + }, + { + "eXTReMe digital": { + "http://extremetracking.com/": [ + "extreme-dm.com", + "extremetracking.com" + ] + } + }, + { + "Eyeota": { + "http://eyeota.net/": ["eyeota.net"] + } + }, + { + "Feedjit": { + "http://feedjit.com/": ["feedjit.com"] + } + }, + { + "Flashtalking": { + "http://www.flashtalking.com/": [ + "encoremetrics.com", + "sitecompass.com" + ] + } + }, + { + "Footprint": { + "http://www.footprintlive.com/": ["footprintlive.com"] + } + }, + { + "Free Online Users": { + "http://www.freeonlineusers.com/": ["freeonlineusers.com"] + } + }, + { + "Free-PageRank.com": { + "http://www.free-pagerank.com/": ["free-pagerank.com"] + } + }, + { + "Friends2Follow": { + "https://friends2follow.com/": ["antifraudjs.friends2follow.com"] + } + }, + { + "Fullstory": { + "https://www.fullstory.com/": ["fullstory.com"], + "session-replay": "true" + } + }, + { + "GetSiteControl": { + "https://getsitecontrol.com/": ["getsitecontrol.com"] + } + }, + { + "GfK Group": { + "http://www.gfk.com/": ["daphnecm.com", "gfk.com", "gfkdaphne.com"] + } + }, + { + "GitHub": { + "https://github.com/": ["gaug.es"] + } + }, + { + "Go Daddy": { + "http://www.godaddy.com/": ["godaddy.com", "trafficfacts.com"] + } + }, + { + "Google": { + "http://www.google.com/": ["google-analytics.com", "postrank.com"] + } + }, + { + "GoSquared": { + "https://www.gosquared.com/": ["gosquared.com"] + } + }, + { + "GoStats": { + "http://gostats.com/": ["gostats.com"] + } + }, + { + "GrapheneMedia": { + "http://graphenemedia.in/": ["graphenedigitalanalytics.in"] + } + }, + { + "GTop": { + "http://www.gtop.ro/": ["gtop.ro", "gtopstats.com"] + } + }, + { + "Hearst": { + "http://www.hearst.com/": ["raasnet.com", "redaril.com"] + } + }, + { + "Histats": { + "http://www.histats.com/": ["histats.com"] + } + }, + { + "HitsLink": { + "http://www.hitslink.com/": ["hitslink.com"] + } + }, + { + "Hit Sniffer": { + "http://www.hitsniffer.com/": ["hitsniffer.com"] + } + }, + { + "Hotjar": { + "https://www.hotjar.com": ["hotjar.com"] + } + }, + { + "HubSpot": { + "http://www.hubspot.com/": ["hs-analytics.net"] + } + }, + { + "IBM": { + "http://www.ibm.com/": ["cmcore.com", "coremetrics.com", "ibm.com"] + } + }, + { + "InboundWriter": { + "http://www.inboundwriter.com/": [ + "enquisite.com", + "inboundwriter.com" + ] + } + }, + { + "Infernotions": { + "https://infernotions.com/": ["infernotions.com"] + } + }, + { + "INFOnline": { + "https://www.infonline.de/": ["infonline.de", "ioam.de", "ivwbox.de"] + } + }, + { + "InfoStars": { + "http://infostars.ru/": ["hotlog.ru", "infostars.ru"] + } + }, + { + "Inspectlet": { + "http://www.inspectlet.com/": ["inspectlet.com"] + } + }, + { + "IntelligenceFocus": { + "http://www.intelligencefocus.com/": [ + "domodomain.com", + "intelligencefocus.com" + ] + } + }, + { + "iPerceptions": { + "http://www.iperceptions.com/": ["iperceptions.com"] + } + }, + { + "IslayTech": { + "http://islay.tech": ["islay.tech"] + } + }, + { + "ItIsATracker": { + "https://itisatracker.com/": ["itisatracker.com"], + "dnt": "eff" + } + }, + { + "KeyMetric": { + "http://www.keymetric.net/": ["keymetric.net"] + } + }, + { + "KISSmetrics": { + "http://kissmetrics.com/": ["kissmetrics.com"] + } + }, + { + "Kitcode": { + "http://src.kitcode.net/": ["src.kitcode.net"] + } + }, + { + "LeadForensics": { + "https://www.leadforensics.com": ["leadforensics.com"] + } + }, + { + "LineZing": { + "http://www.linezing.com/": ["linezing.com"] + } + }, + { + "LivePerson": { + "http://www.liveperson.net/": ["liveperson.com", "nuconomy.com"] + } + }, + { + "Logdy": { + "http://logdy.com/": ["logdy.com"] + } + }, + { + "Lotame": { + "http://www.lotame.com/": ["crwdcntrl.net", "lotame.com"] + } + }, + { + "LuckyOrange": { + "https://www.luckyorange.com": ["luckyorange.com", "luckyorange.net"], + "session-replay": "true" + } + }, + { + "Lynchpin": { + "http://www.lynchpin.com/": ["lynchpin.com", "lypn.com"] + } + }, + { + "Lyris": { + "http://www.lyris.com/": ["clicktracks.com", "lyris.com"] + } + }, + { + "Lytiks": { + "http://www.lytiks.com/": ["lytiks.com"] + } + }, + { + "MarkMonitor": { + "https://www.markmonitor.com": ["9c9media.ca", "markmonitor.com"] + } + }, + { + "Marktest": { + "http://www.marktest.com/": ["marktest.com", "marktest.pt"] + } + }, + { + "MaxMind": { + "https://www.maxmind.com/en/home": ["maxmind.com", "mmapiws.com"] + } + }, + { + "Médiamétrie-eStat": { + "http://www.mediametrie-estat.com/": [ + "estat.com", + "mediametrie-estat.com" + ] + } + }, + { + "Merkle": { + "https://www.merkleinc.com/": ["merkleinc.com", "rkdms.com"] + } + }, + { + "Mixpanel": { + "https://mixpanel.com/": ["mixpanel.com", "mxpnl.com"] + } + }, + { + "Mongoose Metrics": { + "http://www.mongoosemetrics.com/": ["mongoosemetrics.com"] + } + }, + { + "Monitus": { + "http://www.monitus.net/": ["monitus.net"] + } + }, + { + "motigo": { + "http://motigo.com/": ["motigo.com", "nedstatbasic.net"] + } + }, + { + "Mouseflow": { + "http://mouseflow.com/": ["mouseflow.com"] + } + }, + { + "MyPagerank.Net": { + "http://www.mypagerank.net/": ["mypagerank.net"] + } + }, + { + "Mystighty": { + "http://mystighty.info/": ["mystighty.info", "sweeterge.info"] + } + }, + { + "Narrative": { + "http://narrative.io/2/": ["narrative.io"] + } + }, + { + "Net Applications": { + "http://www.netapplications.com/": [ + "hitsprocessor.com", + "netapplications.com" + ] + } + }, + { + "New Relic": { + "http://newrelic.com/": ["newrelic.com", "nr-data.net"] + } + }, + { + "NewsRight": { + "http://www.newsright.com/": ["apnewsregistry.com"] + } + }, + { + "NextSTAT": { + "http://www.nextstat.com/": ["nextstat.com"] + } + }, + { + "Nielsen": { + "http://www.nielsen.com/": ["glanceguide.com", "nielsen.com"] + } + }, + { + "NuDataSecurity": { + "https://nudatasecurity.com/": ["nudatasecurity.com"] + } + }, + { + "nurago": { + "http://www.nurago.com/": ["nurago.com", "nurago.de", "sensic.net"] + } + }, + { + "Observer": { + "http://observerapp.com/": ["observerapp.com"] + } + }, + { + "OnAudience": { + "http://www.onaudience.com/": [ + "behavioralengine.com", + "onaudience.com" + ] + } + }, + { + "OneStat": { + "http://www.onestat.com/": ["onestat.com"] + } + }, + { + "Openstat": { + "https://www.openstat.ru/": ["openstat.ru", "spylog.com"] + } + }, + { + "Opentracker": { + "http://www.opentracker.net/": ["opentracker.net"] + } + }, + { + "Opolen": { + "https://opolen.com.br": ["opolen.com.br"] + } + }, + { + "Optimizely": { + "https://www.optimizely.com/": ["optimizely.com"] + } + }, + { + "Oracle": { + "http://www.oracle.com/": ["eloqua.com", "maxymiser.com"] + } + }, + { + "ÖWA": { + "http://www.oewa.at/": ["oewa.at", "oewabox.at"] + } + }, + { + "Parse.ly": { + "http://parsely.com/": ["parsely.com"] + } + }, + { + "PersianStat.com": { + "http://www.persianstat.com/": ["persianstat.com"] + } + }, + { + "Phonalytics": { + "http://www.phonalytics.com/": ["phonalytics.com"] + } + }, + { + "phpMyVisites": { + "http://www.phpmyvisites.us/": ["phpmyvisites.us"] + } + }, + { + "Piwik": { + "http://piwik.org/": ["piwik.org"] + } + }, + { + "PixAnalytics": { + "https://pixanalytics.com/": ["pixanalytics.com"] + } + }, + { + "Poool": { + "http://poool.fr/": ["poool.fr"] + } + }, + { + "Pronunciator": { + "http://www.pronunciator.com/": [ + "pronunciator.com", + "visitorville.com" + ] + } + }, + { + "Qualaroo": { + "http://qualaroo.com/": ["kissinsights.com", "qualaroo.com"] + } + }, + { + "QuinStreet": { + "http://quinstreet.com/": ["thecounter.com"] + } + }, + { + "Quintelligence": { + "http://www.quintelligence.com/": ["quintelligence.com"] + } + }, + { + "RadarURL": { + "http://radarurl.com/": ["radarurl.com"] + } + }, + { + "Research Now": { + "http://www.researchnow.com/": [ + "researchnow.com", + "valuedopinions.co.uk" + ] + } + }, + { + "Retail Automata": { + "https://retailautomata.com": ["retailautomata.com"] + } + }, + { + "Revtracks": { + "http://revtrax.com/": ["revtrax.com"] + } + }, + { + "Ringier": { + "http://ringier.cz/": ["ringier.cz"] + } + }, + { + "Rollick": { + "https://gorollick.com": ["rollick.io"] + } + }, + { + "Roxr": { + "http://roxr.net/": ["getclicky.com", "roxr.net", "staticstuff.net"] + } + }, + { + "Safecount": { + "http://www.safecount.net/": [ + "dl-rms.com", + "dlqm.net", + "questionmarket.com", + "safecount.net" + ] + } + }, + { + "SageMetrics": { + "http://www.sagemetrics.com/": ["sageanalyst.net", "sagemetrics.com"] + } + }, + { + "Salesintelligence": { + "https://salesintelligence.pl/": ["plugin.management"] + } + }, + { + "SeeVolution": { + "https://www.seevolution.com/": ["seevolution.com", "svlu.net"] + } + }, + { + "Segment.io": { + "https://segment.io/": ["segment.io"] + } + }, + { + "SendPulse": { + "https://sendpulse.com/": ["sendpulse.com"] + } + }, + { + "SessionCam": { + "https://sessioncam.com/": ["sessioncam.com"], + "session-replay": "true" + } + }, + { + "ShinyStat": { + "http://www.shinystat.com/": ["shinystat.com"] + } + }, + { + "Smartlook": { + "https://www.smartlook.com/": ["smartlook.com"], + "session-replay": "true" + } + }, + { + "Snoobi": { + "http://www.snoobi.com/": ["snoobi.com"] + } + }, + { + "Sourcepoint": { + "https://www.sourcepoint.com/": ["summerhamster.com"] + } + }, + { + "Sputnik.ru": { + "http://sputnik.ru": ["sputnik.ru"] + } + }, + { + "StackTrack": { + "http://stat-track.com": ["stat-track.com"] + } + }, + { + "stat4u": { + "http://stat.4u.pl/": ["4u.pl"] + } + }, + { + "StatCounter": { + "http://statcounter.com/": ["statcounter.com"] + } + }, + { + "Statisfy": { + "http://statisfy.net": ["statisfy.net"] + } + }, + { + "STATSIT": { + "http://www.statsit.com/": ["statsit.com"] + } + }, + { + "Storeland": { + "https://storeland.ru/": ["storeland.ru"] + } + }, + { + "Stratigent": { + "http://www.stratigent.com/": ["stratigent.com"] + } + }, + { + "Tealium": { + "https://tealium.com": ["tealiumiq.com"] + } + }, + { + "TechSolutions": { + "https://www.techsolutions.com.tw/": ["techsolutions.com.tw"] + } + }, + { + "TENSQUARE": { + "http://www.tensquare.com/": ["tensquare.com"] + } + }, + { + "The Heron Partnership": { + "http://www.heronpartners.com.au/": [ + "heronpartners.com.au", + "marinsm.com" + ] + } + }, + { + "TNS": { + "http://www.tnsglobal.com/": [ + "sesamestats.com", + "statistik-gallup.net", + "tns-counter.ru", + "tns-cs.net", + "tnsglobal.com" + ] + } + }, + { + "TrackingSoft": { + "http://trackingsoft.com/": ["roia.biz", "trackingsoft.com"] + } + }, + { + "TrafficScore": { + "https://trafficscore.com/": ["trafficscore.com"] + } + }, + { + "Twitter": { + "https://twitter.com/": ["crashlytics.com", "tweetdeck.com"] + } + }, + { + "Umbel": { + "https://www.umbel.com/": ["umbel.com"] + } + }, + { + "User Local": { + "http://nakanohito.jp/": ["nakanohito.jp"] + } + }, + { + "V12 Data": { + "https://www.v12data.com/": ["v12data.com", "v12group.com"] + } + }, + { + "Vertster": { + "http://www.vertster.com/": ["vertster.com"] + } + }, + { + "VisiStat": { + "http://www.visistat.com/": ["sa-as.com", "visistat.com"] + } + }, + { + "Visit Streamer": { + "http://www.visitstreamer.com/": ["visitstreamer.com"] + } + }, + { + "vistrac": { + "http://vistrac.com/": ["vistrac.com"] + } + }, + { + "ViziSense": { + "http://www.vizisense.com/": ["vizisense.com", "vizisense.net"] + } + }, + { + "Webclicktracker": { + "http://www.webclicktracker.com/": ["webclicktracker.com"] + } + }, + { + "Web Stats": { + "http://www.onlinewebstats.com/": ["onlinewebstats.com"] + } + }, + { + "Web Tracking Services": { + "http://www.webtrackingservices.com/": [ + "web-stat.com", + "webtrackingservices.com" + ] + } + }, + { + "Web Traxs": { + "http://www.webtraxs.com/": ["webtraxs.com"] + } + }, + { + "Webtrekk": { + "http://www.webtrekk.com/": ["webtrekk.com", "webtrekk.net"] + } + }, + { + "Webtrends": { + "http://webtrends.com/": [ + "reinvigorate.net", + "webtrends.com", + "webtrendslive.com" + ] + } + }, + { + "White Ops": { + "https://www.whiteops.com/": ["adzmath.com", "whiteops.com"] + } + }, + { + "whos.amung.us": { + "http://whos.amung.us/": ["amung.us"] + } + }, + { + "Wingify": { + "http://wingify.com/": ["visualwebsiteoptimizer.com", "wingify.com"] + } + }, + { + "Woopra": { + "http://www.woopra.com/": ["woopra-ns.com", "woopra.com"] + } + }, + { + "WOW Analytics": { + "http://www.wowanalytics.co.uk/": ["wowanalytics.co.uk"] + } + }, + { + "WPP": { + "http://www.wpp.com/": ["compete.com"] + } + }, + { + "Wysistat": { + "http://www.wysistat.com/": ["wysistat.com"] + } + }, + { + "Yahoo!": { + "http://www.yahoo.com/": ["analytics.yahoo.com"] + } + }, + { + "YellowTracker": { + "http://www.yellowtracker.com/": ["yellowtracker.com"] + } + }, + { + "YSance": { + "https://www.ysance.com/data-services/fr/home/": ["y-track.com"] + } + } + ], + "Fingerprinting": [ + { + "Adabra": { + "https://www.adabra.com/": ["adabra.com"] + } + }, + { + "Adbot": { + "https://adbot.tw/": ["adbot.tw"] + } + }, + { + "AdGainerSolutions": { + "http://adgainersolutions.com/adgainer/": ["adgainersolutions.com"] + } + }, + { + "AdMaven": { + "https://ad-maven.com/": [ + "ad-maven.com", + "agreensdistra.info", + "boudja.com", + "rensovetors.info", + "wrethicap.info" + ] + } + }, + { + "Admicro": { + "http://www.admicro.vn/": ["admicro.vn", "vcmedia.vn"] + } + }, + { + "Adnium": { + "https://adnium.com": ["adnium.com", "montwam.top"] + } + }, + { + "AdScore": { + "http://www.adscoremarketing.com/": ["adsco.re"] + } + }, + { + "AdYouLike": { + "https://www.adyoulike.com/": ["pulpix.com"] + } + }, + { + "AivaLabs": { + "https://aivalabs.com": ["aivalabs.com"] + } + }, + { + "Albacross": { + "https://albacross.com": ["albacross.com"] + } + }, + { + "AppCast": { + "https://appcast.io/": ["appcast.io"] + } + }, + { + "AuditedMedia": { + "https://auditedmedia.com/": [ + "aamapi.com", + "aamsitecertifier.com", + "auditedmedia.com" + ] + } + }, + { + "Augur": { + "http://www.augur.io/": ["augur.io"] + } + }, + { + "Azet": { + "http://mediaimpact.sk/": ["azetklik.sk", "rsz.sk"] + } + }, + { + "BetssonPalantir": { + "https://betssonpalantir.com/": ["betssonpalantir.com"] + } + }, + { + "BigClick": { + "http://bigclick.me/": ["bgclck.me", "xcvgdf.party"] + } + }, + { + "BitMedia": { + "https://bitmedia.io/": ["bitmedia.io"] + } + }, + { + "BlueCava": { + "http://www.bluecava.com/": ["bluecava.com"] + } + }, + { + "BoostBox": { + "https://www.boostbox.com.br/": ["boostbox.com.br"] + } + }, + { + "Brandcrumb": { + "http://www.brandcrumb.com": ["brandcrumb.com"] + } + }, + { + "BreakTime": { + "https://www.breaktime.com.tw/": ["breaktime.com.tw"] + } + }, + { + "BrightEdge": { + "http://www.brightedge.com/": ["b0e8.com"] + } + }, + { + "C3 Metrics": { + "http://c3metrics.com/": [ + "attributionmodel.com", + "c3metrics.com", + "c3tag.com" + ] + } + }, + { + "CallSource": { + "https://www.callsource.com/": ["leadtrackingdata.com"] + } + }, + { + "CartsGuru": { + "https://carts.guru/": ["carts.guru"] + } + }, + { + "ClearLink": { + "https://www.clearlink.com/": ["clearlink.com"] + } + }, + { + "Clickayab": { + "http://www.clickyab.com": ["clickyab.com"] + } + }, + { + "ClickFrog": { + "https://clickfrog.ru/": [ + "bashirian.biz", + "buckridge.link", + "franecki.net", + "quitzon.net", + "reichelcormier.bid", + "wisokykulas.bid" + ] + } + }, + { + "ClickGuard": { + "https://www.clickguard.com/": ["clickguard.com"] + } + }, + { + "Clixtell": { + "https://www.clixtell.com/": ["clixtell.com"] + } + }, + { + "Consumable": { + "http://consumable.com/": ["consumable.com"] + } + }, + { + "dmpxs": { + "http://bob.dmpxs.com": ["dmpxs.com"] + } + }, + { + "ECSAnalytics": { + "https://www.theecsinc.com/": ["ecsanalytics.com"] + } + }, + { + "EroAdvertising": { + "http://www.ero-advertising.com/": ["ero-advertising.com"] + } + }, + { + "eyeReturn Marketing": { + "http://www.eyereturnmarketing.com/": [ + "eyereturn.com", + "eyereturnmarketing.com" + ] + } + }, + { + "Fanplayr": { + "https://fanplayr.com/": ["fanplayr.com"] + } + }, + { + "Foresee": { + "https://www.foresee.com": ["answerscloud.com", "foresee.com"] + } + }, + { + "Friends2Follow": { + "https://friends2follow.com/": ["antifraudjs.friends2follow.com"] + } + }, + { + "FuelX": { + "https://fuelx.com/": ["fuel451.com", "fuelx.com"] + } + }, + { + "Gleam": { + "https://gleam.io/": ["fraudjs.io"] + } + }, + { + "GrapheneMedia": { + "http://graphenemedia.in/": ["graphenedigitalanalytics.in"] + } + }, + { + "Gruner + Jahr": { + "http://www.guj.de/": ["ligatus.com"] + } + }, + { + "HilltopAds": { + "https://hilltopads.com/": ["hilltopads.net", "shoporielder.pro"] + } + }, + { + "HotelChamp": { + "https://www.hotelchamp.com": ["hotelchamp.com"] + } + }, + { + "iMedia": { + "http://www.imedia.cz": ["imedia.cz"] + } + }, + { + "IslayTech": { + "http://islay.tech": ["islay.tech"] + } + }, + { + "ismatlab.com": { + "http://ismatlab.com": ["ismatlab.com"] + } + }, + { + "Itch": { + "https://itch.io/": ["itch.io"] + } + }, + { + "justuno": { + "https://www.justuno.com/": ["justuno.com"] + } + }, + { + "Konduto": { + "http://konduto.com": ["k-analytix.com", "konduto.com"] + } + }, + { + "LeadsHub": { + "https://ztsrv.com/": ["ztsrv.com"] + } + }, + { + "lptracker": { + "https://lptracker.io/": ["lptracker.io"] + } + }, + { + "MaxMind": { + "https://www.maxmind.com/en/home": ["maxmind.com", "mmapiws.com"] + } + }, + { + "Mercadopago": { + "https://www.mercadopago.com/": ["mercadopago.com"] + } + }, + { + "Mobials": { + "http://mobials.com": ["mobials.com"] + } + }, + { + "Mystighty": { + "http://mystighty.info/": ["mystighty.info", "sweeterge.info"] + } + }, + { + "Negishim": { + "http://www.negishim.org": ["negishim.org"] + } + }, + { + "NuDataSecurity": { + "https://nudatasecurity.com/": ["nudatasecurity.com"] + } + }, + { + "OneAd": { + "https://www.onead.com.tw/": [ + "guoshipartners.com", + "onevision.com.tw" + ] + } + }, + { + "OnlineMetrix": { + "http://h.online-metrix.net": ["online-metrix.net"] + } + }, + { + "Opolen": { + "https://opolen.com.br": ["opolen.com.br"] + } + }, + { + "PaymentsMB": { + "https://paymentsmb.com": ["paymentsmb.com"] + } + }, + { + "Paypal": { + "https://www.paypal.com": ["simility.com"] + } + }, + { + "PerimeterX": { + "https://www.perimeterx.com": ["perimeterx.net"] + } + }, + { + "PixAnalytics": { + "https://pixanalytics.com/": ["pixanalytics.com"] + } + }, + { + "Pixlee": { + "https://www.pixlee.com/": ["pixlee.com"] + } + }, + { + "Poool": { + "http://poool.fr/": ["poool.fr"] + } + }, + { + "PPCProtect": { + "https://ppcprotect.com": ["ppcprotect.com"] + } + }, + { + "PrismApp": { + "https://www.prismapp.io/": ["prismapp.io"] + } + }, + { + "PrometheusIntelligenceTechnology": { + "https://prometheusintelligencetechnology.com/": [ + "prometheusintelligencetechnology.com" + ] + } + }, + { + "Provers": { + "http://provers.pro": ["provers.pro"] + } + }, + { + "Psonstrentie": { + "http://psonstrentie.info": ["psonstrentie.info"] + } + }, + { + "Rollick": { + "https://gorollick.com": ["rollick.io"] + } + }, + { + "SAP": { + "https://www.sap.com": ["seewhy.com"] + } + }, + { + "Selectable Media": { + "http://selectablemedia.com/": ["nabbr.com", "selectablemedia.com"] + } + }, + { + "Semantiqo": { + "http://semantiqo.com/": ["semantiqo.com"] + } + }, + { + "SendPulse": { + "https://sendpulse.com/": ["sendpulse.com"] + } + }, + { + "ShaftTraffic": { + "https://shafttraffic.com": ["libertystmedia.com"] + } + }, + { + "Shortest": { + "http://shorte.st/": ["shorte.st"] + } + }, + { + "SiftScience": { + "https://sift.com/": ["siftscience.com"] + } + }, + { + "Signifyd": { + "https://www.signifyd.com/": ["signifyd.com"] + } + }, + { + "Smi": { + "http://24smi.net": ["24smi.net"] + } + }, + { + "Socital": { + "https://www.socital.com": ["socital.com"] + } + }, + { + "Storeland": { + "https://storeland.ru/": ["storeland.ru"] + } + }, + { + "Stripe": { + "https://stripe.com": ["stripe.network"] + } + }, + { + "TechSolutions": { + "https://www.techsolutions.com.tw/": ["techsolutions.com.tw"] + } + }, + { + "tongdun.cn": { + "https://www.tongdun.cn/?lan=EN": ["fraudmetrix.cn", "tongdun.net"] + } + }, + { + "Upland": { + "https://uplandsoftware.com/": ["leadlander.com", "sf14g.com"] + } + }, + { + "Vendemore": { + "https://vendemore.com/": ["vendemore.com"] + } + }, + { + "VerticalHealth": { + "https://www.verticalhealth.com/": ["verticalhealth.net"] + } + }, + { + "Webmecanik": { + "https://www.webmecanik.com/": ["webmecanik.com"] + } + }, + { + "WideOrbit": { + "https://www.wideorbit.com/": ["dep-x.com"] + } + }, + { + "YSance": { + "https://www.ysance.com/data-services/fr/home/": ["y-track.com"] + } + }, + { + "ZafulAffiliate": { + "https://affiliate.zaful.com/": [ + "affasi.com", + "gw-ec.com", + "zaful.com" + ] + } + }, + { + "Zefir": { + "https://ze-fir.com/": ["ze-fir.com"] + } + } + ], + "Social": [ + { + "AddThis": { + "http://www.addthis.com/": [ + "addthis.com", + "addthiscdn.com", + "addthisedge.com", + "clearspring.com", + "connectedads.net", + "xgraph.com", + "xgraph.net" + ] + } + }, + { + "Causes": { + "http://www.causes.com/": ["causes.com"] + } + }, + { + "Digg": { + "http://digg.com/": ["digg.com"] + } + }, + { + "Facebook": { + "http://www.facebook.com/": [ + "apps.fbsbx.com", + "atdmt.com", + "facebook.com", + "facebook.de", + "facebook.fr", + "facebook.net", + "fb.com", + "fbsbx.com", + "friendfeed.com" + ] + } + }, + { + "Google": { + "http://www.google.com/": [ + "developers.google.com", + "gmail.com", + "googlemail.com", + "inbox.google.com", + "mail.google.com", + "orkut.com", + "plus.google.com", + "plusone.google.com", + "smartlock.google.com", + "voice.google.com", + "wave.google.com" + ] + } + }, + { + "LinkedIn": { + "http://www.linkedin.com/": ["licdn.com", "linkedin.com"] + } + }, + { + "Lockerz": { + "http://lockerz.com/": ["lockerz.com"] + } + }, + { + "Mail.Ru": { + "http://mail.ru/": ["list.ru", "mail.ru"] + } + }, + { + "Meebo": { + "https://www.meebo.com/": ["meebo.com", "meebocdn.net"] + } + }, + { + "Papaya": { + "http://papayamobile.com/": ["papayamobile.com"] + } + }, + { + "reddit": { + "http://www.reddit.com/": ["reddit.com"] + } + }, + { + "Shareaholic": { + "http://www.shareaholic.com/": ["shareaholic.com"] + } + }, + { + "ShareThis": { + "http://sharethis.com/": ["sharethis.com"] + } + }, + { + "StumbleUpon": { + "http://www.stumbleupon.com/": ["stumble-upon.com", "stumbleupon.com"] + } + }, + { + "Twitter": { + "https://twitter.com/": ["twimg.com", "twitter.com", "twitter.jp"] + } + }, + { + "VKontakte": { + "http://vk.com/": ["userapi.com", "vk.com", "vkontakte.ru"] + } + }, + { + "Yahoo!": { + "http://www.yahoo.com/": [ + "address.yahoo.com", + "alerts.yahoo.com", + "avatars.yahoo.com", + "buzz.yahoo.com", + "calendar.yahoo.com", + "edit.yahoo.com", + "legalredirect.yahoo.com", + "login.yahoo.com", + "mail.yahoo.com", + "my.yahoo.com", + "mybloglog.com", + "notepad.yahoo.com", + "pulse.yahoo.com", + "rocketmail.com", + "webmessenger.yahoo.com", + "ymail.com" + ] + } + } + ], + "Cryptomining": [ + { + "a.js": { + "http://zymerget.bid": [ + "alflying.date", + "alflying.win", + "anybest.site", + "flightsy.bid", + "flightsy.win", + "flightzy.bid", + "flightzy.date", + "flightzy.win", + "zymerget.bid", + "zymerget.faith" + ], + "performance": "true" + } + }, + { + "CashBeet": { + "http://cashbeet.com": ["cashbeet.com", "serv1swork.com"] + } + }, + { + "CoinHive": { + "https://coinhive.com": [ + "ad-miner.com", + "authedmine.com", + "bmst.pw", + "cnhv.co", + "coin-hive.com", + "coinhive.com", + "wsservices.org" + ], + "performance": "true" + } + }, + { + "CoinPot": { + "http://coinpot.co": ["coinpot.co"], + "performance": "true" + } + }, + { + "CryptoLoot": { + "https://crypto-loot.com": [ + "cryptaloot.pro", + "crypto-loot.com", + "cryptolootminer.com", + "flashx.pw", + "gitgrub.pro", + "reauthenticator.com", + "statdynamic.com", + "webmine.pro" + ], + "performance": "true" + } + }, + { + "CryptoWebMiner": { + "https://www.crypto-webminer.com": [ + "bitcoin-pay.eu", + "crypto-webminer.com", + "ethpocket.de", + "ethtrader.de" + ] + } + }, + { + "Gridcash": { + "https://www.gridcash.net/": ["adless.io", "gridcash.net"], + "performance": "true" + } + }, + { + "JSE": { + "http://jsecoin.com": [ + "freecontent.bid", + "freecontent.date", + "freecontent.stream", + "hashing.win", + "hostingcloud.racing", + "hostingcloud.science", + "jsecoin.com" + ], + "performance": "true" + } + }, + { + "MinerAlt": { + "http://mineralt.io": [ + "1q2w3.website", + "analytics.blue", + "aster18cdn.nl", + "belicimo.pw", + "besstahete.info", + "dinorslick.icu", + "feesocrald.com", + "gramombird.com", + "istlandoll.com", + "mepirtedic.com", + "mineralt.io", + "pampopholf.com", + "tercabilis.info", + "tulip18.com", + "vidzi.tv", + "yololike.space" + ], + "performance": "true" + } + }, + { + "Minescripts": { + "http://minescripts.info": ["minescripts.info", "sslverify.info"], + "performance": "true" + } + }, + { + "MineXMR": { + "http://minexmr.stream": ["minexmr.stream"], + "performance": "true" + } + }, + { + "NeroHut": { + "https://nerohut.com": ["nerohut.com", "nhsrv.cf"], + "performance": "true" + } + }, + { + "Service4refresh": { + "https://service4refresh.info": ["service4refresh.info"] + } + }, + { + "SpareChange": { + "http://sparechange.io": ["sparechange.io"], + "performance": "true" + } + }, + { + "SwiftMining": { + "https://swiftmining.win/": ["swiftmining.win"] + } + }, + { + "Webmine": { + "https://webmine.cz/": ["authedwebmine.cz", "webmine.cz"] + } + }, + { + "WebminePool": { + "http://webminepool.com": ["webminepool.com"], + "performance": "true" + } + }, + { + "Webmining": { + "https://webmining.co/": ["webmining.co"] + } + } + ] + } } diff --git a/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_safelist.json b/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_safelist.json index a2c80176b6..862e7db0de 100644 --- a/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_safelist.json +++ b/mobile/android/android-components/components/browser/engine-system/src/main/res/raw/domain_safelist.json @@ -1,12347 +1,6558 @@ { - "2leep.com": { - "properties": [ - "2leep.com" - ], - "resources": [ - "2leep.com" - ] - }, - "33Across": { - "properties": [ - "33across.com", - "tynt.com" - ], - "resources": [ - "33across.com", - "tynt.com" - ] - }, - "365Media": { - "properties": [ - "aggregateintelligence.com" - ], - "resources": [ - "365media.com", - "aggregateintelligence.com" - ] - }, - "4INFO": { - "properties": [ - "4info.com", - "adhaven.com" - ], - "resources": [ - "4info.com", - "adhaven.com" - ] - }, - "4mads": { - "properties": [ - "4mads.com" - ], - "resources": [ - "4mads.com" - ] - }, - "63 Squares": { - "properties": [ - "63labs.com" - ], - "resources": [ - "63labs.com", - "63squares.com", - "i-stats.com" - ] - }, - "Abax Interactive": { - "properties": [ - "abaxinteractive.com" - ], - "resources": [ - "abaxinteractive.com" - ] - }, - "Accelia": { - "properties": [ - "accelia.net", - "durasite.net" - ], - "resources": [ - "accelia.net", - "durasite.net" - ] - }, - "Accordant Media": { - "properties": [ - "accordantmedia.com" - ], - "resources": [ - "accordantmedia.com" - ] - }, - "Acquisio": { - "properties": [ - "acquisio.com", - "clickequations.net" - ], - "resources": [ - "acquisio.com", - "clickequations.net" - ] - }, - "Actisens": { - "properties": [ - "actisens.com", - "gestionpub.com" - ], - "resources": [ - "actisens.com", - "gestionpub.com" - ] - }, - "ActiveConversion": { - "properties": [ - "activeconversion.com", - "activemeter.com" - ], - "resources": [ - "activeconversion.com", - "activemeter.com" - ] - }, - "ActivEngage": { - "properties": [ - "activengage.com" - ], - "resources": [ - "activengage.com" - ] - }, - "Act-On": { - "properties": [ - "act-on.com", - "actonsoftware.com" - ], - "resources": [ - "act-on.com", - "actonsoftware.com" - ] - }, - "Acuity": { - "properties": [ - "acuity.com", - "acuityads.com", - "acuityplatform.com" - ], - "resources": [ - "acuity.com", - "acuityads.com", - "acuityplatform.com" - ] - }, - "Acxiom": { - "properties": [ - "acxiom.com", - "mm7.net" - ], - "resources": [ - "acxiom.com", - "acxiomapac.com", - "mm7.net", - "pippio.com" - ] - }, - "AD2ONE": { - "properties": [ - "ad2onegroup.com" - ], - "resources": [ - "ad2onegroup.com" - ] - }, - "Ad4Game": { - "properties": [ - "ad4game.com" - ], - "resources": [ - "ad4game.com" - ] - }, - "ad6media": { - "properties": [ - "ad6media.fr" - ], - "resources": [ - "ad6media.fr" - ] - }, - "Adabra": { - "properties": [ - "adabra.com" - ], - "resources": [ - "adabra.com" - ] - }, - "Adality": { - "properties": [ - "adality.de" - ], - "resources": [ - "adality.de", - "adrtx.net" - ] - }, - "AdaptiveAds": { - "properties": [ - "adaptiveads.com" - ], - "resources": [ - "adaptiveads.com" - ] - }, - "Adaptly": { - "properties": [ - "adaptly.com" - ], - "resources": [ - "adaptly.com" - ] - }, - "Adap.tv": { - "properties": [ - "adap.tv" - ], - "resources": [ - "adap.tv" - ] - }, - "Adara Media": { - "properties": [ - "adaramedia.com", - "opinmind.com", - "yieldoptimizer.com" - ], - "resources": [ - "adaramedia.com", - "opinmind.com", - "yieldoptimizer.com" - ] - }, - "Adatus": { - "properties": [ - "adatus.com" - ], - "resources": [ - "adatus.com" - ] - }, - "Adbot": { - "properties": [ - "adbot.tw" - ], - "resources": [ - "adbot.tw" - ] - }, - "Adbrain": { - "properties": [ - "adbrain.com" - ], - "resources": [ - "adbrain.com", - "adbrn.com" - ] - }, - "adBrite": { - "properties": [ - "adbrite.com" - ], - "resources": [ - "adbrite.com" - ] - }, - "Adbroker.de": { - "properties": [ - "adbroker.de" - ], - "resources": [ - "adbroker.de" - ] - }, - "Adchemy": { - "properties": [ - "adchemy.com" - ], - "resources": [ - "adchemy.com" - ] - }, - "AdCirrus": { - "properties": [ - "adcirrus.com" - ], - "resources": [ - "adcirrus.com" - ] - }, - "Ad Decisive": { - "properties": [ - "a2dfp.net", - "addecisive.com" - ], - "resources": [ - "a2dfp.net", - "addecisive.com" - ] - }, - "AddFreeStats": { - "properties": [ - "3dstats.com", - "addfreestats.com" - ], - "resources": [ - "3dstats.com", - "addfreestats.com" - ] - }, - "addGloo": { - "properties": [ - "addgloo.com" - ], - "resources": [ - "addgloo.com" - ] - }, - "AddThis": { - "properties": [ - "addthis.com" - ], - "resources": [ - "addthis.com", - "addthiscdn.com", - "addthisedge.com", - "clearspring.com", - "connectedads.net", - "xgraph.com", - "xgraph.net" - ] - }, - "Addvantage Media": { - "properties": [ - "addvantagemedia.com" - ], - "resources": [ - "addvantagemedia.com" - ] - }, - "Ad Dynamo": { - "properties": [ - "addynamo.com" - ], - "resources": [ - "addynamo.com", - "addynamo.net" - ] - }, - "Adelphic": { - "properties": [ - "adelphic.com" - ], - "resources": [ - "adelphic.com", - "ipredictive.com" - ] - }, - "AdEngage": { - "properties": [ - "adengage.com" - ], - "resources": [ - "adengage.com" - ] - }, - "AD Europe": { - "properties": [ - "adeurope.com" - ], - "resources": [ - "adeurope.com" - ] - }, - "AdExtent": { - "properties": [ - "adextent.com" - ], - "resources": [ - "adextent.com" - ] - }, - "AdF.ly": { - "properties": [ - "adf.ly" - ], - "resources": [ - "adf.ly" - ] - }, - "Adfonic": { - "properties": [ - "adfonic.com" - ], - "resources": [ - "adfonic.com" - ] - }, - "Adforge": { - "properties": [ - "adforgeinc.com" - ], - "resources": [ - "adforgeinc.com" - ] - }, - "Adform": { - "properties": [ - "adform.com" - ], - "resources": [ - "adform.com", - "adform.net", - "adformdsp.net" - ] - }, - "AdFox": { - "properties": [ - "adfox.ru" - ], - "resources": [ - "adfox.ru" - ] - }, - "AdFrontiers": { - "properties": [ - "adfrontiers.com" - ], - "resources": [ - "adfrontiers.com" - ] - }, - "Adfunky": { - "properties": [ - "adfunky.com", - "adfunkyserver.com" - ], - "resources": [ - "adfunky.com", - "adfunkyserver.com" - ] - }, - "Adfusion": { - "properties": [ - "adfusion.com" - ], - "resources": [ - "adfusion.com" - ] - }, - "AdGainerSolutions": { - "properties": [ - "adgainersolutions.com" - ], - "resources": [ - "adgainersolutions.com" - ] - }, - "AdGent Digital": { - "properties": [ - "adgentdigital.com" - ], - "resources": [ - "adgentdigital.com", - "shorttailmedia.com" - ] - }, - "AdGibbon": { - "properties": [ - "adgibbon.com" - ], - "resources": [ - "adgibbon.com" - ] - }, - "Adglare": { - "properties": [ - "adglare.com" - ], - "resources": [ - "adglare.com", - "adglare.net" - ] - }, - "adhood": { - "properties": [ - "adhood.com" - ], - "resources": [ - "adhood.com" - ] - }, - "Adiant": { - "properties": [ - "adblade.com", - "adiant.com" - ], - "resources": [ - "adblade.com", - "adiant.com" - ] - }, - "AdInsight": { - "properties": [ - "responsetap.com" - ], - "resources": [ - "adinsight.com", - "adinsight.eu", - "responsetap.com" - ] - }, - "AdIQuity": { - "properties": [ - "adiquity.com" - ], - "resources": [ - "adiquity.com" - ] - }, - "ADITION": { - "properties": [ - "adition.com" - ], - "resources": [ - "adition.com" - ] - }, - "AdJug": { - "properties": [ - "adjug.com" - ], - "resources": [ - "adjug.com" - ] - }, - "AdJuggler": { - "properties": [ - "adjuggler.com", - "adjuggler.net" - ], - "resources": [ - "adjuggler.com", - "adjuggler.net" - ] - }, - "Adjust": { - "properties": [ - "adjust.com" - ], - "resources": [ - "adjust.com" - ] - }, - "AdKeeper": { - "properties": [ - "keep.com" - ], - "resources": [ - "adkeeper.com", - "akncdn.com", - "keep.com" - ] - }, - "AdKernel": { - "properties": [ - "adkernel.com" - ], - "resources": [ - "adkernel.com" - ] - }, - "Ad Knife": { - "properties": [ - "adknife.com" - ], - "resources": [ - "adknife.com" - ] - }, - "Adknowledge": { - "properties": [ - "adknowledge.com", - "adparlor.com", - "bidsystem.com", - "cubics.com", - "lookery.com" - ], - "resources": [ - "adknowledge.com", - "adparlor.com", - "bidsystem.com", - "cubics.com", - "lookery.com" - ] - }, - "AdLantis": { - "properties": [ - "adimg.net", - "adlantis.jp", - "www.adlantis.jp" - ], - "resources": [ - "adimg.net", - "adlantis.jp", - "www.adlantis.jp" - ] - }, - "AdLeave": { - "properties": [ - "adleave.com" - ], - "resources": [ - "adleave.com" - ] - }, - "Adlibrium": { - "properties": [ - "adlibrium.com" - ], - "resources": [ - "adlibrium.com" - ] - }, - "Adloox": { - "properties": [ - "adloox.com" - ], - "resources": [ - "adloox.com", - "adlooxtracking.com" - ] - }, - "Adlucent": { - "properties": [ - "adlucent.com" - ], - "resources": [ - "adlucent.com" - ] - }, - "Ad Magnet": { - "properties": [ - "admagnet.com", - "admagnet.net" - ], - "resources": [ - "admagnet.com", - "admagnet.net" - ] - }, - "Admarketplace": { - "properties": [ - "admarketplace.com" - ], - "resources": [ - "admarketplace.com", - "admarketplace.net", - "ampxchange.com" - ] - }, - "AdMarvel": { - "properties": [ - "admarvel.com" - ], - "resources": [ - "admarvel.com" - ] - }, - "AdMatrix": { - "properties": [ - "admatrix.jp" - ], - "resources": [ - "admatrix.jp" - ] - }, - "AdMaven": { - "properties": [ - "ad-maven.com" - ], - "resources": [ - "ad-maven.com", - "agreensdistra.info", - "boudja.com", - "rensovetors.info", - "wrethicap.info" - ] - }, - "AdMaximizer Network": { - "properties": [ - "admaximizer.com" - ], - "resources": [ - "admaximizer.com" - ] - }, - "AdMedia": { - "properties": [ - "admedia.com" - ], - "resources": [ - "admedia.com" - ] - }, - "Admeta": { - "properties": [ - "admeta.com", - "atemda.com" - ], - "resources": [ - "admeta.com", - "atemda.com" - ] - }, - "Admicro": { - "properties": [ - "admicro.vn" - ], - "resources": [ - "admicro.vn", - "vcmedia.vn" - ] - }, - "Admixer": { - "properties": [ - "admixer.co.kr" - ], - "resources": [ - "admixer.co.kr" - ] - }, - "Admized": { - "properties": [ - "admized.com" - ], - "resources": [ - "admized.com" - ] - }, - "Admobile": { - "properties": [ - "admobile.com" - ], - "resources": [ - "admobile.com" - ] - }, - "Admotion": { - "properties": [ - "admotion.com" - ], - "resources": [ - "admotion.com", - "nspmotion.com" - ] - }, - "Adnetik": { - "properties": [ - "wtp101.com" - ], - "resources": [ - "adnetik.com", - "wtp101.com" - ] - }, - "AdNetwork.net": { - "properties": [ - "adnetwork.net" - ], - "resources": [ - "adnetwork.net" - ] - }, - "Adnium": { - "properties": [ - "adnium.com" - ], - "resources": [ - "adnium.com", - "montwam.top" - ] - }, - "adnologies": { - "properties": [ - "adnologies.com", - "heias.com" - ], - "resources": [ - "adnologies.com", - "heias.com" - ] - }, - "Adobe": { - "properties": [ - "adobe.com", - "livefyre.com", - "typekit.com" - ], - "resources": [ - "2o7.net", - "adobe.com", - "auditude.com", - "demdex.com", - "demdex.net", - "dmtracker.com", - "efrontier.com", - "everestads.net", - "everestjs.net", - "everesttech.net", - "fyre.co", - "hitbox.com", - "livefyre.com", - "omniture.com", - "omtrdc.net", - "touchclarity.com", - "typekit.com" - ] - }, - "AdOcean": { - "properties": [ - "adocean-global.com", - "adocean.pl" - ], - "resources": [ - "adocean-global.com", - "adocean.pl" - ] - }, - "Adometry": { - "properties": [ - "adometry.com" - ], - "resources": [ - "adometry.com", - "dmtry.com" - ] - }, - "Adomik": { - "properties": [ - "adomik.com" - ], - "resources": [ - "adomik.com" - ] - }, - "AdOnion": { - "properties": [ - "adonion.com" - ], - "resources": [ - "adonion.com" - ] - }, - "Adorika": { - "properties": [ - "clickotmedia.com" - ], - "resources": [ - "clickotmedia.com" - ] - }, - "Adotmob": { - "properties": [ - "adotmob.com" - ], - "resources": [ - "adotmob.com" - ] - }, - "ADP Dealer Services": { - "properties": [ - "cdkglobal.com" - ], - "resources": [ - "admission.net", - "adpdealerservices.com", - "cdkglobal.com", - "cobalt.com" - ] - }, - "ad pepper media": { - "properties": [ - "adpepper.com", - "adpepper.us" - ], - "resources": [ - "adpepper.com", - "adpepper.us" - ] - }, - "AdPerfect": { - "properties": [ - "adperfect.com" - ], - "resources": [ - "adperfect.com" - ] - }, - "Adperium": { - "properties": [ - "adperium.com" - ], - "resources": [ - "adperium.com" - ] - }, - "Adpersia": { - "properties": [ - "adpersia.com" - ], - "resources": [ - "adpersia.com" - ] - }, - "adPrecision": { - "properties": [ - "adprecision.net", - "adprs.net" - ], - "resources": [ - "adprecision.net", - "adprs.net", - "aprecision.net" - ] - }, - "AdPredictive": { - "properties": [ - "adpredictive.com" - ], - "resources": [ - "adpredictive.com" - ] - }, - "AdReactor": { - "properties": [ - "adreactor.com" - ], - "resources": [ - "adreactor.com" - ] - }, - "AdReady": { - "properties": [ - "digitalremedy.com" - ], - "resources": [ - "adready.com", - "adreadytractions.com", - "digitalremedy" - ] - }, - "AdRevolution": { - "properties": [ - "adrevolution.com" - ], - "resources": [ - "adrevolution.com" - ] - }, - "AdRiver": { - "properties": [ - "adriver.ru" - ], - "resources": [ - "adriver.ru" - ] - }, - "adrolays": { - "properties": [ - "contactimpact.de" - ], - "resources": [ - "adrolays.com", - "adrolays.de", - "contactimpact.de" - ] - }, - "AdRoll": { - "properties": [ - "adroll.com" - ], - "resources": [ - "adroll.com" - ] - }, - "adscale": { - "properties": [ - "stroeer.de" - ], - "resources": [ - "adscale.de", - "stroeer.de" - ] - }, - "Adscience": { - "properties": [ - "adscience.nl" - ], - "resources": [ - "adscience.nl" - ] - }, - "AdScore": { - "properties": [ - "adscoremarketing.com" - ], - "resources": [ - "adsco.re" - ] - }, - "AdServerPub": { - "properties": [ - "adserverpub.com" - ], - "resources": [ - "adserverpub.com" - ] - }, - "AdShuffle": { - "properties": [ - "adshuffle.com" - ], - "resources": [ - "adshuffle.com" - ] - }, - "AdSide": { - "properties": [ - "adside.com", - "doclix.com" - ], - "resources": [ - "adside.com", - "doclix.com" - ] - }, - "AdSpeed": { - "properties": [ - "adspeed.com", - "adspeed.net" - ], - "resources": [ - "adspeed.com", - "adspeed.net" - ] - }, - "Adsperity": { - "properties": [ - "adsperity.com" - ], - "resources": [ - "adsperity.com" - ] - }, - "AdSpirit": { - "properties": [ - "adspirit.com", - "adspirit.de", - "adspirit.net" - ], - "resources": [ - "adspirit.com", - "adspirit.de", - "adspirit.net" - ] - }, - "Adsrevenue.net": { - "properties": [ - "adsrevenue.net" - ], - "resources": [ - "adsrevenue.net" - ] - }, - "AdStir": { - "properties": [ - "ad-stir.com" - ], - "resources": [ - "ad-stir.com" - ] - }, - "AdsTours": { - "properties": [ - "adstours.com", - "clickintext.net" - ], - "resources": [ - "adstours.com", - "clickintext.net" - ] - }, - "Adsty": { - "properties": [ - "adsty.com", - "adx1.com" - ], - "resources": [ - "adsty.com", - "adx1.com" - ] - }, - "Adsupply": { - "properties": [ - "4dsply.com", - "adsupply.com" - ], - "resources": [ - "4dsply.com", - "adsupply.com" - ] - }, - "Adswizz": { - "properties": [ - "adswizz.com" - ], - "resources": [ - "adswizz.com" - ] - }, - "ADTECH": { - "properties": [ - "adtech.com", - "adtech.de", - "adtechus.com" - ], - "resources": [ - "adtech.com", - "adtech.de", - "adtechus.com" - ] - }, - "Adtegrity.com": { - "properties": [ - "adtegrity.com", - "adtegrity.net" - ], - "resources": [ - "adtegrity.com", - "adtegrity.net" - ] - }, - "ADTELLIGENCE": { - "properties": [ - "adtelligence.de" - ], - "resources": [ - "adtelligence.de" - ] - }, - "Adthink": { - "properties": [ - "adthink.com" - ], - "resources": [ - "adthink.com", - "audienceinsights.net" - ] - }, - "AdTiger": { - "properties": [ - "adtiger.de" - ], - "resources": [ - "adtiger.de" - ] - }, - "AdTruth": { - "properties": [ - "adtruth.com" - ], - "resources": [ - "adtruth.com" - ] - }, - "Adult AdWorld": { - "properties": [ - "adultadworld.com" - ], - "resources": [ - "adultadworld.com" - ] - }, - "Adultmoda": { - "properties": [ - "adultmoda.com" - ], - "resources": [ - "adultmoda.com" - ] - }, - "Adventive": { - "properties": [ - "adventive.com" - ], - "resources": [ - "adventive.com" - ] - }, - "Adventori": { - "properties": [ - "adventori.com" - ], - "resources": [ - "adventori.com" - ] - }, - "Adverline": { - "properties": [ - "adnext.fr", - "adverline.com" - ], - "resources": [ - "adnext.fr", - "adverline.com" - ] - }, - "Adversal.com": { - "properties": [ - "adv-adserver.com", - "adversal.com" - ], - "resources": [ - "adv-adserver.com", - "adversal.com" - ] - }, - "Adverticum": { - "properties": [ - "adsmart.com", - "adverticum.com", - "adverticum.net" - ], - "resources": [ - "adsmart.com", - "adverticum.com", - "adverticum.net" - ] - }, - "Advertise.com": { - "properties": [ - "advertise.com" - ], - "resources": [ - "advertise.com" - ] - }, - "AdvertiseSpace": { - "properties": [ - "advertisespace.com" - ], - "resources": [ - "advertisespace.com" - ] - }, - "Advert Stream": { - "properties": [ - "advertstream.com" - ], - "resources": [ - "advertstream.com" - ] - }, - "Advisor Media": { - "properties": [ - "advisormedia.cz" - ], - "resources": [ - "advisormedia.cz" - ] - }, - "Adworx": { - "properties": [ - "adworx.at", - "adworx.be", - "adworx.nl" - ], - "resources": [ - "adworx.at", - "adworx.be", - "adworx.nl" - ] - }, - "AdXpansion": { - "properties": [ - "adxpansion.com" - ], - "resources": [ - "adxpansion.com" - ] - }, - "Adxvalue": { - "properties": [ - "adxvalue.com", - "adxvalue.de" - ], - "resources": [ - "adxvalue.com", - "adxvalue.de" - ] - }, - "adyard": { - "properties": [ - "adyard.de" - ], - "resources": [ - "adyard.de" - ] - }, - "AdYield": { - "properties": [ - "adyield.com" - ], - "resources": [ - "adxyield.com", - "adyield.com" - ] - }, - "AdYouLike": { - "properties": [ - "adyoulike.com" - ], - "resources": [ - "adyoulike.com", - "omnitagjs.com", - "pulpix.com" - ] - }, - "ADZ": { - "properties": [ - "adzcentral.com" - ], - "resources": [ - "adzcentral.com" - ] - }, - "Adzerk": { - "properties": [ - "adzerk.com" - ], - "resources": [ - "adzerk.com", - "adzerk.net" - ] - }, - "adzly": { - "properties": [ - "adzly.com" - ], - "resources": [ - "adzly.com" - ] - }, - "Aegis Group": { - "properties": [ - "aemedia.com", - "bluestreak.com", - "dentsuaegisnetwork.com" - ], - "resources": [ - "aemedia.com", - "bluestreak.com", - "dentsuaegisnetwork.com" - ] - }, - "AERIFY MEDIA": { - "properties": [ - "aerifymedia.com", - "anonymous-media.com" - ], - "resources": [ - "aerifymedia.com", - "anonymous-media.com" - ] - }, - "Affectv": { - "properties": [ - "affectv.co.uk" - ], - "resources": [ - "affectv.co.uk" - ] - }, - "affilinet": { - "properties": [ - "affili.net", - "affilinet-inside.de" - ], - "resources": [ - "affili.net", - "affilinet-inside.de", - "banner-rotation.com", - "successfultogether.co.uk" - ] - }, - "Affine": { - "properties": [ - "affine.tv", - "affinesystems.com" - ], - "resources": [ - "affine.tv", - "affinesystems.com" - ] - }, - "Affinity": { - "properties": [ - "affinity.com" - ], - "resources": [ - "affinity.com" - ] - }, - "AfterDownload": { - "properties": [ - "afdads.com", - "afterdownload.com" - ], - "resources": [ - "afdads.com", - "afterdownload.com" - ] - }, - "AIData": { - "properties": [ - "advombat.ru", - "aidata.me" - ], - "resources": [ - "advombat.ru", - "aidata.me" - ] - }, - "Aim4Media": { - "properties": [ - "aim4media.com" - ], - "resources": [ - "aim4media.com" - ] - }, - "Airpush": { - "properties": [ - "airpush.com" - ], - "resources": [ - "airpush.com" - ] - }, - "AivaLabs": { - "properties": [ - "aivalabs.com" - ], - "resources": [ - "aivalabs.com" - ] - }, - "a.js": { - "properties": [ - "alflying.date", - "alflying.win", - "anybest.site", - "flightsy.bid", - "flightsy.win", - "flightzy.bid", - "flightzy.date", - "flightzy.win", - "zymerget.bid", - "zymerget.faith" - ], - "resources": [ - "alflying.date", - "alflying.win", - "anybest.site", - "flightsy.bid", - "flightsy.win", - "flightzy.bid", - "flightzy.date", - "flightzy.win", - "zymerget.bid", - "zymerget.faith" - ] - }, - "AK": { - "properties": [ - "aggregateknowledge.com", - "agkn.com" - ], - "resources": [ - "aggregateknowledge.com", - "agkn.com" - ] - }, - "Akamai": { - "properties": [ - "akamai.com" - ], - "resources": [ - "abmr.net", - "akamai.com", - "edgesuite.net", - "go-mpulse.net", - "imiclk.com" - ] - }, - "AKQA": { - "properties": [ - "akqa.com" - ], - "resources": [ - "akqa.com", - "srtk.net" - ] - }, - "Albacross": { - "properties": [ - "albacross.com" - ], - "resources": [ - "albacross.com" - ] - }, - "AllStarMediaGroup": { - "properties": [ - "allstarmediagroup.com" - ], - "resources": [ - "allstarmediagroup.com" - ] - }, - "Aloodo": { - "properties": [ - "aloodo.com" - ], - "resources": [ - "aloodo.com" - ] - }, - "AlterGeo": { - "properties": [ - "altergeo.ru" - ], - "resources": [ - "altergeo.ru" - ] - }, - "Amadesa": { - "properties": [ - "amadesa.com" - ], - "resources": [ - "amadesa.com" - ] - }, - "Amazing Counters": { - "properties": [ - "amazingcounters.com" - ], - "resources": [ - "amazingcounters.com" - ] - }, - "Amazon.com": { - "properties": [ - "6pm.com", - "abebooks.co.uk", - "abebooks.com", - "abebooks.de", - "abebooks.fr", - "abebooks.it", - "acx.com", - "alexa.com", - "amazon.ae", - "amazon.ca", - "amazon.cn", - "amazon.co.jp", - "amazon.co.uk", - "amazon.com", - "amazon.com.au", - "amazon.com.br", - "amazon.com.mx", - "amazon.com.sg", - "amazon.com.tr", - "amazon.de", - "amazon.es", - "amazon.fr", - "amazon.in", - "amazon.it", - "amazon.nl", - "amazon.sa", - "amazonaws.com", - "amazoninspire.com", - "assoc-amazon.com", - "audible.co.jp", - "audible.co.uk", - "audible.com", - "audible.de", - "audible.fr", - "audible.in", - "audible.it", - "bookdepository.com", - "boxofficemojo.com", - "brilliancepublishing.com", - "comixology.com", - "createspace.com", - "dpreview.com", - "dpreview.in", - "eastdane.com", - "fabric.com", - "goodreads.com", - "iberlibro.com", - "imdb.com", - "imdb.de", - "junglee.com", - "look.com", - "pillpack.com", - "shopbop.com", - "souq.com", - "twitch.com", - "twitch.tv", - "wholefoodsmarket.com", - "withoutabox.com", - "woot.com", - "yoyo.com", - "zappos.com", - "zvab.com" - ], - "resources": [ - "alexa.com", - "alexametrics.com", - "amazon-adsystem.com", - "amazon.ca", - "amazon.co.jp", - "amazon.co.uk", - "amazon.com", - "amazon.de", - "amazon.es", - "amazon.fr", - "amazon.it", - "amazonaws.com", - "assoc-amazon.com", - "cloudfront.net", - "ssl-images-amazon.com" - ] - }, - "Ambient Digital": { - "properties": [ - "adnetwork.vn", - "ambientdigital.com.vn" - ], - "resources": [ - "adnetwork.vn", - "ambientdigital.com.vn" - ] - }, - "Amobee": { - "properties": [ - "amobee.com", - "smartclip.com" - ], - "resources": [ - "adconion.com", - "amgdgt.com", - "amobee.com", - "euroclick.com", - "smartclip.com", - "turn.com" - ] - }, - "Amplitude": { - "properties": [ - "amplitude.com" - ], - "resources": [ - "amplitude.com" - ] - }, - "AndBeyond": { - "properties": [ - "andbeyond.media" - ], - "resources": [ - "andbeyond.media" - ] - }, - "anormal-media.de": { - "properties": [ - "anormal-media.de", - "primawebtools.de" - ], - "resources": [ - "anormal-media.de", - "anormal-tracker.de", - "primawebtools.de" - ] - }, - "Answers.com": { - "properties": [ - "answers.com", - "dsply.com" - ], - "resources": [ - "dsply.com" - ] - }, - "AOL": { - "properties": [ - "5min.com", - "adsonar.com", - "advertising.com", - "aim.com", - "aol.com", - "aolcdn.com", - "aoltechguru.com", - "atwola.com", - "autoblog.com", - "cambio.com", - "dailyfinance.com", - "editions.com", - "engadget.com", - "games.com", - "homesessive.com", - "huffingtonpost.com", - "leadback.com", - "makers.com", - "mandatory.com", - "mapquest.com", - "moviefone.com", - "noisecreep.com", - "patch.com", - "pawnation.com", - "shortcuts.com", - "shoutcast.com", - "spinner.com", - "stylelist.com", - "stylemepretty.com", - "surphace.com", - "tacoda.net", - "techcrunch.com", - "theboombox.com", - "theboot.com", - "userplane.com", - "winamp.com" - ], - "resources": [ - "5min.com", - "adsonar.com", - "adtechjp.com", - "advertising.com", - "aim.com", - "aol.com", - "aolcdn.com", - "aolcloud.net", - "atwola.com", - "editions.com", - "leadback.com", - "mapquest.com", - "patch.com", - "shortcuts.com", - "shoutcast.com", - "spinner.com", - "surphace.com", - "tacoda.net", - "userplane.com", - "vidible.tv", - "winamp.com" - ] - }, - "AppCast": { - "properties": [ - "appcast.io" - ], - "resources": [ - "appcast.io" - ] - }, - "Appenda": { - "properties": [ - "appenda.com" - ], - "resources": [ - "appenda.com" - ] - }, - "AppFlood": { - "properties": [ - "appflood.com" - ], - "resources": [ - "appflood.com" - ] - }, - "Appier": { - "properties": [ - "appier.com" - ], - "resources": [ - "appier.com" - ] - }, - "Applifier": { - "properties": [ - "applifier.com" - ], - "resources": [ - "applifier.com" - ] - }, - "Applovin": { - "properties": [ - "applovin.com" - ], - "resources": [ - "applovin.com" - ] - }, - "AppNexus": { - "properties": [ - "adlantic.nl", - "adnxs.com", - "adrdgt.com", - "appnexus.com" - ], - "resources": [ - "adlantic.nl", - "adnxs.com", - "adrdgt.com", - "appnexus.com" - ] - }, - "AppsFlyer": { - "properties": [ - "appsflyer.com" - ], - "resources": [ - "appsflyer.com" - ] - }, - "appssavvy": { - "properties": [ - "appssavvy.com" - ], - "resources": [ - "appssavvy.com" - ] - }, - "Arkwrights Homebrew": { - "properties": [ - "whiskyandwines.com" - ], - "resources": [ - "arkwrightshomebrew.com", - "ctasnet.com", - "whiskyandwines.com" - ] - }, - "AT Internet": { - "properties": [ - "atinternet.com", - "xiti.com" - ], - "resources": [ - "at-o.net", - "atinternet.com", - "hit-parade.com", - "xiti.com" - ] - }, - "ATN": { - "properties": [ - "affiliatetracking.com" - ], - "resources": [ - "affiliatetracking.com" - ] - }, - "Atoomic.com": { - "properties": [ - "atoomic.com" - ], - "resources": [ - "atoomic.com" - ] - }, - "Atrinsic": { - "properties": [ - "atrinsic.com" - ], - "resources": [ - "atrinsic.com" - ] - }, - "AT&T": { - "properties": [ - "att.com", - "yp.com" - ], - "resources": [ - "att.com", - "yp.com" - ] - }, - "Attracta": { - "properties": [ - "attracta.com" - ], - "resources": [ - "attracta.com" - ] - }, - "Audience2Media": { - "properties": [ - "audience2media.com" - ], - "resources": [ - "audience2media.com" - ] - }, - "Audience Ad Network": { - "properties": [ - "audienceadnetwork.com" - ], - "resources": [ - "audienceadnetwork.com" - ] - }, - "AudienceScience": { - "properties": [ - "audiencescience.com" - ], - "resources": [ - "audiencescience.com", - "revsci.net", - "targetingmarketplace.com", - "wunderloop.net" - ] - }, - "AuditedMedia": { - "properties": [ - "auditedmedia.com" - ], - "resources": [ - "aamapi.com", - "aamsitecertifier.com", - "auditedmedia.com" - ] - }, - "Augme": { - "properties": [ - "hipcricket.com" - ], - "resources": [ - "augme.com", - "hipcricket.com" - ] - }, - "Augur": { - "properties": [ - "augur.io" - ], - "resources": [ - "augur.io" - ] - }, - "AUTOCENTRE.UA": { - "properties": [ - "am.ua", - "autocentre.ua" - ], - "resources": [ - "am.ua", - "autocentre.ua" - ] - }, - "Automattic": { - "properties": [ - "automattic.com", - "gravatar.com", - "intensedebate.com", - "polldaddy.com" - ], - "resources": [ - "automattic.com", - "gravatar.com", - "intensedebate.com", - "polldaddy.com", - "pubmine.com" - ] - }, - "Avalanchers": { - "properties": [ - "avalanchers.com" - ], - "resources": [ - "avalanchers.com" - ] - }, - "AvantLink": { - "properties": [ - "avantlink.com", - "avantmetrics.com" - ], - "resources": [ - "avantlink.com", - "avmws.com" - ] - }, - "Avocet": { - "properties": [ - "avocet.io" - ], - "resources": [ - "avocet.io" - ] - }, - "Avsads": { - "properties": [ - "avsads.com" - ], - "resources": [ - "avsads.com" - ] - }, - "AWeber": { - "properties": [ - "aweber.com" - ], - "resources": [ - "aweber.com" - ] - }, - "Awin": { - "properties": [ - "awin.com" - ], - "resources": [ - "awin.com", - "digitalwindow.com", - "dwin1.com", - "perfiliate.com" - ] - }, - "Awio": { - "properties": [ - "awio.com", - "w3counter.com" - ], - "resources": [ - "awio.com", - "w3counter.com", - "w3roi.com" - ] - }, - "Azet": { - "properties": [ - "azet.sk", - "mediaimpact.sk" - ], - "resources": [ - "azet.sk", - "azetklik.sk", - "mediaimpact.sk", - "rsz.sk" - ] - }, - "BackBeat Media": { - "properties": [ - "backbeatmedia.com" - ], - "resources": [ - "backbeatmedia.com" - ] - }, - "Bannerconnect": { - "properties": [ - "bannerconnect.net" - ], - "resources": [ - "bannerconnect.net" - ] - }, - "Barilliance": { - "properties": [ - "barilliance.com" - ], - "resources": [ - "barilliance.com" - ] - }, - "BaronsNetworks": { - "properties": [ - "baronsoffers.com" - ], - "resources": [ - "baronsoffers.com" - ] - }, - "Batanga Network": { - "properties": [ - "batanga.com", - "corp.vix.com", - "vix.com" - ], - "resources": [ - "batanga.com", - "batanganetwork.com", - "vix.com" - ] - }, - "Baynote": { - "properties": [ - "baynote.com" - ], - "resources": [ - "baynote.com", - "baynote.net" - ] - }, - "Bazaarvoice": { - "properties": [ - "bazaarvoice.com" - ], - "resources": [ - "bazaarvoice.com" - ] - }, - "BeachFront": { - "properties": [ - "beachfront.com" - ], - "resources": [ - "beachfront.com" - ] - }, - "Beanstock Media": { - "properties": [ - "beanstockmedia.com" - ], - "resources": [ - "beanstockmedia.com" - ] - }, - "beencounter": { - "properties": [ - "beencounter.com" - ], - "resources": [ - "beencounter.com" - ] - }, - "Begun": { - "properties": [ - "begun.ru" - ], - "resources": [ - "begun.ru" - ] - }, - "belboon": { - "properties": [ - "belboon.com" - ], - "resources": [ - "adbutler.de", - "belboon.com" - ] - }, - "Belstat": { - "properties": [ - "belstat.be", - "belstat.com", - "belstat.de", - "belstat.fr", - "belstat.nl" - ], - "resources": [ - "belstat.be", - "belstat.com", - "belstat.de", - "belstat.fr", - "belstat.nl" - ] - }, - "Betgenius": { - "properties": [ - "betgenius.com", - "connextra.com" - ], - "resources": [ - "betgenius.com", - "connextra.com" - ] - }, - "BetssonPalantir": { - "properties": [ - "betssonpalantir.com" - ], - "resources": [ - "betssonpalantir.com" - ] - }, - "BetweenDigital": { - "properties": [ - "betweendigital.com" - ], - "resources": [ - "betweendigital.com" - ] - }, - "Bidfluence": { - "properties": [ - "bidfluence.com" - ], - "resources": [ - "bidfluence.com" - ] - }, - "Bidr": { - "properties": [ - "bidr.io" - ], - "resources": [ - "bidr.io" - ] - }, - "BidSwitch": { - "properties": [ - "bidswitch.com" - ], - "resources": [ - "bidswitch.net", - "mfadsrvr.com" - ] - }, - "Bidtellect": { - "properties": [ - "bidtellect.com", - "bttrack.com" - ], - "resources": [ - "bidtellect.com", - "bttrack.com" - ] - }, - "BidVertiser": { - "properties": [ - "bidvertiser.com" - ], - "resources": [ - "bidvertiser.com" - ] - }, - "BigClick": { - "properties": [ - "bigclick.me" - ], - "resources": [ - "bgclck.me", - "xcvgdf.party" - ] - }, - "BigDoor": { - "properties": [ - "bigdoor.com" - ], - "resources": [ - "bigdoor.com", - "onetruefan.com" - ] - }, - "bigmirnet": { - "properties": [ - "bigmir.net" - ], - "resources": [ - "bigmir.net" - ] - }, - "BinLayer": { - "properties": [ - "binlayer.com" - ], - "resources": [ - "binlayer.com" - ] - }, - "Bitcoin Plus": { - "properties": [ - "bitcoinplus.com" - ], - "resources": [ - "bitcoinplus.com" - ] - }, - "BitMedia": { - "properties": [ - "bitmedia.io" - ], - "resources": [ - "bitmedia.io" - ] - }, - "BittAds": { - "properties": [ - "bittads.com" - ], - "resources": [ - "bittads.com" - ] - }, - "Bizo": { - "properties": [ - "bizo.com", - "bizographics.com" - ], - "resources": [ - "bizo.com", - "bizographics.com" - ] - }, - "Black Label Ads": { - "properties": [ - "blacklabelads.com" - ], - "resources": [ - "blacklabelads.com" - ] - }, - "BlogCatalog": { - "properties": [ - "blogcatalog.com" - ], - "resources": [ - "blogcatalog.com" - ] - }, - "BlogCounter.com": { - "properties": [ - "blogcounter.de" - ], - "resources": [ - "blogcounter.de" - ] - }, - "BlogFrog": { - "properties": [ - "theblogfrog.com" - ], - "resources": [ - "theblogfrog.com" - ] - }, - "BlogHer": { - "properties": [ - "blogher.com", - "blogherads.com" - ], - "resources": [ - "blogher.com", - "blogherads.com" - ] - }, - "BlogRollr": { - "properties": [ - "blogrollr.com" - ], - "resources": [ - "blogrollr.com" - ] - }, - "BLOOM Digital Platforms": { - "properties": [ - "adgear.com", - "bloom-hq.com" - ], - "resources": [ - "adgear.com", - "adgrx.com", - "bloom-hq.com" - ] - }, - "BloomReach": { - "properties": [ - "bloomreach.com", - "brcdn.com" - ], - "resources": [ - "bloomreach.com", - "brcdn.com", - "brsrvr.com" - ] - }, - "BlueCava": { - "properties": [ - "bluecava.com" - ], - "resources": [ - "bluecava.com" - ] - }, - "BlueKai": { - "properties": [ - "bluekai.com", - "tracksimple.com" - ], - "resources": [ - "bkrtx.com", - "bluekai.com", - "tracksimple.com" - ] - }, - "Bluemetrix": { - "properties": [ - "bluemetrix.com", - "bmmetrix.com" - ], - "resources": [ - "bluemetrix.com", - "bmmetrix.com" - ] - }, - "Blu Trumpet": { - "properties": [ - "blutrumpet.com" - ], - "resources": [ - "blutrumpet.com" - ] - }, - "Bombora": { - "properties": [ - "bombora.com" - ], - "resources": [ - "ml314.com" - ] - }, - "Boo-Box": { - "properties": [ - "boo-box.com" - ], - "resources": [ - "boo-box.com" - ] - }, - "BoostBox": { - "properties": [ - "boostbox.com.br" - ], - "resources": [ - "boostbox.com.br" - ] - }, - "Bouncex": { - "properties": [ - "bouncex.com" - ], - "resources": [ - "bounceexchange.com", - "bouncex.com", - "bouncex.net" - ] - }, - "Brainient": { - "properties": [ - "brainient.com" - ], - "resources": [ - "brainient.com" - ] - }, - "Branch": { - "properties": [ - "branch.io" - ], - "resources": [ - "branch.io" - ] - }, - "Brand Affinity Technologies": { - "properties": [ - "brandaffinity.net" - ], - "resources": [ - "brandaffinity.net" - ] - }, - "Brandcrumb": { - "properties": [ - "brandcrumb.com" - ], - "resources": [ - "brandcrumb.com" - ] - }, - "Brand.net": { - "properties": [ - "brand.net" - ], - "resources": [ - "brand.net" - ] - }, - "Brandscreen": { - "properties": [ - "brandscreen.com", - "rtbidder.net" - ], - "resources": [ - "brandscreen.com", - "rtbidder.net" - ] - }, - "Branica": { - "properties": [ - "branica.com" - ], - "resources": [ - "branica.com" - ] - }, - "BreakTime": { - "properties": [ - "breaktime.com.tw" - ], - "resources": [ - "breaktime.com.tw" - ] - }, - "Brightcove": { - "properties": [ - "brightcove.com" - ], - "resources": [ - "brightcove.com" - ] - }, - "BrightEdge": { - "properties": [ - "brightedge.com" - ], - "resources": [ - "b0e8.com", - "brightedge.com" - ] - }, - "BrightRoll": { - "properties": [ - "brightroll.com" - ], - "resources": [ - "brightroll.com", - "btrll.com" - ] - }, - "BrightTag": { - "properties": [ - "brighttag.com", - "btstatic.com", - "thebrighttag.com" - ], - "resources": [ - "brighttag.com", - "btstatic.com", - "thebrighttag.com" - ] - }, - "Brilig": { - "properties": [ - "brilig.com" - ], - "resources": [ - "brilig.com" - ] - }, - "Browser-Update.org": { - "properties": [ - "browser-update.org" - ], - "resources": [ - "browser-update.org" - ] - }, - "BTBuckets": { - "properties": [ - "btbuckets.com" - ], - "resources": [ - "btbuckets.com" - ] - }, - "Bubblestat": { - "properties": [ - "bubblestat.com" - ], - "resources": [ - "bubblestat.com" - ] - }, - "BuckSense": { - "properties": [ - "bucksense.com" - ], - "resources": [ - "bucksense.com" - ] - }, - "Buffer": { - "properties": [ - "bufferapp.com" - ], - "resources": [ - "bufferapp.com" - ] - }, - "Bunchball": { - "properties": [ - "bunchball.com" - ], - "resources": [ - "bunchball.com" - ] - }, - "Burstly": { - "properties": [ - "burstly.com" - ], - "resources": [ - "burstly.com" - ] - }, - "Burst Media": { - "properties": [ - "burstbeacon.com", - "burstdirectads.com", - "burstmedia.com", - "burstnet.com", - "giantrealm.com" - ], - "resources": [ - "burstbeacon.com", - "burstdirectads.com", - "burstmedia.com", - "burstnet.com", - "giantrealm.com" - ] - }, - "BusinessOnline": { - "properties": [ - "businessol.com" - ], - "resources": [ - "businessol.com" - ] - }, - "Button": { - "properties": [ - "usebutton.com" - ], - "resources": [ - "usebutton.com" - ] - }, - "buySAFE": { - "properties": [ - "buysafe.com" - ], - "resources": [ - "buysafe.com" - ] - }, - "BuySellAds": { - "properties": [ - "beaconads.com", - "buysellads.com" - ], - "resources": [ - "beaconads.com", - "buysellads.com" - ] - }, - "Buysight": { - "properties": [ - "buysight.com", - "permuto.com", - "pulsemgr.com" - ], - "resources": [ - "buysight.com", - "permuto.com", - "pulsemgr.com" - ] - }, - "BuzzFeed": { - "properties": [ - "buzzfeed.com" - ], - "resources": [ - "buzzfed.com", - "buzzfeed.com" - ] - }, - "BuzzParadise": { - "properties": [ - "buzzparadise.com" - ], - "resources": [ - "buzzparadise.com" - ] - }, - "BV! MEDIA": { - "properties": [ - "branchez-vous.com", - "bvmedia.ca" - ], - "resources": [ - "branchez-vous.com", - "bvmedia.ca", - "networldmedia.com", - "networldmedia.net" - ] - }, - "c1exchange": { - "properties": [ - "c1exchange.com" - ], - "resources": [ - "c1exchange.com" - ] - }, - "C3 Metrics": { - "properties": [ - "attributionmodel.com", - "c3metrics.com", - "c3tag.com" - ], - "resources": [ - "attributionmodel.com", - "c3metrics.com", - "c3tag.com" - ] - }, - "Cadreon": { - "properties": [ - "cadreon.com" - ], - "resources": [ - "cadreon.com" - ] - }, - "CallSource": { - "properties": [ - "callsource.com" - ], - "resources": [ - "leadtrackingdata.com" - ] - }, - "CampaignGrid": { - "properties": [ - "campaigngrid.com" - ], - "resources": [ - "campaigngrid.com" - ] - }, - "CAPITALDATA": { - "properties": [ - "capitaldata.fr" - ], - "resources": [ - "capitaldata.fr" - ] - }, - "Carambola": { - "properties": [ - "carambola.com" - ], - "resources": [ - "carambo.la" - ] - }, - "Caraytech": { - "properties": [ - "caraytech.com.ar", - "e-planning.net", - "www.caraytech.com.ar" - ], - "resources": [ - "caraytech.com.ar", - "e-planning.net", - "www.caraytech.com.ar" - ] - }, - "Cardlytics": { - "properties": [ - "cardlytics.com" - ], - "resources": [ - "cardlytics.com" - ] - }, - "Cart.ro": { - "properties": [ - "cart.ro" - ], - "resources": [ - "cart.ro", - "statistics.ro" - ] - }, - "CartsGuru": { - "properties": [ - "carts.guru" - ], - "resources": [ - "carts.guru" - ] - }, - "Casale Media": { - "properties": [ - "casalemedia.com", - "medianet.com" - ], - "resources": [ - "casalemedia.com", - "medianet.com" - ] - }, - "CashBeet": { - "properties": [ - "cashbeet.com" - ], - "resources": [ - "cashbeet.com", - "serv1swork.com" - ] - }, - "Causes": { - "properties": [ - "causes.com" - ], - "resources": [ - "causes.com" - ] - }, - "Cbox": { - "properties": [ - "cbox.ws" - ], - "resources": [ - "cbox.ws" - ] - }, - "CBproADS": { - "properties": [ - "cbproads.com" - ], - "resources": [ - "cbproads.com" - ] - }, - "CBS Interactive": { - "properties": [ - "cbsinteractive.com", - "com.com" - ], - "resources": [ - "cbsinteractive.com", - "com.com" - ] - }, - "Cedato": { - "properties": [ - "cedato.com" - ], - "resources": [ - "cedato.com" - ] - }, - "Cedexis": { - "properties": [ - "cedexis.com" - ], - "resources": [ - "cedexis.com", - "cedexis.net" - ] - }, - "Certona": { - "properties": [ - "certona.com", - "res-x.com" - ], - "resources": [ - "certona.com", - "res-x.com" - ] - }, - "Chango": { - "properties": [ - "chango.ca", - "chango.com" - ], - "resources": [ - "chango.ca", - "chango.com" - ] - }, - "ChannelAdvisor": { - "properties": [ - "channeladvisor.com", - "searchmarketing.com" - ], - "resources": [ - "channeladvisor.com", - "searchmarketing.com" - ] - }, - "Channel Intelligence": { - "properties": [ - "channelintelligence.com" - ], - "resources": [ - "channelintelligence.com" - ] - }, - "Chartbeat": { - "properties": [ - "chartbeat.com", - "chartbeat.net" - ], - "resources": [ - "chartbeat.com", - "chartbeat.net" - ] - }, - "Chartboost": { - "properties": [ - "chartboost.com" - ], - "resources": [ - "chartboost.com" - ] - }, - "CheckM8": { - "properties": [ - "checkm8.com" - ], - "resources": [ - "checkm8.com" - ] - }, - "Chitika": { - "properties": [ - "chitika.com" - ], - "resources": [ - "chitika.com", - "chitika.net" - ] - }, - "ChoiceStream": { - "properties": [ - "choicestream.com" - ], - "resources": [ - "choicestream.com" - ] - }, - "ClearLink": { - "properties": [ - "clearlink.com" - ], - "resources": [ - "clearlink.com" - ] - }, - "ClearSaleing": { - "properties": [ - "clearsaleing.com" - ], - "resources": [ - "clearsaleing.com", - "csdata1.com", - "csdata2.com", - "csdata3.com" - ] - }, - "Clearsearch Media": { - "properties": [ - "pathinteractive.com" - ], - "resources": [ - "clearsearchmedia.com", - "csm-secure.com", - "pathinteractive.com" - ] - }, - "ClearSight Interactive": { - "properties": [ - "clearsightinteractive.com", - "csi-tracking.com" - ], - "resources": [ - "clearsightinteractive.com", - "csi-tracking.com" - ] - }, - "ClickAider": { - "properties": [ - "clickaider.com" - ], - "resources": [ - "clickaider.com" - ] - }, - "Clickayab": { - "properties": [ - "clickyab.com" - ], - "resources": [ - "clickyab.com" - ] - }, - "Clickbooth": { - "properties": [ - "clickbooth.com" - ], - "resources": [ - "adtoll.com", - "clickbooth.com" - ] - }, - "Clickdensity": { - "properties": [ - "clickdensity.com" - ], - "resources": [ - "clickdensity.com" - ] - }, - "ClickDimensions": { - "properties": [ - "clickdimensions.com" - ], - "resources": [ - "clickdimensions.com" - ] - }, - "ClickDistrict": { - "properties": [ - "clickdistrict.com", - "creative-serving.com" - ], - "resources": [ - "clickdistrict.com", - "creative-serving.com" - ] - }, - "ClickFrog": { - "properties": [ - "clickfrog.ru" - ], - "resources": [ - "bashirian.biz", - "buckridge.link", - "clickfrog.ru", - "franecki.net", - "quitzon.net", - "reichelcormier.bid", - "wisokykulas.bid" - ] - }, - "ClickFuel": { - "properties": [ - "clickfuel.com", - "myconversionlab.com" - ], - "resources": [ - "clickfuel.com", - "conversiondashboard.com", - "myconversionlab.com" - ] - }, - "ClickGuard": { - "properties": [ - "clickguard.com" - ], - "resources": [ - "clickguard.com" - ] - }, - "ClickInc": { - "properties": [ - "clickinc.com" - ], - "resources": [ - "clickinc.com" - ] - }, - "Clicksor": { - "properties": [ - "clicksor.com", - "clicksor.net" - ], - "resources": [ - "clicksor.com", - "clicksor.net" - ] - }, - "ClickTale": { - "properties": [ - "clicktale.com" - ], - "resources": [ - "clicktale.com", - "clicktale.net", - "pantherssl.com" - ] - }, - "Clickwinks": { - "properties": [ - "clickwinks.com" - ], - "resources": [ - "clickwinks.com" - ] - }, - "ClicManager": { - "properties": [ - "clicmanager.fr" - ], - "resources": [ - "clicmanager.fr" - ] - }, - "ClipSyndicate": { - "properties": [ - "clipsyndicate.com" - ], - "resources": [ - "clipsyndicate.com" - ] - }, - "ClixMetrix": { - "properties": [ - "clixmetrix.com" - ], - "resources": [ - "clixmetrix.com" - ] - }, - "Clixpy": { - "properties": [ - "clixpy.com" - ], - "resources": [ - "clixpy.com" - ] - }, - "Clixtell": { - "properties": [ - "clixtell.com" - ], - "resources": [ - "clixtell.com" - ] - }, - "Clove Network": { - "properties": [ - "clovenetwork.com" - ], - "resources": [ - "clovenetwork.com" - ] - }, - "ClustrMaps": { - "properties": [ - "clustrmaps.com" - ], - "resources": [ - "clustrmaps.com" - ] - }, - "CNZZ": { - "properties": [ - "cnzz.com" - ], - "resources": [ - "cnzz.com" - ] - }, - "Cognitive Match": { - "properties": [ - "cmads.com.tw", - "cmadsasia.com", - "cmadseu.com", - "cmmeglobal.com", - "cognitivematch.com" - ], - "resources": [ - "cmads.com.tw", - "cmadsasia.com", - "cmadseu.com", - "cmmeglobal.com", - "cognitivematch.com" - ] - }, - "CoinHive": { - "properties": [ - "authedmine.com", - "coinhive.com" - ], - "resources": [ - "ad-miner.com", - "authedmine.com", - "bmst.pw", - "cnhv.co", - "coin-hive.com", - "coinhive.com", - "wsservices.org" - ] - }, - "CoinPot": { - "properties": [ - "coinpot.co" - ], - "resources": [ - "coinpot.co" - ] - }, - "Collarity": { - "properties": [ - "collarity.com" - ], - "resources": [ - "collarity.com" - ] - }, - "Collective": { - "properties": [ - "collective.com" - ], - "resources": [ - "collective-media.net", - "collective.com", - "oggifinogi.com", - "tumri.com", - "tumri.net", - "yt1187.net" - ] - }, - "Commission Junction": { - "properties": [ - "cj.com" - ], - "resources": [ - "apmebf.com", - "awltovhc.com", - "cj.com", - "ftjcfx.com", - "kcdwa.com", - "qksz.com", - "qksz.net", - "tqlkg.com", - "yceml.net" - ] - }, - "Communicator Corp": { - "properties": [ - "communicatorcorp.com" - ], - "resources": [ - "communicatorcorp.com" - ] - }, - "Compass Labs": { - "properties": [ - "compasslabs.com" - ], - "resources": [ - "compasslabs.com" - ] - }, - "Complex Media": { - "properties": [ - "collider.com", - "complex.com", - "complexmedianetwork.com", - "firstwefeast.com", - "pigeonsandplanes.com", - "solecollector.com", - "theridechannel.com" - ], - "resources": [ - "complex.com", - "complexmedianetwork.com" - ] - }, - "Compuware": { - "properties": [ - "axf8.net", - "compuware.com", - "dynatrace.com" - ], - "resources": [ - "axf8.net", - "compuware.com", - "dynatrace.com", - "gomez.com" - ] - }, - "comScore": { - "properties": [ - "adxpose.com", - "comscore.com", - "scorecardresearch.com", - "sitestat.com", - "voicefive.com" - ], - "resources": [ - "adxpose.com", - "certifica.com", - "comscore.com", - "mdotlabs.com", - "proxilinks.com", - "proximic.com", - "proximic.net", - "scorecardresearch.com", - "sitestat.com", - "voicefive.com" - ] - }, - "Conduit": { - "properties": [ - "conduit-banners.com", - "conduit.com" - ], - "resources": [ - "conduit-banners.com", - "conduit-services.com", - "conduit.com", - "wibiya.com" - ] - }, - "Congoo": { - "properties": [ - "congoo.com" - ], - "resources": [ - "congoo.com" - ] - }, - "Connatix.com": { - "properties": [ - "connatix.com" - ], - "resources": [ - "connatix.com" - ] - }, - "Connexity": { - "properties": [ - "connexity.com", - "pricegrabber.com" - ], - "resources": [ - "connexity.com", - "connexity.net", - "pricegrabber.com" - ] - }, - "Consilium Media": { - "properties": [ - "consiliummedia.com" - ], - "resources": [ - "consiliummedia.com" - ] - }, - "Consumable": { - "properties": [ - "consumable.com" - ], - "resources": [ - "consumable.com" - ] - }, - "Contact At Once!": { - "properties": [ - "contactatonce.com" - ], - "resources": [ - "contactatonce.com" - ] - }, - "CONTAXE": { - "properties": [ - "contaxe.com" - ], - "resources": [ - "contaxe.com" - ] - }, - "ContentABC": { - "properties": [ - "contentabc.com" - ], - "resources": [ - "contentabc.com" - ] - }, - "CONTEXTin": { - "properties": [ - "admailtiser.com", - "contextin.com" - ], - "resources": [ - "admailtiser.com", - "contextin.com" - ] - }, - "ContextuAds": { - "properties": [ - "agencytradingdesk.net", - "contextuads.com" - ], - "resources": [ - "agencytradingdesk.net", - "contextuads.com" - ] - }, - "CONTEXTWEB": { - "properties": [ - "contextweb.com" - ], - "resources": [ - "contextweb.com" - ] - }, - "ConvergeDirect": { - "properties": [ - "convergedirect.com", - "convergetrack.com" - ], - "resources": [ - "convergedirect.com", - "convergetrack.com" - ] - }, - "ConversantMedia": { - "properties": [ - "conversantmedia.com" - ], - "resources": [ - "adserver.com", - "conversantmedia.com", - "dotomi.com", - "dtmpub.com", - "emjcd.com", - "fastclick.com", - "fastclick.net", - "greystripe.com", - "lduhtrp.net", - "mediaplex.com", - "valueclick.com", - "valueclick.net", - "valueclickmedia.com" - ] - }, - "ConversionRuler": { - "properties": [ - "conversionruler.com" - ], - "resources": [ - "conversionruler.com" - ] - }, - "Conversive": { - "properties": [ - "conversive.nl" - ], - "resources": [ - "conversive.nl" - ] - }, - "Convert Insights": { - "properties": [ - "convert.com", - "reedge.com" - ], - "resources": [ - "convert.com", - "reedge.com" - ] - }, - "Convertro": { - "properties": [ - "convertro.com" - ], - "resources": [ - "convertro.com" - ] - }, - "Conviva": { - "properties": [ - "conviva.com" - ], - "resources": [ - "conviva.com" - ] - }, - "CoreMotives": { - "properties": [ - "coremotives.com" - ], - "resources": [ - "coremotives.com" - ] - }, - "Cox Digital Solutions": { - "properties": [ - "adify.com", - "coxdigitalsolutions.com", - "novomotus.com" - ], - "resources": [ - "adify.com", - "afy11.net", - "coxdigitalsolutions.com", - "novomotus.com" - ] - }, - "CPMStar": { - "properties": [ - "cpmstar.com" - ], - "resources": [ - "cpmstar.com" - ] - }, - "CPX Interactive": { - "properties": [ - "cpxadroit.com" - ], - "resources": [ - "adreadypixels.com", - "cpxadroit.com", - "cpxinteractive.com" - ] - }, - "Crazy Egg": { - "properties": [ - "cetrk.com", - "crazyegg.com" - ], - "resources": [ - "cetrk.com", - "crazyegg.com" - ] - }, - "Creafi": { - "properties": [ - "creafi.com" - ], - "resources": [ - "creafi.com" - ] - }, - "Crimtan": { - "properties": [ - "crimtan.com" - ], - "resources": [ - "crimtan.com" - ] - }, - "Crisp Media": { - "properties": [ - "crispmedia.com" - ], - "resources": [ - "crispmedia.com" - ] - }, - "Criteo": { - "properties": [ - "criteo.com", - "criteo.net" - ], - "resources": [ - "criteo.com", - "criteo.net", - "hlserve.com", - "hooklogic.com", - "storetail.io" - ] - }, - "Cross Pixel": { - "properties": [ - "crosspixel.net" - ], - "resources": [ - "crosspixel.net", - "crosspixelmedia.com", - "crsspxl.com" - ] - }, - "Crowd Science": { - "properties": [ - "crowdscience.com" - ], - "resources": [ - "crowdscience.com" - ] - }, - "CryptoLoot": { - "properties": [ - "crypto-loot.com" - ], - "resources": [ - "cryptaloot.pro", - "crypto-loot.com", - "cryptolootminer.com", - "flashx.pw", - "gitgrub.pro", - "reauthenticator.com", - "statdynamic.com", - "webmine.pro" - ] - }, - "CryptoWebMiner": { - "properties": [ - "crypto-webminer.com" - ], - "resources": [ - "bitcoin-pay.eu", - "crypto-webminer.com", - "ethpocket.de", - "ethtrader.de" - ] - }, - "cXense": { - "properties": [ - "cxense.com" - ], - "resources": [ - "cxense.com", - "emediate.biz", - "emediate.com", - "emediate.dk", - "emediate.eu" - ] - }, - "Cya2": { - "properties": [ - "cya2.net" - ], - "resources": [ - "cya2.net" - ] - }, - "Cyberplex": { - "properties": [ - "cyberplex.com" - ], - "resources": [ - "cyberplex.com" - ] - }, - "Dada": { - "properties": [ - "dada.eu", - "dada.pro", - "simply.com" - ], - "resources": [ - "dada.eu", - "dada.pro", - "simply.com" - ] - }, - "DailyMe": { - "properties": [ - "dailyme.com", - "newstogram.com" - ], - "resources": [ - "dailyme.com", - "newstogram.com" - ] - }, - "Dataium": { - "properties": [ - "collserve.com", - "ihs.com" - ], - "resources": [ - "collserve.com", - "dataium.com", - "ihs.com" - ] - }, - "Datalogix": { - "properties": [ - "datalogix.com", - "nexac.com" - ], - "resources": [ - "datalogix.com", - "nexac.com", - "nextaction.net" - ] - }, - "DataSift": { - "properties": [ - "datasift.com", - "tweetmeme.com" - ], - "resources": [ - "datasift.com", - "tweetmeme.com" - ] - }, - "DataXu": { - "properties": [ - "dataxu.com", - "mexad.com", - "w55c.net" - ], - "resources": [ - "dataxu.com", - "dataxu.net", - "mexad.com", - "w55c.net" - ] - }, - "Datonics": { - "properties": [ - "datonics.com" - ], - "resources": [ - "datonics.com", - "pro-market.net" - ] - }, - "Datran Media": { - "properties": [ - "datranmedia.com", - "displaymarketplace.com" - ], - "resources": [ - "datranmedia.com", - "displaymarketplace.com" - ] - }, - "Datvantage": { - "properties": [ - "datvantage.com" - ], - "resources": [ - "datvantage.com" - ] - }, - "DC Storm": { - "properties": [ - "dc-storm.com", - "stormiq.com" - ], - "resources": [ - "dc-storm.com", - "stormiq.com" - ] - }, - "Dedicated Media": { - "properties": [ - "dedicatedmedia.com", - "dedicatednetworks.com" - ], - "resources": [ - "dedicatedmedia.com", - "dedicatednetworks.com" - ] - }, - "Deep Intent": { - "properties": [ - "deepintent.com" - ], - "resources": [ - "deepintent.com" - ] - }, - "Delivr": { - "properties": [ - "delivr.com" - ], - "resources": [ - "delivr.com", - "percentmobile.com" - ] - }, - "Delta Projects": { - "properties": [ - "deltaprojects.com" - ], - "resources": [ - "adaction.se", - "de17a.com", - "deltaprojects.com", - "deltaprojects.se" - ] - }, - "Demandbase": { - "properties": [ - "demandbase.com" - ], - "resources": [ - "company-target.com", - "demandbase.com" - ] - }, - "Demand Media": { - "properties": [ - "leafgroup.com" - ], - "resources": [ - "demandmedia.com", - "indieclick.com" - ] - }, - "Deutsche Post DHL": { - "properties": [ - "dpdhl.com" - ], - "resources": [ - "adcloud.com", - "adcloud.net", - "dp-dhl.com", - "dpdhl.com" - ] - }, - "Developer Media": { - "properties": [ - "developermedia.com" - ], - "resources": [ - "developermedia.com", - "lqcdn.com" - ] - }, - "DG": { - "properties": [ - "dgit.com", - "sizmek.com" - ], - "resources": [ - "dgit.com", - "eyeblaster.com", - "eyewonder.com", - "mdadx.com", - "serving-sys.com", - "unicast.com" - ] - }, - "dianomi": { - "properties": [ - "dianomi.com" - ], - "resources": [ - "dianomi.com" - ] - }, - "Didit": { - "properties": [ - "didit.com" - ], - "resources": [ - "did-it.com", - "didit.com" - ] - }, - "Digg": { - "properties": [ - "digg.com" - ], - "resources": [ - "digg.com" - ] - }, - "DigitalAdConsortium": { - "properties": [ - "dac.co.jp" - ], - "resources": [ - "impact-ad.jp" - ] - }, - "Digital River": { - "properties": [ - "digitalriver.com", - "keywordmax.com", - "netflame.cc" - ], - "resources": [ - "digitalriver.com", - "keywordmax.com", - "netflame.cc" - ] - }, - "Digital Target": { - "properties": [ - "digitaltarget.ru" - ], - "resources": [ - "digitaltarget.ru" - ] - }, - "Digitize": { - "properties": [ - "digitize.ie" - ], - "resources": [ - "digitize.ie" - ] - }, - "DirectAdvert": { - "properties": [ - "directadvert.ru" - ], - "resources": [ - "directadvert.ru" - ] - }, - "DirectCORP": { - "properties": [ - "directcorp.de", - "ipcounter.de" - ], - "resources": [ - "directcorp.de", - "ipcounter.de" - ] - }, - "Direct Response Group": { - "properties": [ - "directresponsegroup.com" - ], - "resources": [ - "directresponsegroup.com", - "ppctracking.net" - ] - }, - "Directtrack": { - "properties": [ - "directtrack.com" - ], - "resources": [ - "directtrack.com" - ] - }, - "Disqus": { - "properties": [ - "disqus.com", - "disqusads.com" - ], - "resources": [ - "disqus.com", - "disqusads.com" - ] - }, - "DistilNetworks": { - "properties": [ - "distilnetworks.com" - ], - "resources": [ - "distilnetworks.com", - "distiltag.com" - ] - }, - "DistrictM": { - "properties": [ - "districtm.net" - ], - "resources": [ - "districtm.io" - ] - }, - "dmpxs": { - "properties": [ - "dmpxs.com" - ], - "resources": [ - "dmpxs.com" - ] - }, - "DoublePimp": { - "properties": [ - "doublepimp.com" - ], - "resources": [ - "doublepimp.com" - ] - }, - "DoublePositive": { - "properties": [ - "doublepositive.com" - ], - "resources": [ - "bid-tag.com", - "doublepositive.com" - ] - }, - "DoubleVerify": { - "properties": [ - "doubleverify.com" - ], - "resources": [ - "doubleverify.com" - ] - }, - "Drawbridge": { - "properties": [ - "drawbridge.com" - ], - "resources": [ - "adsymptotic.com", - "drawbrid.ge", - "drawbridge.com" - ] - }, - "DS-IQ": { - "properties": [ - "ds-iq.com" - ], - "resources": [ - "ds-iq.com" - ] - }, - "DSNR Group": { - "properties": [ - "dsnrgroup.com", - "dsnrmg.com", - "traffiliate.com", - "z5x.net" - ], - "resources": [ - "dsnrgroup.com", - "dsnrmg.com", - "traffiliate.com", - "z5x.com", - "z5x.net" - ] - }, - "dwstat.com": { - "properties": [ - "dwstat.cn" - ], - "resources": [ - "dwstat.cn" - ] - }, - "DynAdmic": { - "properties": [ - "dynadmic.com" - ], - "resources": [ - "dynadmic.com", - "dyntrk.com" - ] - }, - "DynamicOxygen": { - "properties": [ - "dynamicoxygen.com", - "exitjunction.com" - ], - "resources": [ - "dynamicoxygen.com", - "exitjunction.com" - ] - }, - "DynamicYield": { - "properties": [ - "dynamicyield.com" - ], - "resources": [ - "dynamicyield.com" - ] - }, - "Earnify": { - "properties": [ - "earnify.com" - ], - "resources": [ - "earnify.com" - ] - }, - "eBay": { - "properties": [ - "ebay.at", - "ebay.ba", - "ebay.be", - "ebay.ca", - "ebay.ch", - "ebay.cn", - "ebay.co.jp", - "ebay.co.kr", - "ebay.co.uk", - "ebay.com", - "ebay.com.au", - "ebay.com.hk", - "ebay.com.my", - "ebay.com.ph", - "ebay.com.sg", - "ebay.com.tw", - "ebay.de", - "ebay.es", - "ebay.fr", - "ebay.ie", - "ebay.in", - "ebay.it", - "ebay.nl", - "ebay.pl" - ], - "resources": [ - "ebay.com" - ] - }, - "Echo": { - "properties": [ - "aboutecho.com", - "haloscan.com", - "js-kit.com" - ], - "resources": [ - "aboutecho.com", - "haloscan.com", - "js-kit.com" - ] - }, - "ECSAnalytics": { - "properties": [ - "ecsanalytics.com", - "theecsinc.com" - ], - "resources": [ - "ecsanalytics.com" - ] - }, - "EFF": { - "properties": [ - "do-not-tracker.org", - "eff.org", - "eviltracker.net", - "trackersimulator.org" - ], - "resources": [ - "do-not-tracker.org", - "eff.org", - "eviltracker.net", - "trackersimulator.org" - ] - }, - "Effective Measure": { - "properties": [ - "effectivemeasure.com", - "effectivemeasure.net" - ], - "resources": [ - "effectivemeasure.com", - "effectivemeasure.net" - ] - }, - "ekolay": { - "properties": [ - "hurriyet.com.tr" - ], - "resources": [ - "e-kolay.net", - "ekolay.net", - "hurriyet.com.tr" - ] - }, - "Eleavers": { - "properties": [ - "eleavers.com" - ], - "resources": [ - "eleavers.com" - ] - }, - "Emego": { - "properties": [ - "usemax.de" - ], - "resources": [ - "usemax.de" - ] - }, - "Emerse": { - "properties": [ - "emerse.com" - ], - "resources": [ - "emerse.com" - ] - }, - "EMX": { - "properties": [ - "emxdigital.com" - ], - "resources": [ - "brealtime.com", - "clearstream.tv", - "emxdgt.com", - "emxdigital.com" - ] - }, - "Enecto": { - "properties": [ - "enecto.com" - ], - "resources": [ - "enecto.com" - ] - }, - "engage:BDR": { - "properties": [ - "engagebdr.com" - ], - "resources": [ - "bnmla.com", - "engagebdr.com" - ] - }, - "Engago Technology": { - "properties": [ - "engago.com" - ], - "resources": [ - "appmetrx.com", - "engago.com" - ] - }, - "Engine Network": { - "properties": [ - "enginenetwork.com" - ], - "resources": [ - "enginenetwork.com" - ] - }, - "Ensighten": { - "properties": [ - "ensighten.com" - ], - "resources": [ - "ensighten.com" - ] - }, - "Entireweb": { - "properties": [ - "entireweb.com" - ], - "resources": [ - "entireweb.com" - ] - }, - "Epic Media Group": { - "properties": [ - "epicadvertising.com", - "epicmarketplace.com", - "theepicmediagroup.com" - ], - "resources": [ - "epicadvertising.com", - "epicmarketplace.com", - "epicmobileads.com", - "theepicmediagroup.com", - "trafficmp.com" - ] - }, - "eProof.com": { - "properties": [ - "eproof.com" - ], - "resources": [ - "eproof.com" - ] - }, - "Epsilon": { - "properties": [ - "epsilon.com" - ], - "resources": [ - "epsilon.com" - ] - }, - "EQ Ads": { - "properties": [ - "eqads.com" - ], - "resources": [ - "eqads.com" - ] - }, - "EroAdvertising": { - "properties": [ - "ero-advertising.com" - ], - "resources": [ - "ero-advertising.com" - ] - }, - "Etarget": { - "properties": [ - "etarget.net", - "etargetnet.com" - ], - "resources": [ - "etarget.net", - "etargetnet.com" - ] - }, - "Etineria": { - "properties": [ - "adwitserver.com", - "etineria.com" - ], - "resources": [ - "adwitserver.com", - "etineria.com" - ] - }, - "etracker": { - "properties": [ - "etracker.com", - "etracker.de" - ], - "resources": [ - "etracker.com", - "etracker.de", - "sedotracker.com", - "sedotracker.de" - ] - }, - "eTrigue": { - "properties": [ - "etrigue.com" - ], - "resources": [ - "etrigue.com" - ] - }, - "Eulerian Technologies": { - "properties": [ - "eulerian.com" - ], - "resources": [ - "eulerian.com", - "eulerian.net" - ] - }, - "Evergage": { - "properties": [ - "evergage.com" - ], - "resources": [ - "mybuys.com", - "veruta.com" - ] - }, - "Everyday Health": { - "properties": [ - "everydayhealth.com", - "waterfrontmedia.com" - ], - "resources": [ - "everydayhealth.com", - "waterfrontmedia.com" - ] - }, - "Evisions Marketing": { - "properties": [ - "engineseeker.com", - "evisionsmarketing.com" - ], - "resources": [ - "engineseeker.com", - "evisionsmarketing.com" - ] - }, - "Evolve": { - "properties": [ - "evolvemediacorp.com", - "gorillanation.com" - ], - "resources": [ - "evolvemediacorp.com", - "evolvemediametrics.com", - "gorillanation.com" - ] - }, - "eWayDirect": { - "properties": [ - "ewaydirect.com" - ], - "resources": [ - "ewaydirect.com", - "ixs1.net" - ] - }, - "ewebse": { - "properties": [ - "777seo.com", - "ewebse.com" - ], - "resources": [ - "777seo.com", - "ewebse.com" - ] - }, - "excitad": { - "properties": [ - "excitad.com" - ], - "resources": [ - "excitad.com" - ] - }, - "eXelate": { - "properties": [ - "exelate.com" - ], - "resources": [ - "exelate.com", - "exelator.com" - ] - }, - "ExoClick": { - "properties": [ - "exoclick.com" - ], - "resources": [ - "exoclick.com" - ] - }, - "Exosrv": { - "properties": [ - "exosrv.com" - ], - "resources": [ - "exosrv.com" - ] - }, - "Experian": { - "properties": [ - "experian.com" - ], - "resources": [ - "audienceiq.com", - "experian.com" - ] - }, - "expo-MAX": { - "properties": [ - "expo-max.com" - ], - "resources": [ - "expo-max.com" - ] - }, - "Exponential Interactive": { - "properties": [ - "exponential.com", - "fulltango.com" - ], - "resources": [ - "adotube.com", - "exponential.com", - "fulltango.com", - "tribalfusion.com" - ] - }, - "Extension Factory": { - "properties": [ - "extensionfactory.com" - ], - "resources": [ - "extensionfactory.com" - ] - }, - "EXTENSIONS.RU": { - "properties": [ - "extensions.ru" - ], - "resources": [ - "extensions.ru" - ] - }, - "eXTReMe digital": { - "properties": [ - "extremetracking.com" - ], - "resources": [ - "extreme-dm.com", - "extremetracking.com" - ] - }, - "Eyeconomy": { - "properties": [ - "eyeconomy.co.uk" - ], - "resources": [ - "eyeconomy.co.uk", - "eyeconomy.com", - "sublimemedia.net", - "www.eyeconomy.co.uk" - ] - }, - "EyeNewton": { - "properties": [ - "eyenewton.ru" - ], - "resources": [ - "eyenewton.ru" - ] - }, - "Eyeota": { - "properties": [ - "eyeota.net" - ], - "resources": [ - "eyeota.net" - ] - }, - "eyeReturn Marketing": { - "properties": [ - "eyereturnmarketing.com" - ], - "resources": [ - "eyereturn.com", - "eyereturnmarketing.com" - ] - }, - "Eyeviewdigital": { - "properties": [ - "eyeviewdigital.com" - ], - "resources": [ - "eyeviewads.com", - "eyeviewdigital.com" - ] - }, - "Facebook": { - "properties": [ - "atlassolutions.com", - "facebook.com", - "facebook.de", - "facebook.fr", - "facebook.net", - "fb.com", - "fb.me", - "fbcdn.net", - "friendfeed.com", - "instagram.com", - "internalfb.com", - "messenger.com", - "oculus.com", - "whatsapp.com", - "workplace.com" - ], - "resources": [ - "apps.fbsbx.com", - "atdmt.com", - "atlassolutions.com", - "facebook.com", - "facebook.de", - "facebook.fr", - "facebook.net", - "fb.com", - "fb.me", - "fbcdn.net", - "fbsbx.com", - "friendfeed.com", - "instagram.com", - "messenger.com" - ] - }, - "Facilitate Digital": { - "properties": [ - "adsfac.eu", - "adsfac.net", - "adsfac.us", - "facilitatedigital.com" - ], - "resources": [ - "adsfac.eu", - "adsfac.info", - "adsfac.net", - "adsfac.sg", - "adsfac.us", - "facilitatedigital.com" - ] - }, - "Fairfax Media": { - "properties": [ - "fairfax.com.au", - "fxj.com.au", - "www.fxj.com.au" - ], - "resources": [ - "fairfax.com.au", - "fxj.com.au", - "www.fxj.com.au" - ] - }, - "faithadnet": { - "properties": [ - "faithadnet.com" - ], - "resources": [ - "faithadnet.com" - ] - }, - "Fanplayr": { - "properties": [ - "fanplayr.com" - ], - "resources": [ - "fanplayr.com" - ] - }, - "Fathom": { - "properties": [ - "fathomdelivers.com", - "fathomseo.com" - ], - "resources": [ - "fathomdelivers.com", - "fathomseo.com" - ] - }, - "Federated Media": { - "properties": [ - "hyfn.com", - "lijit.com" - ], - "resources": [ - "federatedmedia.net", - "fmpub.net", - "hyfn.com", - "lijit.com" - ] - }, - "Feedjit": { - "properties": [ - "feedjit.com" - ], - "resources": [ - "feedjit.com" - ] - }, - "FetchBack": { - "properties": [ - "fetchback.com" - ], - "resources": [ - "fetchback.com" - ] - }, - "Fiksu": { - "properties": [ - "fiksu.com" - ], - "resources": [ - "fiksu.com" - ] - }, - "FinancialContent": { - "properties": [ - "financialcontent.com" - ], - "resources": [ - "financialcontent.com" - ] - }, - "Fizz-Buzz Media": { - "properties": [ - "fizzbuzzmedia.com", - "fizzbuzzmedia.net" - ], - "resources": [ - "fizzbuzzmedia.com", - "fizzbuzzmedia.net" - ] - }, - "Flashtalking": { - "properties": [ - "flashtalking.com" - ], - "resources": [ - "encoremetrics.com", - "flashtalking.com", - "sitecompass.com" - ] - }, - "Flattr": { - "properties": [ - "flattr.com" - ], - "resources": [ - "flattr.com" - ] - }, - "Flite": { - "properties": [ - "flite.com", - "widgetserver.com" - ], - "resources": [ - "flite.com", - "widgetserver.com" - ] - }, - "Fluct": { - "properties": [ - "adingo.jp", - "fluct.jp" - ], - "resources": [ - "adingo.jp", - "fluct.jp" - ] - }, - "Flytxt": { - "properties": [ - "flytxt.com" - ], - "resources": [ - "flytxt.com" - ] - }, - "Footprint": { - "properties": [ - "footprintlive.com" - ], - "resources": [ - "footprintlive.com" - ] - }, - "Forbes": { - "properties": [ - "brandsideplatform.com", - "forbes.com" - ], - "resources": [ - "brandsideplatform.com", - "forbes.com" - ] - }, - "Foresee": { - "properties": [ - "foresee.com" - ], - "resources": [ - "answerscloud.com" - ] - }, - "Fox One Stop Media": { - "properties": [ - "fimserve.com", - "foxnetworks.com", - "foxonestop.com", - "mobsmith.com", - "myads.com", - "othersonline.com" - ], - "resources": [ - "fimserve.com", - "foxnetworks.com", - "foxonestop.com", - "mobsmith.com", - "myads.com", - "othersonline.com" - ] - }, - "FreakOut": { - "properties": [ - "fout.jp" - ], - "resources": [ - "fout.jp" - ] - }, - "Freedom Communications": { - "properties": [ - "freedom.com" - ], - "resources": [ - "freedom.com" - ] - }, - "Free Online Users": { - "properties": [ - "freeonlineusers.com" - ], - "resources": [ - "freeonlineusers.com" - ] - }, - "Free-PageRank.com": { - "properties": [ - "free-pagerank.com" - ], - "resources": [ - "free-pagerank.com" - ] - }, - "FreeWheel": { - "properties": [ - "freewheel.tv", - "fwmrm.net" - ], - "resources": [ - "freewheel.tv", - "fwmrm.net", - "stickyadstv.com" - ] - }, - "FriendFinder Networks": { - "properties": [ - "adultfriendfinder.com", - "ffn.com", - "pop6.com" - ], - "resources": [ - "adultfriendfinder.com", - "ffn.com", - "pop6.com" - ] - }, - "Friends2Follow": { - "properties": [ - "friends2follow.com" - ], - "resources": [ - "friends2follow.com" - ] - }, - "Frog Sex": { - "properties": [ - "double-check.com", - "frogsex.com" - ], - "resources": [ - "double-check.com", - "frogsex.com" - ] - }, - "FuelX": { - "properties": [ - "fuelx.com" - ], - "resources": [ - "fuel451.com" - ] - }, - "Fullstory": { - "properties": [ - "fullstory.com" - ], - "resources": [ - "fullstory.com" - ] - }, - "Future Ads": { - "properties": [ - "futureads.com", - "resultlinks.com" - ], - "resources": [ - "futureads.com", - "resultlinks.com" - ] - }, - "Fyber": { - "properties": [ - "fyber.com" - ], - "resources": [ - "fyber.com" - ] - }, - "Game Advertising Online": { - "properties": [ - "game-advertising-online.com" - ], - "resources": [ - "game-advertising-online.com" - ] - }, - "Games2win": { - "properties": [ - "games2win.com", - "inviziads.com" - ], - "resources": [ - "games2win.com", - "inviziads.com" - ] - }, - "Gamned": { - "properties": [ - "gamned.com" - ], - "resources": [ - "gamned.com" - ] - }, - "Gannett": { - "properties": [ - "gannett.com", - "pointroll.com" - ], - "resources": [ - "gannett.com", - "pointroll.com" - ] - }, - "GB-World": { - "properties": [ - "gb-world.net" - ], - "resources": [ - "gb-world.net" - ] - }, - "Gemius": { - "properties": [ - "gemius.com", - "gemius.pl" - ], - "resources": [ - "gemius.com", - "gemius.pl" - ] - }, - "Genesis Media": { - "properties": [ - "genesismedia.com" - ], - "resources": [ - "genesismedia.com", - "genesismediaus.com" - ] - }, - "GENIEE": { - "properties": [ - "geniee.co.jp" - ], - "resources": [ - "geniee.co.jp", - "gssprt.jp" - ] - }, - "GENIE GROUP": { - "properties": [ - "geniegroupltd.co.uk", - "www.geniegroupltd.co.uk" - ], - "resources": [ - "geniegroupltd.co.uk", - "www.geniegroupltd.co.uk" - ] - }, - "Genius.com": { - "properties": [ - "genius.com", - "rsvpgenius.com" - ], - "resources": [ - "genius.com", - "rsvpgenius.com" - ] - }, - "GeoAds": { - "properties": [ - "geoads.com" - ], - "resources": [ - "geoads.com" - ] - }, - "GetGlue": { - "properties": [ - "elfie.com", - "smrtlnks.com" - ], - "resources": [ - "getglue.com", - "smrtlnks.com" - ] - }, - "GetIntent": { - "properties": [ - "adhigh.net", - "getintent.com" - ], - "resources": [ - "adhigh.net", - "getintent.com" - ] - }, - "Get Satisfaction": { - "properties": [ - "getsatisfaction.com" - ], - "resources": [ - "getsatisfaction.com" - ] - }, - "GetSiteControl": { - "properties": [ - "getsitecontrol.com" - ], - "resources": [ - "getsitecontrol.com" - ] - }, - "GfK Group": { - "properties": [ - "gfk.com" - ], - "resources": [ - "daphnecm.com", - "gfk.com", - "gfkdaphne.com" - ] - }, - "Gigya": { - "properties": [ - "gigya.com" - ], - "resources": [ - "gigcount.com", - "gigya.com" - ] - }, - "GISMAds": { - "properties": [ - "gismads.jp" - ], - "resources": [ - "gismads.jp" - ] - }, - "GitHub": { - "properties": [ - "gaug.es", - "github.com" - ], - "resources": [ - "gaug.es", - "github.com" - ] - }, - "Glam Media": { - "properties": [ - "glam.com", - "glammedia.com" - ], - "resources": [ - "glam.com", - "glammedia.com" - ] - }, - "Gleam": { - "properties": [ - "gleam.io" - ], - "resources": [ - "fraudjs.io", - "gleam.io" - ] - }, - "Global Takeoff": { - "properties": [ - "globaltakeoff.com", - "globaltakeoff.net" - ], - "resources": [ - "globaltakeoff.com", - "globaltakeoff.net" - ] - }, - "Globe7": { - "properties": [ - "globe7.com" - ], - "resources": [ - "globe7.com" - ] - }, - "Go Daddy": { - "properties": [ - "godaddy.com", - "trafficfacts.com" - ], - "resources": [ - "godaddy.com", - "trafficfacts.com" - ] - }, - "GoDataFeed": { - "properties": [ - "godatafeed.com" - ], - "resources": [ - "godatafeed.com" - ] - }, - "GoGrid": { - "properties": [ - "datapipe.com", - "formalyzer.com" - ], - "resources": [ - "datapipe.com", - "formalyzer.com", - "gogrid.com", - "komli.net" - ] - }, - "Goldbach": { - "properties": [ - "goldbachgroup.com" - ], - "resources": [ - "goldbach.com", - "goldbachgroup.com" - ] - }, - "GoldSpot Media": { - "properties": [ - "goldspotmedia.com" - ], - "resources": [ - "goldspotmedia.com" - ] - }, - "Google": { - "properties": [ - "abc.xyz", - "admeld.com", - "blogger.com", - "blogspot.com", - "crashlytics.com", - "google-melange.com", - "google.ac", - "google.ad", - "google.ae", - "google.al", - "google.am", - "google.as", - "google.at", - "google.az", - "google.ba", - "google.be", - "google.bf", - "google.bg", - "google.bi", - "google.bj", - "google.bs", - "google.bt", - "google.by", - "google.ca", - "google.cat", - "google.cd", - "google.cf", - "google.cg", - "google.ch", - "google.ci", - "google.cl", - "google.cm", - "google.cn", - "google.co.ao", - "google.co.bw", - "google.co.ck", - "google.co.cr", - "google.co.id", - "google.co.il", - "google.co.in", - "google.co.jp", - "google.co.ke", - "google.co.kr", - "google.co.ls", - "google.co.ma", - "google.co.mz", - "google.co.nz", - "google.co.th", - "google.co.tz", - "google.co.ug", - "google.co.uk", - "google.co.uz", - "google.co.ve", - "google.co.vi", - "google.co.za", - "google.co.zm", - "google.co.zw", - "google.com", - "google.com.af", - "google.com.ag", - "google.com.ai", - "google.com.ar", - "google.com.au", - "google.com.bd", - "google.com.bh", - "google.com.bn", - "google.com.bo", - "google.com.br", - "google.com.bz", - "google.com.co", - "google.com.cu", - "google.com.cy", - "google.com.do", - "google.com.ec", - "google.com.eg", - "google.com.et", - "google.com.fj", - "google.com.gh", - "google.com.gi", - "google.com.gt", - "google.com.hk", - "google.com.jm", - "google.com.kh", - "google.com.kw", - "google.com.lb", - "google.com.ly", - "google.com.mm", - "google.com.mt", - "google.com.mx", - "google.com.my", - "google.com.na", - "google.com.nf", - "google.com.ng", - "google.com.ni", - "google.com.np", - "google.com.om", - "google.com.pa", - "google.com.pe", - "google.com.pg", - "google.com.ph", - "google.com.pk", - "google.com.pr", - "google.com.py", - "google.com.qa", - "google.com.sa", - "google.com.sb", - "google.com.sg", - "google.com.sl", - "google.com.sv", - "google.com.tj", - "google.com.tr", - "google.com.tw", - "google.com.ua", - "google.com.uy", - "google.com.vc", - "google.com.vn", - "google.cv", - "google.cz", - "google.de", - "google.dj", - "google.dk", - "google.dm", - "google.dz", - "google.ee", - "google.es", - "google.fi", - "google.fm", - "google.fr", - "google.ga", - "google.ge", - "google.gg", - "google.gl", - "google.gm", - "google.gp", - "google.gr", - "google.gy", - "google.hn", - "google.hr", - "google.ht", - "google.hu", - "google.ie", - "google.im", - "google.iq", - "google.is", - "google.it", - "google.je", - "google.jo", - "google.kg", - "google.ki", - "google.kz", - "google.la", - "google.li", - "google.lk", - "google.lt", - "google.lu", - "google.lv", - "google.md", - "google.me", - "google.mg", - "google.mk", - "google.ml", - "google.mn", - "google.ms", - "google.mu", - "google.mv", - "google.mw", - "google.ne", - "google.nl", - "google.no", - "google.nr", - "google.nu", - "google.pl", - "google.pn", - "google.ps", - "google.pt", - "google.ro", - "google.rs", - "google.ru", - "google.rw", - "google.sc", - "google.se", - "google.sh", - "google.si", - "google.sk", - "google.sm", - "google.sn", - "google.so", - "google.st", - "google.td", - "google.tg", - "google.tk", - "google.tl", - "google.tm", - "google.tn", - "google.to", - "google.tt", - "google.vg", - "google.vu", - "google.ws", - "googlesource.com", - "ingress.com", - "nest.com", - "panoramio.com", - "pinpoint-dot-chromeperf.appspot.com", - "youtube.com" - ], - "resources": [ - "2mdn.net", - "admeld.com", - "admob.com", - "apture.com", - "blogger.com", - "cc-dt.com", - "crashlytics.com", - "destinationurl.com", - "doubleclick.net", - "ggpht.com", - "gmail.com", - "gmodules.com", - "google-analytics.com", - "google.ac", - "google.ad", - "google.ae", - "google.al", - "google.am", - "google.as", - "google.at", - "google.az", - "google.ba", - "google.be", - "google.bf", - "google.bg", - "google.bi", - "google.bj", - "google.bs", - "google.bt", - "google.by", - "google.ca", - "google.cat", - "google.cc", - "google.cd", - "google.cf", - "google.cg", - "google.ch", - "google.ci", - "google.cl", - "google.cm", - "google.cn", - "google.co.ao", - "google.co.bw", - "google.co.ck", - "google.co.cr", - "google.co.id", - "google.co.il", - "google.co.in", - "google.co.jp", - "google.co.ke", - "google.co.kr", - "google.co.ls", - "google.co.ma", - "google.co.mz", - "google.co.nz", - "google.co.th", - "google.co.tz", - "google.co.ug", - "google.co.uk", - "google.co.uz", - "google.co.ve", - "google.co.vi", - "google.co.za", - "google.co.zm", - "google.co.zw", - "google.com", - "google.com.af", - "google.com.ag", - "google.com.ai", - "google.com.ar", - "google.com.au", - "google.com.bd", - "google.com.bh", - "google.com.bn", - "google.com.bo", - "google.com.br", - "google.com.bz", - "google.com.co", - "google.com.cu", - "google.com.cy", - "google.com.do", - "google.com.ec", - "google.com.eg", - "google.com.et", - "google.com.fj", - "google.com.gh", - "google.com.gi", - "google.com.gt", - "google.com.hk", - "google.com.jm", - "google.com.kh", - "google.com.kw", - "google.com.lb", - "google.com.lc", - "google.com.ly", - "google.com.mm", - "google.com.mt", - "google.com.mx", - "google.com.my", - "google.com.na", - "google.com.nf", - "google.com.ng", - "google.com.ni", - "google.com.np", - "google.com.om", - "google.com.pa", - "google.com.pe", - "google.com.pg", - "google.com.ph", - "google.com.pk", - "google.com.pr", - "google.com.py", - "google.com.qa", - "google.com.sa", - "google.com.sb", - "google.com.sg", - "google.com.sl", - "google.com.sv", - "google.com.tj", - "google.com.tn", - "google.com.tr", - "google.com.tw", - "google.com.ua", - "google.com.uy", - "google.com.vc", - "google.com.vn", - "google.cv", - "google.cz", - "google.de", - "google.dj", - "google.dk", - "google.dm", - "google.dz", - "google.ee", - "google.es", - "google.fi", - "google.fm", - "google.fr", - "google.ga", - "google.ge", - "google.gf", - "google.gg", - "google.gl", - "google.gm", - "google.gp", - "google.gr", - "google.gy", - "google.hn", - "google.hr", - "google.ht", - "google.hu", - "google.ie", - "google.im", - "google.io", - "google.iq", - "google.is", - "google.it", - "google.je", - "google.jo", - "google.kg", - "google.ki", - "google.kz", - "google.la", - "google.li", - "google.lk", - "google.lt", - "google.lu", - "google.lv", - "google.md", - "google.me", - "google.mg", - "google.mk", - "google.ml", - "google.mn", - "google.ms", - "google.mu", - "google.mv", - "google.mw", - "google.ne", - "google.nl", - "google.no", - "google.nr", - "google.nu", - "google.pl", - "google.pn", - "google.ps", - "google.pt", - "google.ro", - "google.rs", - "google.ru", - "google.rw", - "google.sc", - "google.se", - "google.sh", - "google.si", - "google.sk", - "google.sm", - "google.sn", - "google.so", - "google.st", - "google.td", - "google.tg", - "google.tk", - "google.tl", - "google.tm", - "google.tn", - "google.to", - "google.tt", - "google.vg", - "google.vu", - "google.ws", - "googleadservices.com", - "googleapis.com", - "googlemail.com", - "googlesyndication.com", - "googletagservices.com", - "googleusercontent.com", - "googlevideo.com", - "gstatic.com", - "invitemedia.com", - "postrank.com", - "recaptcha.net", - "smtad.net", - "youtube.com" - ] - }, - "GoSquared": { - "properties": [ - "gosquared.com" - ], - "resources": [ - "gosquared.com" - ] - }, - "GoStats": { - "properties": [ - "gostats.com" - ], - "resources": [ - "gostats.com" - ] - }, - "Grapeshot": { - "properties": [ - "grapeshot.co.uk", - "www.grapeshot.co.uk" - ], - "resources": [ - "grapeshot.co.uk", - "www.grapeshot.co.uk" - ] - }, - "GrapheneMedia": { - "properties": [ - "graphenemedia.in" - ], - "resources": [ - "graphenedigitalanalytics.in" - ] - }, - "Graphnium": { - "properties": [ - "graphinium.com" - ], - "resources": [ - "crm4d.com" - ] - }, - "Gravity": { - "properties": [ - "gravity.com", - "grvcdn.com" - ], - "resources": [ - "gravity.com", - "grvcdn.com" - ] - }, - "Gridcash": { - "properties": [ - "adless.io", - "gridcash.net" - ], - "resources": [ - "adless.io", - "gridcash.net" - ] - }, - "Grocery Shopping Network": { - "properties": [ - "groceryshopping.net" - ], - "resources": [ - "groceryshopping.net" - ] - }, - "GroovinAds": { - "properties": [ - "groovinads.com" - ], - "resources": [ - "groovinads.com" - ] - }, - "Gruner + Jahr": { - "properties": [ - "guj.de", - "ligatus.com" - ], - "resources": [ - "guj.de", - "ligatus.com" - ] - }, - "GTop": { - "properties": [ - "arenaweb.ro" - ], - "resources": [ - "arenaweb.ro", - "gtop.ro", - "gtopstats.com" - ] - }, - "GumGum": { - "properties": [ - "gumgum.com" - ], - "resources": [ - "gumgum.com" - ] - }, - "Gunggo": { - "properties": [ - "gunggo.com" - ], - "resources": [ - "gunggo.com" - ] - }, - "Hands Mobile": { - "properties": [ - "hands.com.br", - "www.hands.com.br" - ], - "resources": [ - "hands.com.br", - "www.hands.com.br" - ] - }, - "Harrenmedia": { - "properties": [ - "harrenmedia.com", - "harrenmedianetwork.com" - ], - "resources": [ - "harrenmedia.com", - "harrenmedianetwork.com" - ] - }, - "HealthPricer": { - "properties": [ - "adacado.com", - "healthpricer.com" - ], - "resources": [ - "adacado.com", - "healthpricer.com" - ] - }, - "Hearst": { - "properties": [ - "hearst.com", - "ic-live.com", - "iclive.com", - "icrossing.com", - "raasnet.com" - ], - "resources": [ - "hearst.com", - "ic-live.com", - "iclive.com", - "icrossing.com", - "raasnet.com", - "redaril.com", - "sptag.com", - "sptag1.com", - "sptag2.com", - "sptag3.com" - ] - }, - "Heyzap": { - "properties": [ - "heyzap.com" - ], - "resources": [ - "heyzap.com" - ] - }, - "HilltopAds": { - "properties": [ - "hilltopads.com" - ], - "resources": [ - "hilltopads.com", - "hilltopads.net", - "shoporielder.pro" - ] - }, - "Hi-media": { - "properties": [ - "himediagroup.com" - ], - "resources": [ - "comclick.com", - "hi-media.com", - "himediagroup.com" - ] - }, - "Histats": { - "properties": [ - "histats.com" - ], - "resources": [ - "histats.com" - ] - }, - "HitsLink": { - "properties": [ - "hitslink.com" - ], - "resources": [ - "hitslink.com" - ] - }, - "Hit Sniffer": { - "properties": [ - "hitsniffer.com" - ], - "resources": [ - "hitsniffer.com" - ] - }, - "Horyzon Media": { - "properties": [ - "horyzon-media.com" - ], - "resources": [ - "horyzon-media.com" - ] - }, - "HotelChamp": { - "properties": [ - "hotelchamp.com" - ], - "resources": [ - "hotelchamp.com" - ] - }, - "Hotjar": { - "properties": [ - "hotjar.com" - ], - "resources": [ - "hotjar.com" - ] - }, - "HotMart": { - "properties": [ - "hotmart.com" - ], - "resources": [ - "hotmart.com" - ] - }, - "HOTWords": { - "properties": [ - "hotwords.com", - "hotwords.es" - ], - "resources": [ - "hotwords.com", - "hotwords.es" - ] - }, - "HP": { - "properties": [ - "hp.com", - "opentext.com", - "optimost.com" - ], - "resources": [ - "hp.com", - "optimost.com" - ] - }, - "Httpool": { - "properties": [ - "httpool.com" - ], - "resources": [ - "httpool.com" - ] - }, - "HubSpot": { - "properties": [ - "hubspot.com" - ], - "resources": [ - "hs-analytics.net", - "hubspot.com" - ] - }, - "HUNT Mobile Ads": { - "properties": [ - "huntmads.com" - ], - "resources": [ - "huntmads.com" - ] - }, - "Hurra.com": { - "properties": [ - "hurra.com" - ], - "resources": [ - "hurra.com" - ] - }, - "IAB": { - "properties": [ - "digitru.st", - "iabtechlab.com" - ], - "resources": [ - "digitru.st" - ] - }, - "IAC": { - "properties": [ - "iac.com", - "iacadvertising.com" - ], - "resources": [ - "iac.com", - "iacadvertising.com" - ] - }, - "iBehavior": { - "properties": [ - "i-behavior.com", - "ib-ibi.com" - ], - "resources": [ - "i-behavior.com", - "ib-ibi.com" - ] - }, - "IBM": { - "properties": [ - "ibm.com", - "multicloud-ibm.com" - ], - "resources": [ - "cmcore.com", - "coremetrics.com", - "ibm.com", - "unica.com", - "xtify.com" - ] - }, - "ID5": { - "properties": [ - "id5.io" - ], - "resources": [ - "id5-sync.com" - ] - }, - "IDG": { - "properties": [ - "idg.com", - "idgtechnetwork.com" - ], - "resources": [ - "idg.com", - "idgtechnetwork.com" - ] - }, - "iEntry": { - "properties": [ - "600z.com", - "ientry.com" - ], - "resources": [ - "600z.com", - "ientry.com" - ] - }, - "IgnitAd": { - "properties": [ - "ignitad.com" - ], - "resources": [ - "ignitad.com" - ] - }, - "IgnitionOne": { - "properties": [ - "ignitionone.com", - "ignitionone.net", - "searchignite.com" - ], - "resources": [ - "ignitionone.com", - "ignitionone.net", - "searchignite.com" - ] - }, - "iMedia": { - "properties": [ - "imedia.cz" - ], - "resources": [ - "imedia.cz" - ] - }, - "Improve Digital": { - "properties": [ - "360yield.com", - "improvedigital.com" - ], - "resources": [ - "360yield.com", - "improvedigital.com" - ] - }, - "Inadco": { - "properties": [ - "inadco.com" - ], - "resources": [ - "anadcoads.com", - "inadco.com", - "inadcoads.com" - ] - }, - "InboundWriter": { - "properties": [ - "enquisite.com", - "inboundwriter.com" - ], - "resources": [ - "enquisite.com", - "inboundwriter.com" - ] - }, - "IndexExchange": { - "properties": [ - "indexexchange.com" - ], - "resources": [ - "indexexchange.com" - ] - }, - "Infectious Media": { - "properties": [ - "infectiousmedia.com" - ], - "resources": [ - "impressiondesk.com", - "infectiousmedia.com" - ] - }, - "Infernotions": { - "properties": [ - "infernotions.com" - ], - "resources": [ - "infernotions.com" - ] - }, - "Inflection Point Media": { - "properties": [ - "inflectionpointmedia.com" - ], - "resources": [ - "inflectionpointmedia.com" - ] - }, - "Infogroup": { - "properties": [ - "infogroup.com" - ], - "resources": [ - "infogroup.com" - ] - }, - "Infolinks": { - "properties": [ - "infolinks.com" - ], - "resources": [ - "infolinks.com" - ] - }, - "INFOnline": { - "properties": [ - "infonline.de" - ], - "resources": [ - "infonline.de", - "ioam.de", - "ivwbox.de" - ] - }, - "InfoStars": { - "properties": [ - "hotlog.ru", - "infostars.ru" - ], - "resources": [ - "hotlog.ru", - "infostars.ru" - ] - }, - "Infra-Ad": { - "properties": [ - "infra-ad.com" - ], - "resources": [ - "infra-ad.com" - ] - }, - "InMobi": { - "properties": [ - "aerserv.com", - "inmobi.com", - "sproutinc.com" - ], - "resources": [ - "aerserv.com", - "inmobi.com", - "sproutinc.com" - ] - }, - "inneractive": { - "properties": [ - "inner-active.com" - ], - "resources": [ - "inner-active.com" - ] - }, - "Innity": { - "properties": [ - "innity.com" - ], - "resources": [ - "innity.com" - ] - }, - "InsightExpress": { - "properties": [ - "insightexpress.com" - ], - "resources": [ - "insightexpress.com", - "insightexpressai.com" - ] - }, - "InSkin Media": { - "properties": [ - "inskinmedia.com" - ], - "resources": [ - "inskinmedia.com" - ] - }, - "Inspectlet": { - "properties": [ - "inspectlet.com" - ], - "resources": [ - "inspectlet.com" - ] - }, - "Instinctive": { - "properties": [ - "instinctive.io" - ], - "resources": [ - "instinctive.io", - "instinctiveads.com" - ] - }, - "Integral Ad Science": { - "properties": [ - "integralads.com" - ], - "resources": [ - "adsafemedia.com", - "adsafeprotected.com", - "iasds01.com", - "integralads.com" - ] - }, - "IntelligenceFocus": { - "properties": [ - "intelligencefocus.com", - "leadchampion.com" - ], - "resources": [ - "domodomain.com", - "intelligencefocus.com", - "leadchampion.com" - ] - }, - "Intent Media": { - "properties": [ - "intentmedia.com" - ], - "resources": [ - "intentmedia.com", - "intentmedia.net" - ] - }, - "Intergi": { - "properties": [ - "intergi.com" - ], - "resources": [ - "intergi.com" - ] - }, - "Intermarkets": { - "properties": [ - "intermarkets.net" - ], - "resources": [ - "intermarkets.net" - ] - }, - "Intermundo Media": { - "properties": [ - "intermundomedia.com" - ], - "resources": [ - "intermundomedia.com" - ] - }, - "Internet Brands": { - "properties": [ - "ibpxl.com", - "internetbrands.com" - ], - "resources": [ - "ibpxl.com", - "internetbrands.com" - ] - }, - "Interpolls": { - "properties": [ - "interpolls.com" - ], - "resources": [ - "interpolls.com" - ] - }, - "Inuvo": { - "properties": [ - "inuvo.com" - ], - "resources": [ - "inuvo.com" - ] - }, - "InvestingChannel": { - "properties": [ - "investingchannel.com" - ], - "resources": [ - "investingchannel.com" - ] - }, - "iovation": { - "properties": [ - "iovation.com" - ], - "resources": [ - "iesnare.com", - "iovation.com" - ] - }, - "iPerceptions": { - "properties": [ - "iperceptions.com" - ], - "resources": [ - "iperceptions.com" - ] - }, - "IponWeb": { - "properties": [ - "iponweb.com" - ], - "resources": [ - "iponweb.com", - "iponweb.net" - ] - }, - "iPROM": { - "properties": [ - "centraliprom.com", - "iprom.net", - "iprom.si", - "mediaiprom.com" - ], - "resources": [ - "centraliprom.com", - "iprom.net", - "iprom.si", - "mediaiprom.com" - ] - }, - "iPromote": { - "properties": [ - "ipromote.com" - ], - "resources": [ - "ipromote.com" - ] - }, - "iProspect": { - "properties": [ - "iprospect.com" - ], - "resources": [ - "clickmanage.com", - "iprospect.com" - ] - }, - "ISI Technologies": { - "properties": [ - "adversalservers.com", - "digbro.com" - ], - "resources": [ - "adversalservers.com", - "digbro.com" - ] - }, - "IslayTech": { - "properties": [ - "islay.tech" - ], - "resources": [ - "islay.tech" - ] - }, - "ismatlab.com": { - "properties": [ - "ismatlab.com" - ], - "resources": [ - "ismatlab.com" - ] - }, - "Itch": { - "properties": [ - "itch.io" - ], - "resources": [ - "itch.io" - ] - }, - "ItIsATracker": { - "properties": [ - "itisatracker.com" - ], - "resources": [ - "itisatracker.com" - ] - }, - "I.UA": { - "properties": [ - "i.ua" - ], - "resources": [ - "i.ua" - ] - }, - "Jaroop": { - "properties": [ - "jaroop.com" - ], - "resources": [ - "jaroop.com" - ] - }, - "JasperLabs": { - "properties": [ - "jasperlabs.com" - ], - "resources": [ - "jasperlabs.com" - ] - }, - "Jemm": { - "properties": [ - "jemmgroup.com" - ], - "resources": [ - "jemmgroup.com" - ] - }, - "Jink": { - "properties": [ - "jink.de", - "jinkads.com" - ], - "resources": [ - "jink.de", - "jinkads.com" - ] - }, - "Jirbo": { - "properties": [ - "adcolony.com" - ], - "resources": [ - "adcolony.com", - "jirbo.com" - ] - }, - "Jivox": { - "properties": [ - "jivox.com" - ], - "resources": [ - "jivox.com" - ] - }, - "JobThread": { - "properties": [ - "jobthread.com" - ], - "resources": [ - "jobthread.com" - ] - }, - "JSE": { - "properties": [ - "jsecoin.com" - ], - "resources": [ - "freecontent.bid", - "freecontent.date", - "freecontent.stream", - "hashing.win", - "hostingcloud.racing", - "hostingcloud.science", - "jsecoin.com" - ] - }, - "JuicyAds": { - "properties": [ - "juicyads.com" - ], - "resources": [ - "juicyads.com" - ] - }, - "Jumptap": { - "properties": [ - "jumptap.com" - ], - "resources": [ - "jumptap.com" - ] - }, - "justuno": { - "properties": [ - "justuno.com" - ], - "resources": [ - "justuno.com" - ] - }, - "Kaltura": { - "properties": [ - "kaltura.com" - ], - "resources": [ - "kaltura.com" - ] - }, - "Kargo": { - "properties": [ - "kargo.com" - ], - "resources": [ - "kargo.com" - ] - }, - "Kenshoo": { - "properties": [ - "kenshoo.com", - "xg4ken.com" - ], - "resources": [ - "kenshoo.com", - "xg4ken.com" - ] - }, - "Keyade": { - "properties": [ - "keyade.com" - ], - "resources": [ - "keyade.com" - ] - }, - "KeyMetric": { - "properties": [ - "keymetric.net" - ], - "resources": [ - "keymetric.net" - ] - }, - "Keywee": { - "properties": [ - "keywee.co" - ], - "resources": [ - "keywee.co" - ] - }, - "kikin": { - "properties": [ - "kikin.com" - ], - "resources": [ - "kikin.com" - ] - }, - "KISSmetrics": { - "properties": [ - "kissmetrics.com" - ], - "resources": [ - "kissmetrics.com" - ] - }, - "KissMyAds": { - "properties": [ - "kissmyads.com" - ], - "resources": [ - "kissmyads.com" - ] - }, - "Kitara Media": { - "properties": [ - "103092804.com", - "kitaramedia.com" - ], - "resources": [ - "103092804.com", - "kitaramedia.com" - ] - }, - "Kitcode": { - "properties": [ - "kitcode.net" - ], - "resources": [ - "kitcode.net" - ] - }, - "KIT digital": { - "properties": [ - "kitd.com" - ], - "resources": [ - "keewurd.com", - "kitd.com", - "peerset.com" - ] - }, - "Kokteyl": { - "properties": [ - "admost.com", - "kokteyl.com" - ], - "resources": [ - "admost.com", - "kokteyl.com" - ] - }, - "Komli": { - "properties": [ - "komli.com" - ], - "resources": [ - "komli.com" - ] - }, - "Konduto": { - "properties": [ - "konduto.com" - ], - "resources": [ - "k-analytix.com", - "konduto.com" - ] - }, - "Kontera": { - "properties": [ - "kontera.com" - ], - "resources": [ - "kontera.com" - ] - }, - "Korrelate": { - "properties": [ - "korrelate.com" - ], - "resources": [ - "adsummos.com", - "adsummos.net", - "korrelate.com" - ] - }, - "Krux": { - "properties": [ - "krux.com", - "kruxdigital.com" - ], - "resources": [ - "krux.com", - "kruxdigital.com", - "krxd.net" - ] - }, - "Lakana": { - "properties": [ - "lakana.com" - ], - "resources": [ - "ibsys.com", - "lakana.com" - ] - }, - "Layer-Ad.org": { - "properties": [ - "layer-ad.org" - ], - "resources": [ - "layer-ad.org" - ] - }, - "Layer Ads": { - "properties": [ - "layer-ads.net" - ], - "resources": [ - "layer-ads.net" - ] - }, - "LeadBolt": { - "properties": [ - "leadbolt.com" - ], - "resources": [ - "leadbolt.com" - ] - }, - "LeadForensics": { - "properties": [ - "leadforensics.com" - ], - "resources": [ - "leadforensics.com" - ] - }, - "LeadFormix": { - "properties": [ - "calliduscloud.com", - "leadforce1.com", - "leadformix.com" - ], - "resources": [ - "calliduscloud.com", - "leadforce1.com", - "leadformix.com" - ] - }, - "LeadsHub": { - "properties": [ - "ztsrv.com" - ], - "resources": [ - "ztsrv.com" - ] - }, - "LeanPlum": { - "properties": [ - "leanplum.com" - ], - "resources": [ - "leanplum.com" - ] - }, - "Legolas Media": { - "properties": [ - "legolas-media.com" - ], - "resources": [ - "legolas-media.com" - ] - }, - "Levexis": { - "properties": [ - "levexis.com" - ], - "resources": [ - "levexis.com" - ] - }, - "Lexos Media": { - "properties": [ - "adbull.com", - "lexosmedia.com" - ], - "resources": [ - "adbull.com", - "lexosmedia.com" - ] - }, - "LifeStreet": { - "properties": [ - "lfstmedia.com", - "lifestreetmedia.com" - ], - "resources": [ - "lfstmedia.com", - "lifestreetmedia.com" - ] - }, - "Limelight Networks": { - "properties": [ - "limelight.com" - ], - "resources": [ - "clickability.com", - "limelight.com", - "llnwd.net" - ] - }, - "LineZing": { - "properties": [ - "linezing.com" - ], - "resources": [ - "linezing.com" - ] - }, - "LinkConnector": { - "properties": [ - "linkconnector.com" - ], - "resources": [ - "linkconnector.com" - ] - }, - "LinkedIn": { - "properties": [ - "linkedin.com" - ], - "resources": [ - "licdn.com", - "linkedin.com" - ] - }, - "LinkShare": { - "properties": [ - "rakutenmarketing.com" - ], - "resources": [ - "linkshare.com", - "linksynergy.com", - "rakutenmarketing.com" - ] - }, - "Linkz": { - "properties": [ - "linkz.net" - ], - "resources": [ - "linkz.net" - ] - }, - "Listrak": { - "properties": [ - "listrak.com", - "listrakbi.com" - ], - "resources": [ - "listrak.com", - "listrakbi.com" - ] - }, - "LiveIntent": { - "properties": [ - "liveintent.com" - ], - "resources": [ - "liadm.com", - "liveintent.com" - ] - }, - "LiveInternet": { - "properties": [ - "liveinternet.ru", - "yadro.ru" - ], - "resources": [ - "liveinternet.ru", - "yadro.ru" - ] - }, - "LivePerson": { - "properties": [ - "liveperson.com" - ], - "resources": [ - "liveperson.com", - "liveperson.net", - "nuconomy.com" - ] - }, - "LiveRail": { - "properties": [ - "liverail.com" - ], - "resources": [ - "liverail.com" - ] - }, - "LiveRamp": { - "properties": [ - "liveramp.com" - ], - "resources": [ - "liveramp.com", - "tvpixel.com" - ] - }, - "LKQD": { - "properties": [ - "lkqd.com", - "lkqd.net" - ], - "resources": [ - "lkqd.com", - "lkqd.net" - ] - }, - "Local Yokel Media": { - "properties": [ - "localyokelmedia.com" - ], - "resources": [ - "localyokelmedia.com" - ] - }, - "Localytics": { - "properties": [ - "localytics.com" - ], - "resources": [ - "localytics.com" - ] - }, - "LockerDome": { - "properties": [ - "lockerdome.com" - ], - "resources": [ - "lockerdome.com" - ] - }, - "Lockerz": { - "properties": [ - "lockerz.com" - ], - "resources": [ - "lockerz.com" - ] - }, - "Logdy": { - "properties": [ - "logdy.com" - ], - "resources": [ - "logdy.com" - ] - }, - "Longboard Media": { - "properties": [ - "longboardmedia.com" - ], - "resources": [ - "longboardmedia.com" - ] - }, - "LongTail Video": { - "properties": [ - "jwplayer.com" - ], - "resources": [ - "longtailvideo.com", - "ltassrv.com" - ] - }, - "Loomia": { - "properties": [ - "loomia.com" - ], - "resources": [ - "loomia.com" - ] - }, - "LoopFuse": { - "properties": [ - "lfov.net", - "loopfuse.net" - ], - "resources": [ - "lfov.net", - "loopfuse.net" - ] - }, - "LoopMe": { - "properties": [ - "loopme.com" - ], - "resources": [ - "loopme.com" - ] - }, - "Lotame": { - "properties": [ - "crwdcntrl.net", - "lotame.com" - ], - "resources": [ - "crwdcntrl.net", - "lotame.com" - ] - }, - "LotLinx": { - "properties": [ - "lotlinx.com" - ], - "resources": [ - "lotlinx.com" - ] - }, - "Lower My Bills": { - "properties": [ - "lowermybills.com" - ], - "resources": [ - "lowermybills.com" - ] - }, - "lptracker": { - "properties": [ - "lptracker.io" - ], - "resources": [ - "lptracker.io" - ] - }, - "LucidMedia": { - "properties": [ - "lucidmedia.com" - ], - "resources": [ - "lucidmedia.com" - ] - }, - "LuckyOrange": { - "properties": [ - "luckyorange.com" - ], - "resources": [ - "luckyorange.com", - "luckyorange.net" - ] - }, - "Lynchpin": { - "properties": [ - "lynchpin.com" - ], - "resources": [ - "lynchpin.com", - "lypn.com" - ] - }, - "Lyris": { - "properties": [ - "aurea.com" - ], - "resources": [ - "aurea.com", - "clicktracks.com", - "lyris.com" - ] - }, - "Lytiks": { - "properties": [ - "lytiks.com" - ], - "resources": [ - "lytiks.com" - ] - }, - "m6d": { - "properties": [ - "dstillery.com" - ], - "resources": [ - "dstillery.com", - "m6d.com", - "media6degrees.com" - ] - }, - "Madhouse": { - "properties": [ - "madhouse.cn" - ], - "resources": [ - "madhouse.cn" - ] - }, - "Madison Logic": { - "properties": [ - "dinclinx.com", - "madisonlogic.com" - ], - "resources": [ - "dinclinx.com", - "madisonlogic.com" - ] - }, - "madvertise": { - "properties": [ - "madvertise.com" - ], - "resources": [ - "madvertise.com" - ] - }, - "Magnetic": { - "properties": [ - "domdex.net", - "magnetic.com" - ], - "resources": [ - "domdex.com", - "domdex.net", - "magnetic.com", - "qjex.net" - ] - }, - "Magnify360": { - "properties": [ - "dialogmgr.com", - "magnify360.com" - ], - "resources": [ - "dialogmgr.com", - "magnify360.com" - ] - }, - "MailChimp": { - "properties": [ - "campaign-archive1.com", - "mailchi.mp", - "mailchimp.com" - ], - "resources": [ - "campaign-archive1.com", - "list-manage.com", - "mailchi.mp", - "mailchimp.com" - ] - }, - "Mail.Ru": { - "properties": [ - "list.ru", - "mail.ru" - ], - "resources": [ - "list.ru", - "mail.ru" - ] - }, - "Manifest": { - "properties": [ - "bannerbank.ru", - "manifest.ru" - ], - "resources": [ - "bannerbank.ru", - "manifest.ru" - ] - }, - "Marchex": { - "properties": [ - "industrybrains.com", - "marchex.com" - ], - "resources": [ - "industrybrains.com", - "marchex.com" - ] - }, - "Marimedia": { - "properties": [ - "marimedia.net" - ], - "resources": [ - "marimedia.net" - ] - }, - "MarketGid": { - "properties": [ - "dt00.net", - "dt07.net", - "marketgid.com" - ], - "resources": [ - "dt00.net", - "dt07.net", - "marketgid.com" - ] - }, - "Marketo": { - "properties": [ - "marketo.com" - ], - "resources": [ - "marketo.com", - "marketo.net" - ] - }, - "Markit": { - "properties": [ - "markit.com", - "wsod.com" - ], - "resources": [ - "markit.com", - "wsod.com" - ] - }, - "MarkMonitor": { - "properties": [ - "9c9media.ca", - "markmonitor.com" - ], - "resources": [ - "9c9media.ca", - "markmonitor.com" - ] - }, - "Marktest": { - "properties": [ - "marktest.com", - "marktest.pt" - ], - "resources": [ - "marktest.com", - "marktest.pt" - ] - }, - "Martini Media": { - "properties": [ - "martiniadnetwork.com" - ], - "resources": [ - "martiniadnetwork.com", - "martinimedianetwork.com" - ] - }, - "mashero": { - "properties": [ - "mashero.com" - ], - "resources": [ - "mashero.com" - ] - }, - "MashLogic": { - "properties": [ - "mashlogic.com" - ], - "resources": [ - "mashlogic.com" - ] - }, - "Match.com": { - "properties": [ - "chemistry.com", - "match.com" - ], - "resources": [ - "chemistry.com", - "match.com", - "meetic-partners.com" - ] - }, - "Matomy": { - "properties": [ - "matomy.com" - ], - "resources": [ - "adnetinteractive.com", - "adsmarket.com", - "matomy.com", - "matomymarket.com", - "matomymedia.com", - "mediawhiz.com", - "optimatic.com", - "xtendmedia.com" - ] - }, - "MaxBounty": { - "properties": [ - "maxbounty.com", - "mb01.com" - ], - "resources": [ - "maxbounty.com", - "mb01.com" - ] - }, - "MaxMind": { - "properties": [ - "maxmind.com" - ], - "resources": [ - "maxmind.com", - "mmapiws.com" - ] - }, - "MaxPoint": { - "properties": [ - "maxpointinteractive.com", - "maxusglobal.com", - "mxptint.net" - ], - "resources": [ - "maxpointinteractive.com", - "maxusglobal.com", - "mxptint.net" - ] - }, - "McAfee": { - "properties": [ - "mcafee.com", - "mcafeesecure.com" - ], - "resources": [ - "mcafee.com", - "mcafeesecure.com", - "scanalert.com" - ] - }, - "MdotM": { - "properties": [ - "mdotm.com" - ], - "resources": [ - "mdotm.com" - ] - }, - "MediaBrix": { - "properties": [ - "mediabrix.com" - ], - "resources": [ - "mediabrix.com" - ] - }, - "MediaCom": { - "properties": [ - "mediacom.com" - ], - "resources": [ - "mediacom.com" - ] - }, - "mediaFORGE": { - "properties": [ - "mediaforge.com" - ], - "resources": [ - "mediaforge.com" - ] - }, - "Medialets": { - "properties": [ - "medialets.com" - ], - "resources": [ - "medialets.com" - ] - }, - "MediaMath": { - "properties": [ - "mediamath.com" - ], - "resources": [ - "adroitinteractive.com", - "designbloxlive.com", - "mathtag.com", - "mediamath.com" - ] - }, - "Médiamétrie-eStat": { - "properties": [ - "mediametrie-estat.com" - ], - "resources": [ - "estat.com", - "mediametrie-estat.com" - ] - }, - "media.net": { - "properties": [ - "media.net" - ], - "resources": [ - "media.net" - ] - }, - "Mediaocean": { - "properties": [ - "adbuyer.com", - "mediaocean.com" - ], - "resources": [ - "adbuyer.com", - "mediaocean.com" - ] - }, - "MediaShakers": { - "properties": [ - "media-servers.net", - "mediashakers.com" - ], - "resources": [ - "media-servers.net", - "mediashakers.com" - ] - }, - "MediaTrust": { - "properties": [ - "mediatrust.com" - ], - "resources": [ - "mediatrust.com" - ] - }, - "Medicx Media Solutions": { - "properties": [ - "medicxmedia.com" - ], - "resources": [ - "medicxmedia.com" - ] - }, - "Meebo": { - "properties": [ - "meebo.com" - ], - "resources": [ - "meebo.com", - "meebocdn.net" - ] - }, - "MegaIndex": { - "properties": [ - "megaindex.ru" - ], - "resources": [ - "megaindex.ru" - ] - }, - "Mercadopago": { - "properties": [ - "mercadolibre.cl", - "mercadolibre.co.cr", - "mercadolibre.com", - "mercadolibre.com.ar", - "mercadolibre.com.bo", - "mercadolibre.com.co", - "mercadolibre.com.do", - "mercadolibre.com.ec", - "mercadolibre.com.gt", - "mercadolibre.com.hn", - "mercadolibre.com.mx", - "mercadolibre.com.ni", - "mercadolibre.com.pa", - "mercadolibre.com.pe", - "mercadolibre.com.py", - "mercadolibre.com.sv", - "mercadolibre.com.uy", - "mercadolibre.com.ve", - "mercadolivre.com.br", - "mercadopago.com" - ], - "resources": [ - "mercadopago.com" - ] - }, - "Mercent": { - "properties": [ - "mercent.com" - ], - "resources": [ - "mercent.com" - ] - }, - "MerchantAdvantage": { - "properties": [ - "merchantadvantage.com" - ], - "resources": [ - "merchantadvantage.com" - ] - }, - "Merchenta": { - "properties": [ - "merchenta.com" - ], - "resources": [ - "merchenta.com" - ] - }, - "Merkle": { - "properties": [ - "merkleinc.com", - "rkdms.com" - ], - "resources": [ - "merkleinc.com", - "rimmkaufman.com", - "rkdms.com" - ] - }, - "Meta Network": { - "properties": [ - "metanetwork.com" - ], - "resources": [ - "metanetwork.com" - ] - }, - "Meteor": { - "properties": [ - "meteorsolutions.com" - ], - "resources": [ - "meteorsolutions.com" - ] - }, - "MetrixLab": { - "properties": [ - "crm-metrix.com", - "customerconversio.com", - "metrixlab.com", - "opinionbar.com" - ], - "resources": [ - "adoftheyear.com", - "crm-metrix.com", - "customerconversio.com", - "metrixlab.com", - "opinionbar.com" - ] - }, - "MicroAd": { - "properties": [ - "microad.jp", - "www.microad.jp" - ], - "resources": [ - "microad.jp", - "www.microad.jp" - ] - }, - "Microsoft": { - "properties": [ - "acompli.net", - "aka.ms", - "azure.com", - "azure.net", - "azurerms.com", - "bing.com", - "cloudappsecurity.com", - "gamesforwindows.com", - "getgamesmart.com", - "gfx.ms", - "healthvault.com", - "hockeyapp.net", - "ieaddons.com", - "iegallery.com", - "live.com", - "microsoft.com", - "microsoftalumni.com", - "microsoftalumni.org", - "microsoftazuread-sso.com", - "microsoftedgeinsiders.com", - "microsoftonline-p.com", - "microsoftonline-p.net", - "microsoftonline.com", - "microsoftstore.com", - "microsoftstream.com", - "msappproxy.net", - "msft.net", - "msftidentity.com", - "msidentity.com", - "msn.com", - "o365weve.com", - "oaspapps.com", - "office.com", - "office365.com", - "officelive.com", - "onedrive.com", - "onenote.com", - "outlook.com", - "outlookmobile.com", - "phonefactor.net", - "s-msn.com", - "sfx.ms", - "sharepoint.com", - "skype.com", - "skypeforbusiness.com", - "staffhub.ms", - "sway-extensions.com", - "sway.com", - "trafficmanager.net", - "virtualearth.net", - "visualstudio.com", - "windows.net", - "windowsazure.com", - "windowsphone.com", - "worldwidetelescope.org", - "wunderlist.com", - "xbox.com", - "yammer.com" - ], - "resources": [ - "aadrm.com", - "adbureau.net", - "adecn.com", - "aquantive.com", - "aspnetcdn.com", - "assets-yammer.com", - "azure.com", - "azureedge.net", - "bing.com", - "cloudapp.net", - "gamesforwindows.com", - "getgamesmart.com", - "gfx.ms", - "healthvault.com", - "live.com", - "microsoft.com", - "microsoftazuread-sso.com", - "microsoftonline-p.com", - "microsoftonline-p.net", - "microsoftonline.com", - "microsoftstore.com", - "msads.net", - "msauthimages.net", - "msecnd.net", - "msedge.net", - "msndirect.com", - "msocdn.com", - "netconversions.com", - "oaspapps.com", - "office.com", - "office.net", - "officelive.com", - "onenote.net", - "onestore.ms", - "onmicrosoft.com", - "outlook.com", - "roiservice.com", - "s-msn.com", - "sfbassets.com", - "sharepoint.com", - "skype.com", - "skypeassets.com", - "sway-cdn.com", - "sway-extensions.com", - "windows.net", - "windowsazure.com", - "yammerusercontent.com" - ] - }, - "Millennial Media": { - "properties": [ - "decktrade.com", - "millennialmedia.com", - "mydas.mobi" - ], - "resources": [ - "decktrade.com", - "millennialmedia.com", - "mydas.mobi" - ] - }, - "Mindset Media": { - "properties": [ - "mindset-media.com" - ], - "resources": [ - "mindset-media.com", - "mmismm.com" - ] - }, - "MinerAlt": { - "properties": [ - "mineralt.io", - "vidzi.nu", - "vidzi.tv" - ], - "resources": [ - "1q2w3.website", - "analytics.blue", - "aster18cdn.nl", - "belicimo.pw", - "besstahete.info", - "dinorslick.icu", - "feesocrald.com", - "gramombird.com", - "istlandoll.com", - "mepirtedic.com", - "mineralt.io", - "pampopholf.com", - "tercabilis.info", - "tulip18.com", - "vidzi.tv", - "yololike.space" - ] - }, - "Minescripts": { - "properties": [ - "minescripts.info" - ], - "resources": [ - "minescripts.info", - "sslverify.info" - ] - }, - "MineXMR": { - "properties": [ - "minexmr.stream" - ], - "resources": [ - "minexmr.stream" - ] - }, - "Mirando": { - "properties": [ - "mirando.de" - ], - "resources": [ - "mirando.de" - ] - }, - "Mixpanel": { - "properties": [ - "mixpanel.com" - ], - "resources": [ - "mixpanel.com", - "mxpnl.com" - ] - }, - "Mixpo": { - "properties": [ - "mixpo.com" - ], - "resources": [ - "mixpo.com" - ] - }, - "Moat": { - "properties": [ - "moat.com", - "moatads.com" - ], - "resources": [ - "moat.com", - "moatads.com" - ] - }, - "MobFox": { - "properties": [ - "mobfox.com" - ], - "resources": [ - "mobfox.com" - ] - }, - "Mobials": { - "properties": [ - "mobials.com" - ], - "resources": [ - "mobials.com" - ] - }, - "MobileAdTrading": { - "properties": [ - "mobileadtrading.com" - ], - "resources": [ - "mobileadtrading.com" - ] - }, - "Mobile Meteor": { - "properties": [ - "mobilemeteor.com" - ], - "resources": [ - "mobilemeteor.com", - "showmeinn.com" - ] - }, - "Mobile Storm": { - "properties": [ - "mobilestorm.com" - ], - "resources": [ - "mobilestorm.com" - ] - }, - "MobVision": { - "properties": [ - "admoda.com" - ], - "resources": [ - "admoda.com", - "mobvision.com" - ] - }, - "Mocean Mobile": { - "properties": [ - "moceanmobile.com" - ], - "resources": [ - "moceanmobile.com" - ] - }, - "Mochila": { - "properties": [ - "mochila.com" - ], - "resources": [ - "mochila.com" - ] - }, - "Mojiva": { - "properties": [ - "mojiva.com" - ], - "resources": [ - "mojiva.com" - ] - }, - "Monetate": { - "properties": [ - "monetate.com", - "monetate.net" - ], - "resources": [ - "monetate.com", - "monetate.net" - ] - }, - "MONETIZEdigital": { - "properties": [ - "cpalead.com" - ], - "resources": [ - "cpalead.com" - ] - }, - "Monetize More": { - "properties": [ - "monetizemore.com" - ], - "resources": [ - "monetizemore.com" - ] - }, - "Mongoose Metrics": { - "properties": [ - "mongoosemetrics.com" - ], - "resources": [ - "mongoosemetrics.com" - ] - }, - "Monitus": { - "properties": [ - "monitus.net" - ], - "resources": [ - "monitus.net" - ] - }, - "Monoloop": { - "properties": [ - "monoloop.com" - ], - "resources": [ - "monoloop.com" - ] - }, - "Monster": { - "properties": [ - "monster.com" - ], - "resources": [ - "monster.com" - ] - }, - "Moolah Media": { - "properties": [ - "moolah-media.com", - "moolahmedia.com" - ], - "resources": [ - "moolah-media.com", - "moolahmedia.com" - ] - }, - "MoPub": { - "properties": [ - "mopub.com" - ], - "resources": [ - "mopub.com" - ] - }, - "motigo": { - "properties": [ - "motigo.com" - ], - "resources": [ - "motigo.com", - "nedstatbasic.net" - ] - }, - "Mouseflow": { - "properties": [ - "mouseflow.com" - ], - "resources": [ - "mouseflow.com" - ] - }, - "MovieLush.com": { - "properties": [ - "affbuzzads.com", - "movielush.com" - ], - "resources": [ - "affbuzzads.com", - "movielush.com" - ] - }, - "Multiple Stream Media": { - "properties": [ - "adclickmedia.com", - "multiplestreammktg.com" - ], - "resources": [ - "adclickmedia.com", - "multiplestreammktg.com" - ] - }, - "MUNDO Media": { - "properties": [ - "mundomedia.com", - "silver-path.com" - ], - "resources": [ - "mundomedia.com", - "silver-path.com" - ] - }, - "MyCounter": { - "properties": [ - "mycounter.com.ua" - ], - "resources": [ - "mycounter.com.ua" - ] - }, - "MyPagerank.Net": { - "properties": [ - "mypagerank.net" - ], - "resources": [ - "mypagerank.net" - ] - }, - "MyPressPlus": { - "properties": [ - "mypressplus.com", - "ppjol.net" - ], - "resources": [ - "mypressplus.com", - "ppjol.net" - ] - }, - "Mystighty": { - "properties": [ - "mystighty.info" - ], - "resources": [ - "mystighty.info", - "sweeterge.info" - ] - }, - "myThings": { - "properties": [ - "mythings.com", - "mythingsmedia.com" - ], - "resources": [ - "mythings.com", - "mythingsmedia.com" - ] - }, - "MyWebGrocer": { - "properties": [ - "mywebgrocer.com" - ], - "resources": [ - "mywebgrocer.com" - ] - }, - "Nanigans": { - "properties": [ - "nanigans.com" - ], - "resources": [ - "nanigans.com" - ] - }, - "Narrative": { - "properties": [ - "narrative.io" - ], - "resources": [ - "narrative.io" - ] - }, - "NativeAds": { - "properties": [ - "nativeads.com" - ], - "resources": [ - "nativeads.com" - ] - }, - "Nativo": { - "properties": [ - "nativo.com", - "postrelease.com" - ], - "resources": [ - "nativo.com", - "postrelease.com" - ] - }, - "Navegg": { - "properties": [ - "navdmp.com", - "navegg.com" - ], - "resources": [ - "navdmp.com", - "navegg.com" - ] - }, - "NDN": { - "properties": [ - "newsinc.com" - ], - "resources": [ - "newsinc.com" - ] - }, - "Negishim": { - "properties": [ - "negishim.org" - ], - "resources": [ - "negishim.org" - ] - }, - "NeroHut": { - "properties": [ - "nerohut.com" - ], - "resources": [ - "nerohut.com", - "nhsrv.cf" - ] - }, - "NetAffiliation": { - "properties": [ - "netaffiliation.com" - ], - "resources": [ - "netaffiliation.com" - ] - }, - "Net Applications": { - "properties": [ - "netapplications.com" - ], - "resources": [ - "hitsprocessor.com", - "netapplications.com" - ] - }, - "NetBina": { - "properties": [ - "netbina.com" - ], - "resources": [ - "netbina.com" - ] - }, - "NetElixir": { - "properties": [ - "adelixir.com", - "netelixir.com" - ], - "resources": [ - "adelixir.com", - "netelixir.com" - ] - }, - "Netmining": { - "properties": [ - "netmining.com", - "netmng.com" - ], - "resources": [ - "netmining.com", - "netmng.com" - ] - }, - "Net-Results": { - "properties": [ - "net-results.com", - "nr7.us" - ], - "resources": [ - "cdnma.com", - "net-results.com", - "nr7.us" - ] - }, - "NetSeer": { - "properties": [ - "netseer.com" - ], - "resources": [ - "netseer.com" - ] - }, - "NetShelter": { - "properties": [ - "ziffdavistech.com" - ], - "resources": [ - "netshelter.com", - "netshelter.net", - "ziffdavistech.com" - ] - }, - "Neustar": { - "properties": [ - "adadvisor.net", - "home.neustar", - "neustar.biz" - ], - "resources": [ - "adadvisor.net", - "neustar.biz" - ] - }, - "New Relic": { - "properties": [ - "newrelic.com" - ], - "resources": [ - "newrelic.com", - "nr-data.net" - ] - }, - "NewsRight": { - "properties": [ - "apnewsregistry.com", - "newsright.com" - ], - "resources": [ - "apnewsregistry.com", - "newsright.com" - ] - }, - "newtention": { - "properties": [ - "newtention.de", - "newtention.net", - "newtentionassets.net" - ], - "resources": [ - "newtention.de", - "newtention.net", - "newtentionassets.net" - ] - }, - "Nexage": { - "properties": [ - "nexage.com" - ], - "resources": [ - "nexage.com" - ] - }, - "Nextag": { - "properties": [ - "nextag.com" - ], - "resources": [ - "nextag.com" - ] - }, - "NextPerformance": { - "properties": [ - "nextperf.com", - "nextperformance.com", - "nxtck.com" - ], - "resources": [ - "nextperf.com", - "nextperformance.com", - "nxtck.com" - ] - }, - "NextSTAT": { - "properties": [ - "nextstat.com" - ], - "resources": [ - "nextstat.com" - ] - }, - "Nielsen": { - "properties": [ - "glanceguide.com", - "imrworldwide.com", - "imrworldwide.net", - "nielsen.com" - ], - "resources": [ - "glanceguide.com", - "imrworldwide.com", - "imrworldwide.net", - "nielsen.com" - ] - }, - "Ninua": { - "properties": [ - "networkedblogs.com", - "ninua.com" - ], - "resources": [ - "networkedblogs.com", - "ninua.com" - ] - }, - "Nokta": { - "properties": [ - "noktamedya.com", - "virgul.com" - ], - "resources": [ - "noktamedya.com", - "virgul.com" - ] - }, - "NowSpots": { - "properties": [ - "nowspots.com" - ], - "resources": [ - "nowspots.com" - ] - }, - "nrelate": { - "properties": [ - "nrelate.com" - ], - "resources": [ - "nrelate.com" - ] - }, - "NuDataSecurity": { - "properties": [ - "nudatasecurity.com" - ], - "resources": [ - "nudatasecurity.com" - ] - }, - "Nuffnang": { - "properties": [ - "nuffnang.com", - "nuffnang.com.my", - "www.nuffnang.com.my" - ], - "resources": [ - "nuffnang.com", - "nuffnang.com.my", - "www.nuffnang.com.my" - ] - }, - "nugg.ad": { - "properties": [ - "nugg.ad" - ], - "resources": [ - "nugg.ad", - "nuggad.net" - ] - }, - "nurago": { - "properties": [ - "sensic.net" - ], - "resources": [ - "nurago.com", - "nurago.de", - "sensic.net" - ] - }, - "Oberon Media": { - "properties": [ - "iwin.com" - ], - "resources": [ - "blaze.com", - "iwin.com", - "oberon-media.com" - ] - }, - "Observer": { - "properties": [ - "observerapp.com" - ], - "resources": [ - "observerapp.com" - ] - }, - "Ohana Media": { - "properties": [ - "adohana.com", - "ohana-media.com", - "ohanaqb.com" - ], - "resources": [ - "adohana.com", - "ohana-media.com", - "ohanaqb.com" - ] - }, - "Omnicom Group": { - "properties": [ - "accuenmedia.com", - "omnicomgroup.com" - ], - "resources": [ - "accuenmedia.com", - "omnicomgroup.com", - "p-td.com" - ] - }, - "onAd": { - "properties": [ - "onad.eu" - ], - "resources": [ - "onad.eu" - ] - }, - "OnAudience": { - "properties": [ - "behavioralengine.com", - "onaudience.com" - ], - "resources": [ - "behavioralengine.com", - "onaudience.com" - ] - }, - "Onclusive": { - "properties": [ - "onclusive.com" - ], - "resources": [ - "airpr.com" - ] - }, - "OneAd": { - "properties": [ - "onead.com.tw" - ], - "resources": [ - "guoshipartners.com", - "onevision.com.tw" - ] - }, - "One iota": { - "properties": [ - "itsoneiota.com", - "oneiota.co.uk" - ], - "resources": [ - "itsoneiota.com", - "oneiota.co.uk" - ] - }, - "OneStat": { - "properties": [ - "onestat.com" - ], - "resources": [ - "onestat.com" - ] - }, - "Oneupweb": { - "properties": [ - "oneupweb.com", - "sodoit.com" - ], - "resources": [ - "oneupweb.com", - "sodoit.com" - ] - }, - "OnlineMetrix": { - "properties": [ - "online-metrix.net" - ], - "resources": [ - "online-metrix.net" - ] - }, - "Ooyala": { - "properties": [ - "ooyala.com" - ], - "resources": [ - "oo4.com", - "ooyala.com" - ] - }, - "Open New Media": { - "properties": [ - "onm.de" - ], - "resources": [ - "onm.de" - ] - }, - "Openstat": { - "properties": [ - "openstat.com" - ], - "resources": [ - "openstat.com", - "openstat.ru", - "spylog.com" - ] - }, - "Opentracker": { - "properties": [ - "opentracker.net" - ], - "resources": [ - "opentracker.net" - ] - }, - "OpenX": { - "properties": [ - "openx.com", - "openx.net" - ], - "resources": [ - "liftdna.com", - "openx.com", - "openx.net", - "openx.org", - "openxenterprise.com", - "servedbyopenx.com" - ] - }, - "Opera": { - "properties": [ - "opera.com" - ], - "resources": [ - "mobiletheory.com", - "opera.com" - ] - }, - "Opolen": { - "properties": [ - "opolen.com.br" - ], - "resources": [ - "opolen.com.br" - ] - }, - "OPT": { - "properties": [ - "www.opt.ne.jp" - ], - "resources": [ - "advg.jp", - "opt.ne.jp", - "p-advg.com", - "www.opt.ne.jp" - ] - }, - "Optify": { - "properties": [ - "optify.net" - ], - "resources": [ - "optify.net" - ] - }, - "Optimal": { - "properties": [ - "bn.co" - ], - "resources": [ - "cpmadvisors.com", - "cpmatic.com", - "nprove.com", - "optim.al", - "orbengine.com", - "xa.net" - ] - }, - "Optimizely": { - "properties": [ - "optimizely.com" - ], - "resources": [ - "optimizely.com" - ] - }, - "OptimumResponse": { - "properties": [ - "optimumresponse.com" - ], - "resources": [ - "optimumresponse.com" - ] - }, - "OptinMonster": { - "properties": [ - "optinmonster.com", - "optnmstr.com" - ], - "resources": [ - "optinmonster.com", - "optnmstr.com" - ] - }, - "OptMD": { - "properties": [ - "optmd.com" - ], - "resources": [ - "optmd.com" - ] - }, - "Oracle": { - "properties": [ - "oracle.com" - ], - "resources": [ - "atgsvcs.com", - "eloqua.com", - "estara.com", - "instantservice.com", - "istrack.com", - "maxymiser.com", - "oracle.com" - ] - }, - "OrangeSoda": { - "properties": [ - "orangesoda.com", - "otracking.com" - ], - "resources": [ - "orangesoda.com", - "otracking.com" - ] - }, - "Outbrain": { - "properties": [ - "outbrain.com", - "sphere.com" - ], - "resources": [ - "outbrain.com", - "sphere.com", - "visualrevenue.com" - ] - }, - "Out There Media": { - "properties": [ - "out-there-media.com" - ], - "resources": [ - "out-there-media.com" - ] - }, - "Oversee.net": { - "properties": [ - "dsnextgen.com", - "oversee.net" - ], - "resources": [ - "dsnextgen.com", - "oversee.net" - ] - }, - "ÖWA": { - "properties": [ - "oewa.at" - ], - "resources": [ - "oewa.at", - "oewabox.at" - ] - }, - "OwnerIQ": { - "properties": [ - "owneriq.com", - "owneriq.net" - ], - "resources": [ - "owneriq.com", - "owneriq.net" - ] - }, - "OxaMedia": { - "properties": [ - "oxamedia.com" - ], - "resources": [ - "adconnexa.com", - "adsbwm.com", - "oxamedia.com" - ] - }, - "PageFair": { - "properties": [ - "pagefair.com", - "pagefair.net" - ], - "resources": [ - "pagefair.com", - "pagefair.net" - ] - }, - "Paid-To-Promote.net": { - "properties": [ - "paid-to-promote.net" - ], - "resources": [ - "paid-to-promote.net" - ] - }, - "Papaya": { - "properties": [ - "papayamobile.com" - ], - "resources": [ - "papayamobile.com" - ] - }, - "Pardot": { - "properties": [ - "pardot.com" - ], - "resources": [ - "pardot.com" - ] - }, - "Parse.ly": { - "properties": [ - "parsely.com" - ], - "resources": [ - "parsely.com" - ] - }, - "PayHit": { - "properties": [ - "payhit.com" - ], - "resources": [ - "payhit.com" - ] - }, - "PaymentsMB": { - "properties": [ - "paymentsmb.com" - ], - "resources": [ - "paymentsmb.com" - ] - }, - "Paypal": { - "properties": [ - "paypal.com", - "simility.com" - ], - "resources": [ - "paypal.com", - "simility.com" - ] - }, - "Paypopup.com": { - "properties": [ - "paypopup.com" - ], - "resources": [ - "lzjl.com", - "paypopup.com" - ] - }, - "PebblePost": { - "properties": [ - "pebblepost.com" - ], - "resources": [ - "pbbl.co" - ] - }, - "Peer39": { - "properties": [ - "peer39.com", - "peer39.net" - ], - "resources": [ - "peer39.com", - "peer39.net" - ] - }, - "PeerFly": { - "properties": [ - "peerfly.com" - ], - "resources": [ - "peerfly.com" - ] - }, - "Peerius": { - "properties": [ - "peerius.com" - ], - "resources": [ - "peerius.com" - ] - }, - "Performancing": { - "properties": [ - "performancing.com" - ], - "resources": [ - "performancing.com" - ] - }, - "PerimeterX": { - "properties": [ - "perimeterx.com" - ], - "resources": [ - "perimeterx.com" - ] - }, - "PersianStat.com": { - "properties": [ - "persianstat.com" - ], - "resources": [ - "persianstat.com" - ] - }, - "Pheedo": { - "properties": [ - "pheedo.com" - ], - "resources": [ - "pheedo.com" - ] - }, - "Phonalytics": { - "properties": [ - "phonalytics.com" - ], - "resources": [ - "phonalytics.com" - ] - }, - "phpMyVisites": { - "properties": [ - "phpmyvisites.us" - ], - "resources": [ - "phpmyvisites.us" - ] - }, - "Pictela": { - "properties": [ - "pictela.com", - "pictela.net" - ], - "resources": [ - "pictela.com", - "pictela.net" - ] - }, - "PinPoll": { - "properties": [ - "pinpoll.com" - ], - "resources": [ - "pinpoll.com" - ] - }, - "Pinterest": { - "properties": [ - "pinterest.at", - "pinterest.ca", - "pinterest.ch", - "pinterest.cl", - "pinterest.co.kr", - "pinterest.co.uk", - "pinterest.com", - "pinterest.com.au", - "pinterest.com.mx", - "pinterest.de", - "pinterest.dk", - "pinterest.es", - "pinterest.fr", - "pinterest.ie", - "pinterest.jp", - "pinterest.nz", - "pinterest.pt", - "pinterest.se" - ], - "resources": [ - "pinimg.com", - "pinterest.com" - ] - }, - "Piwik": { - "properties": [ - "piwik.org" - ], - "resources": [ - "piwik.org" - ] - }, - "PixAnalytics": { - "properties": [ - "pixanalytics.com" - ], - "resources": [ - "pixanalytics.com" - ] - }, - "Pixel.sg": { - "properties": [ - "pixel.sg" - ], - "resources": [ - "pixel.sg" - ] - }, - "Piximedia": { - "properties": [ - "piximedia.com" - ], - "resources": [ - "piximedia.com" - ] - }, - "Pixlee": { - "properties": [ - "pixlee.com" - ], - "resources": [ - "pixlee.com" - ] - }, - "PLATFORM ONE": { - "properties": [ - "platform-one.co.jp", - "www.platform-one.co.jp" - ], - "resources": [ - "platform-one.co.jp", - "www.platform-one.co.jp" - ] - }, - "plista": { - "properties": [ - "plista.com" - ], - "resources": [ - "plista.com" - ] - }, - "PocketCents": { - "properties": [ - "pocketcents.com" - ], - "resources": [ - "pocketcents.com" - ] - }, - "Polar Mobile": { - "properties": [ - "mediavoice.com" - ], - "resources": [ - "mediavoice.com", - "polarmobile.com" - ] - }, - "Politads": { - "properties": [ - "politads.com" - ], - "resources": [ - "politads.com" - ] - }, - "Polymorph": { - "properties": [ - "getpolymorph.com" - ], - "resources": [ - "adsnative.com", - "getpolymorph.com" - ] - }, - "Pontiflex": { - "properties": [ - "pontiflex.com" - ], - "resources": [ - "pontiflex.com" - ] - }, - "Poool": { - "properties": [ - "poool.fr" - ], - "resources": [ - "poool.fr" - ] - }, - "PopAds": { - "properties": [ - "popads.net" - ], - "resources": [ - "popads.net", - "popadscdn.net" - ] - }, - "PopRule": { - "properties": [ - "gocampaignlive.com", - "poprule.com" - ], - "resources": [ - "gocampaignlive.com", - "poprule.com" - ] - }, - "Popunder.ru": { - "properties": [ - "popunder.ru" - ], - "resources": [ - "popunder.ru" - ] - }, - "Po.st": { - "properties": [ - "po.st" - ], - "resources": [ - "po.st" - ] - }, - "Powerlinks": { - "properties": [ - "powerlinks.com" - ], - "resources": [ - "powerlinks.com" - ] - }, - "PPCProtect": { - "properties": [ - "ppcprotect.com" - ], - "resources": [ - "ppcprotect.com" - ] - }, - "PrecisionClick": { - "properties": [ - "precisionclick.com" - ], - "resources": [ - "precisionclick.com" - ] - }, - "PredictAd": { - "properties": [ - "predictad.com" - ], - "resources": [ - "predictad.com" - ] - }, - "Pressflex": { - "properties": [ - "blogads.com", - "pressflex.com" - ], - "resources": [ - "blogads.com", - "pressflex.com" - ] - }, - "Prime Visibility": { - "properties": [ - "primevisibility.com" - ], - "resources": [ - "adcde.com", - "addlvr.com", - "adonnetwork.com", - "adonnetwork.net", - "adtrgt.com", - "bannertgt.com", - "cptgt.com", - "cpvfeed.com", - "cpvtgt.com", - "dashboardad.net", - "popcde.com", - "primevisibility.com", - "sdfje.com", - "urtbk.com" - ] - }, - "Primis": { - "properties": [ - "primis.tech" - ], - "resources": [ - "sekindo.com" - ] - }, - "PrismApp": { - "properties": [ - "prismapp.io" - ], - "resources": [ - "prismapp.io" - ] - }, - "Proclivity": { - "properties": [ - "proclivitysystems.com", - "pswec.com" - ], - "resources": [ - "proclivitymedia.com", - "proclivitysystems.com", - "pswec.com" - ] - }, - "Project Wonderful": { - "properties": [ - "projectwonderful.com" - ], - "resources": [ - "projectwonderful.com" - ] - }, - "PrometheusIntelligenceTechnology": { - "properties": [ - "prometheusintelligencetechnology.com" - ], - "resources": [ - "prometheusintelligencetechnology.com" - ] - }, - "Pronunciator": { - "properties": [ - "pronunciator.com", - "visitorville.com" - ], - "resources": [ - "pronunciator.com", - "visitorville.com" - ] - }, - "Propeller Ads": { - "properties": [ - "propellerads.com" - ], - "resources": [ - "propellerads.com" - ] - }, - "Prosperent": { - "properties": [ - "prosperent.com" - ], - "resources": [ - "prosperent.com" - ] - }, - "Protected Media": { - "properties": [ - "ad-score.com", - "protected.media" - ], - "resources": [ - "ad-score.com", - "protected.media" - ] - }, - "Provers": { - "properties": [ - "provers.pro" - ], - "resources": [ - "provers.pro" - ] - }, - "Psonstrentie": { - "properties": [ - "psonstrentie.info" - ], - "resources": [ - "psonstrentie.info" - ] - }, - "Public-Idées": { - "properties": [ - "publicidees.com" - ], - "resources": [ - "publicidees.com" - ] - }, - "Publishers Clearing House": { - "properties": [ - "pch.com" - ], - "resources": [ - "pch.com" - ] - }, - "PubMatic": { - "properties": [ - "pubmatic.com" - ], - "resources": [ - "pubmatic.com", - "revinet.com" - ] - }, - "PulsePoint": { - "properties": [ - "pulsepoint.com" - ], - "resources": [ - "pulsepoint.com" - ] - }, - "PunchTab": { - "properties": [ - "punchtab.com" - ], - "resources": [ - "punchtab.com" - ] - }, - "quadrantOne": { - "properties": [ - "quadrantone.com" - ], - "resources": [ - "quadrantone.com" - ] - }, - "Quake Marketing": { - "properties": [ - "quakemarketing.com" - ], - "resources": [ - "quakemarketing.com" - ] - }, - "Qualaroo": { - "properties": [ - "qualaroo.com" - ], - "resources": [ - "kissinsights.com", - "qualaroo.com" - ] - }, - "Quantcast": { - "properties": [ - "quantcast.com", - "quantserve.com" - ], - "resources": [ - "quantcast.com", - "quantserve.com" - ] - }, - "QuantumAdvertising": { - "properties": [ - "quantum-advertising.com" - ], - "resources": [ - "quantum-advertising.com" - ] - }, - "QuinStreet": { - "properties": [ - "quinstreet.com", - "thecounter.com" - ], - "resources": [ - "qnsr.com", - "qsstats.com", - "quinstreet.com", - "thecounter.com" - ] - }, - "Quintelligence": { - "properties": [ - "quintelligence.com" - ], - "resources": [ - "quintelligence.com" - ] - }, - "QUISMA": { - "properties": [ - "quisma.com" - ], - "resources": [ - "iaded.com", - "quisma.com", - "quismatch.com", - "xaded.com", - "xmladed.com" - ] - }, - "RadarURL": { - "properties": [ - "radarurl.com" - ], - "resources": [ - "radarurl.com" - ] - }, - "Radial": { - "properties": [ - "radial.com" - ], - "resources": [ - "gsicommerce.com", - "gsimedia.net" - ] - }, - "Radiate Media": { - "properties": [ - "gtnetwork.com.au", - "solesolution.com" - ], - "resources": [ - "gtnetwork.com.au", - "matchbin.com", - "radiatemedia.com", - "solesolution.com" - ] - }, - "RadiumOne": { - "properties": [ - "radiumone.com" - ], - "resources": [ - "gwallet.com", - "radiumone.com" - ] - }, - "Radius Marketing": { - "properties": [ - "radiusmarketing.com" - ], - "resources": [ - "radiusmarketing.com" - ] - }, - "Rambler": { - "properties": [ - "rambler.ru" - ], - "resources": [ - "rambler.ru" - ] - }, - "Rapleaf": { - "properties": [ - "rapleaf.com", - "rlcdn.com" - ], - "resources": [ - "rapleaf.com", - "rlcdn.com" - ] - }, - "ReachLocal": { - "properties": [ - "reachlocal.com", - "rlcdn.net" - ], - "resources": [ - "reachlocal.com", - "rlcdn.net" - ] - }, - "React2Media": { - "properties": [ - "react2media.com" - ], - "resources": [ - "react2media.com" - ] - }, - "reddit": { - "properties": [ - "reddit.com" - ], - "resources": [ - "reddit.com" - ] - }, - "Redux Media": { - "properties": [ - "reduxmedia.com" - ], - "resources": [ - "reduxmedia.com" - ] - }, - "Rekko": { - "properties": [ - "convertglobal.com", - "rekko.com" - ], - "resources": [ - "convertglobal.com", - "rekko.com" - ] - }, - "Reklamport": { - "properties": [ - "reklamport.com" - ], - "resources": [ - "reklamport.com" - ] - }, - "Reklam Store": { - "properties": [ - "reklamstore.com" - ], - "resources": [ - "reklamstore.com" - ] - }, - "Reklamz": { - "properties": [ - "reklamz.com" - ], - "resources": [ - "reklamz.com" - ] - }, - "Relevad": { - "properties": [ - "relestar.com", - "relevad.com" - ], - "resources": [ - "relestar.com", - "relevad.com" - ] - }, - "Renegade Internet": { - "properties": [ - "advertserve.com", - "renegadeinternet.com" - ], - "resources": [ - "advertserve.com", - "renegadeinternet.com" - ] - }, - "Reporo": { - "properties": [ - "reporo.com" - ], - "resources": [ - "buzzcity.com" - ] - }, - "Research Now": { - "properties": [ - "researchnow.com", - "valuedopinions.co.uk" - ], - "resources": [ - "researchnow.com", - "valuedopinions.co.uk" - ] - }, - "ResolutionMedia": { - "properties": [ - "nonstoppartner.net" - ], - "resources": [ - "nonstoppartner.net" - ] - }, - "Resolution Media": { - "properties": [ - "resolutionmedia.com" - ], - "resources": [ - "resolutionmedia.com" - ] - }, - "Resonate": { - "properties": [ - "resonateinsights.com", - "resonatenetworks.com" - ], - "resources": [ - "reson8.com", - "resonateinsights.com", - "resonatenetworks.com" - ] - }, - "Responsys": { - "properties": [ - "responsys.com" - ], - "resources": [ - "responsys.com" - ] - }, - "Retail Automata": { - "properties": [ - "retailautomata.com" - ], - "resources": [ - "retailautomata.com" - ] - }, - "ReTargeter": { - "properties": [ - "retargeter.com" - ], - "resources": [ - "retargeter.com" - ] - }, - "Retirement Living": { - "properties": [ - "blvdstatus.com", - "retirement-living.com" - ], - "resources": [ - "blvdstatus.com", - "retirement-living.com" - ] - }, - "RevContent": { - "properties": [ - "revcontent.com" - ], - "resources": [ - "revcontent.com" - ] - }, - "RevenueMax": { - "properties": [ - "revenuemax.de" - ], - "resources": [ - "revenuemax.de" - ] - }, - "Revtracks": { - "properties": [ - "revtrax.com" - ], - "resources": [ - "revtrax.com" - ] - }, - "Rhythm": { - "properties": [ - "rhythmone.com" - ], - "resources": [ - "1rx.io", - "rhythmnewmedia.com", - "rhythmone.com", - "rhythmxchange.com", - "rnmd.net" - ] - }, - "RichAudience": { - "properties": [ - "richaudience.com" - ], - "resources": [ - "richaudience.com" - ] - }, - "RichRelevance": { - "properties": [ - "richrelevance.com" - ], - "resources": [ - "richrelevance.com" - ] - }, - "RightAction": { - "properties": [ - "rightaction.com" - ], - "resources": [ - "rightaction.com" - ] - }, - "RIM": { - "properties": [ - "global.blackberry.com", - "laptopverge.com" - ], - "resources": [ - "global.blackberry.com", - "laptopverge.com", - "rim.com", - "scoreloop.com" - ] - }, - "Ringier": { - "properties": [ - "ringier.cz" - ], - "resources": [ - "ringier.cz" - ] - }, - "RMBN": { - "properties": [ - "traforet.com" - ], - "resources": [ - "rmbn.net", - "rmbn.ru", - "traforet.com" - ] - }, - "RMM": { - "properties": [ - "rmmonline.com" - ], - "resources": [ - "rmmonline.com" - ] - }, - "Rocket Fuel": { - "properties": [ - "rfihub.com", - "rfihub.net", - "rocketfuel.com" - ], - "resources": [ - "rfihub.com", - "rfihub.net", - "rocketfuel.com", - "ru4.com", - "xplusone.com" - ] - }, - "Rollick": { - "properties": [ - "gorollick.com" - ], - "resources": [ - "rollick.io" - ] - }, - "Rovion": { - "properties": [ - "rovion.com" - ], - "resources": [ - "rovion.com" - ] - }, - "Roxr": { - "properties": [ - "clicky.com", - "roxr.net" - ], - "resources": [ - "clicky.com", - "getclicky.com", - "roxr.net", - "staticstuff.net" - ] - }, - "rtk": { - "properties": [ - "rtk.io" - ], - "resources": [ - "rtk.io" - ] - }, - "RubiconProject": { - "properties": [ - "rubiconproject.com" - ], - "resources": [ - "adsbyisocket.com", - "isocket.com", - "rubiconproject.com" - ] - }, - "RunAds": { - "properties": [ - "runads.com" - ], - "resources": [ - "runads.com", - "rundsp.com" - ] - }, - "RuTarget": { - "properties": [ - "rutarget.ru" - ], - "resources": [ - "rutarget.ru" - ] - }, - "Sabavision": { - "properties": [ - "sabavision.com" - ], - "resources": [ - "sabavision.com" - ] - }, - "Sabre": { - "properties": [ - "reztrack.com", - "sabre.com", - "sabrehospitality.com" - ], - "resources": [ - "reztrack.com", - "sabre.com", - "sabrehospitality.com" - ] - }, - "Safecount": { - "properties": [ - "safecount.net" - ], - "resources": [ - "dl-rms.com", - "dlqm.net", - "questionmarket.com", - "safecount.net" - ] - }, - "SageMetrics": { - "properties": [ - "sagemetrics.com" - ], - "resources": [ - "sageanalyst.net", - "sagemetrics.com" - ] - }, - "Salesforce.com": { - "properties": [ - "force.com", - "salesforce.com", - "trailblazer.me" - ], - "resources": [ - "documentforce.com", - "force.com", - "forcesslreports.com", - "forceusercontent.com", - "lightning.com", - "salesforce-communities.com", - "salesforce-hub.com", - "salesforce.com", - "salesforceliveagent.com", - "trailblazer.me", - "visualforce.com" - ] - }, - "Salesintelligence": { - "properties": [ - "salesintelligence.pl" - ], - "resources": [ - "plugin.management" - ] - }, - "Samurai Factory": { - "properties": [ - "samurai-factory.jp", - "shinobi.jp" - ], - "resources": [ - "samurai-factory.jp", - "shinobi.jp" - ] - }, - "SAP": { - "properties": [ - "sap.com" - ], - "resources": [ - "sap.com", - "seewhy.com" - ] - }, - "Sapient": { - "properties": [ - "bridgetrack.com", - "sapient.com" - ], - "resources": [ - "bridgetrack.com", - "sapient.com" - ] - }, - "SAS": { - "properties": [ - "aimatch.com", - "sas.com" - ], - "resources": [ - "aimatch.com", - "sas.com" - ] - }, - "SAY": { - "properties": [ - "saymedia.com", - "typepad.com", - "videoegg.com" - ], - "resources": [ - "saymedia.com", - "typepad.com", - "videoegg.com" - ] - }, - "Scandinavian AdNetworks": { - "properties": [ - "scandinavianadnetworks.com" - ], - "resources": [ - "scandinavianadnetworks.com" - ] - }, - "ScribeFire": { - "properties": [ - "scribefire.com" - ], - "resources": [ - "scribefire.com" - ] - }, - "Scribol": { - "properties": [ - "scribol.com" - ], - "resources": [ - "scribol.com" - ] - }, - "SearchForce": { - "properties": [ - "searchforce.com", - "searchforce.net" - ], - "resources": [ - "searchforce.com", - "searchforce.net" - ] - }, - "Seevast": { - "properties": [ - "kanoodle.com" - ], - "resources": [ - "kanoodle.com", - "pulse360.com", - "seevast.com", - "syndigonetworks.com" - ] - }, - "SeeVolution": { - "properties": [ - "seevolution.com", - "svlu.net" - ], - "resources": [ - "seevolution.com", - "svlu.net" - ] - }, - "Segment.io": { - "properties": [ - "segment.io" - ], - "resources": [ - "segment.io" - ] - }, - "Selectable Media": { - "properties": [ - "selectablemedia.com" - ], - "resources": [ - "nabbr.com", - "selectablemedia.com" - ] - }, - "Semantiqo": { - "properties": [ - "semantiqo.com" - ], - "resources": [ - "semantiqo.com" - ] - }, - "Semasio": { - "properties": [ - "semasio.com" - ], - "resources": [ - "semasio.com", - "semasio.net" - ] - }, - "SendPulse": { - "properties": [ - "sendpulse.com" - ], - "resources": [ - "sendpulse.com" - ] - }, - "Service4refresh": { - "properties": [ - "service4refresh.info" - ], - "resources": [ - "service4refresh.info" - ] - }, - "SessionCam": { - "properties": [ - "sessioncam.com" - ], - "resources": [ - "sessioncam.com" - ] - }, - "SevenAds": { - "properties": [ - "sevenads.net" - ], - "resources": [ - "sevenads.net" - ] - }, - "SexInYourCity": { - "properties": [ - "sexinyourcity.com" - ], - "resources": [ - "sexinyourcity.com" - ] - }, - "ShaftTraffic": { - "properties": [ - "shafttraffic.com" - ], - "resources": [ - "libertystmedia.com", - "shafttraffic.com" - ] - }, - "Shareaholic": { - "properties": [ - "shareaholic.com" - ], - "resources": [ - "shareaholic.com" - ] - }, - "ShareASale": { - "properties": [ - "shareasale.com" - ], - "resources": [ - "shareasale.com" - ] - }, - "ShareThis": { - "properties": [ - "sharethis.com" - ], - "resources": [ - "sharethis.com" - ] - }, - "Sharethrough": { - "properties": [ - "sharethrough.com" - ], - "resources": [ - "sharethrough.com" - ] - }, - "ShinyStat": { - "properties": [ - "shinystat.com" - ], - "resources": [ - "shinystat.com" - ] - }, - "Shopzilla": { - "properties": [ - "shopzilla.com" - ], - "resources": [ - "shopzilla.com" - ] - }, - "Shortest": { - "properties": [ - "shorte.st" - ], - "resources": [ - "shorte.st" - ] - }, - "SiftScience": { - "properties": [ - "sift.com" - ], - "resources": [ - "siftscience.com" - ] - }, - "Signifyd": { - "properties": [ - "signifyd.com" - ], - "resources": [ - "signifyd.com" - ] - }, - "Silverpop": { - "properties": [ - "mkt51.net", - "silverpop.com" - ], - "resources": [ - "mkt51.net", - "pages05.net", - "silverpop.com", - "vtrenz.net" - ] - }, - "Simpli.fi": { - "properties": [ - "simpli.fi" - ], - "resources": [ - "simpli.fi" - ] - }, - "SiteScout": { - "properties": [ - "sitescout.com" - ], - "resources": [ - "sitescout.com" - ] - }, - "Six Apart": { - "properties": [ - "movabletype.com", - "sixapart.com" - ], - "resources": [ - "movabletype.com", - "sixapart.com" - ] - }, - "Skimlinks": { - "properties": [ - "skimlinks.com", - "skimresources.com" - ], - "resources": [ - "skimlinks.com", - "skimresources.com" - ] - }, - "Skribit": { - "properties": [ - "paulstamatiou.com" - ], - "resources": [ - "paulstamatiou.com", - "skribit.com" - ] - }, - "Skupe Net": { - "properties": [ - "adcentriconline.com", - "skupenet.com" - ], - "resources": [ - "adcentriconline.com", - "skupenet.com" - ] - }, - "Smaato": { - "properties": [ - "smaato.com" - ], - "resources": [ - "smaato.com" - ] - }, - "SmartAdServer": { - "properties": [ - "smartadserver.com" - ], - "resources": [ - "smartadserver.com" - ] - }, - "Smartlook": { - "properties": [ - "smartlook.com" - ], - "resources": [ - "smartlook.com" - ] - }, - "SmartyAds": { - "properties": [ - "smartyads.com" - ], - "resources": [ - "smartyads.com" - ] - }, - "Smi": { - "properties": [ - "24smi.net" - ], - "resources": [ - "24smi.net" - ] - }, - "Smiley Media": { - "properties": [ - "smileymedia.com" - ], - "resources": [ - "smileymedia.com" - ] - }, - "Smowtion": { - "properties": [ - "smowtion.com" - ], - "resources": [ - "smowtion.com" - ] - }, - "Snap": { - "properties": [ - "snap.com" - ], - "resources": [ - "snap.com" - ] - }, - "SnapEngage": { - "properties": [ - "snapengage.com" - ], - "resources": [ - "snapengage.com" - ] - }, - "Snoobi": { - "properties": [ - "snoobi.fi" - ], - "resources": [ - "snoobi.com", - "snoobi.fi" - ] - }, - "SocialChorus": { - "properties": [ - "socialchorus.com" - ], - "resources": [ - "halogenmediagroup.com", - "halogennetwork.com", - "socialchorus.com" - ] - }, - "SocialInterface": { - "properties": [ - "socialinterface.com" - ], - "resources": [ - "ratevoice.com", - "socialinterface.com" - ] - }, - "SocialTwist": { - "properties": [ - "socialtwist.com" - ], - "resources": [ - "socialtwist.com" - ] - }, - "sociomantic labs": { - "properties": [ - "sociomantic.com" - ], - "resources": [ - "sociomantic.com" - ] - }, - "Socital": { - "properties": [ - "socital.com" - ], - "resources": [ - "socital.com" - ] - }, - "Sojern": { - "properties": [ - "sojern.com" - ], - "resources": [ - "sojern.com" - ] - }, - "SomoAudience": { - "properties": [ - "somoaudience.com" - ], - "resources": [ - "somoaudience.com" - ] - }, - "Sonobi": { - "properties": [ - "sonobi.com" - ], - "resources": [ - "sonobi.com" - ] - }, - "sophus3": { - "properties": [ - "sophus3.com" - ], - "resources": [ - "sophus3.co.uk", - "sophus3.com" - ] - }, - "Sortable": { - "properties": [ - "sortable.com" - ], - "resources": [ - "deployads.com" - ] - }, - "Sourcepoint": { - "properties": [ - "sourcepoint.com" - ], - "resources": [ - "summerhamster.com" - ] - }, - "Sovrn": { - "properties": [ - "sovrn.com" - ], - "resources": [ - "sovrn.com" - ] - }, - "Space Chimp Media": { - "properties": [ - "spacechimpmedia.com" - ], - "resources": [ - "spacechimpmedia.com" - ] - }, - "SpareChange": { - "properties": [ - "sparechange.io" - ], - "resources": [ - "sparechange.io" - ] - }, - "Sparklit": { - "properties": [ - "adbutler.com", - "sparklit.com" - ], - "resources": [ - "adbutler.com", - "sparklit.com" - ] - }, - "Spark Studios": { - "properties": [ - "sparkstudios.com" - ], - "resources": [ - "sparkstudios.com" - ] - }, - "Specific Media": { - "properties": [ - "sitemeter.com", - "specificmedia.com" - ], - "resources": [ - "adviva.co.uk", - "adviva.net", - "sitemeter.com", - "specificclick.net", - "specificmedia.com" - ] - }, - "Spectate": { - "properties": [ - "spectate.com" - ], - "resources": [ - "spectate.com" - ] - }, - "Sponge": { - "properties": [ - "spongegroup.com" - ], - "resources": [ - "spongegroup.com" - ] - }, - "Spongecell": { - "properties": [ - "spongecell.com" - ], - "resources": [ - "spongecell.com" - ] - }, - "SponsorAds": { - "properties": [ - "sponsorads.de" - ], - "resources": [ - "sponsorads.de" - ] - }, - "Spot200": { - "properties": [ - "spot200.com" - ], - "resources": [ - "spot200.com" - ] - }, - "SpotX": { - "properties": [ - "spotx.tv" - ], - "resources": [ - "spotx.tv" - ] - }, - "SpotXchange": { - "properties": [ - "spotxchange.com" - ], - "resources": [ - "spotxcdn.com", - "spotxchange.com" - ] - }, - "Spring Metrics": { - "properties": [ - "springmetrics.com" - ], - "resources": [ - "springmetrics.com" - ] - }, - "SpringServe": { - "properties": [ - "springserve.com" - ], - "resources": [ - "springserve.com" - ] - }, - "Sputnik.ru": { - "properties": [ - "sputnik.ru" - ], - "resources": [ - "sputnik.ru" - ] - }, - "StackAdapt": { - "properties": [ - "stackadapt.com" - ], - "resources": [ - "stackadapt.com" - ] - }, - "StackTrack": { - "properties": [ - "stat-track.com" - ], - "resources": [ - "stat-track.com" - ] - }, - "StarGames": { - "properties": [ - "stargames.net", - "stargamesaffiliate.com" - ], - "resources": [ - "stargames.net", - "stargamesaffiliate.com" - ] - }, - "stat4u": { - "properties": [ - "4u.pl" - ], - "resources": [ - "4u.pl" - ] - }, - "StatCounter": { - "properties": [ - "statcounter.com" - ], - "resources": [ - "statcounter.com" - ] - }, - "Statisfy": { - "properties": [ - "statisfy.net" - ], - "resources": [ - "statisfy.net" - ] - }, - "STATSIT": { - "properties": [ - "statsit.com" - ], - "resources": [ - "statsit.com" - ] - }, - "SteelHouse": { - "properties": [ - "steelhouse.com", - "steelhousemedia.com" - ], - "resources": [ - "steelhouse.com", - "steelhousemedia.com" - ] - }, - "Storeland": { - "properties": [ - "storeland.ru" - ], - "resources": [ - "storeland.ru" - ] - }, - "Storygize": { - "properties": [ - "storygize.com" - ], - "resources": [ - "storygize.com", - "storygize.net" - ] - }, - "Stratigent": { - "properties": [ - "stratigent.com" - ], - "resources": [ - "stratigent.com" - ] - }, - "Streamray": { - "properties": [ - "cams.com", - "streamray.com" - ], - "resources": [ - "cams.com", - "streamray.com" - ] - }, - "StrikeAd": { - "properties": [ - "strikead.com" - ], - "resources": [ - "strikead.com" - ] - }, - "Stripe": { - "properties": [ - "stripe.com" - ], - "resources": [ - "stripe.network" - ] - }, - "StrongMail": { - "properties": [ - "strongmail.com" - ], - "resources": [ - "popularmedia.com", - "strongmail.com" - ] - }, - "Struq": { - "properties": [ - "struq.com" - ], - "resources": [ - "struq.com" - ] - }, - "StumbleUpon": { - "properties": [ - "stumbleupon.com" - ], - "resources": [ - "stumble-upon.com", - "stumbleupon.com" - ] - }, - "Sublime Skinz": { - "properties": [ - "sublime.xyz" - ], - "resources": [ - "ayads.co", - "sublime.xyz" - ] - }, - "Suite 66": { - "properties": [ - "suite66.com" - ], - "resources": [ - "suite66.com" - ] - }, - "Summit": { - "properties": [ - "summitmedia.co.uk", - "www.summit.co.uk" - ], - "resources": [ - "summitmedia.co.uk", - "www.summit.co.uk" - ] - }, - "Superfish": { - "properties": [ - "superfish.com" - ], - "resources": [ - "superfish.com" - ] - }, - "SupersonicAds": { - "properties": [ - "supersonicads.com" - ], - "resources": [ - "supersonicads.com" - ] - }, - "Survata": { - "properties": [ - "survata.com" - ], - "resources": [ - "survata.com" - ] - }, - "SwiftMining": { - "properties": [ - "swiftmining.win" - ], - "resources": [ - "swiftmining.win" - ] - }, - "Switch": { - "properties": [ - "switchadhub.com", - "switchconcepts.com" - ], - "resources": [ - "switchadhub.com", - "switchads.com", - "switchconcepts.co.uk", - "switchconcepts.com" - ] - }, - "Swoop": { - "properties": [ - "swoop.com" - ], - "resources": [ - "swoop.com" - ] - }, - "SymphonyAM": { - "properties": [ - "factortg.com" - ], - "resources": [ - "factortg.com" - ] - }, - "Synacor": { - "properties": [ - "synacor.com" - ], - "resources": [ - "synacor.com" - ] - }, - "Syncapse": { - "properties": [ - "clickable.net", - "syncapse.com" - ], - "resources": [ - "clickable.net", - "syncapse.com" - ] - }, - "Syrup Ad": { - "properties": [ - "adotsolution.com" - ], - "resources": [ - "adotsolution.com" - ] - }, - "Taboola": { - "properties": [ - "taboola.com" - ], - "resources": [ - "perfectmarket.com", - "taboola.com" - ] - }, - "Tailsweep": { - "properties": [ - "tailsweep.com" - ], - "resources": [ - "tailsweep.com" - ] - }, - "Taleria": { - "properties": [ - "telaria.com" - ], - "resources": [ - "freeskreen.com" - ] - }, - "Tapad": { - "properties": [ - "tapad.com" - ], - "resources": [ - "tapad.com" - ] - }, - "Tapgage": { - "properties": [ - "bizmey.com", - "tapgage.com" - ], - "resources": [ - "bizmey.com", - "tapgage.com" - ] - }, - "TapIt!": { - "properties": [ - "tapit.com" - ], - "resources": [ - "tapit.com" - ] - }, - "Tap.me": { - "properties": [ - "tap.me" - ], - "resources": [ - "tap.me" - ] - }, - "Targetix": { - "properties": [ - "targetix.net" - ], - "resources": [ - "targetix.net" - ] - }, - "Tatto Media": { - "properties": [ - "tattomedia.com" - ], - "resources": [ - "quicknoodles.com", - "tattomedia.com" - ] - }, - "Teadma": { - "properties": [ - "teadma.com" - ], - "resources": [ - "teadma.com" - ] - }, - "Teads.tv": { - "properties": [ - "teads.tv" - ], - "resources": [ - "teads.tv" - ] - }, - "Tealium": { - "properties": [ - "tealium.com" - ], - "resources": [ - "tealiumiq.com" - ] - }, - "Technorati": { - "properties": [ - "technorati.com" - ], - "resources": [ - "technorati.com", - "technoratimedia.com" - ] - }, - "TechSolutions": { - "properties": [ - "techsolutions.com.tw" - ], - "resources": [ - "techsolutions.com.tw" - ] - }, - "TellApart": { - "properties": [ - "tellapart.com", - "tellapt.com" - ], - "resources": [ - "tellapart.com", - "tellapt.com" - ] - }, - "Telstra": { - "properties": [ - "sensis.com.au", - "sensisdata.com.au", - "telstra.com.au" - ], - "resources": [ - "sensis.com.au", - "sensisdata.com.au", - "sensisdigitalmedia.com.au", - "telstra.com.au" - ] - }, - "TENSQUARE": { - "properties": [ - "tensquare.com" - ], - "resources": [ - "tensquare.com" - ] - }, - "Terra": { - "properties": [ - "eztargetmedia.com", - "terra.com.br", - "www.terra.com.br" - ], - "resources": [ - "eztargetmedia.com", - "terra.com.br", - "www.terra.com.br" - ] - }, - "The Heron Partnership": { - "properties": [ - "marinsm.com" - ], - "resources": [ - "heronpartners.com.au", - "marinsm.com", - "marinsoftware.com" - ] - }, - "The Numa Group": { - "properties": [ - "hittail.com", - "thenumagroup.com" - ], - "resources": [ - "hittail.com", - "thenumagroup.com" - ] - }, - "The Search Agency": { - "properties": [ - "thesearchagency.com" - ], - "resources": [ - "thesearchagency.com", - "thesearchagency.net" - ] - }, - "The Trade Desk": { - "properties": [ - "thetradedesk.com" - ], - "resources": [ - "adsrvr.org", - "thetradedesk.com" - ] - }, - "ThingLink": { - "properties": [ - "thinglink.com" - ], - "resources": [ - "thinglink.com" - ] - }, - "Think Realtime": { - "properties": [ - "echosearch.com", - "thinkrealtime.com" - ], - "resources": [ - "echosearch.com", - "esm1.net", - "thinkrealtime.com" - ] - }, - "Thismoment": { - "properties": [ - "thismoment.com" - ], - "resources": [ - "thismoment.com" - ] - }, - "Thummit": { - "properties": [ - "thummit.com" - ], - "resources": [ - "thummit.com" - ] - }, - "Tinder": { - "properties": [ - "carbonads.com", - "tinder.com" - ], - "resources": [ - "carbonads.com", - "tinder.com" - ] - }, - "TiqIQ": { - "properties": [ - "tiqiq.com" - ], - "resources": [ - "tiqiq.com" - ] - }, - "Tisoomi": { - "properties": [ - "adternal.com", - "tisoomi.com" - ], - "resources": [ - "adternal.com", - "tisoomi.com" - ] - }, - "TLVMedia": { - "properties": [ - "tlvmedia.com" - ], - "resources": [ - "tlvmedia.com" - ] - }, - "TNS": { - "properties": [ - "statistik-gallup.net", - "tns-counter.ru", - "tns-cs.net", - "tnsglobal.com" - ], - "resources": [ - "sesamestats.com", - "statistik-gallup.net", - "tns-counter.ru", - "tns-cs.net", - "tnsglobal.com" - ] - }, - "Todacell": { - "properties": [ - "todacell.com" - ], - "resources": [ - "todacell.com" - ] - }, - "ToneFuse": { - "properties": [ - "tonefuse.com" - ], - "resources": [ - "tonefuse.com" - ] - }, - "ToneMedia": { - "properties": [ - "clickfuse.com" - ], - "resources": [ - "clickfuse.com", - "tonemedia.com" - ] - }, - "tongdun.cn": { - "properties": [ - "tongdun.cn" - ], - "resources": [ - "fraudmetrix.cn", - "tongdun.net" - ] - }, - "Topsy": { - "properties": [ - "topsy.com" - ], - "resources": [ - "topsy.com" - ] - }, - "TouchCommerce": { - "properties": [ - "nuance.com" - ], - "resources": [ - "inq.com", - "nuance.com", - "touchcommerce.com" - ] - }, - "TraceMyIP.org": { - "properties": [ - "tracemyip.org" - ], - "resources": [ - "tracemyip.org" - ] - }, - "TrackingSoft": { - "properties": [ - "roia.biz", - "trackingsoft.com" - ], - "resources": [ - "roia.biz", - "trackingsoft.com" - ] - }, - "Trackset": { - "properties": [ - "trackset.com" - ], - "resources": [ - "trackset.com" - ] - }, - "Tradedoubler": { - "properties": [ - "tradedoubler.com" - ], - "resources": [ - "tradedoubler.com" - ] - }, - "TradeTracker": { - "properties": [ - "tradetracker.com" - ], - "resources": [ - "tradetracker.com", - "tradetracker.net" - ] - }, - "TrafficHaus": { - "properties": [ - "traffichaus.com", - "traffichouse.com" - ], - "resources": [ - "traffichaus.com", - "traffichouse.com" - ] - }, - "TrafficRevenue": { - "properties": [ - "trafficrevenue.net" - ], - "resources": [ - "trafficrevenue.net" - ] - }, - "TrafficScore": { - "properties": [ - "trafficscore.com" - ], - "resources": [ - "trafficscore.com" - ] - }, - "Traffiq": { - "properties": [ - "traffiq.com" - ], - "resources": [ - "traffiq.com" - ] - }, - "Trafmag": { - "properties": [ - "trafmag.com" - ], - "resources": [ - "trafmag.com" - ] - }, - "Traverse": { - "properties": [ - "traversedata.com" - ], - "resources": [ - "traversedlp.com" - ] - }, - "Travora Media": { - "properties": [ - "travoramedia.com" - ], - "resources": [ - "traveladnetwork.com", - "traveladvertising.com", - "travoramedia.com" - ] - }, - "Tremor Video": { - "properties": [ - "tremorvideo.com" - ], - "resources": [ - "scanscout.com", - "tmnetads.com", - "tremorhub.com", - "tremormedia.com", - "tremorvideo.com" - ] - }, - "Triggit": { - "properties": [ - "triggit.com" - ], - "resources": [ - "triggit.com" - ] - }, - "TripleLift": { - "properties": [ - "triplelift.com" - ], - "resources": [ - "3lift.com", - "triplelift.com" - ] - }, - "Trovus": { - "properties": [ - "trovus.co.uk", - "www.trovus.co.uk" - ], - "resources": [ - "trovus.co.uk", - "www.trovus.co.uk" - ] - }, - "TruEffect": { - "properties": [ - "adlegend.com", - "trueffect.com" - ], - "resources": [ - "adlegend.com", - "trueffect.com" - ] - }, - "Trumba": { - "properties": [ - "trumba.com" - ], - "resources": [ - "trumba.com" - ] - }, - "TRUSTe": { - "properties": [ - "truste.com" - ], - "resources": [ - "truste.com" - ] - }, - "TrustX": { - "properties": [ - "trustx.org" - ], - "resources": [ - "trustx.org" - ] - }, - "TubeMogul": { - "properties": [ - "tmogul.com", - "tubemogul.com" - ], - "resources": [ - "tmogul.com", - "tubemogul.com" - ] - }, - "TurnTo": { - "properties": [ - "turntonetworks.com" - ], - "resources": [ - "turnto.com", - "turntonetworks.com" - ] - }, - "Tweetboard": { - "properties": [ - "tweetboard.com" - ], - "resources": [ - "tweetboard.com" - ] - }, - "Twelvefold": { - "properties": [ - "buzzlogic.com", - "twelvefold.com" - ], - "resources": [ - "buzzlogic.com", - "twelvefold.com" - ] - }, - "Twitter": { - "properties": [ - "digits.com", - "fabric.io", - "tweetdeck.com", - "twitter.com", - "twitter.jp" - ], - "resources": [ - "ads-twitter.com", - "fabric.io", - "tweetdeck.com", - "twimg.com", - "twitter.com", - "twitter.jp" - ] - }, - "Twitter Counter": { - "properties": [ - "twittercounter.com" - ], - "resources": [ - "twittercounter.com" - ] - }, - "Twyn Group": { - "properties": [ - "twyn-group.com", - "twyn.com" - ], - "resources": [ - "twyn-group.com", - "twyn.com" - ] - }, - "Tyroo": { - "properties": [ - "tyroo.com" - ], - "resources": [ - "tyroo.com" - ] - }, - "UberMedia": { - "properties": [ - "tweetup.com", - "ubermedia.com" - ], - "resources": [ - "tweetup.com", - "ubermedia.com" - ] - }, - "UberTags": { - "properties": [ - "ubertags.com" - ], - "resources": [ - "ubertags.com" - ] - }, - "ucfunnel": { - "properties": [ - "ucfunnel.com" - ], - "resources": [ - "aralego.com", - "ucfunnel.com" - ] - }, - "uCoz": { - "properties": [ - "ucoz.ae", - "ucoz.com", - "ucoz.fr", - "ucoz.net", - "ucoz.ru" - ], - "resources": [ - "ucoz.ae", - "ucoz.br", - "ucoz.com", - "ucoz.du", - "ucoz.fr", - "ucoz.net", - "ucoz.ru" - ] - }, - "Umbel": { - "properties": [ - "umbel.com" - ], - "resources": [ - "umbel.com" - ] - }, - "Unanimis": { - "properties": [ - "unanimis.co.uk", - "www.unanimis.co.uk" - ], - "resources": [ - "unanimis.co.uk", - "www.unanimis.co.uk" - ] - }, - "Unbounce": { - "properties": [ - "unbounce.com" - ], - "resources": [ - "unbounce.com" - ] - }, - "Underdog Media": { - "properties": [ - "udmserve.net", - "underdogmedia.com" - ], - "resources": [ - "udmserve.net", - "underdogmedia.com" - ] - }, - "Undertone": { - "properties": [ - "undertone.com", - "undertonevideo.com" - ], - "resources": [ - "undertone.com", - "undertonenetworks.com", - "undertonevideo.com" - ] - }, - "UniQlick": { - "properties": [ - "51network.com", - "uniqlick.com", - "wanmo.com" - ], - "resources": [ - "51network.com", - "uniqlick.com", - "wanmo.com" - ] - }, - "Unruly": { - "properties": [ - "unruly.co" - ], - "resources": [ - "unrulymedia.com" - ] - }, - "Upland": { - "properties": [ - "uplandsoftware.com" - ], - "resources": [ - "leadlander.com", - "sf14g.com", - "trackalyzer.com", - "uplandsoftware.com" - ] - }, - "Uptrends": { - "properties": [ - "uptrends.com" - ], - "resources": [ - "uptrends.com" - ] - }, - "up-value": { - "properties": [ - "up-value.de" - ], - "resources": [ - "up-value.de" - ] - }, - "Usability Sciences": { - "properties": [ - "usabilitysciences.com" - ], - "resources": [ - "usabilitysciences.com", - "webiqonline.com" - ] - }, - "User Local": { - "properties": [ - "nakanohito.jp" - ], - "resources": [ - "nakanohito.jp" - ] - }, - "UserVoice": { - "properties": [ - "uservoice.com" - ], - "resources": [ - "uservoice.com" - ] - }, - "V12 Data": { - "properties": [ - "v12group.com" - ], - "resources": [ - "v12data.com", - "v12group.com" - ] - }, - "Value Ad": { - "properties": [ - "valuead.com" - ], - "resources": [ - "valuead.com" - ] - }, - "Various": { - "properties": [ - "amigos.com", - "getiton.com", - "medley.com", - "nostringsattached.com", - "various.com" - ], - "resources": [ - "amigos.com", - "getiton.com", - "medley.com", - "nostringsattached.com", - "various.com" - ] - }, - "Vdopia": { - "properties": [ - "ivdopia.com", - "vdopia.com" - ], - "resources": [ - "ivdopia.com", - "vdopia.com" - ] - }, - "Veeseo": { - "properties": [ - "veeseo.com" - ], - "resources": [ - "veeseo.com" - ] - }, - "Velocity Media": { - "properties": [ - "adsvelocity.com" - ], - "resources": [ - "adsvelocity.com" - ] - }, - "Velti": { - "properties": [ - "mobclix.com", - "velti.com" - ], - "resources": [ - "mobclix.com", - "velti.com" - ] - }, - "Vemba": { - "properties": [ - "vemba.com" - ], - "resources": [ - "vemba.com" - ] - }, - "Venatus Media": { - "properties": [ - "venatusmedia.com" - ], - "resources": [ - "venatusmedia.com" - ] - }, - "Vendemore": { - "properties": [ - "vendemore.com" - ], - "resources": [ - "vendemore.com" - ] - }, - "Vendio": { - "properties": [ - "singlefeed.com", - "vendio.com" - ], - "resources": [ - "singlefeed.com", - "vendio.com" - ] - }, - "Veoxa": { - "properties": [ - "veoxa.com" - ], - "resources": [ - "veoxa.com" - ] - }, - "Veremedia": { - "properties": [ - "veremedia.com" - ], - "resources": [ - "veremedia.com" - ] - }, - "Vertical Acuity": { - "properties": [ - "verticalacuity.com" - ], - "resources": [ - "verticalacuity.com" - ] - }, - "VerticalHealth": { - "properties": [ - "verticalhealth.com" - ], - "resources": [ - "verticalhealth.net" - ] - }, - "VerticalResponse": { - "properties": [ - "verticalresponse.com", - "vresp.com" - ], - "resources": [ - "verticalresponse.com", - "vresp.com" - ] - }, - "Vertster": { - "properties": [ - "vertster.com" - ], - "resources": [ - "vertster.com" - ] - }, - "VG WORT": { - "properties": [ - "vgwort.de" - ], - "resources": [ - "vgwort.de" - ] - }, - "Vibrant Media": { - "properties": [ - "vibrantmedia.com" - ], - "resources": [ - "intellitxt.com", - "picadmedia.com", - "vibrantmedia.com" - ] - }, - "VideoIntelligence": { - "properties": [ - "vi.ai" - ], - "resources": [ - "vi.ai" - ] - }, - "Videology": { - "properties": [ - "tidaltv.com", - "videologygroup.com" - ], - "resources": [ - "tidaltv.com", - "videologygroup.com" - ] - }, - "Viewbix": { - "properties": [ - "qoof.com", - "viewbix.com" - ], - "resources": [ - "qoof.com", - "viewbix.com" - ] - }, - "VigLink": { - "properties": [ - "viglink.com" - ], - "resources": [ - "viglink.com" - ] - }, - "Vimeo": { - "properties": [ - "vimeo.com", - "vimeocdn.com" - ], - "resources": [ - "vimeo.com", - "vimeocdn.com" - ] - }, - "VINDICO": { - "properties": [ - "vindicogroup.com", - "vindicosuite.com" - ], - "resources": [ - "vindicogroup.com", - "vindicosuite.com" - ] - }, - "VisibleBrands": { - "properties": [ - "visbrands.com" - ], - "resources": [ - "visbrands.com" - ] - }, - "Visible Measures": { - "properties": [ - "visiblemeasures.com" - ], - "resources": [ - "viewablemedia.net", - "visiblemeasures.com" - ] - }, - "VisiStat": { - "properties": [ - "id.kickfire.com", - "sa-as.com" - ], - "resources": [ - "d.kickfire.com", - "sa-as.com", - "visistat.com" - ] - }, - "Visit Streamer": { - "properties": [ - "visitstreamer.com" - ], - "resources": [ - "visitstreamer.com" - ] - }, - "vistrac": { - "properties": [ - "vistrac.com" - ], - "resources": [ - "vistrac.com" - ] - }, - "VisualDNA": { - "properties": [ - "vdna-assets.com", - "visualdna-stats.com", - "visualdna.com" - ], - "resources": [ - "vdna-assets.com", - "visualdna-stats.com", - "visualdna.com" - ] - }, - "ViziSense": { - "properties": [ - "vizisense.com", - "vizisense.net" - ], - "resources": [ - "vizisense.com", - "vizisense.net" - ] - }, - "Vizu": { - "properties": [ - "vizu.com" - ], - "resources": [ - "vizu.com" - ] - }, - "Vizury": { - "properties": [ - "vizury.com" - ], - "resources": [ - "vizury.com" - ] - }, - "VKontakte": { - "properties": [ - "vk.com" - ], - "resources": [ - "userapi.com", - "vk.com", - "vkontakte.ru" - ] - }, - "Voice2Page": { - "properties": [ - "voice2page.com" - ], - "resources": [ - "voice2page.com" - ] - }, - "Vserv": { - "properties": [ - "vserv.com", - "vserv.mobi" - ], - "resources": [ - "vserv.com", - "vserv.mobi" - ] - }, - "Vuble": { - "properties": [ - "vuble.tv" - ], - "resources": [ - "mediabong.com" - ] - }, - "Wahoha": { - "properties": [ - "contentwidgets.net", - "wahoha.com" - ], - "resources": [ - "contentwidgets.net", - "wahoha.com" - ] - }, - "Wayfair": { - "properties": [ - "wayfair.com" - ], - "resources": [ - "wayfair.com" - ] - }, - "WebAds": { - "properties": [ - "webads.co.uk", - "www.webads.co.uk" - ], - "resources": [ - "webads.co.uk", - "www.webads.co.uk" - ] - }, - "Webclicktracker": { - "properties": [ - "webclicktracker.com" - ], - "resources": [ - "webclicktracker.com" - ] - }, - "Web.com": { - "properties": [ - "feedperfect.com", - "web.com" - ], - "resources": [ - "feedperfect.com", - "web.com" - ] - }, - "WebGozar.com": { - "properties": [ - "webgozar.com", - "webgozar.ir" - ], - "resources": [ - "webgozar.com", - "webgozar.ir" - ] - }, - "Webmecanik": { - "properties": [ - "webmecanik.com" - ], - "resources": [ - "webmecanik.com" - ] - }, - "WebMetro": { - "properties": [ - "dsmmadvantage.com", - "revanadigital.com" - ], - "resources": [ - "dsmmadvantage.com", - "revanadigital.com", - "webmetro.com" - ] - }, - "Webmine": { - "properties": [ - "webmine.cz" - ], - "resources": [ - "authedwebmine.cz", - "webmine.cz" - ] - }, - "WebminePool": { - "properties": [ - "webminepool.com" - ], - "resources": [ - "webminepool.com" - ] - }, - "Webmining": { - "properties": [ - "webmining.co" - ], - "resources": [ - "webmining.co" - ] - }, - "Weborama": { - "properties": [ - "weborama.com" - ], - "resources": [ - "weborama.com", - "weborama.fr" - ] - }, - "WebsiteAlive": { - "properties": [ - "websitealive.com", - "websitealive0.com", - "websitealive1.com", - "websitealive2.com", - "websitealive3.com", - "websitealive4.com", - "websitealive5.com", - "websitealive6.com", - "websitealive7.com", - "websitealive8.com", - "websitealive9.com" - ], - "resources": [ - "websitealive.com" - ] - }, - "Web Stats": { - "properties": [ - "onlinewebstats.com" - ], - "resources": [ - "onlinewebstats.com" - ] - }, - "Web Tracking Services": { - "properties": [ - "web-stat.com", - "webtrackingservices.com" - ], - "resources": [ - "web-stat.com", - "webtrackingservices.com" - ] - }, - "Webtraffic": { - "properties": [ - "webtraffic.no", - "webtraffic.se" - ], - "resources": [ - "webtraffic.no", - "webtraffic.se" - ] - }, - "Web Traxs": { - "properties": [ - "webtraxs.com" - ], - "resources": [ - "webtraxs.com" - ] - }, - "Webtrekk": { - "properties": [ - "webtrekk.com", - "webtrekk.net" - ], - "resources": [ - "webtrekk.com", - "webtrekk.net" - ] - }, - "Webtrends": { - "properties": [ - "webtrends.com" - ], - "resources": [ - "reinvigorate.net", - "webtrends.com", - "webtrendslive.com" - ] - }, - "White Ops": { - "properties": [ - "adzmath.com", - "whiteops.com" - ], - "resources": [ - "adzmath.com", - "whiteops.com" - ] - }, - "whos.amung.us": { - "properties": [ - "amung.us" - ], - "resources": [ - "amung.us" - ] - }, - "WideOrbit": { - "properties": [ - "wideorbit.com" - ], - "resources": [ - "dep-x.com" - ] - }, - "Wingify": { - "properties": [ - "vwo.com", - "wingify.com" - ], - "resources": [ - "visualwebsiteoptimizer.com", - "vwo.com", - "wingify.com" - ] - }, - "WiredMinds": { - "properties": [ - "wiredminds.de" - ], - "resources": [ - "wiredminds.com", - "wiredminds.de" - ] - }, - "Wishabi": { - "properties": [ - "wishabi.com", - "wishabi.net" - ], - "resources": [ - "flipp.com", - "wishabi.com", - "wishabi.net" - ] - }, - "Woopra": { - "properties": [ - "woopra-ns.com", - "woopra.com" - ], - "resources": [ - "woopra-ns.com", - "woopra.com" - ] - }, - "WordStream": { - "properties": [ - "wordstream.com" - ], - "resources": [ - "wordstream.com" - ] - }, - "WOW Analytics": { - "properties": [ - "wowanalytics.co.uk" - ], - "resources": [ - "wowanalytics.co.uk" - ] - }, - "WPP": { - "properties": [ - "compete.com", - "decdna.net", - "groupm.com", - "kantarmedia.com", - "mecglobal.com", - "mindshareworld.com", - "themig.com", - "wpp.com", - "xaxis.com" - ], - "resources": [ - "247realmedia.com", - "accelerator-media.com", - "acceleratorusa.com", - "compete.com", - "decdna.net", - "decideinteractive.com", - "gmads.net", - "groupm.com", - "kantarmedia.com", - "mecglobal.com", - "mindshare.nl", - "mindshareworld.com", - "mookie1.com", - "pm14.com", - "realmedia.com", - "targ.ad", - "themig.com", - "wpp.com", - "xaxis.com" - ] - }, - "Wysistat": { - "properties": [ - "wysistat.net" - ], - "resources": [ - "wysistat.com", - "wysistat.net" - ] - }, - "xAd": { - "properties": [ - "xad.com" - ], - "resources": [ - "xad.com" - ] - }, - "Xertive Media": { - "properties": [ - "xertivemedia.com" - ], - "resources": [ - "admanager-xertive.com", - "xertivemedia.com" - ] - }, - "xplosion interactive": { - "properties": [ - "xplosion.de" - ], - "resources": [ - "xplosion.de" - ] - }, - "Xrost DS": { - "properties": [ - "adplan-ds.com" - ], - "resources": [ - "adplan-ds.com" - ] - }, - "Yabuka": { - "properties": [ - "yabuka.com" - ], - "resources": [ - "yabuka.com" - ] - }, - "Yahoo!": { - "properties": [ - "flickr.com", - "flurry.com", - "tumblr.com", - "yahoo.co.jp", - "yahoo.com", - "yahoostudios.com", - "yuilibrary.com" - ], - "resources": [ - "adinterax.com", - "adrevolver.com", - "bluelithium.com", - "dapper.net", - "flickr.com", - "flurry.com", - "interclick.com", - "luminate.com", - "mybloglog.com", - "overture.com", - "pixazza.com", - "rightmedia.com", - "rmxads.com", - "rocketmail.com", - "secure-adserver.com", - "staticflickr.com", - "tumblr.com", - "yahoo.co.jp", - "yahoo.com", - "yahooapis.com", - "yahooapis.jp", - "yahoofs.com", - "yieldmanager.com", - "yieldmanager.net", - "yimg.com", - "yimg.jp", - "yldmgrimg.net", - "ymail.com", - "yuilibrary.com", - "zenfs.com" - ] - }, - "Yandex": { - "properties": [ - "kinopoisk.ru", - "moikrug.ru", - "yadi.sk", - "yandex.by", - "yandex.com", - "yandex.com.tr", - "yandex.ru", - "yandex.st", - "yandex.ua" - ], - "resources": [ - "api-maps.yandex.ru", - "moikrug.ru", - "web-visor.com", - "yandex.by", - "yandex.com", - "yandex.com.tr", - "yandex.ru", - "yandex.st", - "yandex.ua" - ] - }, - "Ybrant Digital": { - "properties": [ - "addynamix.com", - "brightcom.com", - "luj.sdsjweb.com" - ], - "resources": [ - "addynamix.com", - "adserverplus.com", - "brightcom.com", - "oridian.com", - "ybrantdigital.com" - ] - }, - "YD": { - "properties": [ - "ydworld.com", - "yieldivision.com" - ], - "resources": [ - "ydworld.com", - "yieldivision.com" - ] - }, - "YellowHammer": { - "properties": [ - "yhmg.com" - ], - "resources": [ - "attracto.com", - "clickhype.com", - "yellowhammermg.com", - "yhmg.com" - ] - }, - "YellowTracker": { - "properties": [ - "yellowtracker.com" - ], - "resources": [ - "yellowtracker.com" - ] - }, - "Yes Ads": { - "properties": [ - "yesads.com" - ], - "resources": [ - "yesads.com" - ] - }, - "YieldAds": { - "properties": [ - "yieldads.com" - ], - "resources": [ - "yieldads.com" - ] - }, - "YieldBids": { - "properties": [ - "ybx.io" - ], - "resources": [ - "ybx.io" - ] - }, - "YieldBot": { - "properties": [ - "yieldbot.com" - ], - "resources": [ - "yldbt.com" - ] - }, - "YieldBuild": { - "properties": [ - "yieldbuild.com" - ], - "resources": [ - "yieldbuild.com" - ] - }, - "Yieldify": { - "properties": [ - "yieldify.com" - ], - "resources": [ - "yieldify.com" - ] - }, - "Yieldlab": { - "properties": [ - "yieldlab.de", - "yieldlab.net" - ], - "resources": [ - "yieldlab.de", - "yieldlab.net" - ] - }, - "Yieldmo": { - "properties": [ - "yieldmo.com" - ], - "resources": [ - "yieldmo.com" - ] - }, - "YieldNexus": { - "properties": [ - "ynxs.io" - ], - "resources": [ - "ynxs.io" - ] - }, - "YOC": { - "properties": [ - "yoc.com" - ], - "resources": [ - "yoc.com" - ] - }, - "Yoggrt": { - "properties": [ - "yoggrt.com" - ], - "resources": [ - "yoggrt.com" - ] - }, - "youknowbest": { - "properties": [ - "youknowbest.com" - ], - "resources": [ - "youknowbest.com" - ] - }, - "YSance": { - "properties": [ - "ysance.com" - ], - "resources": [ - "y-track.com" - ] - }, - "YuMe": { - "properties": [ - "yume.com", - "yumenetworks.com" - ], - "resources": [ - "yume.com", - "yumenetworks.com" - ] - }, - "ZafulAffiliate": { - "properties": [ - "zaful.com" - ], - "resources": [ - "zaful.com" - ] - }, - "Zango": { - "properties": [ - "metricsdirect.com", - "zango.com" - ], - "resources": [ - "metricsdirect.com", - "zango.com" - ] - }, - "zanox": { - "properties": [ - "buy.at", - "zanox-affiliate.de", - "zanox.com" - ], - "resources": [ - "buy.at", - "zanox-affiliate.de", - "zanox.com" - ] - }, - "zapunited": { - "properties": [ - "zaparena.com", - "zapunited.com" - ], - "resources": [ - "zaparena.com", - "zapunited.com" - ] - }, - "ZEDO": { - "properties": [ - "zedo.com", - "zincx.com" - ], - "resources": [ - "zedo.com", - "zincx.com" - ] - }, - "Zefir": { - "properties": [ - "ze-fir.com" - ], - "resources": [ - "ze-fir.com" - ] - }, - "Zemanta": { - "properties": [ - "zemanta.com" - ], - "resources": [ - "zemanta.com" - ] - }, - "Zendesk": { - "properties": [ - "zendesk.com" - ], - "resources": [ - "zendesk.com" - ] - }, - "ZestAd": { - "properties": [ - "zestad.com" - ], - "resources": [ - "zestad.com" - ] - }, - "Zeta Email Solutions": { - "properties": [ - "insightgrit.com", - "zetaemailsolutions.com" - ], - "resources": [ - "insightgrit.com", - "zetaemailsolutions.com" - ] - }, - "Zopim": { - "properties": [ - "zopim.com" - ], - "resources": [ - "zopim.com" - ] - }, - "Zumobi": { - "properties": [ - "zumobi.com" - ], - "resources": [ - "zumobi.com" - ] - }, - "ZypMedia": { - "properties": [ - "zypmedia.com" - ], - "resources": [ - "extend.tv", - "zypmedia.com" - ] - } + "2leep.com": { + "properties": ["2leep.com"], + "resources": ["2leep.com"] + }, + "33Across": { + "properties": ["33across.com", "tynt.com"], + "resources": ["33across.com", "tynt.com"] + }, + "365Media": { + "properties": ["aggregateintelligence.com"], + "resources": ["365media.com", "aggregateintelligence.com"] + }, + "4INFO": { + "properties": ["4info.com", "adhaven.com"], + "resources": ["4info.com", "adhaven.com"] + }, + "4mads": { + "properties": ["4mads.com"], + "resources": ["4mads.com"] + }, + "63 Squares": { + "properties": ["63labs.com"], + "resources": ["63labs.com", "63squares.com", "i-stats.com"] + }, + "Abax Interactive": { + "properties": ["abaxinteractive.com"], + "resources": ["abaxinteractive.com"] + }, + "Accelia": { + "properties": ["accelia.net", "durasite.net"], + "resources": ["accelia.net", "durasite.net"] + }, + "Accordant Media": { + "properties": ["accordantmedia.com"], + "resources": ["accordantmedia.com"] + }, + "Acquisio": { + "properties": ["acquisio.com", "clickequations.net"], + "resources": ["acquisio.com", "clickequations.net"] + }, + "Actisens": { + "properties": ["actisens.com", "gestionpub.com"], + "resources": ["actisens.com", "gestionpub.com"] + }, + "ActiveConversion": { + "properties": ["activeconversion.com", "activemeter.com"], + "resources": ["activeconversion.com", "activemeter.com"] + }, + "ActivEngage": { + "properties": ["activengage.com"], + "resources": ["activengage.com"] + }, + "Act-On": { + "properties": ["act-on.com", "actonsoftware.com"], + "resources": ["act-on.com", "actonsoftware.com"] + }, + "Acuity": { + "properties": ["acuity.com", "acuityads.com", "acuityplatform.com"], + "resources": ["acuity.com", "acuityads.com", "acuityplatform.com"] + }, + "Acxiom": { + "properties": ["acxiom.com", "mm7.net"], + "resources": ["acxiom.com", "acxiomapac.com", "mm7.net", "pippio.com"] + }, + "AD2ONE": { + "properties": ["ad2onegroup.com"], + "resources": ["ad2onegroup.com"] + }, + "Ad4Game": { + "properties": ["ad4game.com"], + "resources": ["ad4game.com"] + }, + "ad6media": { + "properties": ["ad6media.fr"], + "resources": ["ad6media.fr"] + }, + "Adabra": { + "properties": ["adabra.com"], + "resources": ["adabra.com"] + }, + "Adality": { + "properties": ["adality.de"], + "resources": ["adality.de", "adrtx.net"] + }, + "AdaptiveAds": { + "properties": ["adaptiveads.com"], + "resources": ["adaptiveads.com"] + }, + "Adaptly": { + "properties": ["adaptly.com"], + "resources": ["adaptly.com"] + }, + "Adap.tv": { + "properties": ["adap.tv"], + "resources": ["adap.tv"] + }, + "Adara Media": { + "properties": ["adaramedia.com", "opinmind.com", "yieldoptimizer.com"], + "resources": ["adaramedia.com", "opinmind.com", "yieldoptimizer.com"] + }, + "Adatus": { + "properties": ["adatus.com"], + "resources": ["adatus.com"] + }, + "Adbot": { + "properties": ["adbot.tw"], + "resources": ["adbot.tw"] + }, + "Adbrain": { + "properties": ["adbrain.com"], + "resources": ["adbrain.com", "adbrn.com"] + }, + "adBrite": { + "properties": ["adbrite.com"], + "resources": ["adbrite.com"] + }, + "Adbroker.de": { + "properties": ["adbroker.de"], + "resources": ["adbroker.de"] + }, + "Adchemy": { + "properties": ["adchemy.com"], + "resources": ["adchemy.com"] + }, + "AdCirrus": { + "properties": ["adcirrus.com"], + "resources": ["adcirrus.com"] + }, + "Ad Decisive": { + "properties": ["a2dfp.net", "addecisive.com"], + "resources": ["a2dfp.net", "addecisive.com"] + }, + "AddFreeStats": { + "properties": ["3dstats.com", "addfreestats.com"], + "resources": ["3dstats.com", "addfreestats.com"] + }, + "addGloo": { + "properties": ["addgloo.com"], + "resources": ["addgloo.com"] + }, + "AddThis": { + "properties": ["addthis.com"], + "resources": [ + "addthis.com", + "addthiscdn.com", + "addthisedge.com", + "clearspring.com", + "connectedads.net", + "xgraph.com", + "xgraph.net" + ] + }, + "Addvantage Media": { + "properties": ["addvantagemedia.com"], + "resources": ["addvantagemedia.com"] + }, + "Ad Dynamo": { + "properties": ["addynamo.com"], + "resources": ["addynamo.com", "addynamo.net"] + }, + "Adelphic": { + "properties": ["adelphic.com"], + "resources": ["adelphic.com", "ipredictive.com"] + }, + "AdEngage": { + "properties": ["adengage.com"], + "resources": ["adengage.com"] + }, + "AD Europe": { + "properties": ["adeurope.com"], + "resources": ["adeurope.com"] + }, + "AdExtent": { + "properties": ["adextent.com"], + "resources": ["adextent.com"] + }, + "AdF.ly": { + "properties": ["adf.ly"], + "resources": ["adf.ly"] + }, + "Adfonic": { + "properties": ["adfonic.com"], + "resources": ["adfonic.com"] + }, + "Adforge": { + "properties": ["adforgeinc.com"], + "resources": ["adforgeinc.com"] + }, + "Adform": { + "properties": ["adform.com"], + "resources": ["adform.com", "adform.net", "adformdsp.net"] + }, + "AdFox": { + "properties": ["adfox.ru"], + "resources": ["adfox.ru"] + }, + "AdFrontiers": { + "properties": ["adfrontiers.com"], + "resources": ["adfrontiers.com"] + }, + "Adfunky": { + "properties": ["adfunky.com", "adfunkyserver.com"], + "resources": ["adfunky.com", "adfunkyserver.com"] + }, + "Adfusion": { + "properties": ["adfusion.com"], + "resources": ["adfusion.com"] + }, + "AdGainerSolutions": { + "properties": ["adgainersolutions.com"], + "resources": ["adgainersolutions.com"] + }, + "AdGent Digital": { + "properties": ["adgentdigital.com"], + "resources": ["adgentdigital.com", "shorttailmedia.com"] + }, + "AdGibbon": { + "properties": ["adgibbon.com"], + "resources": ["adgibbon.com"] + }, + "Adglare": { + "properties": ["adglare.com"], + "resources": ["adglare.com", "adglare.net"] + }, + "adhood": { + "properties": ["adhood.com"], + "resources": ["adhood.com"] + }, + "Adiant": { + "properties": ["adblade.com", "adiant.com"], + "resources": ["adblade.com", "adiant.com"] + }, + "AdInsight": { + "properties": ["responsetap.com"], + "resources": ["adinsight.com", "adinsight.eu", "responsetap.com"] + }, + "AdIQuity": { + "properties": ["adiquity.com"], + "resources": ["adiquity.com"] + }, + "ADITION": { + "properties": ["adition.com"], + "resources": ["adition.com"] + }, + "AdJug": { + "properties": ["adjug.com"], + "resources": ["adjug.com"] + }, + "AdJuggler": { + "properties": ["adjuggler.com", "adjuggler.net"], + "resources": ["adjuggler.com", "adjuggler.net"] + }, + "Adjust": { + "properties": ["adjust.com"], + "resources": ["adjust.com"] + }, + "AdKeeper": { + "properties": ["keep.com"], + "resources": ["adkeeper.com", "akncdn.com", "keep.com"] + }, + "AdKernel": { + "properties": ["adkernel.com"], + "resources": ["adkernel.com"] + }, + "Ad Knife": { + "properties": ["adknife.com"], + "resources": ["adknife.com"] + }, + "Adknowledge": { + "properties": [ + "adknowledge.com", + "adparlor.com", + "bidsystem.com", + "cubics.com", + "lookery.com" + ], + "resources": [ + "adknowledge.com", + "adparlor.com", + "bidsystem.com", + "cubics.com", + "lookery.com" + ] + }, + "AdLantis": { + "properties": ["adimg.net", "adlantis.jp", "www.adlantis.jp"], + "resources": ["adimg.net", "adlantis.jp", "www.adlantis.jp"] + }, + "AdLeave": { + "properties": ["adleave.com"], + "resources": ["adleave.com"] + }, + "Adlibrium": { + "properties": ["adlibrium.com"], + "resources": ["adlibrium.com"] + }, + "Adloox": { + "properties": ["adloox.com"], + "resources": ["adloox.com", "adlooxtracking.com"] + }, + "Adlucent": { + "properties": ["adlucent.com"], + "resources": ["adlucent.com"] + }, + "Ad Magnet": { + "properties": ["admagnet.com", "admagnet.net"], + "resources": ["admagnet.com", "admagnet.net"] + }, + "Admarketplace": { + "properties": ["admarketplace.com"], + "resources": ["admarketplace.com", "admarketplace.net", "ampxchange.com"] + }, + "AdMarvel": { + "properties": ["admarvel.com"], + "resources": ["admarvel.com"] + }, + "AdMatrix": { + "properties": ["admatrix.jp"], + "resources": ["admatrix.jp"] + }, + "AdMaven": { + "properties": ["ad-maven.com"], + "resources": [ + "ad-maven.com", + "agreensdistra.info", + "boudja.com", + "rensovetors.info", + "wrethicap.info" + ] + }, + "AdMaximizer Network": { + "properties": ["admaximizer.com"], + "resources": ["admaximizer.com"] + }, + "AdMedia": { + "properties": ["admedia.com"], + "resources": ["admedia.com"] + }, + "Admeta": { + "properties": ["admeta.com", "atemda.com"], + "resources": ["admeta.com", "atemda.com"] + }, + "Admicro": { + "properties": ["admicro.vn"], + "resources": ["admicro.vn", "vcmedia.vn"] + }, + "Admixer": { + "properties": ["admixer.co.kr"], + "resources": ["admixer.co.kr"] + }, + "Admized": { + "properties": ["admized.com"], + "resources": ["admized.com"] + }, + "Admobile": { + "properties": ["admobile.com"], + "resources": ["admobile.com"] + }, + "Admotion": { + "properties": ["admotion.com"], + "resources": ["admotion.com", "nspmotion.com"] + }, + "Adnetik": { + "properties": ["wtp101.com"], + "resources": ["adnetik.com", "wtp101.com"] + }, + "AdNetwork.net": { + "properties": ["adnetwork.net"], + "resources": ["adnetwork.net"] + }, + "Adnium": { + "properties": ["adnium.com"], + "resources": ["adnium.com", "montwam.top"] + }, + "adnologies": { + "properties": ["adnologies.com", "heias.com"], + "resources": ["adnologies.com", "heias.com"] + }, + "Adobe": { + "properties": ["adobe.com", "livefyre.com", "typekit.com"], + "resources": [ + "2o7.net", + "adobe.com", + "auditude.com", + "demdex.com", + "demdex.net", + "dmtracker.com", + "efrontier.com", + "everestads.net", + "everestjs.net", + "everesttech.net", + "fyre.co", + "hitbox.com", + "livefyre.com", + "omniture.com", + "omtrdc.net", + "touchclarity.com", + "typekit.com" + ] + }, + "AdOcean": { + "properties": ["adocean-global.com", "adocean.pl"], + "resources": ["adocean-global.com", "adocean.pl"] + }, + "Adometry": { + "properties": ["adometry.com"], + "resources": ["adometry.com", "dmtry.com"] + }, + "Adomik": { + "properties": ["adomik.com"], + "resources": ["adomik.com"] + }, + "AdOnion": { + "properties": ["adonion.com"], + "resources": ["adonion.com"] + }, + "Adorika": { + "properties": ["clickotmedia.com"], + "resources": ["clickotmedia.com"] + }, + "Adotmob": { + "properties": ["adotmob.com"], + "resources": ["adotmob.com"] + }, + "ADP Dealer Services": { + "properties": ["cdkglobal.com"], + "resources": [ + "admission.net", + "adpdealerservices.com", + "cdkglobal.com", + "cobalt.com" + ] + }, + "ad pepper media": { + "properties": ["adpepper.com", "adpepper.us"], + "resources": ["adpepper.com", "adpepper.us"] + }, + "AdPerfect": { + "properties": ["adperfect.com"], + "resources": ["adperfect.com"] + }, + "Adperium": { + "properties": ["adperium.com"], + "resources": ["adperium.com"] + }, + "Adpersia": { + "properties": ["adpersia.com"], + "resources": ["adpersia.com"] + }, + "adPrecision": { + "properties": ["adprecision.net", "adprs.net"], + "resources": ["adprecision.net", "adprs.net", "aprecision.net"] + }, + "AdPredictive": { + "properties": ["adpredictive.com"], + "resources": ["adpredictive.com"] + }, + "AdReactor": { + "properties": ["adreactor.com"], + "resources": ["adreactor.com"] + }, + "AdReady": { + "properties": ["digitalremedy.com"], + "resources": ["adready.com", "adreadytractions.com", "digitalremedy"] + }, + "AdRevolution": { + "properties": ["adrevolution.com"], + "resources": ["adrevolution.com"] + }, + "AdRiver": { + "properties": ["adriver.ru"], + "resources": ["adriver.ru"] + }, + "adrolays": { + "properties": ["contactimpact.de"], + "resources": ["adrolays.com", "adrolays.de", "contactimpact.de"] + }, + "AdRoll": { + "properties": ["adroll.com"], + "resources": ["adroll.com"] + }, + "adscale": { + "properties": ["stroeer.de"], + "resources": ["adscale.de", "stroeer.de"] + }, + "Adscience": { + "properties": ["adscience.nl"], + "resources": ["adscience.nl"] + }, + "AdScore": { + "properties": ["adscoremarketing.com"], + "resources": ["adsco.re"] + }, + "AdServerPub": { + "properties": ["adserverpub.com"], + "resources": ["adserverpub.com"] + }, + "AdShuffle": { + "properties": ["adshuffle.com"], + "resources": ["adshuffle.com"] + }, + "AdSide": { + "properties": ["adside.com", "doclix.com"], + "resources": ["adside.com", "doclix.com"] + }, + "AdSpeed": { + "properties": ["adspeed.com", "adspeed.net"], + "resources": ["adspeed.com", "adspeed.net"] + }, + "Adsperity": { + "properties": ["adsperity.com"], + "resources": ["adsperity.com"] + }, + "AdSpirit": { + "properties": ["adspirit.com", "adspirit.de", "adspirit.net"], + "resources": ["adspirit.com", "adspirit.de", "adspirit.net"] + }, + "Adsrevenue.net": { + "properties": ["adsrevenue.net"], + "resources": ["adsrevenue.net"] + }, + "AdStir": { + "properties": ["ad-stir.com"], + "resources": ["ad-stir.com"] + }, + "AdsTours": { + "properties": ["adstours.com", "clickintext.net"], + "resources": ["adstours.com", "clickintext.net"] + }, + "Adsty": { + "properties": ["adsty.com", "adx1.com"], + "resources": ["adsty.com", "adx1.com"] + }, + "Adsupply": { + "properties": ["4dsply.com", "adsupply.com"], + "resources": ["4dsply.com", "adsupply.com"] + }, + "Adswizz": { + "properties": ["adswizz.com"], + "resources": ["adswizz.com"] + }, + "ADTECH": { + "properties": ["adtech.com", "adtech.de", "adtechus.com"], + "resources": ["adtech.com", "adtech.de", "adtechus.com"] + }, + "Adtegrity.com": { + "properties": ["adtegrity.com", "adtegrity.net"], + "resources": ["adtegrity.com", "adtegrity.net"] + }, + "ADTELLIGENCE": { + "properties": ["adtelligence.de"], + "resources": ["adtelligence.de"] + }, + "Adthink": { + "properties": ["adthink.com"], + "resources": ["adthink.com", "audienceinsights.net"] + }, + "AdTiger": { + "properties": ["adtiger.de"], + "resources": ["adtiger.de"] + }, + "AdTruth": { + "properties": ["adtruth.com"], + "resources": ["adtruth.com"] + }, + "Adult AdWorld": { + "properties": ["adultadworld.com"], + "resources": ["adultadworld.com"] + }, + "Adultmoda": { + "properties": ["adultmoda.com"], + "resources": ["adultmoda.com"] + }, + "Adventive": { + "properties": ["adventive.com"], + "resources": ["adventive.com"] + }, + "Adventori": { + "properties": ["adventori.com"], + "resources": ["adventori.com"] + }, + "Adverline": { + "properties": ["adnext.fr", "adverline.com"], + "resources": ["adnext.fr", "adverline.com"] + }, + "Adversal.com": { + "properties": ["adv-adserver.com", "adversal.com"], + "resources": ["adv-adserver.com", "adversal.com"] + }, + "Adverticum": { + "properties": ["adsmart.com", "adverticum.com", "adverticum.net"], + "resources": ["adsmart.com", "adverticum.com", "adverticum.net"] + }, + "Advertise.com": { + "properties": ["advertise.com"], + "resources": ["advertise.com"] + }, + "AdvertiseSpace": { + "properties": ["advertisespace.com"], + "resources": ["advertisespace.com"] + }, + "Advert Stream": { + "properties": ["advertstream.com"], + "resources": ["advertstream.com"] + }, + "Advisor Media": { + "properties": ["advisormedia.cz"], + "resources": ["advisormedia.cz"] + }, + "Adworx": { + "properties": ["adworx.at", "adworx.be", "adworx.nl"], + "resources": ["adworx.at", "adworx.be", "adworx.nl"] + }, + "AdXpansion": { + "properties": ["adxpansion.com"], + "resources": ["adxpansion.com"] + }, + "Adxvalue": { + "properties": ["adxvalue.com", "adxvalue.de"], + "resources": ["adxvalue.com", "adxvalue.de"] + }, + "adyard": { + "properties": ["adyard.de"], + "resources": ["adyard.de"] + }, + "AdYield": { + "properties": ["adyield.com"], + "resources": ["adxyield.com", "adyield.com"] + }, + "AdYouLike": { + "properties": ["adyoulike.com"], + "resources": ["adyoulike.com", "omnitagjs.com", "pulpix.com"] + }, + "ADZ": { + "properties": ["adzcentral.com"], + "resources": ["adzcentral.com"] + }, + "Adzerk": { + "properties": ["adzerk.com"], + "resources": ["adzerk.com", "adzerk.net"] + }, + "adzly": { + "properties": ["adzly.com"], + "resources": ["adzly.com"] + }, + "Aegis Group": { + "properties": ["aemedia.com", "bluestreak.com", "dentsuaegisnetwork.com"], + "resources": ["aemedia.com", "bluestreak.com", "dentsuaegisnetwork.com"] + }, + "AERIFY MEDIA": { + "properties": ["aerifymedia.com", "anonymous-media.com"], + "resources": ["aerifymedia.com", "anonymous-media.com"] + }, + "Affectv": { + "properties": ["affectv.co.uk"], + "resources": ["affectv.co.uk"] + }, + "affilinet": { + "properties": ["affili.net", "affilinet-inside.de"], + "resources": [ + "affili.net", + "affilinet-inside.de", + "banner-rotation.com", + "successfultogether.co.uk" + ] + }, + "Affine": { + "properties": ["affine.tv", "affinesystems.com"], + "resources": ["affine.tv", "affinesystems.com"] + }, + "Affinity": { + "properties": ["affinity.com"], + "resources": ["affinity.com"] + }, + "AfterDownload": { + "properties": ["afdads.com", "afterdownload.com"], + "resources": ["afdads.com", "afterdownload.com"] + }, + "AIData": { + "properties": ["advombat.ru", "aidata.me"], + "resources": ["advombat.ru", "aidata.me"] + }, + "Aim4Media": { + "properties": ["aim4media.com"], + "resources": ["aim4media.com"] + }, + "Airpush": { + "properties": ["airpush.com"], + "resources": ["airpush.com"] + }, + "AivaLabs": { + "properties": ["aivalabs.com"], + "resources": ["aivalabs.com"] + }, + "a.js": { + "properties": [ + "alflying.date", + "alflying.win", + "anybest.site", + "flightsy.bid", + "flightsy.win", + "flightzy.bid", + "flightzy.date", + "flightzy.win", + "zymerget.bid", + "zymerget.faith" + ], + "resources": [ + "alflying.date", + "alflying.win", + "anybest.site", + "flightsy.bid", + "flightsy.win", + "flightzy.bid", + "flightzy.date", + "flightzy.win", + "zymerget.bid", + "zymerget.faith" + ] + }, + "AK": { + "properties": ["aggregateknowledge.com", "agkn.com"], + "resources": ["aggregateknowledge.com", "agkn.com"] + }, + "Akamai": { + "properties": ["akamai.com"], + "resources": [ + "abmr.net", + "akamai.com", + "edgesuite.net", + "go-mpulse.net", + "imiclk.com" + ] + }, + "AKQA": { + "properties": ["akqa.com"], + "resources": ["akqa.com", "srtk.net"] + }, + "Albacross": { + "properties": ["albacross.com"], + "resources": ["albacross.com"] + }, + "AllStarMediaGroup": { + "properties": ["allstarmediagroup.com"], + "resources": ["allstarmediagroup.com"] + }, + "Aloodo": { + "properties": ["aloodo.com"], + "resources": ["aloodo.com"] + }, + "AlterGeo": { + "properties": ["altergeo.ru"], + "resources": ["altergeo.ru"] + }, + "Amadesa": { + "properties": ["amadesa.com"], + "resources": ["amadesa.com"] + }, + "Amazing Counters": { + "properties": ["amazingcounters.com"], + "resources": ["amazingcounters.com"] + }, + "Amazon.com": { + "properties": [ + "6pm.com", + "abebooks.co.uk", + "abebooks.com", + "abebooks.de", + "abebooks.fr", + "abebooks.it", + "acx.com", + "alexa.com", + "amazon.ae", + "amazon.ca", + "amazon.cn", + "amazon.co.jp", + "amazon.co.uk", + "amazon.com", + "amazon.com.au", + "amazon.com.br", + "amazon.com.mx", + "amazon.com.sg", + "amazon.com.tr", + "amazon.de", + "amazon.es", + "amazon.fr", + "amazon.in", + "amazon.it", + "amazon.nl", + "amazon.sa", + "amazonaws.com", + "amazoninspire.com", + "assoc-amazon.com", + "audible.co.jp", + "audible.co.uk", + "audible.com", + "audible.de", + "audible.fr", + "audible.in", + "audible.it", + "bookdepository.com", + "boxofficemojo.com", + "brilliancepublishing.com", + "comixology.com", + "createspace.com", + "dpreview.com", + "dpreview.in", + "eastdane.com", + "fabric.com", + "goodreads.com", + "iberlibro.com", + "imdb.com", + "imdb.de", + "junglee.com", + "look.com", + "pillpack.com", + "shopbop.com", + "souq.com", + "twitch.com", + "twitch.tv", + "wholefoodsmarket.com", + "withoutabox.com", + "woot.com", + "yoyo.com", + "zappos.com", + "zvab.com" + ], + "resources": [ + "alexa.com", + "alexametrics.com", + "amazon-adsystem.com", + "amazon.ca", + "amazon.co.jp", + "amazon.co.uk", + "amazon.com", + "amazon.de", + "amazon.es", + "amazon.fr", + "amazon.it", + "amazonaws.com", + "assoc-amazon.com", + "cloudfront.net", + "ssl-images-amazon.com" + ] + }, + "Ambient Digital": { + "properties": ["adnetwork.vn", "ambientdigital.com.vn"], + "resources": ["adnetwork.vn", "ambientdigital.com.vn"] + }, + "Amobee": { + "properties": ["amobee.com", "smartclip.com"], + "resources": [ + "adconion.com", + "amgdgt.com", + "amobee.com", + "euroclick.com", + "smartclip.com", + "turn.com" + ] + }, + "Amplitude": { + "properties": ["amplitude.com"], + "resources": ["amplitude.com"] + }, + "AndBeyond": { + "properties": ["andbeyond.media"], + "resources": ["andbeyond.media"] + }, + "anormal-media.de": { + "properties": ["anormal-media.de", "primawebtools.de"], + "resources": ["anormal-media.de", "anormal-tracker.de", "primawebtools.de"] + }, + "Answers.com": { + "properties": ["answers.com", "dsply.com"], + "resources": ["dsply.com"] + }, + "AOL": { + "properties": [ + "5min.com", + "adsonar.com", + "advertising.com", + "aim.com", + "aol.com", + "aolcdn.com", + "aoltechguru.com", + "atwola.com", + "autoblog.com", + "cambio.com", + "dailyfinance.com", + "editions.com", + "engadget.com", + "games.com", + "homesessive.com", + "huffingtonpost.com", + "leadback.com", + "makers.com", + "mandatory.com", + "mapquest.com", + "moviefone.com", + "noisecreep.com", + "patch.com", + "pawnation.com", + "shortcuts.com", + "shoutcast.com", + "spinner.com", + "stylelist.com", + "stylemepretty.com", + "surphace.com", + "tacoda.net", + "techcrunch.com", + "theboombox.com", + "theboot.com", + "userplane.com", + "winamp.com" + ], + "resources": [ + "5min.com", + "adsonar.com", + "adtechjp.com", + "advertising.com", + "aim.com", + "aol.com", + "aolcdn.com", + "aolcloud.net", + "atwola.com", + "editions.com", + "leadback.com", + "mapquest.com", + "patch.com", + "shortcuts.com", + "shoutcast.com", + "spinner.com", + "surphace.com", + "tacoda.net", + "userplane.com", + "vidible.tv", + "winamp.com" + ] + }, + "AppCast": { + "properties": ["appcast.io"], + "resources": ["appcast.io"] + }, + "Appenda": { + "properties": ["appenda.com"], + "resources": ["appenda.com"] + }, + "AppFlood": { + "properties": ["appflood.com"], + "resources": ["appflood.com"] + }, + "Appier": { + "properties": ["appier.com"], + "resources": ["appier.com"] + }, + "Applifier": { + "properties": ["applifier.com"], + "resources": ["applifier.com"] + }, + "Applovin": { + "properties": ["applovin.com"], + "resources": ["applovin.com"] + }, + "AppNexus": { + "properties": ["adlantic.nl", "adnxs.com", "adrdgt.com", "appnexus.com"], + "resources": ["adlantic.nl", "adnxs.com", "adrdgt.com", "appnexus.com"] + }, + "AppsFlyer": { + "properties": ["appsflyer.com"], + "resources": ["appsflyer.com"] + }, + "appssavvy": { + "properties": ["appssavvy.com"], + "resources": ["appssavvy.com"] + }, + "Arkwrights Homebrew": { + "properties": ["whiskyandwines.com"], + "resources": ["arkwrightshomebrew.com", "ctasnet.com", "whiskyandwines.com"] + }, + "AT Internet": { + "properties": ["atinternet.com", "xiti.com"], + "resources": ["at-o.net", "atinternet.com", "hit-parade.com", "xiti.com"] + }, + "ATN": { + "properties": ["affiliatetracking.com"], + "resources": ["affiliatetracking.com"] + }, + "Atoomic.com": { + "properties": ["atoomic.com"], + "resources": ["atoomic.com"] + }, + "Atrinsic": { + "properties": ["atrinsic.com"], + "resources": ["atrinsic.com"] + }, + "AT&T": { + "properties": ["att.com", "yp.com"], + "resources": ["att.com", "yp.com"] + }, + "Attracta": { + "properties": ["attracta.com"], + "resources": ["attracta.com"] + }, + "Audience2Media": { + "properties": ["audience2media.com"], + "resources": ["audience2media.com"] + }, + "Audience Ad Network": { + "properties": ["audienceadnetwork.com"], + "resources": ["audienceadnetwork.com"] + }, + "AudienceScience": { + "properties": ["audiencescience.com"], + "resources": [ + "audiencescience.com", + "revsci.net", + "targetingmarketplace.com", + "wunderloop.net" + ] + }, + "AuditedMedia": { + "properties": ["auditedmedia.com"], + "resources": ["aamapi.com", "aamsitecertifier.com", "auditedmedia.com"] + }, + "Augme": { + "properties": ["hipcricket.com"], + "resources": ["augme.com", "hipcricket.com"] + }, + "Augur": { + "properties": ["augur.io"], + "resources": ["augur.io"] + }, + "AUTOCENTRE.UA": { + "properties": ["am.ua", "autocentre.ua"], + "resources": ["am.ua", "autocentre.ua"] + }, + "Automattic": { + "properties": [ + "automattic.com", + "gravatar.com", + "intensedebate.com", + "polldaddy.com" + ], + "resources": [ + "automattic.com", + "gravatar.com", + "intensedebate.com", + "polldaddy.com", + "pubmine.com" + ] + }, + "Avalanchers": { + "properties": ["avalanchers.com"], + "resources": ["avalanchers.com"] + }, + "AvantLink": { + "properties": ["avantlink.com", "avantmetrics.com"], + "resources": ["avantlink.com", "avmws.com"] + }, + "Avocet": { + "properties": ["avocet.io"], + "resources": ["avocet.io"] + }, + "Avsads": { + "properties": ["avsads.com"], + "resources": ["avsads.com"] + }, + "AWeber": { + "properties": ["aweber.com"], + "resources": ["aweber.com"] + }, + "Awin": { + "properties": ["awin.com"], + "resources": [ + "awin.com", + "digitalwindow.com", + "dwin1.com", + "perfiliate.com" + ] + }, + "Awio": { + "properties": ["awio.com", "w3counter.com"], + "resources": ["awio.com", "w3counter.com", "w3roi.com"] + }, + "Azet": { + "properties": ["azet.sk", "mediaimpact.sk"], + "resources": ["azet.sk", "azetklik.sk", "mediaimpact.sk", "rsz.sk"] + }, + "BackBeat Media": { + "properties": ["backbeatmedia.com"], + "resources": ["backbeatmedia.com"] + }, + "Bannerconnect": { + "properties": ["bannerconnect.net"], + "resources": ["bannerconnect.net"] + }, + "Barilliance": { + "properties": ["barilliance.com"], + "resources": ["barilliance.com"] + }, + "BaronsNetworks": { + "properties": ["baronsoffers.com"], + "resources": ["baronsoffers.com"] + }, + "Batanga Network": { + "properties": ["batanga.com", "corp.vix.com", "vix.com"], + "resources": ["batanga.com", "batanganetwork.com", "vix.com"] + }, + "Baynote": { + "properties": ["baynote.com"], + "resources": ["baynote.com", "baynote.net"] + }, + "Bazaarvoice": { + "properties": ["bazaarvoice.com"], + "resources": ["bazaarvoice.com"] + }, + "BeachFront": { + "properties": ["beachfront.com"], + "resources": ["beachfront.com"] + }, + "Beanstock Media": { + "properties": ["beanstockmedia.com"], + "resources": ["beanstockmedia.com"] + }, + "beencounter": { + "properties": ["beencounter.com"], + "resources": ["beencounter.com"] + }, + "Begun": { + "properties": ["begun.ru"], + "resources": ["begun.ru"] + }, + "belboon": { + "properties": ["belboon.com"], + "resources": ["adbutler.de", "belboon.com"] + }, + "Belstat": { + "properties": [ + "belstat.be", + "belstat.com", + "belstat.de", + "belstat.fr", + "belstat.nl" + ], + "resources": [ + "belstat.be", + "belstat.com", + "belstat.de", + "belstat.fr", + "belstat.nl" + ] + }, + "Betgenius": { + "properties": ["betgenius.com", "connextra.com"], + "resources": ["betgenius.com", "connextra.com"] + }, + "BetssonPalantir": { + "properties": ["betssonpalantir.com"], + "resources": ["betssonpalantir.com"] + }, + "BetweenDigital": { + "properties": ["betweendigital.com"], + "resources": ["betweendigital.com"] + }, + "Bidfluence": { + "properties": ["bidfluence.com"], + "resources": ["bidfluence.com"] + }, + "Bidr": { + "properties": ["bidr.io"], + "resources": ["bidr.io"] + }, + "BidSwitch": { + "properties": ["bidswitch.com"], + "resources": ["bidswitch.net", "mfadsrvr.com"] + }, + "Bidtellect": { + "properties": ["bidtellect.com", "bttrack.com"], + "resources": ["bidtellect.com", "bttrack.com"] + }, + "BidVertiser": { + "properties": ["bidvertiser.com"], + "resources": ["bidvertiser.com"] + }, + "BigClick": { + "properties": ["bigclick.me"], + "resources": ["bgclck.me", "xcvgdf.party"] + }, + "BigDoor": { + "properties": ["bigdoor.com"], + "resources": ["bigdoor.com", "onetruefan.com"] + }, + "bigmirnet": { + "properties": ["bigmir.net"], + "resources": ["bigmir.net"] + }, + "BinLayer": { + "properties": ["binlayer.com"], + "resources": ["binlayer.com"] + }, + "Bitcoin Plus": { + "properties": ["bitcoinplus.com"], + "resources": ["bitcoinplus.com"] + }, + "BitMedia": { + "properties": ["bitmedia.io"], + "resources": ["bitmedia.io"] + }, + "BittAds": { + "properties": ["bittads.com"], + "resources": ["bittads.com"] + }, + "Bizo": { + "properties": ["bizo.com", "bizographics.com"], + "resources": ["bizo.com", "bizographics.com"] + }, + "Black Label Ads": { + "properties": ["blacklabelads.com"], + "resources": ["blacklabelads.com"] + }, + "BlogCatalog": { + "properties": ["blogcatalog.com"], + "resources": ["blogcatalog.com"] + }, + "BlogCounter.com": { + "properties": ["blogcounter.de"], + "resources": ["blogcounter.de"] + }, + "BlogFrog": { + "properties": ["theblogfrog.com"], + "resources": ["theblogfrog.com"] + }, + "BlogHer": { + "properties": ["blogher.com", "blogherads.com"], + "resources": ["blogher.com", "blogherads.com"] + }, + "BlogRollr": { + "properties": ["blogrollr.com"], + "resources": ["blogrollr.com"] + }, + "BLOOM Digital Platforms": { + "properties": ["adgear.com", "bloom-hq.com"], + "resources": ["adgear.com", "adgrx.com", "bloom-hq.com"] + }, + "BloomReach": { + "properties": ["bloomreach.com", "brcdn.com"], + "resources": ["bloomreach.com", "brcdn.com", "brsrvr.com"] + }, + "BlueCava": { + "properties": ["bluecava.com"], + "resources": ["bluecava.com"] + }, + "BlueKai": { + "properties": ["bluekai.com", "tracksimple.com"], + "resources": ["bkrtx.com", "bluekai.com", "tracksimple.com"] + }, + "Bluemetrix": { + "properties": ["bluemetrix.com", "bmmetrix.com"], + "resources": ["bluemetrix.com", "bmmetrix.com"] + }, + "Blu Trumpet": { + "properties": ["blutrumpet.com"], + "resources": ["blutrumpet.com"] + }, + "Bombora": { + "properties": ["bombora.com"], + "resources": ["ml314.com"] + }, + "Boo-Box": { + "properties": ["boo-box.com"], + "resources": ["boo-box.com"] + }, + "BoostBox": { + "properties": ["boostbox.com.br"], + "resources": ["boostbox.com.br"] + }, + "Bouncex": { + "properties": ["bouncex.com"], + "resources": ["bounceexchange.com", "bouncex.com", "bouncex.net"] + }, + "Brainient": { + "properties": ["brainient.com"], + "resources": ["brainient.com"] + }, + "Branch": { + "properties": ["branch.io"], + "resources": ["branch.io"] + }, + "Brand Affinity Technologies": { + "properties": ["brandaffinity.net"], + "resources": ["brandaffinity.net"] + }, + "Brandcrumb": { + "properties": ["brandcrumb.com"], + "resources": ["brandcrumb.com"] + }, + "Brand.net": { + "properties": ["brand.net"], + "resources": ["brand.net"] + }, + "Brandscreen": { + "properties": ["brandscreen.com", "rtbidder.net"], + "resources": ["brandscreen.com", "rtbidder.net"] + }, + "Branica": { + "properties": ["branica.com"], + "resources": ["branica.com"] + }, + "BreakTime": { + "properties": ["breaktime.com.tw"], + "resources": ["breaktime.com.tw"] + }, + "Brightcove": { + "properties": ["brightcove.com"], + "resources": ["brightcove.com"] + }, + "BrightEdge": { + "properties": ["brightedge.com"], + "resources": ["b0e8.com", "brightedge.com"] + }, + "BrightRoll": { + "properties": ["brightroll.com"], + "resources": ["brightroll.com", "btrll.com"] + }, + "BrightTag": { + "properties": ["brighttag.com", "btstatic.com", "thebrighttag.com"], + "resources": ["brighttag.com", "btstatic.com", "thebrighttag.com"] + }, + "Brilig": { + "properties": ["brilig.com"], + "resources": ["brilig.com"] + }, + "Browser-Update.org": { + "properties": ["browser-update.org"], + "resources": ["browser-update.org"] + }, + "BTBuckets": { + "properties": ["btbuckets.com"], + "resources": ["btbuckets.com"] + }, + "Bubblestat": { + "properties": ["bubblestat.com"], + "resources": ["bubblestat.com"] + }, + "BuckSense": { + "properties": ["bucksense.com"], + "resources": ["bucksense.com"] + }, + "Buffer": { + "properties": ["bufferapp.com"], + "resources": ["bufferapp.com"] + }, + "Bunchball": { + "properties": ["bunchball.com"], + "resources": ["bunchball.com"] + }, + "Burstly": { + "properties": ["burstly.com"], + "resources": ["burstly.com"] + }, + "Burst Media": { + "properties": [ + "burstbeacon.com", + "burstdirectads.com", + "burstmedia.com", + "burstnet.com", + "giantrealm.com" + ], + "resources": [ + "burstbeacon.com", + "burstdirectads.com", + "burstmedia.com", + "burstnet.com", + "giantrealm.com" + ] + }, + "BusinessOnline": { + "properties": ["businessol.com"], + "resources": ["businessol.com"] + }, + "Button": { + "properties": ["usebutton.com"], + "resources": ["usebutton.com"] + }, + "buySAFE": { + "properties": ["buysafe.com"], + "resources": ["buysafe.com"] + }, + "BuySellAds": { + "properties": ["beaconads.com", "buysellads.com"], + "resources": ["beaconads.com", "buysellads.com"] + }, + "Buysight": { + "properties": ["buysight.com", "permuto.com", "pulsemgr.com"], + "resources": ["buysight.com", "permuto.com", "pulsemgr.com"] + }, + "BuzzFeed": { + "properties": ["buzzfeed.com"], + "resources": ["buzzfed.com", "buzzfeed.com"] + }, + "BuzzParadise": { + "properties": ["buzzparadise.com"], + "resources": ["buzzparadise.com"] + }, + "BV! MEDIA": { + "properties": ["branchez-vous.com", "bvmedia.ca"], + "resources": [ + "branchez-vous.com", + "bvmedia.ca", + "networldmedia.com", + "networldmedia.net" + ] + }, + "c1exchange": { + "properties": ["c1exchange.com"], + "resources": ["c1exchange.com"] + }, + "C3 Metrics": { + "properties": ["attributionmodel.com", "c3metrics.com", "c3tag.com"], + "resources": ["attributionmodel.com", "c3metrics.com", "c3tag.com"] + }, + "Cadreon": { + "properties": ["cadreon.com"], + "resources": ["cadreon.com"] + }, + "CallSource": { + "properties": ["callsource.com"], + "resources": ["leadtrackingdata.com"] + }, + "CampaignGrid": { + "properties": ["campaigngrid.com"], + "resources": ["campaigngrid.com"] + }, + "CAPITALDATA": { + "properties": ["capitaldata.fr"], + "resources": ["capitaldata.fr"] + }, + "Carambola": { + "properties": ["carambola.com"], + "resources": ["carambo.la"] + }, + "Caraytech": { + "properties": [ + "caraytech.com.ar", + "e-planning.net", + "www.caraytech.com.ar" + ], + "resources": ["caraytech.com.ar", "e-planning.net", "www.caraytech.com.ar"] + }, + "Cardlytics": { + "properties": ["cardlytics.com"], + "resources": ["cardlytics.com"] + }, + "Cart.ro": { + "properties": ["cart.ro"], + "resources": ["cart.ro", "statistics.ro"] + }, + "CartsGuru": { + "properties": ["carts.guru"], + "resources": ["carts.guru"] + }, + "Casale Media": { + "properties": ["casalemedia.com", "medianet.com"], + "resources": ["casalemedia.com", "medianet.com"] + }, + "CashBeet": { + "properties": ["cashbeet.com"], + "resources": ["cashbeet.com", "serv1swork.com"] + }, + "Causes": { + "properties": ["causes.com"], + "resources": ["causes.com"] + }, + "Cbox": { + "properties": ["cbox.ws"], + "resources": ["cbox.ws"] + }, + "CBproADS": { + "properties": ["cbproads.com"], + "resources": ["cbproads.com"] + }, + "CBS Interactive": { + "properties": ["cbsinteractive.com", "com.com"], + "resources": ["cbsinteractive.com", "com.com"] + }, + "Cedato": { + "properties": ["cedato.com"], + "resources": ["cedato.com"] + }, + "Cedexis": { + "properties": ["cedexis.com"], + "resources": ["cedexis.com", "cedexis.net"] + }, + "Certona": { + "properties": ["certona.com", "res-x.com"], + "resources": ["certona.com", "res-x.com"] + }, + "Chango": { + "properties": ["chango.ca", "chango.com"], + "resources": ["chango.ca", "chango.com"] + }, + "ChannelAdvisor": { + "properties": ["channeladvisor.com", "searchmarketing.com"], + "resources": ["channeladvisor.com", "searchmarketing.com"] + }, + "Channel Intelligence": { + "properties": ["channelintelligence.com"], + "resources": ["channelintelligence.com"] + }, + "Chartbeat": { + "properties": ["chartbeat.com", "chartbeat.net"], + "resources": ["chartbeat.com", "chartbeat.net"] + }, + "Chartboost": { + "properties": ["chartboost.com"], + "resources": ["chartboost.com"] + }, + "CheckM8": { + "properties": ["checkm8.com"], + "resources": ["checkm8.com"] + }, + "Chitika": { + "properties": ["chitika.com"], + "resources": ["chitika.com", "chitika.net"] + }, + "ChoiceStream": { + "properties": ["choicestream.com"], + "resources": ["choicestream.com"] + }, + "ClearLink": { + "properties": ["clearlink.com"], + "resources": ["clearlink.com"] + }, + "ClearSaleing": { + "properties": ["clearsaleing.com"], + "resources": [ + "clearsaleing.com", + "csdata1.com", + "csdata2.com", + "csdata3.com" + ] + }, + "Clearsearch Media": { + "properties": ["pathinteractive.com"], + "resources": [ + "clearsearchmedia.com", + "csm-secure.com", + "pathinteractive.com" + ] + }, + "ClearSight Interactive": { + "properties": ["clearsightinteractive.com", "csi-tracking.com"], + "resources": ["clearsightinteractive.com", "csi-tracking.com"] + }, + "ClickAider": { + "properties": ["clickaider.com"], + "resources": ["clickaider.com"] + }, + "Clickayab": { + "properties": ["clickyab.com"], + "resources": ["clickyab.com"] + }, + "Clickbooth": { + "properties": ["clickbooth.com"], + "resources": ["adtoll.com", "clickbooth.com"] + }, + "Clickdensity": { + "properties": ["clickdensity.com"], + "resources": ["clickdensity.com"] + }, + "ClickDimensions": { + "properties": ["clickdimensions.com"], + "resources": ["clickdimensions.com"] + }, + "ClickDistrict": { + "properties": ["clickdistrict.com", "creative-serving.com"], + "resources": ["clickdistrict.com", "creative-serving.com"] + }, + "ClickFrog": { + "properties": ["clickfrog.ru"], + "resources": [ + "bashirian.biz", + "buckridge.link", + "clickfrog.ru", + "franecki.net", + "quitzon.net", + "reichelcormier.bid", + "wisokykulas.bid" + ] + }, + "ClickFuel": { + "properties": ["clickfuel.com", "myconversionlab.com"], + "resources": [ + "clickfuel.com", + "conversiondashboard.com", + "myconversionlab.com" + ] + }, + "ClickGuard": { + "properties": ["clickguard.com"], + "resources": ["clickguard.com"] + }, + "ClickInc": { + "properties": ["clickinc.com"], + "resources": ["clickinc.com"] + }, + "Clicksor": { + "properties": ["clicksor.com", "clicksor.net"], + "resources": ["clicksor.com", "clicksor.net"] + }, + "ClickTale": { + "properties": ["clicktale.com"], + "resources": ["clicktale.com", "clicktale.net", "pantherssl.com"] + }, + "Clickwinks": { + "properties": ["clickwinks.com"], + "resources": ["clickwinks.com"] + }, + "ClicManager": { + "properties": ["clicmanager.fr"], + "resources": ["clicmanager.fr"] + }, + "ClipSyndicate": { + "properties": ["clipsyndicate.com"], + "resources": ["clipsyndicate.com"] + }, + "ClixMetrix": { + "properties": ["clixmetrix.com"], + "resources": ["clixmetrix.com"] + }, + "Clixpy": { + "properties": ["clixpy.com"], + "resources": ["clixpy.com"] + }, + "Clixtell": { + "properties": ["clixtell.com"], + "resources": ["clixtell.com"] + }, + "Clove Network": { + "properties": ["clovenetwork.com"], + "resources": ["clovenetwork.com"] + }, + "ClustrMaps": { + "properties": ["clustrmaps.com"], + "resources": ["clustrmaps.com"] + }, + "CNZZ": { + "properties": ["cnzz.com"], + "resources": ["cnzz.com"] + }, + "Cognitive Match": { + "properties": [ + "cmads.com.tw", + "cmadsasia.com", + "cmadseu.com", + "cmmeglobal.com", + "cognitivematch.com" + ], + "resources": [ + "cmads.com.tw", + "cmadsasia.com", + "cmadseu.com", + "cmmeglobal.com", + "cognitivematch.com" + ] + }, + "CoinHive": { + "properties": ["authedmine.com", "coinhive.com"], + "resources": [ + "ad-miner.com", + "authedmine.com", + "bmst.pw", + "cnhv.co", + "coin-hive.com", + "coinhive.com", + "wsservices.org" + ] + }, + "CoinPot": { + "properties": ["coinpot.co"], + "resources": ["coinpot.co"] + }, + "Collarity": { + "properties": ["collarity.com"], + "resources": ["collarity.com"] + }, + "Collective": { + "properties": ["collective.com"], + "resources": [ + "collective-media.net", + "collective.com", + "oggifinogi.com", + "tumri.com", + "tumri.net", + "yt1187.net" + ] + }, + "Commission Junction": { + "properties": ["cj.com"], + "resources": [ + "apmebf.com", + "awltovhc.com", + "cj.com", + "ftjcfx.com", + "kcdwa.com", + "qksz.com", + "qksz.net", + "tqlkg.com", + "yceml.net" + ] + }, + "Communicator Corp": { + "properties": ["communicatorcorp.com"], + "resources": ["communicatorcorp.com"] + }, + "Compass Labs": { + "properties": ["compasslabs.com"], + "resources": ["compasslabs.com"] + }, + "Complex Media": { + "properties": [ + "collider.com", + "complex.com", + "complexmedianetwork.com", + "firstwefeast.com", + "pigeonsandplanes.com", + "solecollector.com", + "theridechannel.com" + ], + "resources": ["complex.com", "complexmedianetwork.com"] + }, + "Compuware": { + "properties": ["axf8.net", "compuware.com", "dynatrace.com"], + "resources": ["axf8.net", "compuware.com", "dynatrace.com", "gomez.com"] + }, + "comScore": { + "properties": [ + "adxpose.com", + "comscore.com", + "scorecardresearch.com", + "sitestat.com", + "voicefive.com" + ], + "resources": [ + "adxpose.com", + "certifica.com", + "comscore.com", + "mdotlabs.com", + "proxilinks.com", + "proximic.com", + "proximic.net", + "scorecardresearch.com", + "sitestat.com", + "voicefive.com" + ] + }, + "Conduit": { + "properties": ["conduit-banners.com", "conduit.com"], + "resources": [ + "conduit-banners.com", + "conduit-services.com", + "conduit.com", + "wibiya.com" + ] + }, + "Congoo": { + "properties": ["congoo.com"], + "resources": ["congoo.com"] + }, + "Connatix.com": { + "properties": ["connatix.com"], + "resources": ["connatix.com"] + }, + "Connexity": { + "properties": ["connexity.com", "pricegrabber.com"], + "resources": ["connexity.com", "connexity.net", "pricegrabber.com"] + }, + "Consilium Media": { + "properties": ["consiliummedia.com"], + "resources": ["consiliummedia.com"] + }, + "Consumable": { + "properties": ["consumable.com"], + "resources": ["consumable.com"] + }, + "Contact At Once!": { + "properties": ["contactatonce.com"], + "resources": ["contactatonce.com"] + }, + "CONTAXE": { + "properties": ["contaxe.com"], + "resources": ["contaxe.com"] + }, + "ContentABC": { + "properties": ["contentabc.com"], + "resources": ["contentabc.com"] + }, + "CONTEXTin": { + "properties": ["admailtiser.com", "contextin.com"], + "resources": ["admailtiser.com", "contextin.com"] + }, + "ContextuAds": { + "properties": ["agencytradingdesk.net", "contextuads.com"], + "resources": ["agencytradingdesk.net", "contextuads.com"] + }, + "CONTEXTWEB": { + "properties": ["contextweb.com"], + "resources": ["contextweb.com"] + }, + "ConvergeDirect": { + "properties": ["convergedirect.com", "convergetrack.com"], + "resources": ["convergedirect.com", "convergetrack.com"] + }, + "ConversantMedia": { + "properties": ["conversantmedia.com"], + "resources": [ + "adserver.com", + "conversantmedia.com", + "dotomi.com", + "dtmpub.com", + "emjcd.com", + "fastclick.com", + "fastclick.net", + "greystripe.com", + "lduhtrp.net", + "mediaplex.com", + "valueclick.com", + "valueclick.net", + "valueclickmedia.com" + ] + }, + "ConversionRuler": { + "properties": ["conversionruler.com"], + "resources": ["conversionruler.com"] + }, + "Conversive": { + "properties": ["conversive.nl"], + "resources": ["conversive.nl"] + }, + "Convert Insights": { + "properties": ["convert.com", "reedge.com"], + "resources": ["convert.com", "reedge.com"] + }, + "Convertro": { + "properties": ["convertro.com"], + "resources": ["convertro.com"] + }, + "Conviva": { + "properties": ["conviva.com"], + "resources": ["conviva.com"] + }, + "CoreMotives": { + "properties": ["coremotives.com"], + "resources": ["coremotives.com"] + }, + "Cox Digital Solutions": { + "properties": ["adify.com", "coxdigitalsolutions.com", "novomotus.com"], + "resources": [ + "adify.com", + "afy11.net", + "coxdigitalsolutions.com", + "novomotus.com" + ] + }, + "CPMStar": { + "properties": ["cpmstar.com"], + "resources": ["cpmstar.com"] + }, + "CPX Interactive": { + "properties": ["cpxadroit.com"], + "resources": ["adreadypixels.com", "cpxadroit.com", "cpxinteractive.com"] + }, + "Crazy Egg": { + "properties": ["cetrk.com", "crazyegg.com"], + "resources": ["cetrk.com", "crazyegg.com"] + }, + "Creafi": { + "properties": ["creafi.com"], + "resources": ["creafi.com"] + }, + "Crimtan": { + "properties": ["crimtan.com"], + "resources": ["crimtan.com"] + }, + "Crisp Media": { + "properties": ["crispmedia.com"], + "resources": ["crispmedia.com"] + }, + "Criteo": { + "properties": ["criteo.com", "criteo.net"], + "resources": [ + "criteo.com", + "criteo.net", + "hlserve.com", + "hooklogic.com", + "storetail.io" + ] + }, + "Cross Pixel": { + "properties": ["crosspixel.net"], + "resources": ["crosspixel.net", "crosspixelmedia.com", "crsspxl.com"] + }, + "Crowd Science": { + "properties": ["crowdscience.com"], + "resources": ["crowdscience.com"] + }, + "CryptoLoot": { + "properties": ["crypto-loot.com"], + "resources": [ + "cryptaloot.pro", + "crypto-loot.com", + "cryptolootminer.com", + "flashx.pw", + "gitgrub.pro", + "reauthenticator.com", + "statdynamic.com", + "webmine.pro" + ] + }, + "CryptoWebMiner": { + "properties": ["crypto-webminer.com"], + "resources": [ + "bitcoin-pay.eu", + "crypto-webminer.com", + "ethpocket.de", + "ethtrader.de" + ] + }, + "cXense": { + "properties": ["cxense.com"], + "resources": [ + "cxense.com", + "emediate.biz", + "emediate.com", + "emediate.dk", + "emediate.eu" + ] + }, + "Cya2": { + "properties": ["cya2.net"], + "resources": ["cya2.net"] + }, + "Cyberplex": { + "properties": ["cyberplex.com"], + "resources": ["cyberplex.com"] + }, + "Dada": { + "properties": ["dada.eu", "dada.pro", "simply.com"], + "resources": ["dada.eu", "dada.pro", "simply.com"] + }, + "DailyMe": { + "properties": ["dailyme.com", "newstogram.com"], + "resources": ["dailyme.com", "newstogram.com"] + }, + "Dataium": { + "properties": ["collserve.com", "ihs.com"], + "resources": ["collserve.com", "dataium.com", "ihs.com"] + }, + "Datalogix": { + "properties": ["datalogix.com", "nexac.com"], + "resources": ["datalogix.com", "nexac.com", "nextaction.net"] + }, + "DataSift": { + "properties": ["datasift.com", "tweetmeme.com"], + "resources": ["datasift.com", "tweetmeme.com"] + }, + "DataXu": { + "properties": ["dataxu.com", "mexad.com", "w55c.net"], + "resources": ["dataxu.com", "dataxu.net", "mexad.com", "w55c.net"] + }, + "Datonics": { + "properties": ["datonics.com"], + "resources": ["datonics.com", "pro-market.net"] + }, + "Datran Media": { + "properties": ["datranmedia.com", "displaymarketplace.com"], + "resources": ["datranmedia.com", "displaymarketplace.com"] + }, + "Datvantage": { + "properties": ["datvantage.com"], + "resources": ["datvantage.com"] + }, + "DC Storm": { + "properties": ["dc-storm.com", "stormiq.com"], + "resources": ["dc-storm.com", "stormiq.com"] + }, + "Dedicated Media": { + "properties": ["dedicatedmedia.com", "dedicatednetworks.com"], + "resources": ["dedicatedmedia.com", "dedicatednetworks.com"] + }, + "Deep Intent": { + "properties": ["deepintent.com"], + "resources": ["deepintent.com"] + }, + "Delivr": { + "properties": ["delivr.com"], + "resources": ["delivr.com", "percentmobile.com"] + }, + "Delta Projects": { + "properties": ["deltaprojects.com"], + "resources": [ + "adaction.se", + "de17a.com", + "deltaprojects.com", + "deltaprojects.se" + ] + }, + "Demandbase": { + "properties": ["demandbase.com"], + "resources": ["company-target.com", "demandbase.com"] + }, + "Demand Media": { + "properties": ["leafgroup.com"], + "resources": ["demandmedia.com", "indieclick.com"] + }, + "Deutsche Post DHL": { + "properties": ["dpdhl.com"], + "resources": ["adcloud.com", "adcloud.net", "dp-dhl.com", "dpdhl.com"] + }, + "Developer Media": { + "properties": ["developermedia.com"], + "resources": ["developermedia.com", "lqcdn.com"] + }, + "DG": { + "properties": ["dgit.com", "sizmek.com"], + "resources": [ + "dgit.com", + "eyeblaster.com", + "eyewonder.com", + "mdadx.com", + "serving-sys.com", + "unicast.com" + ] + }, + "dianomi": { + "properties": ["dianomi.com"], + "resources": ["dianomi.com"] + }, + "Didit": { + "properties": ["didit.com"], + "resources": ["did-it.com", "didit.com"] + }, + "Digg": { + "properties": ["digg.com"], + "resources": ["digg.com"] + }, + "DigitalAdConsortium": { + "properties": ["dac.co.jp"], + "resources": ["impact-ad.jp"] + }, + "Digital River": { + "properties": ["digitalriver.com", "keywordmax.com", "netflame.cc"], + "resources": ["digitalriver.com", "keywordmax.com", "netflame.cc"] + }, + "Digital Target": { + "properties": ["digitaltarget.ru"], + "resources": ["digitaltarget.ru"] + }, + "Digitize": { + "properties": ["digitize.ie"], + "resources": ["digitize.ie"] + }, + "DirectAdvert": { + "properties": ["directadvert.ru"], + "resources": ["directadvert.ru"] + }, + "DirectCORP": { + "properties": ["directcorp.de", "ipcounter.de"], + "resources": ["directcorp.de", "ipcounter.de"] + }, + "Direct Response Group": { + "properties": ["directresponsegroup.com"], + "resources": ["directresponsegroup.com", "ppctracking.net"] + }, + "Directtrack": { + "properties": ["directtrack.com"], + "resources": ["directtrack.com"] + }, + "Disqus": { + "properties": ["disqus.com", "disqusads.com"], + "resources": ["disqus.com", "disqusads.com"] + }, + "DistilNetworks": { + "properties": ["distilnetworks.com"], + "resources": ["distilnetworks.com", "distiltag.com"] + }, + "DistrictM": { + "properties": ["districtm.net"], + "resources": ["districtm.io"] + }, + "dmpxs": { + "properties": ["dmpxs.com"], + "resources": ["dmpxs.com"] + }, + "DoublePimp": { + "properties": ["doublepimp.com"], + "resources": ["doublepimp.com"] + }, + "DoublePositive": { + "properties": ["doublepositive.com"], + "resources": ["bid-tag.com", "doublepositive.com"] + }, + "DoubleVerify": { + "properties": ["doubleverify.com"], + "resources": ["doubleverify.com"] + }, + "Drawbridge": { + "properties": ["drawbridge.com"], + "resources": ["adsymptotic.com", "drawbrid.ge", "drawbridge.com"] + }, + "DS-IQ": { + "properties": ["ds-iq.com"], + "resources": ["ds-iq.com"] + }, + "DSNR Group": { + "properties": ["dsnrgroup.com", "dsnrmg.com", "traffiliate.com", "z5x.net"], + "resources": [ + "dsnrgroup.com", + "dsnrmg.com", + "traffiliate.com", + "z5x.com", + "z5x.net" + ] + }, + "dwstat.com": { + "properties": ["dwstat.cn"], + "resources": ["dwstat.cn"] + }, + "DynAdmic": { + "properties": ["dynadmic.com"], + "resources": ["dynadmic.com", "dyntrk.com"] + }, + "DynamicOxygen": { + "properties": ["dynamicoxygen.com", "exitjunction.com"], + "resources": ["dynamicoxygen.com", "exitjunction.com"] + }, + "DynamicYield": { + "properties": ["dynamicyield.com"], + "resources": ["dynamicyield.com"] + }, + "Earnify": { + "properties": ["earnify.com"], + "resources": ["earnify.com"] + }, + "eBay": { + "properties": [ + "ebay.at", + "ebay.ba", + "ebay.be", + "ebay.ca", + "ebay.ch", + "ebay.cn", + "ebay.co.jp", + "ebay.co.kr", + "ebay.co.uk", + "ebay.com", + "ebay.com.au", + "ebay.com.hk", + "ebay.com.my", + "ebay.com.ph", + "ebay.com.sg", + "ebay.com.tw", + "ebay.de", + "ebay.es", + "ebay.fr", + "ebay.ie", + "ebay.in", + "ebay.it", + "ebay.nl", + "ebay.pl" + ], + "resources": ["ebay.com"] + }, + "Echo": { + "properties": ["aboutecho.com", "haloscan.com", "js-kit.com"], + "resources": ["aboutecho.com", "haloscan.com", "js-kit.com"] + }, + "ECSAnalytics": { + "properties": ["ecsanalytics.com", "theecsinc.com"], + "resources": ["ecsanalytics.com"] + }, + "EFF": { + "properties": [ + "do-not-tracker.org", + "eff.org", + "eviltracker.net", + "trackersimulator.org" + ], + "resources": [ + "do-not-tracker.org", + "eff.org", + "eviltracker.net", + "trackersimulator.org" + ] + }, + "Effective Measure": { + "properties": ["effectivemeasure.com", "effectivemeasure.net"], + "resources": ["effectivemeasure.com", "effectivemeasure.net"] + }, + "ekolay": { + "properties": ["hurriyet.com.tr"], + "resources": ["e-kolay.net", "ekolay.net", "hurriyet.com.tr"] + }, + "Eleavers": { + "properties": ["eleavers.com"], + "resources": ["eleavers.com"] + }, + "Emego": { + "properties": ["usemax.de"], + "resources": ["usemax.de"] + }, + "Emerse": { + "properties": ["emerse.com"], + "resources": ["emerse.com"] + }, + "EMX": { + "properties": ["emxdigital.com"], + "resources": [ + "brealtime.com", + "clearstream.tv", + "emxdgt.com", + "emxdigital.com" + ] + }, + "Enecto": { + "properties": ["enecto.com"], + "resources": ["enecto.com"] + }, + "engage:BDR": { + "properties": ["engagebdr.com"], + "resources": ["bnmla.com", "engagebdr.com"] + }, + "Engago Technology": { + "properties": ["engago.com"], + "resources": ["appmetrx.com", "engago.com"] + }, + "Engine Network": { + "properties": ["enginenetwork.com"], + "resources": ["enginenetwork.com"] + }, + "Ensighten": { + "properties": ["ensighten.com"], + "resources": ["ensighten.com"] + }, + "Entireweb": { + "properties": ["entireweb.com"], + "resources": ["entireweb.com"] + }, + "Epic Media Group": { + "properties": [ + "epicadvertising.com", + "epicmarketplace.com", + "theepicmediagroup.com" + ], + "resources": [ + "epicadvertising.com", + "epicmarketplace.com", + "epicmobileads.com", + "theepicmediagroup.com", + "trafficmp.com" + ] + }, + "eProof.com": { + "properties": ["eproof.com"], + "resources": ["eproof.com"] + }, + "Epsilon": { + "properties": ["epsilon.com"], + "resources": ["epsilon.com"] + }, + "EQ Ads": { + "properties": ["eqads.com"], + "resources": ["eqads.com"] + }, + "EroAdvertising": { + "properties": ["ero-advertising.com"], + "resources": ["ero-advertising.com"] + }, + "Etarget": { + "properties": ["etarget.net", "etargetnet.com"], + "resources": ["etarget.net", "etargetnet.com"] + }, + "Etineria": { + "properties": ["adwitserver.com", "etineria.com"], + "resources": ["adwitserver.com", "etineria.com"] + }, + "etracker": { + "properties": ["etracker.com", "etracker.de"], + "resources": [ + "etracker.com", + "etracker.de", + "sedotracker.com", + "sedotracker.de" + ] + }, + "eTrigue": { + "properties": ["etrigue.com"], + "resources": ["etrigue.com"] + }, + "Eulerian Technologies": { + "properties": ["eulerian.com"], + "resources": ["eulerian.com", "eulerian.net"] + }, + "Evergage": { + "properties": ["evergage.com"], + "resources": ["mybuys.com", "veruta.com"] + }, + "Everyday Health": { + "properties": ["everydayhealth.com", "waterfrontmedia.com"], + "resources": ["everydayhealth.com", "waterfrontmedia.com"] + }, + "Evisions Marketing": { + "properties": ["engineseeker.com", "evisionsmarketing.com"], + "resources": ["engineseeker.com", "evisionsmarketing.com"] + }, + "Evolve": { + "properties": ["evolvemediacorp.com", "gorillanation.com"], + "resources": [ + "evolvemediacorp.com", + "evolvemediametrics.com", + "gorillanation.com" + ] + }, + "eWayDirect": { + "properties": ["ewaydirect.com"], + "resources": ["ewaydirect.com", "ixs1.net"] + }, + "ewebse": { + "properties": ["777seo.com", "ewebse.com"], + "resources": ["777seo.com", "ewebse.com"] + }, + "excitad": { + "properties": ["excitad.com"], + "resources": ["excitad.com"] + }, + "eXelate": { + "properties": ["exelate.com"], + "resources": ["exelate.com", "exelator.com"] + }, + "ExoClick": { + "properties": ["exoclick.com"], + "resources": ["exoclick.com"] + }, + "Exosrv": { + "properties": ["exosrv.com"], + "resources": ["exosrv.com"] + }, + "Experian": { + "properties": ["experian.com"], + "resources": ["audienceiq.com", "experian.com"] + }, + "expo-MAX": { + "properties": ["expo-max.com"], + "resources": ["expo-max.com"] + }, + "Exponential Interactive": { + "properties": ["exponential.com", "fulltango.com"], + "resources": [ + "adotube.com", + "exponential.com", + "fulltango.com", + "tribalfusion.com" + ] + }, + "Extension Factory": { + "properties": ["extensionfactory.com"], + "resources": ["extensionfactory.com"] + }, + "EXTENSIONS.RU": { + "properties": ["extensions.ru"], + "resources": ["extensions.ru"] + }, + "eXTReMe digital": { + "properties": ["extremetracking.com"], + "resources": ["extreme-dm.com", "extremetracking.com"] + }, + "Eyeconomy": { + "properties": ["eyeconomy.co.uk"], + "resources": [ + "eyeconomy.co.uk", + "eyeconomy.com", + "sublimemedia.net", + "www.eyeconomy.co.uk" + ] + }, + "EyeNewton": { + "properties": ["eyenewton.ru"], + "resources": ["eyenewton.ru"] + }, + "Eyeota": { + "properties": ["eyeota.net"], + "resources": ["eyeota.net"] + }, + "eyeReturn Marketing": { + "properties": ["eyereturnmarketing.com"], + "resources": ["eyereturn.com", "eyereturnmarketing.com"] + }, + "Eyeviewdigital": { + "properties": ["eyeviewdigital.com"], + "resources": ["eyeviewads.com", "eyeviewdigital.com"] + }, + "Facebook": { + "properties": [ + "atlassolutions.com", + "facebook.com", + "facebook.de", + "facebook.fr", + "facebook.net", + "fb.com", + "fb.me", + "fbcdn.net", + "friendfeed.com", + "instagram.com", + "internalfb.com", + "messenger.com", + "oculus.com", + "whatsapp.com", + "workplace.com" + ], + "resources": [ + "apps.fbsbx.com", + "atdmt.com", + "atlassolutions.com", + "facebook.com", + "facebook.de", + "facebook.fr", + "facebook.net", + "fb.com", + "fb.me", + "fbcdn.net", + "fbsbx.com", + "friendfeed.com", + "instagram.com", + "messenger.com" + ] + }, + "Facilitate Digital": { + "properties": [ + "adsfac.eu", + "adsfac.net", + "adsfac.us", + "facilitatedigital.com" + ], + "resources": [ + "adsfac.eu", + "adsfac.info", + "adsfac.net", + "adsfac.sg", + "adsfac.us", + "facilitatedigital.com" + ] + }, + "Fairfax Media": { + "properties": ["fairfax.com.au", "fxj.com.au", "www.fxj.com.au"], + "resources": ["fairfax.com.au", "fxj.com.au", "www.fxj.com.au"] + }, + "faithadnet": { + "properties": ["faithadnet.com"], + "resources": ["faithadnet.com"] + }, + "Fanplayr": { + "properties": ["fanplayr.com"], + "resources": ["fanplayr.com"] + }, + "Fathom": { + "properties": ["fathomdelivers.com", "fathomseo.com"], + "resources": ["fathomdelivers.com", "fathomseo.com"] + }, + "Federated Media": { + "properties": ["hyfn.com", "lijit.com"], + "resources": ["federatedmedia.net", "fmpub.net", "hyfn.com", "lijit.com"] + }, + "Feedjit": { + "properties": ["feedjit.com"], + "resources": ["feedjit.com"] + }, + "FetchBack": { + "properties": ["fetchback.com"], + "resources": ["fetchback.com"] + }, + "Fiksu": { + "properties": ["fiksu.com"], + "resources": ["fiksu.com"] + }, + "FinancialContent": { + "properties": ["financialcontent.com"], + "resources": ["financialcontent.com"] + }, + "Fizz-Buzz Media": { + "properties": ["fizzbuzzmedia.com", "fizzbuzzmedia.net"], + "resources": ["fizzbuzzmedia.com", "fizzbuzzmedia.net"] + }, + "Flashtalking": { + "properties": ["flashtalking.com"], + "resources": ["encoremetrics.com", "flashtalking.com", "sitecompass.com"] + }, + "Flattr": { + "properties": ["flattr.com"], + "resources": ["flattr.com"] + }, + "Flite": { + "properties": ["flite.com", "widgetserver.com"], + "resources": ["flite.com", "widgetserver.com"] + }, + "Fluct": { + "properties": ["adingo.jp", "fluct.jp"], + "resources": ["adingo.jp", "fluct.jp"] + }, + "Flytxt": { + "properties": ["flytxt.com"], + "resources": ["flytxt.com"] + }, + "Footprint": { + "properties": ["footprintlive.com"], + "resources": ["footprintlive.com"] + }, + "Forbes": { + "properties": ["brandsideplatform.com", "forbes.com"], + "resources": ["brandsideplatform.com", "forbes.com"] + }, + "Foresee": { + "properties": ["foresee.com"], + "resources": ["answerscloud.com"] + }, + "Fox One Stop Media": { + "properties": [ + "fimserve.com", + "foxnetworks.com", + "foxonestop.com", + "mobsmith.com", + "myads.com", + "othersonline.com" + ], + "resources": [ + "fimserve.com", + "foxnetworks.com", + "foxonestop.com", + "mobsmith.com", + "myads.com", + "othersonline.com" + ] + }, + "FreakOut": { + "properties": ["fout.jp"], + "resources": ["fout.jp"] + }, + "Freedom Communications": { + "properties": ["freedom.com"], + "resources": ["freedom.com"] + }, + "Free Online Users": { + "properties": ["freeonlineusers.com"], + "resources": ["freeonlineusers.com"] + }, + "Free-PageRank.com": { + "properties": ["free-pagerank.com"], + "resources": ["free-pagerank.com"] + }, + "FreeWheel": { + "properties": ["freewheel.tv", "fwmrm.net"], + "resources": ["freewheel.tv", "fwmrm.net", "stickyadstv.com"] + }, + "FriendFinder Networks": { + "properties": ["adultfriendfinder.com", "ffn.com", "pop6.com"], + "resources": ["adultfriendfinder.com", "ffn.com", "pop6.com"] + }, + "Friends2Follow": { + "properties": ["friends2follow.com"], + "resources": ["friends2follow.com"] + }, + "Frog Sex": { + "properties": ["double-check.com", "frogsex.com"], + "resources": ["double-check.com", "frogsex.com"] + }, + "FuelX": { + "properties": ["fuelx.com"], + "resources": ["fuel451.com"] + }, + "Fullstory": { + "properties": ["fullstory.com"], + "resources": ["fullstory.com"] + }, + "Future Ads": { + "properties": ["futureads.com", "resultlinks.com"], + "resources": ["futureads.com", "resultlinks.com"] + }, + "Fyber": { + "properties": ["fyber.com"], + "resources": ["fyber.com"] + }, + "Game Advertising Online": { + "properties": ["game-advertising-online.com"], + "resources": ["game-advertising-online.com"] + }, + "Games2win": { + "properties": ["games2win.com", "inviziads.com"], + "resources": ["games2win.com", "inviziads.com"] + }, + "Gamned": { + "properties": ["gamned.com"], + "resources": ["gamned.com"] + }, + "Gannett": { + "properties": ["gannett.com", "pointroll.com"], + "resources": ["gannett.com", "pointroll.com"] + }, + "GB-World": { + "properties": ["gb-world.net"], + "resources": ["gb-world.net"] + }, + "Gemius": { + "properties": ["gemius.com", "gemius.pl"], + "resources": ["gemius.com", "gemius.pl"] + }, + "Genesis Media": { + "properties": ["genesismedia.com"], + "resources": ["genesismedia.com", "genesismediaus.com"] + }, + "GENIEE": { + "properties": ["geniee.co.jp"], + "resources": ["geniee.co.jp", "gssprt.jp"] + }, + "GENIE GROUP": { + "properties": ["geniegroupltd.co.uk", "www.geniegroupltd.co.uk"], + "resources": ["geniegroupltd.co.uk", "www.geniegroupltd.co.uk"] + }, + "Genius.com": { + "properties": ["genius.com", "rsvpgenius.com"], + "resources": ["genius.com", "rsvpgenius.com"] + }, + "GeoAds": { + "properties": ["geoads.com"], + "resources": ["geoads.com"] + }, + "GetGlue": { + "properties": ["elfie.com", "smrtlnks.com"], + "resources": ["getglue.com", "smrtlnks.com"] + }, + "GetIntent": { + "properties": ["adhigh.net", "getintent.com"], + "resources": ["adhigh.net", "getintent.com"] + }, + "Get Satisfaction": { + "properties": ["getsatisfaction.com"], + "resources": ["getsatisfaction.com"] + }, + "GetSiteControl": { + "properties": ["getsitecontrol.com"], + "resources": ["getsitecontrol.com"] + }, + "GfK Group": { + "properties": ["gfk.com"], + "resources": ["daphnecm.com", "gfk.com", "gfkdaphne.com"] + }, + "Gigya": { + "properties": ["gigya.com"], + "resources": ["gigcount.com", "gigya.com"] + }, + "GISMAds": { + "properties": ["gismads.jp"], + "resources": ["gismads.jp"] + }, + "GitHub": { + "properties": ["gaug.es", "github.com"], + "resources": ["gaug.es", "github.com"] + }, + "Glam Media": { + "properties": ["glam.com", "glammedia.com"], + "resources": ["glam.com", "glammedia.com"] + }, + "Gleam": { + "properties": ["gleam.io"], + "resources": ["fraudjs.io", "gleam.io"] + }, + "Global Takeoff": { + "properties": ["globaltakeoff.com", "globaltakeoff.net"], + "resources": ["globaltakeoff.com", "globaltakeoff.net"] + }, + "Globe7": { + "properties": ["globe7.com"], + "resources": ["globe7.com"] + }, + "Go Daddy": { + "properties": ["godaddy.com", "trafficfacts.com"], + "resources": ["godaddy.com", "trafficfacts.com"] + }, + "GoDataFeed": { + "properties": ["godatafeed.com"], + "resources": ["godatafeed.com"] + }, + "GoGrid": { + "properties": ["datapipe.com", "formalyzer.com"], + "resources": ["datapipe.com", "formalyzer.com", "gogrid.com", "komli.net"] + }, + "Goldbach": { + "properties": ["goldbachgroup.com"], + "resources": ["goldbach.com", "goldbachgroup.com"] + }, + "GoldSpot Media": { + "properties": ["goldspotmedia.com"], + "resources": ["goldspotmedia.com"] + }, + "Google": { + "properties": [ + "abc.xyz", + "admeld.com", + "blogger.com", + "blogspot.com", + "crashlytics.com", + "google-melange.com", + "google.ac", + "google.ad", + "google.ae", + "google.al", + "google.am", + "google.as", + "google.at", + "google.az", + "google.ba", + "google.be", + "google.bf", + "google.bg", + "google.bi", + "google.bj", + "google.bs", + "google.bt", + "google.by", + "google.ca", + "google.cat", + "google.cd", + "google.cf", + "google.cg", + "google.ch", + "google.ci", + "google.cl", + "google.cm", + "google.cn", + "google.co.ao", + "google.co.bw", + "google.co.ck", + "google.co.cr", + "google.co.id", + "google.co.il", + "google.co.in", + "google.co.jp", + "google.co.ke", + "google.co.kr", + "google.co.ls", + "google.co.ma", + "google.co.mz", + "google.co.nz", + "google.co.th", + "google.co.tz", + "google.co.ug", + "google.co.uk", + "google.co.uz", + "google.co.ve", + "google.co.vi", + "google.co.za", + "google.co.zm", + "google.co.zw", + "google.com", + "google.com.af", + "google.com.ag", + "google.com.ai", + "google.com.ar", + "google.com.au", + "google.com.bd", + "google.com.bh", + "google.com.bn", + "google.com.bo", + "google.com.br", + "google.com.bz", + "google.com.co", + "google.com.cu", + "google.com.cy", + "google.com.do", + "google.com.ec", + "google.com.eg", + "google.com.et", + "google.com.fj", + "google.com.gh", + "google.com.gi", + "google.com.gt", + "google.com.hk", + "google.com.jm", + "google.com.kh", + "google.com.kw", + "google.com.lb", + "google.com.ly", + "google.com.mm", + "google.com.mt", + "google.com.mx", + "google.com.my", + "google.com.na", + "google.com.nf", + "google.com.ng", + "google.com.ni", + "google.com.np", + "google.com.om", + "google.com.pa", + "google.com.pe", + "google.com.pg", + "google.com.ph", + "google.com.pk", + "google.com.pr", + "google.com.py", + "google.com.qa", + "google.com.sa", + "google.com.sb", + "google.com.sg", + "google.com.sl", + "google.com.sv", + "google.com.tj", + "google.com.tr", + "google.com.tw", + "google.com.ua", + "google.com.uy", + "google.com.vc", + "google.com.vn", + "google.cv", + "google.cz", + "google.de", + "google.dj", + "google.dk", + "google.dm", + "google.dz", + "google.ee", + "google.es", + "google.fi", + "google.fm", + "google.fr", + "google.ga", + "google.ge", + "google.gg", + "google.gl", + "google.gm", + "google.gp", + "google.gr", + "google.gy", + "google.hn", + "google.hr", + "google.ht", + "google.hu", + "google.ie", + "google.im", + "google.iq", + "google.is", + "google.it", + "google.je", + "google.jo", + "google.kg", + "google.ki", + "google.kz", + "google.la", + "google.li", + "google.lk", + "google.lt", + "google.lu", + "google.lv", + "google.md", + "google.me", + "google.mg", + "google.mk", + "google.ml", + "google.mn", + "google.ms", + "google.mu", + "google.mv", + "google.mw", + "google.ne", + "google.nl", + "google.no", + "google.nr", + "google.nu", + "google.pl", + "google.pn", + "google.ps", + "google.pt", + "google.ro", + "google.rs", + "google.ru", + "google.rw", + "google.sc", + "google.se", + "google.sh", + "google.si", + "google.sk", + "google.sm", + "google.sn", + "google.so", + "google.st", + "google.td", + "google.tg", + "google.tk", + "google.tl", + "google.tm", + "google.tn", + "google.to", + "google.tt", + "google.vg", + "google.vu", + "google.ws", + "googlesource.com", + "ingress.com", + "nest.com", + "panoramio.com", + "pinpoint-dot-chromeperf.appspot.com", + "youtube.com" + ], + "resources": [ + "2mdn.net", + "admeld.com", + "admob.com", + "apture.com", + "blogger.com", + "cc-dt.com", + "crashlytics.com", + "destinationurl.com", + "doubleclick.net", + "ggpht.com", + "gmail.com", + "gmodules.com", + "google-analytics.com", + "google.ac", + "google.ad", + "google.ae", + "google.al", + "google.am", + "google.as", + "google.at", + "google.az", + "google.ba", + "google.be", + "google.bf", + "google.bg", + "google.bi", + "google.bj", + "google.bs", + "google.bt", + "google.by", + "google.ca", + "google.cat", + "google.cc", + "google.cd", + "google.cf", + "google.cg", + "google.ch", + "google.ci", + "google.cl", + "google.cm", + "google.cn", + "google.co.ao", + "google.co.bw", + "google.co.ck", + "google.co.cr", + "google.co.id", + "google.co.il", + "google.co.in", + "google.co.jp", + "google.co.ke", + "google.co.kr", + "google.co.ls", + "google.co.ma", + "google.co.mz", + "google.co.nz", + "google.co.th", + "google.co.tz", + "google.co.ug", + "google.co.uk", + "google.co.uz", + "google.co.ve", + "google.co.vi", + "google.co.za", + "google.co.zm", + "google.co.zw", + "google.com", + "google.com.af", + "google.com.ag", + "google.com.ai", + "google.com.ar", + "google.com.au", + "google.com.bd", + "google.com.bh", + "google.com.bn", + "google.com.bo", + "google.com.br", + "google.com.bz", + "google.com.co", + "google.com.cu", + "google.com.cy", + "google.com.do", + "google.com.ec", + "google.com.eg", + "google.com.et", + "google.com.fj", + "google.com.gh", + "google.com.gi", + "google.com.gt", + "google.com.hk", + "google.com.jm", + "google.com.kh", + "google.com.kw", + "google.com.lb", + "google.com.lc", + "google.com.ly", + "google.com.mm", + "google.com.mt", + "google.com.mx", + "google.com.my", + "google.com.na", + "google.com.nf", + "google.com.ng", + "google.com.ni", + "google.com.np", + "google.com.om", + "google.com.pa", + "google.com.pe", + "google.com.pg", + "google.com.ph", + "google.com.pk", + "google.com.pr", + "google.com.py", + "google.com.qa", + "google.com.sa", + "google.com.sb", + "google.com.sg", + "google.com.sl", + "google.com.sv", + "google.com.tj", + "google.com.tn", + "google.com.tr", + "google.com.tw", + "google.com.ua", + "google.com.uy", + "google.com.vc", + "google.com.vn", + "google.cv", + "google.cz", + "google.de", + "google.dj", + "google.dk", + "google.dm", + "google.dz", + "google.ee", + "google.es", + "google.fi", + "google.fm", + "google.fr", + "google.ga", + "google.ge", + "google.gf", + "google.gg", + "google.gl", + "google.gm", + "google.gp", + "google.gr", + "google.gy", + "google.hn", + "google.hr", + "google.ht", + "google.hu", + "google.ie", + "google.im", + "google.io", + "google.iq", + "google.is", + "google.it", + "google.je", + "google.jo", + "google.kg", + "google.ki", + "google.kz", + "google.la", + "google.li", + "google.lk", + "google.lt", + "google.lu", + "google.lv", + "google.md", + "google.me", + "google.mg", + "google.mk", + "google.ml", + "google.mn", + "google.ms", + "google.mu", + "google.mv", + "google.mw", + "google.ne", + "google.nl", + "google.no", + "google.nr", + "google.nu", + "google.pl", + "google.pn", + "google.ps", + "google.pt", + "google.ro", + "google.rs", + "google.ru", + "google.rw", + "google.sc", + "google.se", + "google.sh", + "google.si", + "google.sk", + "google.sm", + "google.sn", + "google.so", + "google.st", + "google.td", + "google.tg", + "google.tk", + "google.tl", + "google.tm", + "google.tn", + "google.to", + "google.tt", + "google.vg", + "google.vu", + "google.ws", + "googleadservices.com", + "googleapis.com", + "googlemail.com", + "googlesyndication.com", + "googletagservices.com", + "googleusercontent.com", + "googlevideo.com", + "gstatic.com", + "invitemedia.com", + "postrank.com", + "recaptcha.net", + "smtad.net", + "youtube.com" + ] + }, + "GoSquared": { + "properties": ["gosquared.com"], + "resources": ["gosquared.com"] + }, + "GoStats": { + "properties": ["gostats.com"], + "resources": ["gostats.com"] + }, + "Grapeshot": { + "properties": ["grapeshot.co.uk", "www.grapeshot.co.uk"], + "resources": ["grapeshot.co.uk", "www.grapeshot.co.uk"] + }, + "GrapheneMedia": { + "properties": ["graphenemedia.in"], + "resources": ["graphenedigitalanalytics.in"] + }, + "Graphnium": { + "properties": ["graphinium.com"], + "resources": ["crm4d.com"] + }, + "Gravity": { + "properties": ["gravity.com", "grvcdn.com"], + "resources": ["gravity.com", "grvcdn.com"] + }, + "Gridcash": { + "properties": ["adless.io", "gridcash.net"], + "resources": ["adless.io", "gridcash.net"] + }, + "Grocery Shopping Network": { + "properties": ["groceryshopping.net"], + "resources": ["groceryshopping.net"] + }, + "GroovinAds": { + "properties": ["groovinads.com"], + "resources": ["groovinads.com"] + }, + "Gruner + Jahr": { + "properties": ["guj.de", "ligatus.com"], + "resources": ["guj.de", "ligatus.com"] + }, + "GTop": { + "properties": ["arenaweb.ro"], + "resources": ["arenaweb.ro", "gtop.ro", "gtopstats.com"] + }, + "GumGum": { + "properties": ["gumgum.com"], + "resources": ["gumgum.com"] + }, + "Gunggo": { + "properties": ["gunggo.com"], + "resources": ["gunggo.com"] + }, + "Hands Mobile": { + "properties": ["hands.com.br", "www.hands.com.br"], + "resources": ["hands.com.br", "www.hands.com.br"] + }, + "Harrenmedia": { + "properties": ["harrenmedia.com", "harrenmedianetwork.com"], + "resources": ["harrenmedia.com", "harrenmedianetwork.com"] + }, + "HealthPricer": { + "properties": ["adacado.com", "healthpricer.com"], + "resources": ["adacado.com", "healthpricer.com"] + }, + "Hearst": { + "properties": [ + "hearst.com", + "ic-live.com", + "iclive.com", + "icrossing.com", + "raasnet.com" + ], + "resources": [ + "hearst.com", + "ic-live.com", + "iclive.com", + "icrossing.com", + "raasnet.com", + "redaril.com", + "sptag.com", + "sptag1.com", + "sptag2.com", + "sptag3.com" + ] + }, + "Heyzap": { + "properties": ["heyzap.com"], + "resources": ["heyzap.com"] + }, + "HilltopAds": { + "properties": ["hilltopads.com"], + "resources": ["hilltopads.com", "hilltopads.net", "shoporielder.pro"] + }, + "Hi-media": { + "properties": ["himediagroup.com"], + "resources": ["comclick.com", "hi-media.com", "himediagroup.com"] + }, + "Histats": { + "properties": ["histats.com"], + "resources": ["histats.com"] + }, + "HitsLink": { + "properties": ["hitslink.com"], + "resources": ["hitslink.com"] + }, + "Hit Sniffer": { + "properties": ["hitsniffer.com"], + "resources": ["hitsniffer.com"] + }, + "Horyzon Media": { + "properties": ["horyzon-media.com"], + "resources": ["horyzon-media.com"] + }, + "HotelChamp": { + "properties": ["hotelchamp.com"], + "resources": ["hotelchamp.com"] + }, + "Hotjar": { + "properties": ["hotjar.com"], + "resources": ["hotjar.com"] + }, + "HotMart": { + "properties": ["hotmart.com"], + "resources": ["hotmart.com"] + }, + "HOTWords": { + "properties": ["hotwords.com", "hotwords.es"], + "resources": ["hotwords.com", "hotwords.es"] + }, + "HP": { + "properties": ["hp.com", "opentext.com", "optimost.com"], + "resources": ["hp.com", "optimost.com"] + }, + "Httpool": { + "properties": ["httpool.com"], + "resources": ["httpool.com"] + }, + "HubSpot": { + "properties": ["hubspot.com"], + "resources": ["hs-analytics.net", "hubspot.com"] + }, + "HUNT Mobile Ads": { + "properties": ["huntmads.com"], + "resources": ["huntmads.com"] + }, + "Hurra.com": { + "properties": ["hurra.com"], + "resources": ["hurra.com"] + }, + "IAB": { + "properties": ["digitru.st", "iabtechlab.com"], + "resources": ["digitru.st"] + }, + "IAC": { + "properties": ["iac.com", "iacadvertising.com"], + "resources": ["iac.com", "iacadvertising.com"] + }, + "iBehavior": { + "properties": ["i-behavior.com", "ib-ibi.com"], + "resources": ["i-behavior.com", "ib-ibi.com"] + }, + "IBM": { + "properties": ["ibm.com", "multicloud-ibm.com"], + "resources": [ + "cmcore.com", + "coremetrics.com", + "ibm.com", + "unica.com", + "xtify.com" + ] + }, + "ID5": { + "properties": ["id5.io"], + "resources": ["id5-sync.com"] + }, + "IDG": { + "properties": ["idg.com", "idgtechnetwork.com"], + "resources": ["idg.com", "idgtechnetwork.com"] + }, + "iEntry": { + "properties": ["600z.com", "ientry.com"], + "resources": ["600z.com", "ientry.com"] + }, + "IgnitAd": { + "properties": ["ignitad.com"], + "resources": ["ignitad.com"] + }, + "IgnitionOne": { + "properties": ["ignitionone.com", "ignitionone.net", "searchignite.com"], + "resources": ["ignitionone.com", "ignitionone.net", "searchignite.com"] + }, + "iMedia": { + "properties": ["imedia.cz"], + "resources": ["imedia.cz"] + }, + "Improve Digital": { + "properties": ["360yield.com", "improvedigital.com"], + "resources": ["360yield.com", "improvedigital.com"] + }, + "Inadco": { + "properties": ["inadco.com"], + "resources": ["anadcoads.com", "inadco.com", "inadcoads.com"] + }, + "InboundWriter": { + "properties": ["enquisite.com", "inboundwriter.com"], + "resources": ["enquisite.com", "inboundwriter.com"] + }, + "IndexExchange": { + "properties": ["indexexchange.com"], + "resources": ["indexexchange.com"] + }, + "Infectious Media": { + "properties": ["infectiousmedia.com"], + "resources": ["impressiondesk.com", "infectiousmedia.com"] + }, + "Infernotions": { + "properties": ["infernotions.com"], + "resources": ["infernotions.com"] + }, + "Inflection Point Media": { + "properties": ["inflectionpointmedia.com"], + "resources": ["inflectionpointmedia.com"] + }, + "Infogroup": { + "properties": ["infogroup.com"], + "resources": ["infogroup.com"] + }, + "Infolinks": { + "properties": ["infolinks.com"], + "resources": ["infolinks.com"] + }, + "INFOnline": { + "properties": ["infonline.de"], + "resources": ["infonline.de", "ioam.de", "ivwbox.de"] + }, + "InfoStars": { + "properties": ["hotlog.ru", "infostars.ru"], + "resources": ["hotlog.ru", "infostars.ru"] + }, + "Infra-Ad": { + "properties": ["infra-ad.com"], + "resources": ["infra-ad.com"] + }, + "InMobi": { + "properties": ["aerserv.com", "inmobi.com", "sproutinc.com"], + "resources": ["aerserv.com", "inmobi.com", "sproutinc.com"] + }, + "inneractive": { + "properties": ["inner-active.com"], + "resources": ["inner-active.com"] + }, + "Innity": { + "properties": ["innity.com"], + "resources": ["innity.com"] + }, + "InsightExpress": { + "properties": ["insightexpress.com"], + "resources": ["insightexpress.com", "insightexpressai.com"] + }, + "InSkin Media": { + "properties": ["inskinmedia.com"], + "resources": ["inskinmedia.com"] + }, + "Inspectlet": { + "properties": ["inspectlet.com"], + "resources": ["inspectlet.com"] + }, + "Instinctive": { + "properties": ["instinctive.io"], + "resources": ["instinctive.io", "instinctiveads.com"] + }, + "Integral Ad Science": { + "properties": ["integralads.com"], + "resources": [ + "adsafemedia.com", + "adsafeprotected.com", + "iasds01.com", + "integralads.com" + ] + }, + "IntelligenceFocus": { + "properties": ["intelligencefocus.com", "leadchampion.com"], + "resources": ["domodomain.com", "intelligencefocus.com", "leadchampion.com"] + }, + "Intent Media": { + "properties": ["intentmedia.com"], + "resources": ["intentmedia.com", "intentmedia.net"] + }, + "Intergi": { + "properties": ["intergi.com"], + "resources": ["intergi.com"] + }, + "Intermarkets": { + "properties": ["intermarkets.net"], + "resources": ["intermarkets.net"] + }, + "Intermundo Media": { + "properties": ["intermundomedia.com"], + "resources": ["intermundomedia.com"] + }, + "Internet Brands": { + "properties": ["ibpxl.com", "internetbrands.com"], + "resources": ["ibpxl.com", "internetbrands.com"] + }, + "Interpolls": { + "properties": ["interpolls.com"], + "resources": ["interpolls.com"] + }, + "Inuvo": { + "properties": ["inuvo.com"], + "resources": ["inuvo.com"] + }, + "InvestingChannel": { + "properties": ["investingchannel.com"], + "resources": ["investingchannel.com"] + }, + "iovation": { + "properties": ["iovation.com"], + "resources": ["iesnare.com", "iovation.com"] + }, + "iPerceptions": { + "properties": ["iperceptions.com"], + "resources": ["iperceptions.com"] + }, + "IponWeb": { + "properties": ["iponweb.com"], + "resources": ["iponweb.com", "iponweb.net"] + }, + "iPROM": { + "properties": [ + "centraliprom.com", + "iprom.net", + "iprom.si", + "mediaiprom.com" + ], + "resources": ["centraliprom.com", "iprom.net", "iprom.si", "mediaiprom.com"] + }, + "iPromote": { + "properties": ["ipromote.com"], + "resources": ["ipromote.com"] + }, + "iProspect": { + "properties": ["iprospect.com"], + "resources": ["clickmanage.com", "iprospect.com"] + }, + "ISI Technologies": { + "properties": ["adversalservers.com", "digbro.com"], + "resources": ["adversalservers.com", "digbro.com"] + }, + "IslayTech": { + "properties": ["islay.tech"], + "resources": ["islay.tech"] + }, + "ismatlab.com": { + "properties": ["ismatlab.com"], + "resources": ["ismatlab.com"] + }, + "Itch": { + "properties": ["itch.io"], + "resources": ["itch.io"] + }, + "ItIsATracker": { + "properties": ["itisatracker.com"], + "resources": ["itisatracker.com"] + }, + "I.UA": { + "properties": ["i.ua"], + "resources": ["i.ua"] + }, + "Jaroop": { + "properties": ["jaroop.com"], + "resources": ["jaroop.com"] + }, + "JasperLabs": { + "properties": ["jasperlabs.com"], + "resources": ["jasperlabs.com"] + }, + "Jemm": { + "properties": ["jemmgroup.com"], + "resources": ["jemmgroup.com"] + }, + "Jink": { + "properties": ["jink.de", "jinkads.com"], + "resources": ["jink.de", "jinkads.com"] + }, + "Jirbo": { + "properties": ["adcolony.com"], + "resources": ["adcolony.com", "jirbo.com"] + }, + "Jivox": { + "properties": ["jivox.com"], + "resources": ["jivox.com"] + }, + "JobThread": { + "properties": ["jobthread.com"], + "resources": ["jobthread.com"] + }, + "JSE": { + "properties": ["jsecoin.com"], + "resources": [ + "freecontent.bid", + "freecontent.date", + "freecontent.stream", + "hashing.win", + "hostingcloud.racing", + "hostingcloud.science", + "jsecoin.com" + ] + }, + "JuicyAds": { + "properties": ["juicyads.com"], + "resources": ["juicyads.com"] + }, + "Jumptap": { + "properties": ["jumptap.com"], + "resources": ["jumptap.com"] + }, + "justuno": { + "properties": ["justuno.com"], + "resources": ["justuno.com"] + }, + "Kaltura": { + "properties": ["kaltura.com"], + "resources": ["kaltura.com"] + }, + "Kargo": { + "properties": ["kargo.com"], + "resources": ["kargo.com"] + }, + "Kenshoo": { + "properties": ["kenshoo.com", "xg4ken.com"], + "resources": ["kenshoo.com", "xg4ken.com"] + }, + "Keyade": { + "properties": ["keyade.com"], + "resources": ["keyade.com"] + }, + "KeyMetric": { + "properties": ["keymetric.net"], + "resources": ["keymetric.net"] + }, + "Keywee": { + "properties": ["keywee.co"], + "resources": ["keywee.co"] + }, + "kikin": { + "properties": ["kikin.com"], + "resources": ["kikin.com"] + }, + "KISSmetrics": { + "properties": ["kissmetrics.com"], + "resources": ["kissmetrics.com"] + }, + "KissMyAds": { + "properties": ["kissmyads.com"], + "resources": ["kissmyads.com"] + }, + "Kitara Media": { + "properties": ["103092804.com", "kitaramedia.com"], + "resources": ["103092804.com", "kitaramedia.com"] + }, + "Kitcode": { + "properties": ["kitcode.net"], + "resources": ["kitcode.net"] + }, + "KIT digital": { + "properties": ["kitd.com"], + "resources": ["keewurd.com", "kitd.com", "peerset.com"] + }, + "Kokteyl": { + "properties": ["admost.com", "kokteyl.com"], + "resources": ["admost.com", "kokteyl.com"] + }, + "Komli": { + "properties": ["komli.com"], + "resources": ["komli.com"] + }, + "Konduto": { + "properties": ["konduto.com"], + "resources": ["k-analytix.com", "konduto.com"] + }, + "Kontera": { + "properties": ["kontera.com"], + "resources": ["kontera.com"] + }, + "Korrelate": { + "properties": ["korrelate.com"], + "resources": ["adsummos.com", "adsummos.net", "korrelate.com"] + }, + "Krux": { + "properties": ["krux.com", "kruxdigital.com"], + "resources": ["krux.com", "kruxdigital.com", "krxd.net"] + }, + "Lakana": { + "properties": ["lakana.com"], + "resources": ["ibsys.com", "lakana.com"] + }, + "Layer-Ad.org": { + "properties": ["layer-ad.org"], + "resources": ["layer-ad.org"] + }, + "Layer Ads": { + "properties": ["layer-ads.net"], + "resources": ["layer-ads.net"] + }, + "LeadBolt": { + "properties": ["leadbolt.com"], + "resources": ["leadbolt.com"] + }, + "LeadForensics": { + "properties": ["leadforensics.com"], + "resources": ["leadforensics.com"] + }, + "LeadFormix": { + "properties": ["calliduscloud.com", "leadforce1.com", "leadformix.com"], + "resources": ["calliduscloud.com", "leadforce1.com", "leadformix.com"] + }, + "LeadsHub": { + "properties": ["ztsrv.com"], + "resources": ["ztsrv.com"] + }, + "LeanPlum": { + "properties": ["leanplum.com"], + "resources": ["leanplum.com"] + }, + "Legolas Media": { + "properties": ["legolas-media.com"], + "resources": ["legolas-media.com"] + }, + "Levexis": { + "properties": ["levexis.com"], + "resources": ["levexis.com"] + }, + "Lexos Media": { + "properties": ["adbull.com", "lexosmedia.com"], + "resources": ["adbull.com", "lexosmedia.com"] + }, + "LifeStreet": { + "properties": ["lfstmedia.com", "lifestreetmedia.com"], + "resources": ["lfstmedia.com", "lifestreetmedia.com"] + }, + "Limelight Networks": { + "properties": ["limelight.com"], + "resources": ["clickability.com", "limelight.com", "llnwd.net"] + }, + "LineZing": { + "properties": ["linezing.com"], + "resources": ["linezing.com"] + }, + "LinkConnector": { + "properties": ["linkconnector.com"], + "resources": ["linkconnector.com"] + }, + "LinkedIn": { + "properties": ["linkedin.com"], + "resources": ["licdn.com", "linkedin.com"] + }, + "LinkShare": { + "properties": ["rakutenmarketing.com"], + "resources": ["linkshare.com", "linksynergy.com", "rakutenmarketing.com"] + }, + "Linkz": { + "properties": ["linkz.net"], + "resources": ["linkz.net"] + }, + "Listrak": { + "properties": ["listrak.com", "listrakbi.com"], + "resources": ["listrak.com", "listrakbi.com"] + }, + "LiveIntent": { + "properties": ["liveintent.com"], + "resources": ["liadm.com", "liveintent.com"] + }, + "LiveInternet": { + "properties": ["liveinternet.ru", "yadro.ru"], + "resources": ["liveinternet.ru", "yadro.ru"] + }, + "LivePerson": { + "properties": ["liveperson.com"], + "resources": ["liveperson.com", "liveperson.net", "nuconomy.com"] + }, + "LiveRail": { + "properties": ["liverail.com"], + "resources": ["liverail.com"] + }, + "LiveRamp": { + "properties": ["liveramp.com"], + "resources": ["liveramp.com", "tvpixel.com"] + }, + "LKQD": { + "properties": ["lkqd.com", "lkqd.net"], + "resources": ["lkqd.com", "lkqd.net"] + }, + "Local Yokel Media": { + "properties": ["localyokelmedia.com"], + "resources": ["localyokelmedia.com"] + }, + "Localytics": { + "properties": ["localytics.com"], + "resources": ["localytics.com"] + }, + "LockerDome": { + "properties": ["lockerdome.com"], + "resources": ["lockerdome.com"] + }, + "Lockerz": { + "properties": ["lockerz.com"], + "resources": ["lockerz.com"] + }, + "Logdy": { + "properties": ["logdy.com"], + "resources": ["logdy.com"] + }, + "Longboard Media": { + "properties": ["longboardmedia.com"], + "resources": ["longboardmedia.com"] + }, + "LongTail Video": { + "properties": ["jwplayer.com"], + "resources": ["longtailvideo.com", "ltassrv.com"] + }, + "Loomia": { + "properties": ["loomia.com"], + "resources": ["loomia.com"] + }, + "LoopFuse": { + "properties": ["lfov.net", "loopfuse.net"], + "resources": ["lfov.net", "loopfuse.net"] + }, + "LoopMe": { + "properties": ["loopme.com"], + "resources": ["loopme.com"] + }, + "Lotame": { + "properties": ["crwdcntrl.net", "lotame.com"], + "resources": ["crwdcntrl.net", "lotame.com"] + }, + "LotLinx": { + "properties": ["lotlinx.com"], + "resources": ["lotlinx.com"] + }, + "Lower My Bills": { + "properties": ["lowermybills.com"], + "resources": ["lowermybills.com"] + }, + "lptracker": { + "properties": ["lptracker.io"], + "resources": ["lptracker.io"] + }, + "LucidMedia": { + "properties": ["lucidmedia.com"], + "resources": ["lucidmedia.com"] + }, + "LuckyOrange": { + "properties": ["luckyorange.com"], + "resources": ["luckyorange.com", "luckyorange.net"] + }, + "Lynchpin": { + "properties": ["lynchpin.com"], + "resources": ["lynchpin.com", "lypn.com"] + }, + "Lyris": { + "properties": ["aurea.com"], + "resources": ["aurea.com", "clicktracks.com", "lyris.com"] + }, + "Lytiks": { + "properties": ["lytiks.com"], + "resources": ["lytiks.com"] + }, + "m6d": { + "properties": ["dstillery.com"], + "resources": ["dstillery.com", "m6d.com", "media6degrees.com"] + }, + "Madhouse": { + "properties": ["madhouse.cn"], + "resources": ["madhouse.cn"] + }, + "Madison Logic": { + "properties": ["dinclinx.com", "madisonlogic.com"], + "resources": ["dinclinx.com", "madisonlogic.com"] + }, + "madvertise": { + "properties": ["madvertise.com"], + "resources": ["madvertise.com"] + }, + "Magnetic": { + "properties": ["domdex.net", "magnetic.com"], + "resources": ["domdex.com", "domdex.net", "magnetic.com", "qjex.net"] + }, + "Magnify360": { + "properties": ["dialogmgr.com", "magnify360.com"], + "resources": ["dialogmgr.com", "magnify360.com"] + }, + "MailChimp": { + "properties": ["campaign-archive1.com", "mailchi.mp", "mailchimp.com"], + "resources": [ + "campaign-archive1.com", + "list-manage.com", + "mailchi.mp", + "mailchimp.com" + ] + }, + "Mail.Ru": { + "properties": ["list.ru", "mail.ru"], + "resources": ["list.ru", "mail.ru"] + }, + "Manifest": { + "properties": ["bannerbank.ru", "manifest.ru"], + "resources": ["bannerbank.ru", "manifest.ru"] + }, + "Marchex": { + "properties": ["industrybrains.com", "marchex.com"], + "resources": ["industrybrains.com", "marchex.com"] + }, + "Marimedia": { + "properties": ["marimedia.net"], + "resources": ["marimedia.net"] + }, + "MarketGid": { + "properties": ["dt00.net", "dt07.net", "marketgid.com"], + "resources": ["dt00.net", "dt07.net", "marketgid.com"] + }, + "Marketo": { + "properties": ["marketo.com"], + "resources": ["marketo.com", "marketo.net"] + }, + "Markit": { + "properties": ["markit.com", "wsod.com"], + "resources": ["markit.com", "wsod.com"] + }, + "MarkMonitor": { + "properties": ["9c9media.ca", "markmonitor.com"], + "resources": ["9c9media.ca", "markmonitor.com"] + }, + "Marktest": { + "properties": ["marktest.com", "marktest.pt"], + "resources": ["marktest.com", "marktest.pt"] + }, + "Martini Media": { + "properties": ["martiniadnetwork.com"], + "resources": ["martiniadnetwork.com", "martinimedianetwork.com"] + }, + "mashero": { + "properties": ["mashero.com"], + "resources": ["mashero.com"] + }, + "MashLogic": { + "properties": ["mashlogic.com"], + "resources": ["mashlogic.com"] + }, + "Match.com": { + "properties": ["chemistry.com", "match.com"], + "resources": ["chemistry.com", "match.com", "meetic-partners.com"] + }, + "Matomy": { + "properties": ["matomy.com"], + "resources": [ + "adnetinteractive.com", + "adsmarket.com", + "matomy.com", + "matomymarket.com", + "matomymedia.com", + "mediawhiz.com", + "optimatic.com", + "xtendmedia.com" + ] + }, + "MaxBounty": { + "properties": ["maxbounty.com", "mb01.com"], + "resources": ["maxbounty.com", "mb01.com"] + }, + "MaxMind": { + "properties": ["maxmind.com"], + "resources": ["maxmind.com", "mmapiws.com"] + }, + "MaxPoint": { + "properties": ["maxpointinteractive.com", "maxusglobal.com", "mxptint.net"], + "resources": ["maxpointinteractive.com", "maxusglobal.com", "mxptint.net"] + }, + "McAfee": { + "properties": ["mcafee.com", "mcafeesecure.com"], + "resources": ["mcafee.com", "mcafeesecure.com", "scanalert.com"] + }, + "MdotM": { + "properties": ["mdotm.com"], + "resources": ["mdotm.com"] + }, + "MediaBrix": { + "properties": ["mediabrix.com"], + "resources": ["mediabrix.com"] + }, + "MediaCom": { + "properties": ["mediacom.com"], + "resources": ["mediacom.com"] + }, + "mediaFORGE": { + "properties": ["mediaforge.com"], + "resources": ["mediaforge.com"] + }, + "Medialets": { + "properties": ["medialets.com"], + "resources": ["medialets.com"] + }, + "MediaMath": { + "properties": ["mediamath.com"], + "resources": [ + "adroitinteractive.com", + "designbloxlive.com", + "mathtag.com", + "mediamath.com" + ] + }, + "Médiamétrie-eStat": { + "properties": ["mediametrie-estat.com"], + "resources": ["estat.com", "mediametrie-estat.com"] + }, + "media.net": { + "properties": ["media.net"], + "resources": ["media.net"] + }, + "Mediaocean": { + "properties": ["adbuyer.com", "mediaocean.com"], + "resources": ["adbuyer.com", "mediaocean.com"] + }, + "MediaShakers": { + "properties": ["media-servers.net", "mediashakers.com"], + "resources": ["media-servers.net", "mediashakers.com"] + }, + "MediaTrust": { + "properties": ["mediatrust.com"], + "resources": ["mediatrust.com"] + }, + "Medicx Media Solutions": { + "properties": ["medicxmedia.com"], + "resources": ["medicxmedia.com"] + }, + "Meebo": { + "properties": ["meebo.com"], + "resources": ["meebo.com", "meebocdn.net"] + }, + "MegaIndex": { + "properties": ["megaindex.ru"], + "resources": ["megaindex.ru"] + }, + "Mercadopago": { + "properties": [ + "mercadolibre.cl", + "mercadolibre.co.cr", + "mercadolibre.com", + "mercadolibre.com.ar", + "mercadolibre.com.bo", + "mercadolibre.com.co", + "mercadolibre.com.do", + "mercadolibre.com.ec", + "mercadolibre.com.gt", + "mercadolibre.com.hn", + "mercadolibre.com.mx", + "mercadolibre.com.ni", + "mercadolibre.com.pa", + "mercadolibre.com.pe", + "mercadolibre.com.py", + "mercadolibre.com.sv", + "mercadolibre.com.uy", + "mercadolibre.com.ve", + "mercadolivre.com.br", + "mercadopago.com" + ], + "resources": ["mercadopago.com"] + }, + "Mercent": { + "properties": ["mercent.com"], + "resources": ["mercent.com"] + }, + "MerchantAdvantage": { + "properties": ["merchantadvantage.com"], + "resources": ["merchantadvantage.com"] + }, + "Merchenta": { + "properties": ["merchenta.com"], + "resources": ["merchenta.com"] + }, + "Merkle": { + "properties": ["merkleinc.com", "rkdms.com"], + "resources": ["merkleinc.com", "rimmkaufman.com", "rkdms.com"] + }, + "Meta Network": { + "properties": ["metanetwork.com"], + "resources": ["metanetwork.com"] + }, + "Meteor": { + "properties": ["meteorsolutions.com"], + "resources": ["meteorsolutions.com"] + }, + "MetrixLab": { + "properties": [ + "crm-metrix.com", + "customerconversio.com", + "metrixlab.com", + "opinionbar.com" + ], + "resources": [ + "adoftheyear.com", + "crm-metrix.com", + "customerconversio.com", + "metrixlab.com", + "opinionbar.com" + ] + }, + "MicroAd": { + "properties": ["microad.jp", "www.microad.jp"], + "resources": ["microad.jp", "www.microad.jp"] + }, + "Microsoft": { + "properties": [ + "acompli.net", + "aka.ms", + "azure.com", + "azure.net", + "azurerms.com", + "bing.com", + "cloudappsecurity.com", + "gamesforwindows.com", + "getgamesmart.com", + "gfx.ms", + "healthvault.com", + "hockeyapp.net", + "ieaddons.com", + "iegallery.com", + "live.com", + "microsoft.com", + "microsoftalumni.com", + "microsoftalumni.org", + "microsoftazuread-sso.com", + "microsoftedgeinsiders.com", + "microsoftonline-p.com", + "microsoftonline-p.net", + "microsoftonline.com", + "microsoftstore.com", + "microsoftstream.com", + "msappproxy.net", + "msft.net", + "msftidentity.com", + "msidentity.com", + "msn.com", + "o365weve.com", + "oaspapps.com", + "office.com", + "office365.com", + "officelive.com", + "onedrive.com", + "onenote.com", + "outlook.com", + "outlookmobile.com", + "phonefactor.net", + "s-msn.com", + "sfx.ms", + "sharepoint.com", + "skype.com", + "skypeforbusiness.com", + "staffhub.ms", + "sway-extensions.com", + "sway.com", + "trafficmanager.net", + "virtualearth.net", + "visualstudio.com", + "windows.net", + "windowsazure.com", + "windowsphone.com", + "worldwidetelescope.org", + "wunderlist.com", + "xbox.com", + "yammer.com" + ], + "resources": [ + "aadrm.com", + "adbureau.net", + "adecn.com", + "aquantive.com", + "aspnetcdn.com", + "assets-yammer.com", + "azure.com", + "azureedge.net", + "bing.com", + "cloudapp.net", + "gamesforwindows.com", + "getgamesmart.com", + "gfx.ms", + "healthvault.com", + "live.com", + "microsoft.com", + "microsoftazuread-sso.com", + "microsoftonline-p.com", + "microsoftonline-p.net", + "microsoftonline.com", + "microsoftstore.com", + "msads.net", + "msauthimages.net", + "msecnd.net", + "msedge.net", + "msndirect.com", + "msocdn.com", + "netconversions.com", + "oaspapps.com", + "office.com", + "office.net", + "officelive.com", + "onenote.net", + "onestore.ms", + "onmicrosoft.com", + "outlook.com", + "roiservice.com", + "s-msn.com", + "sfbassets.com", + "sharepoint.com", + "skype.com", + "skypeassets.com", + "sway-cdn.com", + "sway-extensions.com", + "windows.net", + "windowsazure.com", + "yammerusercontent.com" + ] + }, + "Millennial Media": { + "properties": ["decktrade.com", "millennialmedia.com", "mydas.mobi"], + "resources": ["decktrade.com", "millennialmedia.com", "mydas.mobi"] + }, + "Mindset Media": { + "properties": ["mindset-media.com"], + "resources": ["mindset-media.com", "mmismm.com"] + }, + "MinerAlt": { + "properties": ["mineralt.io", "vidzi.nu", "vidzi.tv"], + "resources": [ + "1q2w3.website", + "analytics.blue", + "aster18cdn.nl", + "belicimo.pw", + "besstahete.info", + "dinorslick.icu", + "feesocrald.com", + "gramombird.com", + "istlandoll.com", + "mepirtedic.com", + "mineralt.io", + "pampopholf.com", + "tercabilis.info", + "tulip18.com", + "vidzi.tv", + "yololike.space" + ] + }, + "Minescripts": { + "properties": ["minescripts.info"], + "resources": ["minescripts.info", "sslverify.info"] + }, + "MineXMR": { + "properties": ["minexmr.stream"], + "resources": ["minexmr.stream"] + }, + "Mirando": { + "properties": ["mirando.de"], + "resources": ["mirando.de"] + }, + "Mixpanel": { + "properties": ["mixpanel.com"], + "resources": ["mixpanel.com", "mxpnl.com"] + }, + "Mixpo": { + "properties": ["mixpo.com"], + "resources": ["mixpo.com"] + }, + "Moat": { + "properties": ["moat.com", "moatads.com"], + "resources": ["moat.com", "moatads.com"] + }, + "MobFox": { + "properties": ["mobfox.com"], + "resources": ["mobfox.com"] + }, + "Mobials": { + "properties": ["mobials.com"], + "resources": ["mobials.com"] + }, + "MobileAdTrading": { + "properties": ["mobileadtrading.com"], + "resources": ["mobileadtrading.com"] + }, + "Mobile Meteor": { + "properties": ["mobilemeteor.com"], + "resources": ["mobilemeteor.com", "showmeinn.com"] + }, + "Mobile Storm": { + "properties": ["mobilestorm.com"], + "resources": ["mobilestorm.com"] + }, + "MobVision": { + "properties": ["admoda.com"], + "resources": ["admoda.com", "mobvision.com"] + }, + "Mocean Mobile": { + "properties": ["moceanmobile.com"], + "resources": ["moceanmobile.com"] + }, + "Mochila": { + "properties": ["mochila.com"], + "resources": ["mochila.com"] + }, + "Mojiva": { + "properties": ["mojiva.com"], + "resources": ["mojiva.com"] + }, + "Monetate": { + "properties": ["monetate.com", "monetate.net"], + "resources": ["monetate.com", "monetate.net"] + }, + "MONETIZEdigital": { + "properties": ["cpalead.com"], + "resources": ["cpalead.com"] + }, + "Monetize More": { + "properties": ["monetizemore.com"], + "resources": ["monetizemore.com"] + }, + "Mongoose Metrics": { + "properties": ["mongoosemetrics.com"], + "resources": ["mongoosemetrics.com"] + }, + "Monitus": { + "properties": ["monitus.net"], + "resources": ["monitus.net"] + }, + "Monoloop": { + "properties": ["monoloop.com"], + "resources": ["monoloop.com"] + }, + "Monster": { + "properties": ["monster.com"], + "resources": ["monster.com"] + }, + "Moolah Media": { + "properties": ["moolah-media.com", "moolahmedia.com"], + "resources": ["moolah-media.com", "moolahmedia.com"] + }, + "MoPub": { + "properties": ["mopub.com"], + "resources": ["mopub.com"] + }, + "motigo": { + "properties": ["motigo.com"], + "resources": ["motigo.com", "nedstatbasic.net"] + }, + "Mouseflow": { + "properties": ["mouseflow.com"], + "resources": ["mouseflow.com"] + }, + "MovieLush.com": { + "properties": ["affbuzzads.com", "movielush.com"], + "resources": ["affbuzzads.com", "movielush.com"] + }, + "Multiple Stream Media": { + "properties": ["adclickmedia.com", "multiplestreammktg.com"], + "resources": ["adclickmedia.com", "multiplestreammktg.com"] + }, + "MUNDO Media": { + "properties": ["mundomedia.com", "silver-path.com"], + "resources": ["mundomedia.com", "silver-path.com"] + }, + "MyCounter": { + "properties": ["mycounter.com.ua"], + "resources": ["mycounter.com.ua"] + }, + "MyPagerank.Net": { + "properties": ["mypagerank.net"], + "resources": ["mypagerank.net"] + }, + "MyPressPlus": { + "properties": ["mypressplus.com", "ppjol.net"], + "resources": ["mypressplus.com", "ppjol.net"] + }, + "Mystighty": { + "properties": ["mystighty.info"], + "resources": ["mystighty.info", "sweeterge.info"] + }, + "myThings": { + "properties": ["mythings.com", "mythingsmedia.com"], + "resources": ["mythings.com", "mythingsmedia.com"] + }, + "MyWebGrocer": { + "properties": ["mywebgrocer.com"], + "resources": ["mywebgrocer.com"] + }, + "Nanigans": { + "properties": ["nanigans.com"], + "resources": ["nanigans.com"] + }, + "Narrative": { + "properties": ["narrative.io"], + "resources": ["narrative.io"] + }, + "NativeAds": { + "properties": ["nativeads.com"], + "resources": ["nativeads.com"] + }, + "Nativo": { + "properties": ["nativo.com", "postrelease.com"], + "resources": ["nativo.com", "postrelease.com"] + }, + "Navegg": { + "properties": ["navdmp.com", "navegg.com"], + "resources": ["navdmp.com", "navegg.com"] + }, + "NDN": { + "properties": ["newsinc.com"], + "resources": ["newsinc.com"] + }, + "Negishim": { + "properties": ["negishim.org"], + "resources": ["negishim.org"] + }, + "NeroHut": { + "properties": ["nerohut.com"], + "resources": ["nerohut.com", "nhsrv.cf"] + }, + "NetAffiliation": { + "properties": ["netaffiliation.com"], + "resources": ["netaffiliation.com"] + }, + "Net Applications": { + "properties": ["netapplications.com"], + "resources": ["hitsprocessor.com", "netapplications.com"] + }, + "NetBina": { + "properties": ["netbina.com"], + "resources": ["netbina.com"] + }, + "NetElixir": { + "properties": ["adelixir.com", "netelixir.com"], + "resources": ["adelixir.com", "netelixir.com"] + }, + "Netmining": { + "properties": ["netmining.com", "netmng.com"], + "resources": ["netmining.com", "netmng.com"] + }, + "Net-Results": { + "properties": ["net-results.com", "nr7.us"], + "resources": ["cdnma.com", "net-results.com", "nr7.us"] + }, + "NetSeer": { + "properties": ["netseer.com"], + "resources": ["netseer.com"] + }, + "NetShelter": { + "properties": ["ziffdavistech.com"], + "resources": ["netshelter.com", "netshelter.net", "ziffdavistech.com"] + }, + "Neustar": { + "properties": ["adadvisor.net", "home.neustar", "neustar.biz"], + "resources": ["adadvisor.net", "neustar.biz"] + }, + "New Relic": { + "properties": ["newrelic.com"], + "resources": ["newrelic.com", "nr-data.net"] + }, + "NewsRight": { + "properties": ["apnewsregistry.com", "newsright.com"], + "resources": ["apnewsregistry.com", "newsright.com"] + }, + "newtention": { + "properties": ["newtention.de", "newtention.net", "newtentionassets.net"], + "resources": ["newtention.de", "newtention.net", "newtentionassets.net"] + }, + "Nexage": { + "properties": ["nexage.com"], + "resources": ["nexage.com"] + }, + "Nextag": { + "properties": ["nextag.com"], + "resources": ["nextag.com"] + }, + "NextPerformance": { + "properties": ["nextperf.com", "nextperformance.com", "nxtck.com"], + "resources": ["nextperf.com", "nextperformance.com", "nxtck.com"] + }, + "NextSTAT": { + "properties": ["nextstat.com"], + "resources": ["nextstat.com"] + }, + "Nielsen": { + "properties": [ + "glanceguide.com", + "imrworldwide.com", + "imrworldwide.net", + "nielsen.com" + ], + "resources": [ + "glanceguide.com", + "imrworldwide.com", + "imrworldwide.net", + "nielsen.com" + ] + }, + "Ninua": { + "properties": ["networkedblogs.com", "ninua.com"], + "resources": ["networkedblogs.com", "ninua.com"] + }, + "Nokta": { + "properties": ["noktamedya.com", "virgul.com"], + "resources": ["noktamedya.com", "virgul.com"] + }, + "NowSpots": { + "properties": ["nowspots.com"], + "resources": ["nowspots.com"] + }, + "nrelate": { + "properties": ["nrelate.com"], + "resources": ["nrelate.com"] + }, + "NuDataSecurity": { + "properties": ["nudatasecurity.com"], + "resources": ["nudatasecurity.com"] + }, + "Nuffnang": { + "properties": ["nuffnang.com", "nuffnang.com.my", "www.nuffnang.com.my"], + "resources": ["nuffnang.com", "nuffnang.com.my", "www.nuffnang.com.my"] + }, + "nugg.ad": { + "properties": ["nugg.ad"], + "resources": ["nugg.ad", "nuggad.net"] + }, + "nurago": { + "properties": ["sensic.net"], + "resources": ["nurago.com", "nurago.de", "sensic.net"] + }, + "Oberon Media": { + "properties": ["iwin.com"], + "resources": ["blaze.com", "iwin.com", "oberon-media.com"] + }, + "Observer": { + "properties": ["observerapp.com"], + "resources": ["observerapp.com"] + }, + "Ohana Media": { + "properties": ["adohana.com", "ohana-media.com", "ohanaqb.com"], + "resources": ["adohana.com", "ohana-media.com", "ohanaqb.com"] + }, + "Omnicom Group": { + "properties": ["accuenmedia.com", "omnicomgroup.com"], + "resources": ["accuenmedia.com", "omnicomgroup.com", "p-td.com"] + }, + "onAd": { + "properties": ["onad.eu"], + "resources": ["onad.eu"] + }, + "OnAudience": { + "properties": ["behavioralengine.com", "onaudience.com"], + "resources": ["behavioralengine.com", "onaudience.com"] + }, + "Onclusive": { + "properties": ["onclusive.com"], + "resources": ["airpr.com"] + }, + "OneAd": { + "properties": ["onead.com.tw"], + "resources": ["guoshipartners.com", "onevision.com.tw"] + }, + "One iota": { + "properties": ["itsoneiota.com", "oneiota.co.uk"], + "resources": ["itsoneiota.com", "oneiota.co.uk"] + }, + "OneStat": { + "properties": ["onestat.com"], + "resources": ["onestat.com"] + }, + "Oneupweb": { + "properties": ["oneupweb.com", "sodoit.com"], + "resources": ["oneupweb.com", "sodoit.com"] + }, + "OnlineMetrix": { + "properties": ["online-metrix.net"], + "resources": ["online-metrix.net"] + }, + "Ooyala": { + "properties": ["ooyala.com"], + "resources": ["oo4.com", "ooyala.com"] + }, + "Open New Media": { + "properties": ["onm.de"], + "resources": ["onm.de"] + }, + "Openstat": { + "properties": ["openstat.com"], + "resources": ["openstat.com", "openstat.ru", "spylog.com"] + }, + "Opentracker": { + "properties": ["opentracker.net"], + "resources": ["opentracker.net"] + }, + "OpenX": { + "properties": ["openx.com", "openx.net"], + "resources": [ + "liftdna.com", + "openx.com", + "openx.net", + "openx.org", + "openxenterprise.com", + "servedbyopenx.com" + ] + }, + "Opera": { + "properties": ["opera.com"], + "resources": ["mobiletheory.com", "opera.com"] + }, + "Opolen": { + "properties": ["opolen.com.br"], + "resources": ["opolen.com.br"] + }, + "OPT": { + "properties": ["www.opt.ne.jp"], + "resources": ["advg.jp", "opt.ne.jp", "p-advg.com", "www.opt.ne.jp"] + }, + "Optify": { + "properties": ["optify.net"], + "resources": ["optify.net"] + }, + "Optimal": { + "properties": ["bn.co"], + "resources": [ + "cpmadvisors.com", + "cpmatic.com", + "nprove.com", + "optim.al", + "orbengine.com", + "xa.net" + ] + }, + "Optimizely": { + "properties": ["optimizely.com"], + "resources": ["optimizely.com"] + }, + "OptimumResponse": { + "properties": ["optimumresponse.com"], + "resources": ["optimumresponse.com"] + }, + "OptinMonster": { + "properties": ["optinmonster.com", "optnmstr.com"], + "resources": ["optinmonster.com", "optnmstr.com"] + }, + "OptMD": { + "properties": ["optmd.com"], + "resources": ["optmd.com"] + }, + "Oracle": { + "properties": ["oracle.com"], + "resources": [ + "atgsvcs.com", + "eloqua.com", + "estara.com", + "instantservice.com", + "istrack.com", + "maxymiser.com", + "oracle.com" + ] + }, + "OrangeSoda": { + "properties": ["orangesoda.com", "otracking.com"], + "resources": ["orangesoda.com", "otracking.com"] + }, + "Outbrain": { + "properties": ["outbrain.com", "sphere.com"], + "resources": ["outbrain.com", "sphere.com", "visualrevenue.com"] + }, + "Out There Media": { + "properties": ["out-there-media.com"], + "resources": ["out-there-media.com"] + }, + "Oversee.net": { + "properties": ["dsnextgen.com", "oversee.net"], + "resources": ["dsnextgen.com", "oversee.net"] + }, + "ÖWA": { + "properties": ["oewa.at"], + "resources": ["oewa.at", "oewabox.at"] + }, + "OwnerIQ": { + "properties": ["owneriq.com", "owneriq.net"], + "resources": ["owneriq.com", "owneriq.net"] + }, + "OxaMedia": { + "properties": ["oxamedia.com"], + "resources": ["adconnexa.com", "adsbwm.com", "oxamedia.com"] + }, + "PageFair": { + "properties": ["pagefair.com", "pagefair.net"], + "resources": ["pagefair.com", "pagefair.net"] + }, + "Paid-To-Promote.net": { + "properties": ["paid-to-promote.net"], + "resources": ["paid-to-promote.net"] + }, + "Papaya": { + "properties": ["papayamobile.com"], + "resources": ["papayamobile.com"] + }, + "Pardot": { + "properties": ["pardot.com"], + "resources": ["pardot.com"] + }, + "Parse.ly": { + "properties": ["parsely.com"], + "resources": ["parsely.com"] + }, + "PayHit": { + "properties": ["payhit.com"], + "resources": ["payhit.com"] + }, + "PaymentsMB": { + "properties": ["paymentsmb.com"], + "resources": ["paymentsmb.com"] + }, + "Paypal": { + "properties": ["paypal.com", "simility.com"], + "resources": ["paypal.com", "simility.com"] + }, + "Paypopup.com": { + "properties": ["paypopup.com"], + "resources": ["lzjl.com", "paypopup.com"] + }, + "PebblePost": { + "properties": ["pebblepost.com"], + "resources": ["pbbl.co"] + }, + "Peer39": { + "properties": ["peer39.com", "peer39.net"], + "resources": ["peer39.com", "peer39.net"] + }, + "PeerFly": { + "properties": ["peerfly.com"], + "resources": ["peerfly.com"] + }, + "Peerius": { + "properties": ["peerius.com"], + "resources": ["peerius.com"] + }, + "Performancing": { + "properties": ["performancing.com"], + "resources": ["performancing.com"] + }, + "PerimeterX": { + "properties": ["perimeterx.com"], + "resources": ["perimeterx.com"] + }, + "PersianStat.com": { + "properties": ["persianstat.com"], + "resources": ["persianstat.com"] + }, + "Pheedo": { + "properties": ["pheedo.com"], + "resources": ["pheedo.com"] + }, + "Phonalytics": { + "properties": ["phonalytics.com"], + "resources": ["phonalytics.com"] + }, + "phpMyVisites": { + "properties": ["phpmyvisites.us"], + "resources": ["phpmyvisites.us"] + }, + "Pictela": { + "properties": ["pictela.com", "pictela.net"], + "resources": ["pictela.com", "pictela.net"] + }, + "PinPoll": { + "properties": ["pinpoll.com"], + "resources": ["pinpoll.com"] + }, + "Pinterest": { + "properties": [ + "pinterest.at", + "pinterest.ca", + "pinterest.ch", + "pinterest.cl", + "pinterest.co.kr", + "pinterest.co.uk", + "pinterest.com", + "pinterest.com.au", + "pinterest.com.mx", + "pinterest.de", + "pinterest.dk", + "pinterest.es", + "pinterest.fr", + "pinterest.ie", + "pinterest.jp", + "pinterest.nz", + "pinterest.pt", + "pinterest.se" + ], + "resources": ["pinimg.com", "pinterest.com"] + }, + "Piwik": { + "properties": ["piwik.org"], + "resources": ["piwik.org"] + }, + "PixAnalytics": { + "properties": ["pixanalytics.com"], + "resources": ["pixanalytics.com"] + }, + "Pixel.sg": { + "properties": ["pixel.sg"], + "resources": ["pixel.sg"] + }, + "Piximedia": { + "properties": ["piximedia.com"], + "resources": ["piximedia.com"] + }, + "Pixlee": { + "properties": ["pixlee.com"], + "resources": ["pixlee.com"] + }, + "PLATFORM ONE": { + "properties": ["platform-one.co.jp", "www.platform-one.co.jp"], + "resources": ["platform-one.co.jp", "www.platform-one.co.jp"] + }, + "plista": { + "properties": ["plista.com"], + "resources": ["plista.com"] + }, + "PocketCents": { + "properties": ["pocketcents.com"], + "resources": ["pocketcents.com"] + }, + "Polar Mobile": { + "properties": ["mediavoice.com"], + "resources": ["mediavoice.com", "polarmobile.com"] + }, + "Politads": { + "properties": ["politads.com"], + "resources": ["politads.com"] + }, + "Polymorph": { + "properties": ["getpolymorph.com"], + "resources": ["adsnative.com", "getpolymorph.com"] + }, + "Pontiflex": { + "properties": ["pontiflex.com"], + "resources": ["pontiflex.com"] + }, + "Poool": { + "properties": ["poool.fr"], + "resources": ["poool.fr"] + }, + "PopAds": { + "properties": ["popads.net"], + "resources": ["popads.net", "popadscdn.net"] + }, + "PopRule": { + "properties": ["gocampaignlive.com", "poprule.com"], + "resources": ["gocampaignlive.com", "poprule.com"] + }, + "Popunder.ru": { + "properties": ["popunder.ru"], + "resources": ["popunder.ru"] + }, + "Po.st": { + "properties": ["po.st"], + "resources": ["po.st"] + }, + "Powerlinks": { + "properties": ["powerlinks.com"], + "resources": ["powerlinks.com"] + }, + "PPCProtect": { + "properties": ["ppcprotect.com"], + "resources": ["ppcprotect.com"] + }, + "PrecisionClick": { + "properties": ["precisionclick.com"], + "resources": ["precisionclick.com"] + }, + "PredictAd": { + "properties": ["predictad.com"], + "resources": ["predictad.com"] + }, + "Pressflex": { + "properties": ["blogads.com", "pressflex.com"], + "resources": ["blogads.com", "pressflex.com"] + }, + "Prime Visibility": { + "properties": ["primevisibility.com"], + "resources": [ + "adcde.com", + "addlvr.com", + "adonnetwork.com", + "adonnetwork.net", + "adtrgt.com", + "bannertgt.com", + "cptgt.com", + "cpvfeed.com", + "cpvtgt.com", + "dashboardad.net", + "popcde.com", + "primevisibility.com", + "sdfje.com", + "urtbk.com" + ] + }, + "Primis": { + "properties": ["primis.tech"], + "resources": ["sekindo.com"] + }, + "PrismApp": { + "properties": ["prismapp.io"], + "resources": ["prismapp.io"] + }, + "Proclivity": { + "properties": ["proclivitysystems.com", "pswec.com"], + "resources": ["proclivitymedia.com", "proclivitysystems.com", "pswec.com"] + }, + "Project Wonderful": { + "properties": ["projectwonderful.com"], + "resources": ["projectwonderful.com"] + }, + "PrometheusIntelligenceTechnology": { + "properties": ["prometheusintelligencetechnology.com"], + "resources": ["prometheusintelligencetechnology.com"] + }, + "Pronunciator": { + "properties": ["pronunciator.com", "visitorville.com"], + "resources": ["pronunciator.com", "visitorville.com"] + }, + "Propeller Ads": { + "properties": ["propellerads.com"], + "resources": ["propellerads.com"] + }, + "Prosperent": { + "properties": ["prosperent.com"], + "resources": ["prosperent.com"] + }, + "Protected Media": { + "properties": ["ad-score.com", "protected.media"], + "resources": ["ad-score.com", "protected.media"] + }, + "Provers": { + "properties": ["provers.pro"], + "resources": ["provers.pro"] + }, + "Psonstrentie": { + "properties": ["psonstrentie.info"], + "resources": ["psonstrentie.info"] + }, + "Public-Idées": { + "properties": ["publicidees.com"], + "resources": ["publicidees.com"] + }, + "Publishers Clearing House": { + "properties": ["pch.com"], + "resources": ["pch.com"] + }, + "PubMatic": { + "properties": ["pubmatic.com"], + "resources": ["pubmatic.com", "revinet.com"] + }, + "PulsePoint": { + "properties": ["pulsepoint.com"], + "resources": ["pulsepoint.com"] + }, + "PunchTab": { + "properties": ["punchtab.com"], + "resources": ["punchtab.com"] + }, + "quadrantOne": { + "properties": ["quadrantone.com"], + "resources": ["quadrantone.com"] + }, + "Quake Marketing": { + "properties": ["quakemarketing.com"], + "resources": ["quakemarketing.com"] + }, + "Qualaroo": { + "properties": ["qualaroo.com"], + "resources": ["kissinsights.com", "qualaroo.com"] + }, + "Quantcast": { + "properties": ["quantcast.com", "quantserve.com"], + "resources": ["quantcast.com", "quantserve.com"] + }, + "QuantumAdvertising": { + "properties": ["quantum-advertising.com"], + "resources": ["quantum-advertising.com"] + }, + "QuinStreet": { + "properties": ["quinstreet.com", "thecounter.com"], + "resources": ["qnsr.com", "qsstats.com", "quinstreet.com", "thecounter.com"] + }, + "Quintelligence": { + "properties": ["quintelligence.com"], + "resources": ["quintelligence.com"] + }, + "QUISMA": { + "properties": ["quisma.com"], + "resources": [ + "iaded.com", + "quisma.com", + "quismatch.com", + "xaded.com", + "xmladed.com" + ] + }, + "RadarURL": { + "properties": ["radarurl.com"], + "resources": ["radarurl.com"] + }, + "Radial": { + "properties": ["radial.com"], + "resources": ["gsicommerce.com", "gsimedia.net"] + }, + "Radiate Media": { + "properties": ["gtnetwork.com.au", "solesolution.com"], + "resources": [ + "gtnetwork.com.au", + "matchbin.com", + "radiatemedia.com", + "solesolution.com" + ] + }, + "RadiumOne": { + "properties": ["radiumone.com"], + "resources": ["gwallet.com", "radiumone.com"] + }, + "Radius Marketing": { + "properties": ["radiusmarketing.com"], + "resources": ["radiusmarketing.com"] + }, + "Rambler": { + "properties": ["rambler.ru"], + "resources": ["rambler.ru"] + }, + "Rapleaf": { + "properties": ["rapleaf.com", "rlcdn.com"], + "resources": ["rapleaf.com", "rlcdn.com"] + }, + "ReachLocal": { + "properties": ["reachlocal.com", "rlcdn.net"], + "resources": ["reachlocal.com", "rlcdn.net"] + }, + "React2Media": { + "properties": ["react2media.com"], + "resources": ["react2media.com"] + }, + "reddit": { + "properties": ["reddit.com"], + "resources": ["reddit.com"] + }, + "Redux Media": { + "properties": ["reduxmedia.com"], + "resources": ["reduxmedia.com"] + }, + "Rekko": { + "properties": ["convertglobal.com", "rekko.com"], + "resources": ["convertglobal.com", "rekko.com"] + }, + "Reklamport": { + "properties": ["reklamport.com"], + "resources": ["reklamport.com"] + }, + "Reklam Store": { + "properties": ["reklamstore.com"], + "resources": ["reklamstore.com"] + }, + "Reklamz": { + "properties": ["reklamz.com"], + "resources": ["reklamz.com"] + }, + "Relevad": { + "properties": ["relestar.com", "relevad.com"], + "resources": ["relestar.com", "relevad.com"] + }, + "Renegade Internet": { + "properties": ["advertserve.com", "renegadeinternet.com"], + "resources": ["advertserve.com", "renegadeinternet.com"] + }, + "Reporo": { + "properties": ["reporo.com"], + "resources": ["buzzcity.com"] + }, + "Research Now": { + "properties": ["researchnow.com", "valuedopinions.co.uk"], + "resources": ["researchnow.com", "valuedopinions.co.uk"] + }, + "ResolutionMedia": { + "properties": ["nonstoppartner.net"], + "resources": ["nonstoppartner.net"] + }, + "Resolution Media": { + "properties": ["resolutionmedia.com"], + "resources": ["resolutionmedia.com"] + }, + "Resonate": { + "properties": ["resonateinsights.com", "resonatenetworks.com"], + "resources": ["reson8.com", "resonateinsights.com", "resonatenetworks.com"] + }, + "Responsys": { + "properties": ["responsys.com"], + "resources": ["responsys.com"] + }, + "Retail Automata": { + "properties": ["retailautomata.com"], + "resources": ["retailautomata.com"] + }, + "ReTargeter": { + "properties": ["retargeter.com"], + "resources": ["retargeter.com"] + }, + "Retirement Living": { + "properties": ["blvdstatus.com", "retirement-living.com"], + "resources": ["blvdstatus.com", "retirement-living.com"] + }, + "RevContent": { + "properties": ["revcontent.com"], + "resources": ["revcontent.com"] + }, + "RevenueMax": { + "properties": ["revenuemax.de"], + "resources": ["revenuemax.de"] + }, + "Revtracks": { + "properties": ["revtrax.com"], + "resources": ["revtrax.com"] + }, + "Rhythm": { + "properties": ["rhythmone.com"], + "resources": [ + "1rx.io", + "rhythmnewmedia.com", + "rhythmone.com", + "rhythmxchange.com", + "rnmd.net" + ] + }, + "RichAudience": { + "properties": ["richaudience.com"], + "resources": ["richaudience.com"] + }, + "RichRelevance": { + "properties": ["richrelevance.com"], + "resources": ["richrelevance.com"] + }, + "RightAction": { + "properties": ["rightaction.com"], + "resources": ["rightaction.com"] + }, + "RIM": { + "properties": ["global.blackberry.com", "laptopverge.com"], + "resources": [ + "global.blackberry.com", + "laptopverge.com", + "rim.com", + "scoreloop.com" + ] + }, + "Ringier": { + "properties": ["ringier.cz"], + "resources": ["ringier.cz"] + }, + "RMBN": { + "properties": ["traforet.com"], + "resources": ["rmbn.net", "rmbn.ru", "traforet.com"] + }, + "RMM": { + "properties": ["rmmonline.com"], + "resources": ["rmmonline.com"] + }, + "Rocket Fuel": { + "properties": ["rfihub.com", "rfihub.net", "rocketfuel.com"], + "resources": [ + "rfihub.com", + "rfihub.net", + "rocketfuel.com", + "ru4.com", + "xplusone.com" + ] + }, + "Rollick": { + "properties": ["gorollick.com"], + "resources": ["rollick.io"] + }, + "Rovion": { + "properties": ["rovion.com"], + "resources": ["rovion.com"] + }, + "Roxr": { + "properties": ["clicky.com", "roxr.net"], + "resources": ["clicky.com", "getclicky.com", "roxr.net", "staticstuff.net"] + }, + "rtk": { + "properties": ["rtk.io"], + "resources": ["rtk.io"] + }, + "RubiconProject": { + "properties": ["rubiconproject.com"], + "resources": ["adsbyisocket.com", "isocket.com", "rubiconproject.com"] + }, + "RunAds": { + "properties": ["runads.com"], + "resources": ["runads.com", "rundsp.com"] + }, + "RuTarget": { + "properties": ["rutarget.ru"], + "resources": ["rutarget.ru"] + }, + "Sabavision": { + "properties": ["sabavision.com"], + "resources": ["sabavision.com"] + }, + "Sabre": { + "properties": ["reztrack.com", "sabre.com", "sabrehospitality.com"], + "resources": ["reztrack.com", "sabre.com", "sabrehospitality.com"] + }, + "Safecount": { + "properties": ["safecount.net"], + "resources": [ + "dl-rms.com", + "dlqm.net", + "questionmarket.com", + "safecount.net" + ] + }, + "SageMetrics": { + "properties": ["sagemetrics.com"], + "resources": ["sageanalyst.net", "sagemetrics.com"] + }, + "Salesforce.com": { + "properties": ["force.com", "salesforce.com", "trailblazer.me"], + "resources": [ + "documentforce.com", + "force.com", + "forcesslreports.com", + "forceusercontent.com", + "lightning.com", + "salesforce-communities.com", + "salesforce-hub.com", + "salesforce.com", + "salesforceliveagent.com", + "trailblazer.me", + "visualforce.com" + ] + }, + "Salesintelligence": { + "properties": ["salesintelligence.pl"], + "resources": ["plugin.management"] + }, + "Samurai Factory": { + "properties": ["samurai-factory.jp", "shinobi.jp"], + "resources": ["samurai-factory.jp", "shinobi.jp"] + }, + "SAP": { + "properties": ["sap.com"], + "resources": ["sap.com", "seewhy.com"] + }, + "Sapient": { + "properties": ["bridgetrack.com", "sapient.com"], + "resources": ["bridgetrack.com", "sapient.com"] + }, + "SAS": { + "properties": ["aimatch.com", "sas.com"], + "resources": ["aimatch.com", "sas.com"] + }, + "SAY": { + "properties": ["saymedia.com", "typepad.com", "videoegg.com"], + "resources": ["saymedia.com", "typepad.com", "videoegg.com"] + }, + "Scandinavian AdNetworks": { + "properties": ["scandinavianadnetworks.com"], + "resources": ["scandinavianadnetworks.com"] + }, + "ScribeFire": { + "properties": ["scribefire.com"], + "resources": ["scribefire.com"] + }, + "Scribol": { + "properties": ["scribol.com"], + "resources": ["scribol.com"] + }, + "SearchForce": { + "properties": ["searchforce.com", "searchforce.net"], + "resources": ["searchforce.com", "searchforce.net"] + }, + "Seevast": { + "properties": ["kanoodle.com"], + "resources": [ + "kanoodle.com", + "pulse360.com", + "seevast.com", + "syndigonetworks.com" + ] + }, + "SeeVolution": { + "properties": ["seevolution.com", "svlu.net"], + "resources": ["seevolution.com", "svlu.net"] + }, + "Segment.io": { + "properties": ["segment.io"], + "resources": ["segment.io"] + }, + "Selectable Media": { + "properties": ["selectablemedia.com"], + "resources": ["nabbr.com", "selectablemedia.com"] + }, + "Semantiqo": { + "properties": ["semantiqo.com"], + "resources": ["semantiqo.com"] + }, + "Semasio": { + "properties": ["semasio.com"], + "resources": ["semasio.com", "semasio.net"] + }, + "SendPulse": { + "properties": ["sendpulse.com"], + "resources": ["sendpulse.com"] + }, + "Service4refresh": { + "properties": ["service4refresh.info"], + "resources": ["service4refresh.info"] + }, + "SessionCam": { + "properties": ["sessioncam.com"], + "resources": ["sessioncam.com"] + }, + "SevenAds": { + "properties": ["sevenads.net"], + "resources": ["sevenads.net"] + }, + "SexInYourCity": { + "properties": ["sexinyourcity.com"], + "resources": ["sexinyourcity.com"] + }, + "ShaftTraffic": { + "properties": ["shafttraffic.com"], + "resources": ["libertystmedia.com", "shafttraffic.com"] + }, + "Shareaholic": { + "properties": ["shareaholic.com"], + "resources": ["shareaholic.com"] + }, + "ShareASale": { + "properties": ["shareasale.com"], + "resources": ["shareasale.com"] + }, + "ShareThis": { + "properties": ["sharethis.com"], + "resources": ["sharethis.com"] + }, + "Sharethrough": { + "properties": ["sharethrough.com"], + "resources": ["sharethrough.com"] + }, + "ShinyStat": { + "properties": ["shinystat.com"], + "resources": ["shinystat.com"] + }, + "Shopzilla": { + "properties": ["shopzilla.com"], + "resources": ["shopzilla.com"] + }, + "Shortest": { + "properties": ["shorte.st"], + "resources": ["shorte.st"] + }, + "SiftScience": { + "properties": ["sift.com"], + "resources": ["siftscience.com"] + }, + "Signifyd": { + "properties": ["signifyd.com"], + "resources": ["signifyd.com"] + }, + "Silverpop": { + "properties": ["mkt51.net", "silverpop.com"], + "resources": ["mkt51.net", "pages05.net", "silverpop.com", "vtrenz.net"] + }, + "Simpli.fi": { + "properties": ["simpli.fi"], + "resources": ["simpli.fi"] + }, + "SiteScout": { + "properties": ["sitescout.com"], + "resources": ["sitescout.com"] + }, + "Six Apart": { + "properties": ["movabletype.com", "sixapart.com"], + "resources": ["movabletype.com", "sixapart.com"] + }, + "Skimlinks": { + "properties": ["skimlinks.com", "skimresources.com"], + "resources": ["skimlinks.com", "skimresources.com"] + }, + "Skribit": { + "properties": ["paulstamatiou.com"], + "resources": ["paulstamatiou.com", "skribit.com"] + }, + "Skupe Net": { + "properties": ["adcentriconline.com", "skupenet.com"], + "resources": ["adcentriconline.com", "skupenet.com"] + }, + "Smaato": { + "properties": ["smaato.com"], + "resources": ["smaato.com"] + }, + "SmartAdServer": { + "properties": ["smartadserver.com"], + "resources": ["smartadserver.com"] + }, + "Smartlook": { + "properties": ["smartlook.com"], + "resources": ["smartlook.com"] + }, + "SmartyAds": { + "properties": ["smartyads.com"], + "resources": ["smartyads.com"] + }, + "Smi": { + "properties": ["24smi.net"], + "resources": ["24smi.net"] + }, + "Smiley Media": { + "properties": ["smileymedia.com"], + "resources": ["smileymedia.com"] + }, + "Smowtion": { + "properties": ["smowtion.com"], + "resources": ["smowtion.com"] + }, + "Snap": { + "properties": ["snap.com"], + "resources": ["snap.com"] + }, + "SnapEngage": { + "properties": ["snapengage.com"], + "resources": ["snapengage.com"] + }, + "Snoobi": { + "properties": ["snoobi.fi"], + "resources": ["snoobi.com", "snoobi.fi"] + }, + "SocialChorus": { + "properties": ["socialchorus.com"], + "resources": [ + "halogenmediagroup.com", + "halogennetwork.com", + "socialchorus.com" + ] + }, + "SocialInterface": { + "properties": ["socialinterface.com"], + "resources": ["ratevoice.com", "socialinterface.com"] + }, + "SocialTwist": { + "properties": ["socialtwist.com"], + "resources": ["socialtwist.com"] + }, + "sociomantic labs": { + "properties": ["sociomantic.com"], + "resources": ["sociomantic.com"] + }, + "Socital": { + "properties": ["socital.com"], + "resources": ["socital.com"] + }, + "Sojern": { + "properties": ["sojern.com"], + "resources": ["sojern.com"] + }, + "SomoAudience": { + "properties": ["somoaudience.com"], + "resources": ["somoaudience.com"] + }, + "Sonobi": { + "properties": ["sonobi.com"], + "resources": ["sonobi.com"] + }, + "sophus3": { + "properties": ["sophus3.com"], + "resources": ["sophus3.co.uk", "sophus3.com"] + }, + "Sortable": { + "properties": ["sortable.com"], + "resources": ["deployads.com"] + }, + "Sourcepoint": { + "properties": ["sourcepoint.com"], + "resources": ["summerhamster.com"] + }, + "Sovrn": { + "properties": ["sovrn.com"], + "resources": ["sovrn.com"] + }, + "Space Chimp Media": { + "properties": ["spacechimpmedia.com"], + "resources": ["spacechimpmedia.com"] + }, + "SpareChange": { + "properties": ["sparechange.io"], + "resources": ["sparechange.io"] + }, + "Sparklit": { + "properties": ["adbutler.com", "sparklit.com"], + "resources": ["adbutler.com", "sparklit.com"] + }, + "Spark Studios": { + "properties": ["sparkstudios.com"], + "resources": ["sparkstudios.com"] + }, + "Specific Media": { + "properties": ["sitemeter.com", "specificmedia.com"], + "resources": [ + "adviva.co.uk", + "adviva.net", + "sitemeter.com", + "specificclick.net", + "specificmedia.com" + ] + }, + "Spectate": { + "properties": ["spectate.com"], + "resources": ["spectate.com"] + }, + "Sponge": { + "properties": ["spongegroup.com"], + "resources": ["spongegroup.com"] + }, + "Spongecell": { + "properties": ["spongecell.com"], + "resources": ["spongecell.com"] + }, + "SponsorAds": { + "properties": ["sponsorads.de"], + "resources": ["sponsorads.de"] + }, + "Spot200": { + "properties": ["spot200.com"], + "resources": ["spot200.com"] + }, + "SpotX": { + "properties": ["spotx.tv"], + "resources": ["spotx.tv"] + }, + "SpotXchange": { + "properties": ["spotxchange.com"], + "resources": ["spotxcdn.com", "spotxchange.com"] + }, + "Spring Metrics": { + "properties": ["springmetrics.com"], + "resources": ["springmetrics.com"] + }, + "SpringServe": { + "properties": ["springserve.com"], + "resources": ["springserve.com"] + }, + "Sputnik.ru": { + "properties": ["sputnik.ru"], + "resources": ["sputnik.ru"] + }, + "StackAdapt": { + "properties": ["stackadapt.com"], + "resources": ["stackadapt.com"] + }, + "StackTrack": { + "properties": ["stat-track.com"], + "resources": ["stat-track.com"] + }, + "StarGames": { + "properties": ["stargames.net", "stargamesaffiliate.com"], + "resources": ["stargames.net", "stargamesaffiliate.com"] + }, + "stat4u": { + "properties": ["4u.pl"], + "resources": ["4u.pl"] + }, + "StatCounter": { + "properties": ["statcounter.com"], + "resources": ["statcounter.com"] + }, + "Statisfy": { + "properties": ["statisfy.net"], + "resources": ["statisfy.net"] + }, + "STATSIT": { + "properties": ["statsit.com"], + "resources": ["statsit.com"] + }, + "SteelHouse": { + "properties": ["steelhouse.com", "steelhousemedia.com"], + "resources": ["steelhouse.com", "steelhousemedia.com"] + }, + "Storeland": { + "properties": ["storeland.ru"], + "resources": ["storeland.ru"] + }, + "Storygize": { + "properties": ["storygize.com"], + "resources": ["storygize.com", "storygize.net"] + }, + "Stratigent": { + "properties": ["stratigent.com"], + "resources": ["stratigent.com"] + }, + "Streamray": { + "properties": ["cams.com", "streamray.com"], + "resources": ["cams.com", "streamray.com"] + }, + "StrikeAd": { + "properties": ["strikead.com"], + "resources": ["strikead.com"] + }, + "Stripe": { + "properties": ["stripe.com"], + "resources": ["stripe.network"] + }, + "StrongMail": { + "properties": ["strongmail.com"], + "resources": ["popularmedia.com", "strongmail.com"] + }, + "Struq": { + "properties": ["struq.com"], + "resources": ["struq.com"] + }, + "StumbleUpon": { + "properties": ["stumbleupon.com"], + "resources": ["stumble-upon.com", "stumbleupon.com"] + }, + "Sublime Skinz": { + "properties": ["sublime.xyz"], + "resources": ["ayads.co", "sublime.xyz"] + }, + "Suite 66": { + "properties": ["suite66.com"], + "resources": ["suite66.com"] + }, + "Summit": { + "properties": ["summitmedia.co.uk", "www.summit.co.uk"], + "resources": ["summitmedia.co.uk", "www.summit.co.uk"] + }, + "Superfish": { + "properties": ["superfish.com"], + "resources": ["superfish.com"] + }, + "SupersonicAds": { + "properties": ["supersonicads.com"], + "resources": ["supersonicads.com"] + }, + "Survata": { + "properties": ["survata.com"], + "resources": ["survata.com"] + }, + "SwiftMining": { + "properties": ["swiftmining.win"], + "resources": ["swiftmining.win"] + }, + "Switch": { + "properties": ["switchadhub.com", "switchconcepts.com"], + "resources": [ + "switchadhub.com", + "switchads.com", + "switchconcepts.co.uk", + "switchconcepts.com" + ] + }, + "Swoop": { + "properties": ["swoop.com"], + "resources": ["swoop.com"] + }, + "SymphonyAM": { + "properties": ["factortg.com"], + "resources": ["factortg.com"] + }, + "Synacor": { + "properties": ["synacor.com"], + "resources": ["synacor.com"] + }, + "Syncapse": { + "properties": ["clickable.net", "syncapse.com"], + "resources": ["clickable.net", "syncapse.com"] + }, + "Syrup Ad": { + "properties": ["adotsolution.com"], + "resources": ["adotsolution.com"] + }, + "Taboola": { + "properties": ["taboola.com"], + "resources": ["perfectmarket.com", "taboola.com"] + }, + "Tailsweep": { + "properties": ["tailsweep.com"], + "resources": ["tailsweep.com"] + }, + "Taleria": { + "properties": ["telaria.com"], + "resources": ["freeskreen.com"] + }, + "Tapad": { + "properties": ["tapad.com"], + "resources": ["tapad.com"] + }, + "Tapgage": { + "properties": ["bizmey.com", "tapgage.com"], + "resources": ["bizmey.com", "tapgage.com"] + }, + "TapIt!": { + "properties": ["tapit.com"], + "resources": ["tapit.com"] + }, + "Tap.me": { + "properties": ["tap.me"], + "resources": ["tap.me"] + }, + "Targetix": { + "properties": ["targetix.net"], + "resources": ["targetix.net"] + }, + "Tatto Media": { + "properties": ["tattomedia.com"], + "resources": ["quicknoodles.com", "tattomedia.com"] + }, + "Teadma": { + "properties": ["teadma.com"], + "resources": ["teadma.com"] + }, + "Teads.tv": { + "properties": ["teads.tv"], + "resources": ["teads.tv"] + }, + "Tealium": { + "properties": ["tealium.com"], + "resources": ["tealiumiq.com"] + }, + "Technorati": { + "properties": ["technorati.com"], + "resources": ["technorati.com", "technoratimedia.com"] + }, + "TechSolutions": { + "properties": ["techsolutions.com.tw"], + "resources": ["techsolutions.com.tw"] + }, + "TellApart": { + "properties": ["tellapart.com", "tellapt.com"], + "resources": ["tellapart.com", "tellapt.com"] + }, + "Telstra": { + "properties": ["sensis.com.au", "sensisdata.com.au", "telstra.com.au"], + "resources": [ + "sensis.com.au", + "sensisdata.com.au", + "sensisdigitalmedia.com.au", + "telstra.com.au" + ] + }, + "TENSQUARE": { + "properties": ["tensquare.com"], + "resources": ["tensquare.com"] + }, + "Terra": { + "properties": ["eztargetmedia.com", "terra.com.br", "www.terra.com.br"], + "resources": ["eztargetmedia.com", "terra.com.br", "www.terra.com.br"] + }, + "The Heron Partnership": { + "properties": ["marinsm.com"], + "resources": ["heronpartners.com.au", "marinsm.com", "marinsoftware.com"] + }, + "The Numa Group": { + "properties": ["hittail.com", "thenumagroup.com"], + "resources": ["hittail.com", "thenumagroup.com"] + }, + "The Search Agency": { + "properties": ["thesearchagency.com"], + "resources": ["thesearchagency.com", "thesearchagency.net"] + }, + "The Trade Desk": { + "properties": ["thetradedesk.com"], + "resources": ["adsrvr.org", "thetradedesk.com"] + }, + "ThingLink": { + "properties": ["thinglink.com"], + "resources": ["thinglink.com"] + }, + "Think Realtime": { + "properties": ["echosearch.com", "thinkrealtime.com"], + "resources": ["echosearch.com", "esm1.net", "thinkrealtime.com"] + }, + "Thismoment": { + "properties": ["thismoment.com"], + "resources": ["thismoment.com"] + }, + "Thummit": { + "properties": ["thummit.com"], + "resources": ["thummit.com"] + }, + "Tinder": { + "properties": ["carbonads.com", "tinder.com"], + "resources": ["carbonads.com", "tinder.com"] + }, + "TiqIQ": { + "properties": ["tiqiq.com"], + "resources": ["tiqiq.com"] + }, + "Tisoomi": { + "properties": ["adternal.com", "tisoomi.com"], + "resources": ["adternal.com", "tisoomi.com"] + }, + "TLVMedia": { + "properties": ["tlvmedia.com"], + "resources": ["tlvmedia.com"] + }, + "TNS": { + "properties": [ + "statistik-gallup.net", + "tns-counter.ru", + "tns-cs.net", + "tnsglobal.com" + ], + "resources": [ + "sesamestats.com", + "statistik-gallup.net", + "tns-counter.ru", + "tns-cs.net", + "tnsglobal.com" + ] + }, + "Todacell": { + "properties": ["todacell.com"], + "resources": ["todacell.com"] + }, + "ToneFuse": { + "properties": ["tonefuse.com"], + "resources": ["tonefuse.com"] + }, + "ToneMedia": { + "properties": ["clickfuse.com"], + "resources": ["clickfuse.com", "tonemedia.com"] + }, + "tongdun.cn": { + "properties": ["tongdun.cn"], + "resources": ["fraudmetrix.cn", "tongdun.net"] + }, + "Topsy": { + "properties": ["topsy.com"], + "resources": ["topsy.com"] + }, + "TouchCommerce": { + "properties": ["nuance.com"], + "resources": ["inq.com", "nuance.com", "touchcommerce.com"] + }, + "TraceMyIP.org": { + "properties": ["tracemyip.org"], + "resources": ["tracemyip.org"] + }, + "TrackingSoft": { + "properties": ["roia.biz", "trackingsoft.com"], + "resources": ["roia.biz", "trackingsoft.com"] + }, + "Trackset": { + "properties": ["trackset.com"], + "resources": ["trackset.com"] + }, + "Tradedoubler": { + "properties": ["tradedoubler.com"], + "resources": ["tradedoubler.com"] + }, + "TradeTracker": { + "properties": ["tradetracker.com"], + "resources": ["tradetracker.com", "tradetracker.net"] + }, + "TrafficHaus": { + "properties": ["traffichaus.com", "traffichouse.com"], + "resources": ["traffichaus.com", "traffichouse.com"] + }, + "TrafficRevenue": { + "properties": ["trafficrevenue.net"], + "resources": ["trafficrevenue.net"] + }, + "TrafficScore": { + "properties": ["trafficscore.com"], + "resources": ["trafficscore.com"] + }, + "Traffiq": { + "properties": ["traffiq.com"], + "resources": ["traffiq.com"] + }, + "Trafmag": { + "properties": ["trafmag.com"], + "resources": ["trafmag.com"] + }, + "Traverse": { + "properties": ["traversedata.com"], + "resources": ["traversedlp.com"] + }, + "Travora Media": { + "properties": ["travoramedia.com"], + "resources": [ + "traveladnetwork.com", + "traveladvertising.com", + "travoramedia.com" + ] + }, + "Tremor Video": { + "properties": ["tremorvideo.com"], + "resources": [ + "scanscout.com", + "tmnetads.com", + "tremorhub.com", + "tremormedia.com", + "tremorvideo.com" + ] + }, + "Triggit": { + "properties": ["triggit.com"], + "resources": ["triggit.com"] + }, + "TripleLift": { + "properties": ["triplelift.com"], + "resources": ["3lift.com", "triplelift.com"] + }, + "Trovus": { + "properties": ["trovus.co.uk", "www.trovus.co.uk"], + "resources": ["trovus.co.uk", "www.trovus.co.uk"] + }, + "TruEffect": { + "properties": ["adlegend.com", "trueffect.com"], + "resources": ["adlegend.com", "trueffect.com"] + }, + "Trumba": { + "properties": ["trumba.com"], + "resources": ["trumba.com"] + }, + "TRUSTe": { + "properties": ["truste.com"], + "resources": ["truste.com"] + }, + "TrustX": { + "properties": ["trustx.org"], + "resources": ["trustx.org"] + }, + "TubeMogul": { + "properties": ["tmogul.com", "tubemogul.com"], + "resources": ["tmogul.com", "tubemogul.com"] + }, + "TurnTo": { + "properties": ["turntonetworks.com"], + "resources": ["turnto.com", "turntonetworks.com"] + }, + "Tweetboard": { + "properties": ["tweetboard.com"], + "resources": ["tweetboard.com"] + }, + "Twelvefold": { + "properties": ["buzzlogic.com", "twelvefold.com"], + "resources": ["buzzlogic.com", "twelvefold.com"] + }, + "Twitter": { + "properties": [ + "digits.com", + "fabric.io", + "tweetdeck.com", + "twitter.com", + "twitter.jp" + ], + "resources": [ + "ads-twitter.com", + "fabric.io", + "tweetdeck.com", + "twimg.com", + "twitter.com", + "twitter.jp" + ] + }, + "Twitter Counter": { + "properties": ["twittercounter.com"], + "resources": ["twittercounter.com"] + }, + "Twyn Group": { + "properties": ["twyn-group.com", "twyn.com"], + "resources": ["twyn-group.com", "twyn.com"] + }, + "Tyroo": { + "properties": ["tyroo.com"], + "resources": ["tyroo.com"] + }, + "UberMedia": { + "properties": ["tweetup.com", "ubermedia.com"], + "resources": ["tweetup.com", "ubermedia.com"] + }, + "UberTags": { + "properties": ["ubertags.com"], + "resources": ["ubertags.com"] + }, + "ucfunnel": { + "properties": ["ucfunnel.com"], + "resources": ["aralego.com", "ucfunnel.com"] + }, + "uCoz": { + "properties": ["ucoz.ae", "ucoz.com", "ucoz.fr", "ucoz.net", "ucoz.ru"], + "resources": [ + "ucoz.ae", + "ucoz.br", + "ucoz.com", + "ucoz.du", + "ucoz.fr", + "ucoz.net", + "ucoz.ru" + ] + }, + "Umbel": { + "properties": ["umbel.com"], + "resources": ["umbel.com"] + }, + "Unanimis": { + "properties": ["unanimis.co.uk", "www.unanimis.co.uk"], + "resources": ["unanimis.co.uk", "www.unanimis.co.uk"] + }, + "Unbounce": { + "properties": ["unbounce.com"], + "resources": ["unbounce.com"] + }, + "Underdog Media": { + "properties": ["udmserve.net", "underdogmedia.com"], + "resources": ["udmserve.net", "underdogmedia.com"] + }, + "Undertone": { + "properties": ["undertone.com", "undertonevideo.com"], + "resources": [ + "undertone.com", + "undertonenetworks.com", + "undertonevideo.com" + ] + }, + "UniQlick": { + "properties": ["51network.com", "uniqlick.com", "wanmo.com"], + "resources": ["51network.com", "uniqlick.com", "wanmo.com"] + }, + "Unruly": { + "properties": ["unruly.co"], + "resources": ["unrulymedia.com"] + }, + "Upland": { + "properties": ["uplandsoftware.com"], + "resources": [ + "leadlander.com", + "sf14g.com", + "trackalyzer.com", + "uplandsoftware.com" + ] + }, + "Uptrends": { + "properties": ["uptrends.com"], + "resources": ["uptrends.com"] + }, + "up-value": { + "properties": ["up-value.de"], + "resources": ["up-value.de"] + }, + "Usability Sciences": { + "properties": ["usabilitysciences.com"], + "resources": ["usabilitysciences.com", "webiqonline.com"] + }, + "User Local": { + "properties": ["nakanohito.jp"], + "resources": ["nakanohito.jp"] + }, + "UserVoice": { + "properties": ["uservoice.com"], + "resources": ["uservoice.com"] + }, + "V12 Data": { + "properties": ["v12group.com"], + "resources": ["v12data.com", "v12group.com"] + }, + "Value Ad": { + "properties": ["valuead.com"], + "resources": ["valuead.com"] + }, + "Various": { + "properties": [ + "amigos.com", + "getiton.com", + "medley.com", + "nostringsattached.com", + "various.com" + ], + "resources": [ + "amigos.com", + "getiton.com", + "medley.com", + "nostringsattached.com", + "various.com" + ] + }, + "Vdopia": { + "properties": ["ivdopia.com", "vdopia.com"], + "resources": ["ivdopia.com", "vdopia.com"] + }, + "Veeseo": { + "properties": ["veeseo.com"], + "resources": ["veeseo.com"] + }, + "Velocity Media": { + "properties": ["adsvelocity.com"], + "resources": ["adsvelocity.com"] + }, + "Velti": { + "properties": ["mobclix.com", "velti.com"], + "resources": ["mobclix.com", "velti.com"] + }, + "Vemba": { + "properties": ["vemba.com"], + "resources": ["vemba.com"] + }, + "Venatus Media": { + "properties": ["venatusmedia.com"], + "resources": ["venatusmedia.com"] + }, + "Vendemore": { + "properties": ["vendemore.com"], + "resources": ["vendemore.com"] + }, + "Vendio": { + "properties": ["singlefeed.com", "vendio.com"], + "resources": ["singlefeed.com", "vendio.com"] + }, + "Veoxa": { + "properties": ["veoxa.com"], + "resources": ["veoxa.com"] + }, + "Veremedia": { + "properties": ["veremedia.com"], + "resources": ["veremedia.com"] + }, + "Vertical Acuity": { + "properties": ["verticalacuity.com"], + "resources": ["verticalacuity.com"] + }, + "VerticalHealth": { + "properties": ["verticalhealth.com"], + "resources": ["verticalhealth.net"] + }, + "VerticalResponse": { + "properties": ["verticalresponse.com", "vresp.com"], + "resources": ["verticalresponse.com", "vresp.com"] + }, + "Vertster": { + "properties": ["vertster.com"], + "resources": ["vertster.com"] + }, + "VG WORT": { + "properties": ["vgwort.de"], + "resources": ["vgwort.de"] + }, + "Vibrant Media": { + "properties": ["vibrantmedia.com"], + "resources": ["intellitxt.com", "picadmedia.com", "vibrantmedia.com"] + }, + "VideoIntelligence": { + "properties": ["vi.ai"], + "resources": ["vi.ai"] + }, + "Videology": { + "properties": ["tidaltv.com", "videologygroup.com"], + "resources": ["tidaltv.com", "videologygroup.com"] + }, + "Viewbix": { + "properties": ["qoof.com", "viewbix.com"], + "resources": ["qoof.com", "viewbix.com"] + }, + "VigLink": { + "properties": ["viglink.com"], + "resources": ["viglink.com"] + }, + "Vimeo": { + "properties": ["vimeo.com", "vimeocdn.com"], + "resources": ["vimeo.com", "vimeocdn.com"] + }, + "VINDICO": { + "properties": ["vindicogroup.com", "vindicosuite.com"], + "resources": ["vindicogroup.com", "vindicosuite.com"] + }, + "VisibleBrands": { + "properties": ["visbrands.com"], + "resources": ["visbrands.com"] + }, + "Visible Measures": { + "properties": ["visiblemeasures.com"], + "resources": ["viewablemedia.net", "visiblemeasures.com"] + }, + "VisiStat": { + "properties": ["id.kickfire.com", "sa-as.com"], + "resources": ["d.kickfire.com", "sa-as.com", "visistat.com"] + }, + "Visit Streamer": { + "properties": ["visitstreamer.com"], + "resources": ["visitstreamer.com"] + }, + "vistrac": { + "properties": ["vistrac.com"], + "resources": ["vistrac.com"] + }, + "VisualDNA": { + "properties": ["vdna-assets.com", "visualdna-stats.com", "visualdna.com"], + "resources": ["vdna-assets.com", "visualdna-stats.com", "visualdna.com"] + }, + "ViziSense": { + "properties": ["vizisense.com", "vizisense.net"], + "resources": ["vizisense.com", "vizisense.net"] + }, + "Vizu": { + "properties": ["vizu.com"], + "resources": ["vizu.com"] + }, + "Vizury": { + "properties": ["vizury.com"], + "resources": ["vizury.com"] + }, + "VKontakte": { + "properties": ["vk.com"], + "resources": ["userapi.com", "vk.com", "vkontakte.ru"] + }, + "Voice2Page": { + "properties": ["voice2page.com"], + "resources": ["voice2page.com"] + }, + "Vserv": { + "properties": ["vserv.com", "vserv.mobi"], + "resources": ["vserv.com", "vserv.mobi"] + }, + "Vuble": { + "properties": ["vuble.tv"], + "resources": ["mediabong.com"] + }, + "Wahoha": { + "properties": ["contentwidgets.net", "wahoha.com"], + "resources": ["contentwidgets.net", "wahoha.com"] + }, + "Wayfair": { + "properties": ["wayfair.com"], + "resources": ["wayfair.com"] + }, + "WebAds": { + "properties": ["webads.co.uk", "www.webads.co.uk"], + "resources": ["webads.co.uk", "www.webads.co.uk"] + }, + "Webclicktracker": { + "properties": ["webclicktracker.com"], + "resources": ["webclicktracker.com"] + }, + "Web.com": { + "properties": ["feedperfect.com", "web.com"], + "resources": ["feedperfect.com", "web.com"] + }, + "WebGozar.com": { + "properties": ["webgozar.com", "webgozar.ir"], + "resources": ["webgozar.com", "webgozar.ir"] + }, + "Webmecanik": { + "properties": ["webmecanik.com"], + "resources": ["webmecanik.com"] + }, + "WebMetro": { + "properties": ["dsmmadvantage.com", "revanadigital.com"], + "resources": ["dsmmadvantage.com", "revanadigital.com", "webmetro.com"] + }, + "Webmine": { + "properties": ["webmine.cz"], + "resources": ["authedwebmine.cz", "webmine.cz"] + }, + "WebminePool": { + "properties": ["webminepool.com"], + "resources": ["webminepool.com"] + }, + "Webmining": { + "properties": ["webmining.co"], + "resources": ["webmining.co"] + }, + "Weborama": { + "properties": ["weborama.com"], + "resources": ["weborama.com", "weborama.fr"] + }, + "WebsiteAlive": { + "properties": [ + "websitealive.com", + "websitealive0.com", + "websitealive1.com", + "websitealive2.com", + "websitealive3.com", + "websitealive4.com", + "websitealive5.com", + "websitealive6.com", + "websitealive7.com", + "websitealive8.com", + "websitealive9.com" + ], + "resources": ["websitealive.com"] + }, + "Web Stats": { + "properties": ["onlinewebstats.com"], + "resources": ["onlinewebstats.com"] + }, + "Web Tracking Services": { + "properties": ["web-stat.com", "webtrackingservices.com"], + "resources": ["web-stat.com", "webtrackingservices.com"] + }, + "Webtraffic": { + "properties": ["webtraffic.no", "webtraffic.se"], + "resources": ["webtraffic.no", "webtraffic.se"] + }, + "Web Traxs": { + "properties": ["webtraxs.com"], + "resources": ["webtraxs.com"] + }, + "Webtrekk": { + "properties": ["webtrekk.com", "webtrekk.net"], + "resources": ["webtrekk.com", "webtrekk.net"] + }, + "Webtrends": { + "properties": ["webtrends.com"], + "resources": ["reinvigorate.net", "webtrends.com", "webtrendslive.com"] + }, + "White Ops": { + "properties": ["adzmath.com", "whiteops.com"], + "resources": ["adzmath.com", "whiteops.com"] + }, + "whos.amung.us": { + "properties": ["amung.us"], + "resources": ["amung.us"] + }, + "WideOrbit": { + "properties": ["wideorbit.com"], + "resources": ["dep-x.com"] + }, + "Wingify": { + "properties": ["vwo.com", "wingify.com"], + "resources": ["visualwebsiteoptimizer.com", "vwo.com", "wingify.com"] + }, + "WiredMinds": { + "properties": ["wiredminds.de"], + "resources": ["wiredminds.com", "wiredminds.de"] + }, + "Wishabi": { + "properties": ["wishabi.com", "wishabi.net"], + "resources": ["flipp.com", "wishabi.com", "wishabi.net"] + }, + "Woopra": { + "properties": ["woopra-ns.com", "woopra.com"], + "resources": ["woopra-ns.com", "woopra.com"] + }, + "WordStream": { + "properties": ["wordstream.com"], + "resources": ["wordstream.com"] + }, + "WOW Analytics": { + "properties": ["wowanalytics.co.uk"], + "resources": ["wowanalytics.co.uk"] + }, + "WPP": { + "properties": [ + "compete.com", + "decdna.net", + "groupm.com", + "kantarmedia.com", + "mecglobal.com", + "mindshareworld.com", + "themig.com", + "wpp.com", + "xaxis.com" + ], + "resources": [ + "247realmedia.com", + "accelerator-media.com", + "acceleratorusa.com", + "compete.com", + "decdna.net", + "decideinteractive.com", + "gmads.net", + "groupm.com", + "kantarmedia.com", + "mecglobal.com", + "mindshare.nl", + "mindshareworld.com", + "mookie1.com", + "pm14.com", + "realmedia.com", + "targ.ad", + "themig.com", + "wpp.com", + "xaxis.com" + ] + }, + "Wysistat": { + "properties": ["wysistat.net"], + "resources": ["wysistat.com", "wysistat.net"] + }, + "xAd": { + "properties": ["xad.com"], + "resources": ["xad.com"] + }, + "Xertive Media": { + "properties": ["xertivemedia.com"], + "resources": ["admanager-xertive.com", "xertivemedia.com"] + }, + "xplosion interactive": { + "properties": ["xplosion.de"], + "resources": ["xplosion.de"] + }, + "Xrost DS": { + "properties": ["adplan-ds.com"], + "resources": ["adplan-ds.com"] + }, + "Yabuka": { + "properties": ["yabuka.com"], + "resources": ["yabuka.com"] + }, + "Yahoo!": { + "properties": [ + "flickr.com", + "flurry.com", + "tumblr.com", + "yahoo.co.jp", + "yahoo.com", + "yahoostudios.com", + "yuilibrary.com" + ], + "resources": [ + "adinterax.com", + "adrevolver.com", + "bluelithium.com", + "dapper.net", + "flickr.com", + "flurry.com", + "interclick.com", + "luminate.com", + "mybloglog.com", + "overture.com", + "pixazza.com", + "rightmedia.com", + "rmxads.com", + "rocketmail.com", + "secure-adserver.com", + "staticflickr.com", + "tumblr.com", + "yahoo.co.jp", + "yahoo.com", + "yahooapis.com", + "yahooapis.jp", + "yahoofs.com", + "yieldmanager.com", + "yieldmanager.net", + "yimg.com", + "yimg.jp", + "yldmgrimg.net", + "ymail.com", + "yuilibrary.com", + "zenfs.com" + ] + }, + "Yandex": { + "properties": [ + "kinopoisk.ru", + "moikrug.ru", + "yadi.sk", + "yandex.by", + "yandex.com", + "yandex.com.tr", + "yandex.ru", + "yandex.st", + "yandex.ua" + ], + "resources": [ + "api-maps.yandex.ru", + "moikrug.ru", + "web-visor.com", + "yandex.by", + "yandex.com", + "yandex.com.tr", + "yandex.ru", + "yandex.st", + "yandex.ua" + ] + }, + "Ybrant Digital": { + "properties": ["addynamix.com", "brightcom.com", "luj.sdsjweb.com"], + "resources": [ + "addynamix.com", + "adserverplus.com", + "brightcom.com", + "oridian.com", + "ybrantdigital.com" + ] + }, + "YD": { + "properties": ["ydworld.com", "yieldivision.com"], + "resources": ["ydworld.com", "yieldivision.com"] + }, + "YellowHammer": { + "properties": ["yhmg.com"], + "resources": [ + "attracto.com", + "clickhype.com", + "yellowhammermg.com", + "yhmg.com" + ] + }, + "YellowTracker": { + "properties": ["yellowtracker.com"], + "resources": ["yellowtracker.com"] + }, + "Yes Ads": { + "properties": ["yesads.com"], + "resources": ["yesads.com"] + }, + "YieldAds": { + "properties": ["yieldads.com"], + "resources": ["yieldads.com"] + }, + "YieldBids": { + "properties": ["ybx.io"], + "resources": ["ybx.io"] + }, + "YieldBot": { + "properties": ["yieldbot.com"], + "resources": ["yldbt.com"] + }, + "YieldBuild": { + "properties": ["yieldbuild.com"], + "resources": ["yieldbuild.com"] + }, + "Yieldify": { + "properties": ["yieldify.com"], + "resources": ["yieldify.com"] + }, + "Yieldlab": { + "properties": ["yieldlab.de", "yieldlab.net"], + "resources": ["yieldlab.de", "yieldlab.net"] + }, + "Yieldmo": { + "properties": ["yieldmo.com"], + "resources": ["yieldmo.com"] + }, + "YieldNexus": { + "properties": ["ynxs.io"], + "resources": ["ynxs.io"] + }, + "YOC": { + "properties": ["yoc.com"], + "resources": ["yoc.com"] + }, + "Yoggrt": { + "properties": ["yoggrt.com"], + "resources": ["yoggrt.com"] + }, + "youknowbest": { + "properties": ["youknowbest.com"], + "resources": ["youknowbest.com"] + }, + "YSance": { + "properties": ["ysance.com"], + "resources": ["y-track.com"] + }, + "YuMe": { + "properties": ["yume.com", "yumenetworks.com"], + "resources": ["yume.com", "yumenetworks.com"] + }, + "ZafulAffiliate": { + "properties": ["zaful.com"], + "resources": ["zaful.com"] + }, + "Zango": { + "properties": ["metricsdirect.com", "zango.com"], + "resources": ["metricsdirect.com", "zango.com"] + }, + "zanox": { + "properties": ["buy.at", "zanox-affiliate.de", "zanox.com"], + "resources": ["buy.at", "zanox-affiliate.de", "zanox.com"] + }, + "zapunited": { + "properties": ["zaparena.com", "zapunited.com"], + "resources": ["zaparena.com", "zapunited.com"] + }, + "ZEDO": { + "properties": ["zedo.com", "zincx.com"], + "resources": ["zedo.com", "zincx.com"] + }, + "Zefir": { + "properties": ["ze-fir.com"], + "resources": ["ze-fir.com"] + }, + "Zemanta": { + "properties": ["zemanta.com"], + "resources": ["zemanta.com"] + }, + "Zendesk": { + "properties": ["zendesk.com"], + "resources": ["zendesk.com"] + }, + "ZestAd": { + "properties": ["zestad.com"], + "resources": ["zestad.com"] + }, + "Zeta Email Solutions": { + "properties": ["insightgrit.com", "zetaemailsolutions.com"], + "resources": ["insightgrit.com", "zetaemailsolutions.com"] + }, + "Zopim": { + "properties": ["zopim.com"], + "resources": ["zopim.com"] + }, + "Zumobi": { + "properties": ["zumobi.com"], + "resources": ["zumobi.com"] + }, + "ZypMedia": { + "properties": ["zypmedia.com"], + "resources": ["extend.tv", "zypmedia.com"] + } } diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/assets/errorPageScripts.js b/mobile/android/android-components/components/browser/errorpages/src/main/assets/errorPageScripts.js index 7836e30ac0..09804908f7 100644 --- a/mobile/android/android-components/components/browser/errorpages/src/main/assets/errorPageScripts.js +++ b/mobile/android/android-components/components/browser/errorpages/src/main/assets/errorPageScripts.js @@ -6,50 +6,54 @@ * Handles the parsing of the ErrorPages URI and then passes them to injectValues */ function parseQuery(queryString) { - if (queryString[0] === '?') { - queryString = queryString.substr(1); - } - const query = Object.fromEntries(new URLSearchParams(queryString).entries()); - injectValues(query) - updateShowSSL(query) - updateShowHSTS(query) -}; + if (queryString[0] === "?") { + queryString = queryString.substr(1); + } + const query = Object.fromEntries(new URLSearchParams(queryString).entries()); + injectValues(query); + updateShowSSL(query); + updateShowHSTS(query); +} /** * Updates the HTML elements based on the queryMap */ function injectValues(queryMap) { - const tryAgainButton = document.getElementById('errorTryAgain') - const continueHttpButton = document.getElementById("continueHttp") - + const tryAgainButton = document.getElementById("errorTryAgain"); + const continueHttpButton = document.getElementById("continueHttp"); - // Go through each element and inject the values - document.title = queryMap.title - tryAgainButton.innerHTML = queryMap.button - continueHttpButton.innerHTML = queryMap.continueHttpButton - document.getElementById('errorTitleText').innerHTML = queryMap.title - document.getElementById('errorShortDesc').innerHTML = queryMap.description - document.getElementById('advancedButton').innerHTML = queryMap.badCertAdvanced - document.getElementById('badCertTechnicalInfo').innerHTML = queryMap.badCertTechInfo - document.getElementById('advancedPanelBackButton').innerHTML = queryMap.badCertGoBack - document.getElementById('advancedPanelAcceptButton').innerHTML = queryMap.badCertAcceptTemporary - document.getElementById('advancedPanelAcceptButton').s = queryMap.badCertAcceptTemporary + // Go through each element and inject the values + document.title = queryMap.title; + tryAgainButton.innerHTML = queryMap.button; + continueHttpButton.innerHTML = queryMap.continueHttpButton; + document.getElementById("errorTitleText").innerHTML = queryMap.title; + document.getElementById("errorShortDesc").innerHTML = queryMap.description; + document.getElementById("advancedButton").innerHTML = + queryMap.badCertAdvanced; + document.getElementById("badCertTechnicalInfo").innerHTML = + queryMap.badCertTechInfo; + document.getElementById("advancedPanelBackButton").innerHTML = + queryMap.badCertGoBack; + document.getElementById("advancedPanelAcceptButton").innerHTML = + queryMap.badCertAcceptTemporary; + document.getElementById("advancedPanelAcceptButton").s = + queryMap.badCertAcceptTemporary; - // If no image is passed in, remove the element so as not to leave an empty iframe - const errorImage = document.getElementById('errorImage'); - if (!queryMap.image) { - errorImage.remove(); - } else { - errorImage.src = "resource://android/assets/" + queryMap.image; - } + // If no image is passed in, remove the element so as not to leave an empty iframe + const errorImage = document.getElementById("errorImage"); + if (!queryMap.image) { + errorImage.remove(); + } else { + errorImage.src = "resource://android/assets/" + queryMap.image; + } - if (queryMap.showContinueHttp === "true") { - // On the "HTTPS-Only" error page "Try again" doesn't make sense since reloading the page - // will just show an error page again. - tryAgainButton.style.display = 'none'; - } else { - continueHttpButton.style.display = 'none'; - } + if (queryMap.showContinueHttp === "true") { + // On the "HTTPS-Only" error page "Try again" doesn't make sense since reloading the page + // will just show an error page again. + tryAgainButton.style.display = "none"; + } else { + continueHttpButton.style.display = "none"; + } } var advancedVisible = false; @@ -58,65 +62,75 @@ var advancedVisible = false; * Used to show or hide the "advanced" button based on the validity of the SSL certificate */ function updateShowSSL(queryMap) { - /** @type {'true' | 'false'} */ - const showSSL = queryMap.showSSL; - if (typeof document.addCertException === "undefined") { - document.getElementById('advancedButton').style.display='none'; + /** @type {'true' | 'false'} */ + const showSSL = queryMap.showSSL; + if (typeof document.addCertException === "undefined") { + document.getElementById("advancedButton").style.display = "none"; + } else { + if (showSSL === "true") { + document.getElementById("advancedButton").style.display = "block"; } else { - if (showSSL === 'true') { - document.getElementById('advancedButton').style.display='block'; - } else { - document.getElementById('advancedButton').style.display='none'; - } + document.getElementById("advancedButton").style.display = "none"; } + } } /** * Used to show or hide the "accept" button based for the HSTS error page */ function updateShowHSTS(queryMap) { - const showHSTS = queryMap.showHSTS; - if (showHSTS === 'true') { - document.getElementById('advancedButton').style.display='block'; - document.getElementById('advancedPanelAcceptButton').style.display='none'; - } + const showHSTS = queryMap.showHSTS; + if (showHSTS === "true") { + document.getElementById("advancedButton").style.display = "block"; + document.getElementById("advancedPanelAcceptButton").style.display = "none"; + } } /** * Used to display information about the SSL certificate in `error_pages.html` */ function toggleAdvanced() { - if (advancedVisible) { - document.getElementById('badCertAdvancedPanel').style.display='none'; - } else { - document.getElementById('badCertAdvancedPanel').style.display='block'; - } - advancedVisible = !advancedVisible; + if (advancedVisible) { + document.getElementById("badCertAdvancedPanel").style.display = "none"; + } else { + document.getElementById("badCertAdvancedPanel").style.display = "block"; + } + advancedVisible = !advancedVisible; } /** * Used to bypass an SSL pages in `error_pages.html` */ async function acceptAndContinue(temporary) { - try { - await document.addCertException(temporary); - location.reload(); - } catch (error) { - console.error("Unexpected error: " + error) - } + try { + await document.addCertException(temporary); + location.reload(); + } catch (error) { + console.error("Unexpected error: " + error); + } } -document.addEventListener('DOMContentLoaded', function () { - if (window.history.length == 1) { - document.getElementById('advancedPanelBackButton').style.display = 'none'; - } else { - document.getElementById('advancedPanelBackButton').addEventListener('click', () => window.history.back()); - } +document.addEventListener("DOMContentLoaded", function () { + if (window.history.length == 1) { + document.getElementById("advancedPanelBackButton").style.display = "none"; + } else { + document + .getElementById("advancedPanelBackButton") + .addEventListener("click", () => window.history.back()); + } - document.getElementById('errorTryAgain').addEventListener('click', () => window.location.reload()); - document.getElementById('advancedButton').addEventListener('click', toggleAdvanced); - document.getElementById('advancedPanelAcceptButton').addEventListener('click', () => acceptAndContinue(true)); - document.getElementById('continueHttp').addEventListener('click', () => document.reloadWithHttpsOnlyException()); + document + .getElementById("errorTryAgain") + .addEventListener("click", () => window.location.reload()); + document + .getElementById("advancedButton") + .addEventListener("click", toggleAdvanced); + document + .getElementById("advancedPanelAcceptButton") + .addEventListener("click", () => acceptAndContinue(true)); + document + .getElementById("continueHttp") + .addEventListener("click", () => document.reloadWithHttpsOnlyException()); }); parseQuery(document.documentURI); diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/assets/error_page_js.html b/mobile/android/android-components/components/browser/errorpages/src/main/assets/error_page_js.html index 397e237303..a6ae19df7c 100644 --- a/mobile/android/android-components/components/browser/errorpages/src/main/assets/error_page_js.html +++ b/mobile/android/android-components/components/browser/errorpages/src/main/assets/error_page_js.html @@ -5,54 +5,64 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - - - - - - - - - -
- - - - - -
-

-
- - -
-
-
- - - - - - - - - - -
-
-

-
- -
-
- -
-
-
-
+ + + + + + + + + +
+ + + + +
+

+
+ + +
+
+
- + + + + + + + + + +
+
+

+
+ +
+
+ +
+
+
+
+ - - + + diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-azb/strings.xml index 158dbeb60f..b9ca4c9688 100644 --- a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-azb/strings.xml +++ b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-azb/strings.xml @@ -82,6 +82,21 @@ تانینمایان پروتکل + + فایل تاپیلمادی + + + فایلؽن ایشلدیلمه‌سینه ایجازه وئریلمه‌دی + + + پروکسی سرور باغلانتی‌نی رد ائتدی + + + پروکسی سرور تاپیلمادی + + + ضرر وئریجی سایت سورونو + ایستنمیه‌ن سایت سورونو diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-su/strings.xml index 2620051760..cc7efa64eb 100644 --- a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-su/strings.xml +++ b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-su/strings.xml @@ -115,7 +115,7 @@ Mode Oplén - Panyungsi keur leumpang dina mode luring na jeung teu tiasa nyambung ka barang anu dipénta.

+ Panyungsi keur leumpang dina modeu luring na jeung teu tiasa nyambung ka barang anu dipénta.

  • Ieu parangkat nyambung ka jaringan aktip heunteu?
  • Pencét "Cobi deui" pikeun ngalih ka modeu daring sareng ngamuat deui ieu kaca.
  • @@ -125,7 +125,7 @@ Port diwates pikeun kaamanan - Alamat anu dipénta nyebutkeun port (contona mozilla.org:80 pikeun port 80 di mozilla.org) galibna dipaké lain pikeun nyungsi Raramat. Panyungsi geus ngabolaykeun paménta pikeun kaamanan anjeun.

    ]]>
    + Alamat anu dipénta nyebutkeun port (contona mozilla.org:80 pikeun port 80 di mozilla.org) umumna dipaké lain pikeun nyungsi Raramat. Panyungsi geus ngabolaykeun paménta pikeun kaamanan anjeun.

    ]]>
    Sambunganana dirését @@ -206,7 +206,7 @@
]]>
- Protokol Teu Dipikawanoh + Protokol Teu Dipiwanoh Alamatna méré protocol (e.g., wxyz://) anu teu dipikawanoh ku pamaluruh, ku kituna pamaluruhna teu bisa nyambung kalawan bener ka lokana.

    @@ -273,7 +273,7 @@ Situs Aman Teu Sayaga - %1$s teu sayaga.]]> + %1$s teu sayaga.]]> Teruskeun ka Situs HTTP diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-sv-rSE/strings.xml index 9a0bf69388..4f22ac030e 100644 --- a/mobile/android/android-components/components/browser/errorpages/src/main/res/values-sv-rSE/strings.xml +++ b/mobile/android/android-components/components/browser/errorpages/src/main/res/values-sv-rSE/strings.xml @@ -241,7 +241,7 @@
    • Kan objektet ha bytt namn, tagits bort eller flyttat?
    • Finns det stavfel, stor bokstav eller annat typografiskt fel i adressen?
    • -
    • Har du tillräckliga åtkomstbehörigheter till den begärda objektet?
    • +
    • Har du tillräckliga åtkomstbehörigheter till det begärda objektet?
    ]]> diff --git a/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js b/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js index 20eada9a19..bc2c6ee35e 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js +++ b/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js @@ -2,80 +2,81 @@ * 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 web extension looks for known icon tags, collects URLs and available - * meta data (e.g. sizes) and passes that to the app code. - */ +/* + * This web extension looks for known icon tags, collects URLs and available + * meta data (e.g. sizes) and passes that to the app code. + */ /** * Takes a DOMTokenList and returns a String array. */ function sizesToList(sizes) { - if (sizes == null) { - return [] - } + if (sizes == null) { + return []; + } - if (!(sizes instanceof DOMTokenList)) { - return [] - } + if (!(sizes instanceof DOMTokenList)) { + return []; + } - return Array.from(sizes) + return Array.from(sizes); } function collect_link_icons(icons, rel) { - document.querySelectorAll('link[rel="' + rel + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': rel, - 'href': currentValue.href, - 'sizes': sizesToList(currentValue.sizes), - 'mimeType': currentValue.type - }); - }) + document + .querySelectorAll('link[rel="' + rel + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: rel, + href: currentValue.href, + sizes: sizesToList(currentValue.sizes), + mimeType: currentValue.type, + }); + }); } function collect_meta_property_icons(icons, property) { - document.querySelectorAll('meta[property="' + property + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': property, - 'href': currentValue.content - }) - } - ) + document + .querySelectorAll('meta[property="' + property + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: property, + href: currentValue.content, + }); + }); } function collect_meta_name_icons(icons, name) { - document.querySelectorAll('meta[name="' + name + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': name, - 'href': currentValue.content - }) - } - ) + document + .querySelectorAll('meta[name="' + name + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: name, + href: currentValue.content, + }); + }); } let icons = []; -collect_link_icons(icons, 'icon'); -collect_link_icons(icons, 'shortcut icon'); -collect_link_icons(icons, 'fluid-icon') -collect_link_icons(icons, 'apple-touch-icon') -collect_link_icons(icons, 'image_src') -collect_link_icons(icons, 'apple-touch-icon image_src') -collect_link_icons(icons, 'apple-touch-icon-precomposed') +collect_link_icons(icons, "icon"); +collect_link_icons(icons, "shortcut icon"); +collect_link_icons(icons, "fluid-icon"); +collect_link_icons(icons, "apple-touch-icon"); +collect_link_icons(icons, "image_src"); +collect_link_icons(icons, "apple-touch-icon image_src"); +collect_link_icons(icons, "apple-touch-icon-precomposed"); -collect_meta_property_icons(icons, 'og:image') -collect_meta_property_icons(icons, 'og:image:url') -collect_meta_property_icons(icons, 'og:image:secure_url') +collect_meta_property_icons(icons, "og:image"); +collect_meta_property_icons(icons, "og:image:url"); +collect_meta_property_icons(icons, "og:image:secure_url"); -collect_meta_name_icons(icons, 'twitter:image') -collect_meta_name_icons(icons, 'msapplication-TileImage') +collect_meta_name_icons(icons, "twitter:image"); +collect_meta_name_icons(icons, "msapplication-TileImage"); let message = { - 'url': document.location.href, - 'icons': icons -} + url: document.location.href, + icons: icons, +}; browser.runtime.sendNativeMessage("MozacBrowserIcons", message); diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt index 3bf5c2b2ff..fe44cfc6be 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt @@ -38,10 +38,12 @@ import mozilla.components.browser.icons.extension.IconMessageHandler import mozilla.components.browser.icons.generator.DefaultIconGenerator import mozilla.components.browser.icons.generator.IconGenerator import mozilla.components.browser.icons.loader.DataUriIconLoader +import mozilla.components.browser.icons.loader.DefaultMemoryInfoProvider import mozilla.components.browser.icons.loader.DiskIconLoader import mozilla.components.browser.icons.loader.HttpIconLoader import mozilla.components.browser.icons.loader.IconLoader import mozilla.components.browser.icons.loader.MemoryIconLoader +import mozilla.components.browser.icons.loader.MemoryInfoProvider import mozilla.components.browser.icons.loader.NonBlockingHttpIconLoader import mozilla.components.browser.icons.pipeline.IconResourceComparator import mozilla.components.browser.icons.preparer.DiskIconPreparer @@ -91,6 +93,7 @@ class BrowserIcons constructor( private val context: Context, httpClient: Client, private val generator: IconGenerator = DefaultIconGenerator(), + private val memoryInfoProvider: MemoryInfoProvider = DefaultMemoryInfoProvider(context), private val preparers: List = listOf( TippyTopIconPreparer(context.assets), MemoryIconPreparer(sharedMemoryCache), @@ -99,7 +102,10 @@ class BrowserIcons constructor( internal var loaders: List = listOf( MemoryIconLoader(sharedMemoryCache), DiskIconLoader(sharedDiskCache), - HttpIconLoader(httpClient), + HttpIconLoader( + httpClient = httpClient, + memoryInfoProvider = memoryInfoProvider, + ), DataUriIconLoader(), ), private val decoders: List = listOf( @@ -120,7 +126,10 @@ class BrowserIcons constructor( private val maximumSize = context.resources.getDimensionPixelSize(R.dimen.mozac_browser_icons_maximum_size) private val minimumSize = context.resources.getDimensionPixelSize(R.dimen.mozac_browser_icons_minimum_size) private val scope = CoroutineScope(jobDispatcher) - private val backgroundHttpIconLoader = NonBlockingHttpIconLoader(httpClient) { request, resource, result -> + private val backgroundHttpIconLoader = NonBlockingHttpIconLoader( + httpClient = httpClient, + memoryInfoProvider = DefaultMemoryInfoProvider(context), + ) { request, resource, result -> val desiredSize = request.getDesiredSize(context, minimumSize, maximumSize) val icon = decodeIconLoaderResult(result, decoders, desiredSize) diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt index 430f46f3ec..d3217bc4b2 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt @@ -12,26 +12,30 @@ import androidx.core.net.toUri import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.Headers import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Response import mozilla.components.concept.fetch.isSuccess import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.android.net.isHttpOrHttps import mozilla.components.support.ktx.kotlin.sanitizeURL +import java.io.ByteArrayOutputStream import java.io.IOException import java.util.concurrent.TimeUnit private const val CONNECT_TIMEOUT = 2L // Seconds private const val READ_TIMEOUT = 10L // Seconds +private const val MAX_DOWNLOAD_BYTES = 1048576 // 1MB /** * [IconLoader] implementation that will try to download the icon for resources that point to an http(s) URL. */ open class HttpIconLoader( private val httpClient: Client, + private val memoryInfoProvider: MemoryInfoProvider, ) : IconLoader { - private val logger = Logger("HttpIconLoader") private val failureCache = FailureCache() + private val logger = Logger("HttpIconLoader") override fun load(context: Context, request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { if (!shouldDownload(resource)) { @@ -78,10 +82,45 @@ open class HttpIconLoader( protected fun shouldDownload(resource: IconRequest.Resource): Boolean { return resource.url.sanitizeURL().toUri().isHttpOrHttps && !failureCache.hasFailedRecently(resource.url) } -} -private fun Response.toIconLoaderResult() = body.useStream { - IconLoader.Result.BytesResult(it.readBytes(), Icon.Source.DOWNLOAD) + private fun Response.toIconLoaderResult(): IconLoader.Result { + // Compare the Response Content-Length header with the available memory on device + val contentLengthHeader = headers[Headers.Names.CONTENT_LENGTH] + if (!contentLengthHeader.isNullOrEmpty()) { + val contentLength = contentLengthHeader.toLong() + return if (contentLength > MAX_DOWNLOAD_BYTES || contentLength > memoryInfoProvider.getAvailMem()) { + IconLoader.Result.NoResult + } else { + // Load the icon without reading to buffers since the checks above passed + body.useStream { + IconLoader.Result.BytesResult(it.readBytes(), Icon.Source.DOWNLOAD) + } + } + } else { + // Read the response body in chunks and check with available memory to prevent exceeding it + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + return ByteArrayOutputStream().use { outStream -> + body.useStream { inputStream -> + var bytesRead = 0 + var bytesInChunk: Int + + while (inputStream.read(buffer).also { bytesInChunk = it } != -1) { + outStream.write(buffer, 0, bytesInChunk) + bytesRead += bytesInChunk + + if (bytesRead > MAX_DOWNLOAD_BYTES || bytesRead > memoryInfoProvider.getAvailMem()) { + return@useStream IconLoader.Result.NoResult + } + + if (bytesInChunk < DEFAULT_BUFFER_SIZE) { + break + } + } + IconLoader.Result.BytesResult(outStream.toByteArray(), Icon.Source.DOWNLOAD) + } + } + } + } } private const val MAX_FAILURE_URLS = 25 diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt new file mode 100644 index 0000000000..52f8bcbb28 --- /dev/null +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.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.browser.icons.loader + +import android.app.ActivityManager +import android.content.Context +import androidx.core.content.ContextCompat + +/** + * This class provides information about the device memory info without exposing the android + * framework APIs directly, making it easier to test the code that depends on it. + */ +interface MemoryInfoProvider { + /** + * Returns the device's available memory + */ + fun getAvailMem(): Long +} + +/** + * This class retrieves the available memory on device using activity manager. + */ +class DefaultMemoryInfoProvider(private val context: Context) : MemoryInfoProvider { + override fun getAvailMem(): Long { + val activityManager = ContextCompat.getSystemService(context, ActivityManager::class.java) + val memoryInfo = ActivityManager.MemoryInfo() + activityManager?.getMemoryInfo(memoryInfo) + return memoryInfo.availMem + } +} diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt index c3203dc13f..fcd64662d6 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt @@ -24,9 +24,10 @@ import mozilla.components.concept.fetch.Client */ class NonBlockingHttpIconLoader( httpClient: Client, + memoryInfoProvider: MemoryInfoProvider, private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), private val loadCallback: (IconRequest, IconRequest.Resource, IconLoader.Result) -> Unit, -) : HttpIconLoader(httpClient) { +) : HttpIconLoader(httpClient, memoryInfoProvider) { override fun load(context: Context, request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { if (!shouldDownload(resource)) { return IconLoader.Result.NoResult diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt index 67a392d0a2..b14b5a30b3 100644 --- a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import mozilla.components.browser.icons.generator.IconGenerator +import mozilla.components.browser.icons.loader.MemoryInfoProvider import mozilla.components.concept.engine.manifest.Size import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient import mozilla.components.support.test.any @@ -46,6 +47,11 @@ import java.io.OutputStream @ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class BrowserIconsTest { + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @get:Rule val coroutinesTestRule = MainCoroutineRule() @@ -65,7 +71,12 @@ class BrowserIconsTest { `when`(generator.generate(any(), any())).thenReturn(mockedIcon) val request = IconRequest(url = "https://www.mozilla_test.org") - val icon = BrowserIcons(testContext, httpClient = mock(), generator = generator) + val icon = BrowserIcons( + context = testContext, + httpClient = mock(), + generator = generator, + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) .loadIcon(request) assertEquals(mockedIcon, icon.await()) @@ -114,6 +125,7 @@ class BrowserIconsTest { val icon = BrowserIcons( testContext, httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), ).loadIcon(request).await() assertNotNull(icon) @@ -139,7 +151,11 @@ class BrowserIconsTest { server.start() try { - val icons = BrowserIcons(testContext, httpClient = HttpURLConnectionClient()) + val icons = BrowserIcons( + context = testContext, + httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) val request = IconRequest( url = "https://www.mozilla.org", @@ -182,7 +198,11 @@ class BrowserIconsTest { server.start() try { - val icons = BrowserIcons(testContext, httpClient = HttpURLConnectionClient()) + val icons = BrowserIcons( + context = testContext, + httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) val request = IconRequest( url = "https://www.mozilla.org", diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt index 3670066921..872440900a 100644 --- a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt @@ -34,6 +34,11 @@ import java.io.InputStream @RunWith(AndroidJUnit4::class) class HttpIconLoaderTest { + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @Test fun `Loader downloads data and uses appropriate headers`() { @@ -56,7 +61,7 @@ class HttpIconLoaderTest { server.start() try { - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) val result = loader.load( mock(), mock(), @@ -94,7 +99,7 @@ class HttpIconLoaderTest { fun `Loader will not perform any requests for data uris`() { val client: Client = mock() - val result = HttpIconLoader(client).load( + val result = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)).load( mock(), mock(), IconRequest.Resource( @@ -112,7 +117,7 @@ class HttpIconLoaderTest { fun `Request has timeouts applied`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -144,7 +149,7 @@ class HttpIconLoaderTest { fun `NoResult is returned for non-successful requests`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -170,7 +175,7 @@ class HttpIconLoaderTest { fun `Loader will not try to load URL again that just recently failed`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -203,7 +208,7 @@ class HttpIconLoaderTest { val client: Client = mock() doThrow(IOException("Mock")).`when`(client).fetch(any()) - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) val resource = IconRequest.Resource( url = "https://www.example.org", @@ -223,7 +228,7 @@ class HttpIconLoaderTest { } } - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -241,11 +246,165 @@ class HttpIconLoaderTest { assertEquals(IconLoader.Result.NoResult, loader.load(mock(), mock(), resource)) } + @Test + fun `Loader will return NoResult for response with large Content-Length size`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + // Create a mock Response object with the Content-Length header set to a large size + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).addHeader("Content-Length", "2048576"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader will return NoResult for valid Content-Length size and low available memory`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).addHeader("Content-Length", "10000"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(availMem = 0)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader will return NoResult for null Content-Length header and low available memory`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).removeHeader("Content-Length"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(availMem = 0)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader downloads data for null Content-Length header and response size within limits`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).removeHeader("Content-Length"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + assertTrue("Result should match BytesResult", result is IconLoader.Result.BytesResult) + val data = (result as IconLoader.Result.BytesResult).bytes + assertTrue("Data should not be empty", data.isNotEmpty()) + } finally { + server.shutdown() + } + } + } + @Test fun `Loader will sanitize URL`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt index 6b2fe8d8ad..2a2da044bf 100644 --- a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt @@ -45,6 +45,11 @@ class NonBlockingHttpIconLoaderTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() private val scope = coroutinesTestRule.scope + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @Test fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runTestOnMain { @@ -71,7 +76,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -106,7 +111,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -132,7 +137,7 @@ class NonBlockingHttpIconLoaderTest { @Test fun `Request has timeouts applied`() = runTestOnMain { val client: Client = mock() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", @@ -165,7 +170,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -198,7 +203,7 @@ class NonBlockingHttpIconLoaderTest { @Test fun `Loader will not try to load URL again that just recently failed`() = runTestOnMain { val client: Client = mock() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", @@ -231,7 +236,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -256,7 +261,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -292,7 +297,7 @@ class NonBlockingHttpIconLoaderTest { fun `Loader will sanitize URL`() = runTestOnMain { val client: Client = mock() val captor = argumentCaptor() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", diff --git a/mobile/android/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/item/CustomTooltip.kt b/mobile/android/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/item/CustomTooltip.kt index 9e7ce8b674..6f83c1ffd9 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/item/CustomTooltip.kt +++ b/mobile/android/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/item/CustomTooltip.kt @@ -14,7 +14,6 @@ import android.view.WindowManager import android.widget.LinearLayout import android.widget.PopupWindow import android.widget.TextView -import androidx.core.view.ViewCompat import androidx.core.widget.PopupWindowCompat import mozilla.components.browser.menu.R @@ -34,7 +33,7 @@ internal class CustomTooltip private constructor( } override fun onLongClick(view: View): Boolean { - if (ViewCompat.isAttachedToWindow(anchor)) { + if (anchor.isAttachedToWindow()) { show() anchor.addOnAttachStateChangeListener(this) } diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-be/strings.xml index f3b3bb1755..9edc4724a9 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-be/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-be/strings.xml @@ -10,6 +10,12 @@ Пашырэнні Менеджар дадаткаў + + Менеджар пашырэнняў Перайсці ўверх - + + Дадаткі, перайсці ўніз + + Пашырэнні, перайсці ўверх + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-br/strings.xml index 548cbd7785..f8e14c773d 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-br/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-br/strings.xml @@ -14,4 +14,8 @@ Merañ an askouezhioù Adpignat - + + Enlugelladoù, pignat + + Askouezhioù, pignat + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-cak/strings.xml index 7ccb1be616..7623a1f39f 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-cak/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-cak/strings.xml @@ -5,11 +5,17 @@ Ya\'on ruq\'ij - Taq tz\'aqat + Taq tz\'aqat + + Taq k\'amal - Kinuk\'samajel taq Tz\'aqat + Kinuk\'samajel taq Tz\'aqat + + Runuk\'samajel taq K\'amal Tib\'an okem ajsik - Taq tz\'aqat, tijote\' chi rokem - + Taq tz\'aqat, tijote\' chi rokem + + Taq k\'amal, tok q\'anij + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-eo/strings.xml index 63b30a2150..1ca46d5302 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-eo/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-eo/strings.xml @@ -5,11 +5,17 @@ Elstarigitaj - Aldonaĵoj + Aldonaĵoj + + Etendaĵoj - Administrilo de aldonaĵoj + Administrilo de aldonaĵoj + + Administranto de etendaĵoj Iri supren - Aldonaĵoj, supren - + Aldonaĵoj, supren + + Etendaĵoj, supren + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-eu/strings.xml index 53f1a556a3..b69e874c01 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-eu/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-eu/strings.xml @@ -5,11 +5,17 @@ Nabarmendua - Gehigarriak + Gehigarriak + + Hedapenak - Gehigarrien kudeatzailea + Gehigarrien kudeatzailea + + Hedapenen kudeatzailea Nabigatu gora - Gehigarriak, nabigatu gora - + Gehigarriak, nabigatu gora + + Hedapenak, nabigatu gora + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-kab/strings.xml index f36a1014b1..58d8809a2c 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-kab/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-kab/strings.xml @@ -10,6 +10,12 @@ Isiɣzaf Amsefrak n izegrar + + Amsefrak n yisiɣzaf Inig d asawen - + + Izegrar niḍen, ulin-d + + Isiɣzaf niḍen, ulin-d + diff --git a/mobile/android/android-components/components/browser/menu/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/browser/menu/src/main/res/values-sc/strings.xml index 4fd82314b3..0a94a4fc21 100644 --- a/mobile/android/android-components/components/browser/menu/src/main/res/values-sc/strings.xml +++ b/mobile/android/android-components/components/browser/menu/src/main/res/values-sc/strings.xml @@ -5,9 +5,15 @@ In evidèntzia - Cumplementos + Cumplementos + + Estensiones - Gestore de cumplementos + Gestore de cumplementos + + Gestore de estensiones Nàviga in artu - + + Estensiones, torra a coa + diff --git a/mobile/android/android-components/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/BrowserMenuPositioningTest.kt b/mobile/android/android-components/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/BrowserMenuPositioningTest.kt index d9be746793..23c421bd7c 100644 --- a/mobile/android/android-components/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/BrowserMenuPositioningTest.kt +++ b/mobile/android/android-components/components/browser/menu2/src/test/java/mozilla/components/browser/menu2/ext/BrowserMenuPositioningTest.kt @@ -7,7 +7,6 @@ package mozilla.components.browser.menu2.ext import android.graphics.Rect import android.view.View import android.widget.PopupWindow -import androidx.core.view.ViewCompat import androidx.recyclerview.widget.RecyclerView import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.menu2.R @@ -755,9 +754,9 @@ internal fun createAnchor(x: Int, y: Int, isRTL: Boolean = false): View { }.`when`(view).getLocationInWindow(any()) if (isRTL) { - doReturn(ViewCompat.LAYOUT_DIRECTION_RTL).`when`(view).layoutDirection + doReturn(View.LAYOUT_DIRECTION_RTL).`when`(view).layoutDirection } else { - doReturn(ViewCompat.LAYOUT_DIRECTION_LTR).`when`(view).layoutDirection + doReturn(View.LAYOUT_DIRECTION_LTR).`when`(view).layoutDirection } doReturn(10).`when`(view).height doReturn(15).`when`(view).width diff --git a/mobile/android/android-components/components/browser/session-storage/src/androidTest/assets/index.html b/mobile/android/android-components/components/browser/session-storage/src/androidTest/assets/index.html index b511ab2f19..199b5f61d4 100644 --- a/mobile/android/android-components/components/browser/session-storage/src/androidTest/assets/index.html +++ b/mobile/android/android-components/components/browser/session-storage/src/androidTest/assets/index.html @@ -1,8 +1,8 @@ - + Restore Test - - + +

    Hello World

    - + diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt index cd492b18e5..42ad594c8f 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt @@ -1047,6 +1047,30 @@ sealed class TranslationsAction : BrowserAction() { val setting: Boolean, ) : TranslationsAction(), ActionWithTab + /** + * Sets the translations offer setting on the global store. + * The translations offer setting controls when to offer a translation on a page. + * + * See [SetPageSettingsAction] for setting the offer setting on the session store. + * + * @property offerTranslation The offer setting to set. + */ + data class SetGlobalOfferTranslateSettingAction( + val offerTranslation: Boolean, + ) : TranslationsAction() + + /** + * Updates the specified translation offer setting on the translation engine and ensures the final + * state on the global store remains in-sync. + * + * See [UpdatePageSettingAction] for updating the offer setting on the session store. + * + * @property offerTranslation The offer setting to set. + */ + data class UpdateGlobalOfferTranslateSettingAction( + val offerTranslation: Boolean, + ) : TranslationsAction() + /** * Sets the map of BCP 47 language codes (key) and the [LanguageSetting] option (value). * @@ -1057,27 +1081,37 @@ sealed class TranslationsAction : BrowserAction() { val languageSettings: Map, ) : TranslationsAction() + /** + * Updates the specified translation language setting on the translation engine and ensures the + * final state on the global store remains in-sync. + * + * See [UpdatePageSettingAction] for updating the language setting on the session store. + * + * @property languageCode The BCP-47 language code to update. + * @property setting The [LanguageSetting] for the language. + */ + data class UpdateLanguageSettingsAction( + val languageCode: String, + val setting: LanguageSetting, + ) : TranslationsAction() + /** * Sets the list of sites that the user has opted to never translate. * - * @property tabId The ID of the tab the [EngineSession] that requested the list. * @property neverTranslateSites The never translate sites. */ data class SetNeverTranslateSitesAction( - override val tabId: String, val neverTranslateSites: List, - ) : TranslationsAction(), ActionWithTab + ) : TranslationsAction() /** * Remove from the list of sites the user has opted to never translate. * - * @property tabId The ID of the tab the [EngineSession] that requested the removal. * @property origin A site origin URI that will have the specified never translate permission set. */ data class RemoveNeverTranslateSiteAction( - override val tabId: String, val origin: String, - ) : TranslationsAction(), ActionWithTab + ) : TranslationsAction() /** * Sets the list of language machine learning translation models the translation engine has available. @@ -1255,6 +1289,7 @@ sealed class EngineAction : BrowserAction() { override val tabId: String, val skipLoading: Boolean = false, val followupAction: BrowserAction? = null, + val includeParent: Boolean = false, ) : EngineAction(), ActionWithTab /** @@ -1265,6 +1300,7 @@ sealed class EngineAction : BrowserAction() { val url: String, val flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(), val additionalHeaders: Map? = null, + val includeParent: Boolean = false, ) : EngineAction(), ActionWithTab /** @@ -1402,6 +1438,7 @@ sealed class EngineAction : BrowserAction() { val engineSession: EngineSession, val timestamp: Long = Clock.elapsedRealtime(), val skipLoading: Boolean = false, + val includeParent: Boolean = false, ) : EngineAction(), ActionWithTab /** diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddleware.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddleware.kt index 6f807eff16..e83fefb9e1 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddleware.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/CreateEngineSessionMiddleware.kt @@ -69,6 +69,7 @@ internal class CreateEngineSessionMiddleware( logger, store, action.tabId, + action.includeParent, ) action.followupAction?.let { @@ -85,6 +86,7 @@ private fun getOrCreateEngineSession( logger: Logger, store: Store, tabId: String, + includeParent: Boolean, ): EngineSession? { val tab = store.state.findTabOrCustomTab(tabId) if (tab == null) { @@ -102,7 +104,7 @@ private fun getOrCreateEngineSession( return it } - return createEngineSession(engine, logger, store, tab) + return createEngineSession(engine, logger, store, tab, includeParent) } @MainThread @@ -111,6 +113,7 @@ private fun createEngineSession( logger: Logger, store: Store, tab: SessionState, + includeParent: Boolean, ): EngineSession { val engineSession = engine.createSession(tab.content.private, tab.contextId) logger.debug("Created engine session for tab ${tab.id}") @@ -127,6 +130,7 @@ private fun createEngineSession( tab.id, engineSession, skipLoading = skipLoading, + includeParent = includeParent, ), ) diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddleware.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddleware.kt index 1b7744ff41..82298ef2ce 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddleware.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddleware.kt @@ -70,11 +70,11 @@ internal class EngineDelegateMiddleware( // session is already pointing to. Creating an EngineSession will do exactly // that in the linking step. So let's do that. Otherwise we would load the URL // twice. - store.dispatch(EngineAction.CreateEngineSessionAction(action.tabId)) + store.dispatch(EngineAction.CreateEngineSessionAction(action.tabId, includeParent = action.includeParent)) return@launch } - val parentEngineSession = if (tab is TabSessionState) { + val parentEngineSession = if (action.includeParent && tab is TabSessionState) { tab.parentId?.let { store.state.findTabOrCustomTab(it)?.engineState?.engineSession } } else { null diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/LinkingMiddleware.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/LinkingMiddleware.kt index c6f1d01d39..ba714b5e90 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/LinkingMiddleware.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/LinkingMiddleware.kt @@ -37,7 +37,13 @@ internal class LinkingMiddleware( when (action) { is TabListAction.AddTabAction -> { if (action.tab.engineState.engineSession != null && action.tab.engineState.engineObserver == null) { - engineObserver = link(context, action.tab.engineState.engineSession, action.tab) + engineObserver = link( + context, + action.tab.engineState.engineSession, + action.tab, + skipLoading = true, + includeParent = false, + ) } } is TabListAction.AddMultipleTabsAction -> { @@ -58,7 +64,7 @@ internal class LinkingMiddleware( when (action) { is EngineAction.LinkEngineSessionAction -> { context.state.findTabOrCustomTab(action.tabId)?.let { tab -> - engineObserver = link(context, action.engineSession, tab, action.skipLoading) + engineObserver = link(context, action.engineSession, tab, action.skipLoading, action.includeParent) } } else -> { @@ -77,6 +83,7 @@ internal class LinkingMiddleware( engineSession: EngineSession, tab: SessionState, skipLoading: Boolean = true, + includeParent: Boolean, ): Pair { val observer = EngineObserver(tab.id, context.store) engineSession.register(observer) @@ -91,7 +98,7 @@ internal class LinkingMiddleware( // tab, but opened by an extension e.g. via browser.tabs.update. performLoadOnMainThread(engineSession, tab.content.url, loadFlags = tab.engineState.initialLoadFlags) } else { - val parentEngineSession = if (tab is TabSessionState) { + val parentEngineSession = if (includeParent && tab is TabSessionState) { tab.parentId?.let { context.state.findTabOrCustomTab(it)?.engineState?.engineSession } } else { null diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddleware.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddleware.kt index 81e3b8b48b..3c8e645a97 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddleware.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddleware.kt @@ -79,6 +79,12 @@ class TranslationsMiddleware( requestPageSettings(context, action.tabId) } } + TranslationOperation.FETCH_OFFER_SETTING -> { + scope.launch { + requestOfferSetting(context, action.tabId) + } + } + TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> { scope.launch { requestLanguageSettings(context, action.tabId) @@ -86,7 +92,7 @@ class TranslationsMiddleware( } TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> { scope.launch { - getNeverTranslateSites(context, action.tabId) + requestNeverTranslateSites(context, action.tabId) } } TranslationOperation.TRANSLATE, @@ -108,7 +114,7 @@ class TranslationsMiddleware( is TranslationsAction.RemoveNeverTranslateSiteAction -> { scope.launch { - removeNeverTranslateSite(context, action.tabId, action.origin) + removeNeverTranslateSite(context, action.origin) } } @@ -151,6 +157,25 @@ class TranslationsMiddleware( } } } + + is TranslationsAction.UpdateGlobalOfferTranslateSettingAction -> { + scope.launch { + updateAlwaysOfferPopupPageSetting( + setting = action.offerTranslation, + ) + } + } + + is TranslationsAction.UpdateLanguageSettingsAction -> { + scope.launch { + updateLanguageSetting( + context = context, + languageCode = action.languageCode, + setting = action.setting, + ) + } + } + else -> { // no-op } @@ -168,6 +193,8 @@ class TranslationsMiddleware( * Language Support - [requestSupportedLanguages] * Language Models - [requestLanguageModels] * Language Settings - [requestLanguageSettings] + * Never Translate Sites List - [requestNeverTranslateSites] + * Offer Setting - [requestOfferSetting] * * @param context Context to use to dispatch to the store. */ @@ -177,6 +204,8 @@ class TranslationsMiddleware( requestSupportedLanguages(context) requestLanguageModels(context) requestLanguageSettings(context) + requestNeverTranslateSites(context) + requestOfferSetting(context) } /** @@ -336,22 +365,22 @@ class TranslationsMiddleware( } /** - * Retrieves the list of never translate sites using [scope] and dispatches the result to the - * store via [TranslationsAction.SetNeverTranslateSitesAction] or else dispatches the failure - * [TranslationsAction.TranslateExceptionAction]. + * Retrieves the list of never translate sites and dispatches the result to the + * store via [TranslationsAction.SetNeverTranslateSitesAction] or else + * dispatches the failure via [TranslationsAction.EngineExceptionAction] and + * when a [tabId] is provided, [TranslationsAction.TranslateExceptionAction]. * * @param context Context to use to dispatch to the store. * @param tabId Tab ID associated with the request. */ - private fun getNeverTranslateSites( + private fun requestNeverTranslateSites( context: MiddlewareContext, - tabId: String, + tabId: String? = null, ) { engine.getNeverTranslateSiteList( onSuccess = { context.store.dispatch( TranslationsAction.SetNeverTranslateSitesAction( - tabId = tabId, neverTranslateSites = it, ), ) @@ -360,12 +389,19 @@ class TranslationsMiddleware( onError = { context.store.dispatch( - TranslationsAction.TranslateExceptionAction( - tabId = tabId, - operation = TranslationOperation.FETCH_NEVER_TRANSLATE_SITES, - translationError = TranslationError.CouldNotLoadNeverTranslateSites(it), + TranslationsAction.EngineExceptionAction( + error = TranslationError.CouldNotLoadNeverTranslateSites(it), ), ) + if (tabId != null) { + context.store.dispatch( + TranslationsAction.TranslateExceptionAction( + tabId = tabId, + operation = TranslationOperation.FETCH_NEVER_TRANSLATE_SITES, + translationError = TranslationError.CouldNotLoadNeverTranslateSites(it), + ), + ) + } logger.error("Error requesting never translate sites: ", it) }, ) @@ -377,38 +413,23 @@ class TranslationsMiddleware( * [TranslationsAction.TranslateExceptionAction]. * * @param context Context to use to dispatch to the store. - * @param tabId Tab ID associated with the request. * @param origin A site origin URI that will have the specified never translate permission set. */ private fun removeNeverTranslateSite( context: MiddlewareContext, - tabId: String, origin: String, ) { engine.setNeverTranslateSpecifiedSite( origin = origin, setting = false, onSuccess = { - logger.info("Success requesting never translate sites.") - - // Fetch page settings to ensure the state matches the engine. - context.store.dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tabId, - operation = TranslationOperation.FETCH_PAGE_SETTINGS, - ), - ) + logger.info("Success changing never translate sites.") }, onError = { logger.error("Error removing site from never translate list: ", it) - - // Fetch never translate sites to ensure the state matches the engine. - context.store.dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tabId, - operation = TranslationOperation.FETCH_NEVER_TRANSLATE_SITES, - ), - ) + // Fetch never translate sites to ensure the state matches the engine, because it + // was proactively removed in the reducer. + requestNeverTranslateSites(context) }, ) } @@ -471,6 +492,38 @@ class TranslationsMiddleware( } } + /** + * Retrieves the setting to always offer to translate and dispatches the result to the + * store via [TranslationsAction.SetGlobalOfferTranslateSettingAction]. Will additionally + * dispatch a request to update page settings, when a [tabId] is provided. + * + * @param context Context to use to dispatch to the store. + * @param tabId Tab ID associated with the request. + */ + private fun requestOfferSetting( + context: MiddlewareContext, + tabId: String? = null, + ) { + logger.info("Requesting offer setting.") + val alwaysOfferPopup: Boolean = engine.getTranslationsOfferPopup() + + context.store.dispatch( + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = alwaysOfferPopup, + ), + ) + + if (tabId != null) { + // Fetch page settings to ensure the state matches the engine. + context.store.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = tabId, + operation = TranslationOperation.FETCH_PAGE_SETTINGS, + ), + ) + } + } + /** * Fetches the always or never language setting synchronously from the engine. Will * return null if an error occurs. @@ -708,8 +761,8 @@ class TranslationsMiddleware( /** * Updates the language settings with the [Engine]. * - * If an error occurs, then the method will request the page settings be re-fetched and set on - * the browser store. + * If an error occurs, and a [tabId] is known then the method will request the page settings be + * re-fetched and set on the browser store. * * @param context The context used to request the page settings. * @param tabId Tab ID associated with the request. @@ -718,7 +771,7 @@ class TranslationsMiddleware( */ private fun updateLanguageSetting( context: MiddlewareContext, - tabId: String, + tabId: String? = null, languageCode: String, setting: LanguageSetting, ) { @@ -729,26 +782,37 @@ class TranslationsMiddleware( languageSetting = setting, onSuccess = { - // Ensure the session's page settings remain in sync with this update. - context.store.dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tabId, - operation = TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS, - ), - ) + // Value was proactively updated in [TranslationsStateReducer] for + // [TranslationsBrowserState.languageSettings] + + if (tabId != null) { + // Ensure the session's page settings remain in sync with this update. + context.store.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = tabId, + operation = TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS, + ), + ) + } + logger.info("Successfully updated the language preference.") }, onError = { logger.error("Could not update the language preference.", it) + // The browser store [TranslationsBrowserState.languageSettings] is out of sync, + // re-request to sync the state. + requestLanguageSettings(context, tabId) - // Fetch page settings to ensure the state matches the engine. - context.store.dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tabId, - operation = TranslationOperation.FETCH_PAGE_SETTINGS, - ), - ) + if (tabId != null) { + // Fetch page settings to ensure the state matches the engine. + context.store.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = tabId, + operation = TranslationOperation.FETCH_PAGE_SETTINGS, + ), + ) + } }, ) } diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/ext/TabSessionState.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/ext/TabSessionState.kt new file mode 100644 index 0000000000..b0f1c2e9e9 --- /dev/null +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/ext/TabSessionState.kt @@ -0,0 +1,18 @@ +/* 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.state.ext + +import mozilla.components.browser.state.state.TabSessionState + +/** + * Returns the URL of the [TabSessionState]. + */ +fun TabSessionState.getUrl(): String? { + return if (this.readerState.active) { + this.readerState.activeUrl + } else { + this.content.url + } +} diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/reducer/TranslationsStateReducer.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/reducer/TranslationsStateReducer.kt index 266964455e..89c7ee3ed4 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/reducer/TranslationsStateReducer.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/reducer/TranslationsStateReducer.kt @@ -62,11 +62,8 @@ internal object TranslationsStateReducer { } // Checking for if the translations engine is in the fully translated state or not based - // on the values of the translation pair. - if (action.translationEngineState.requestedTranslationPair == null || - action.translationEngineState.requestedTranslationPair?.fromLanguage == null || - action.translationEngineState.requestedTranslationPair?.toLanguage == null - ) { + // on if a visual change has occurred on the browser. + if (action.translationEngineState.hasVisibleChange != true) { // In an untranslated state var translationsError: TranslationError? = null if (action.translationEngineState.detectedLanguages?.supportedDocumentLang == false) { @@ -111,9 +108,9 @@ internal object TranslationsStateReducer { is TranslationsAction.TranslateSuccessAction -> { when (action.operation) { TranslationOperation.TRANSLATE -> { + // The isTranslated state will be identified on a translation state change. state.copyWithTranslationsState(action.tabId) { it.copy( - isTranslated = true, isTranslateProcessing = false, translationError = null, ) @@ -163,6 +160,17 @@ internal object TranslationsStateReducer { } } + TranslationOperation.FETCH_OFFER_SETTING -> { + // Reset the error state, and then generally expect + // [TranslationsAction.SetGlobalOfferTranslateSettingAction] to update state in the + // success case. + state.copyWithTranslationsState(action.tabId) { + it.copy( + settingsError = null, + ) + } + } + TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> { state.copy( translationEngine = state.translationEngine.copy( @@ -175,11 +183,11 @@ internal object TranslationsStateReducer { // Reset the error state, and then generally expect // [TranslationsAction.SetNeverTranslateSitesAction] to update // state in the success case. - state.copyWithTranslationsState(action.tabId) { - it.copy( + state.copy( + translationEngine = state.translationEngine.copy( neverTranslateSites = null, - ) - } + ), + ) } } } @@ -229,6 +237,14 @@ internal object TranslationsStateReducer { } } + TranslationOperation.FETCH_OFFER_SETTING -> { + state.copyWithTranslationsState(action.tabId) { + it.copy( + translationError = action.translationError, + ) + } + } + TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> { state.copyWithTranslationsState(action.tabId) { it.copy( @@ -240,7 +256,6 @@ internal object TranslationsStateReducer { TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> { state.copyWithTranslationsState(action.tabId) { it.copy( - neverTranslateSites = null, settingsError = action.translationError, ) } @@ -277,20 +292,20 @@ internal object TranslationsStateReducer { } is TranslationsAction.SetNeverTranslateSitesAction -> - state.copyWithTranslationsState(action.tabId) { - it.copy( + state.copy( + translationEngine = state.translationEngine.copy( neverTranslateSites = action.neverTranslateSites, - ) - } + ), + ) is TranslationsAction.RemoveNeverTranslateSiteAction -> { - val neverTranslateSites = state.findTab(action.tabId)?.translationsState?.neverTranslateSites + val neverTranslateSites = state.translationEngine.neverTranslateSites val updatedNeverTranslateSites = neverTranslateSites?.filter { it != action.origin }?.toList() - state.copyWithTranslationsState(action.tabId) { - it.copy( + state.copy( + translationEngine = state.translationEngine.copy( neverTranslateSites = updatedNeverTranslateSites, - ) - } + ), + ) } is TranslationsAction.OperationRequestedAction -> @@ -326,12 +341,20 @@ internal object TranslationsStateReducer { } } + TranslationOperation.FETCH_OFFER_SETTING -> { + state.copy( + translationEngine = state.translationEngine.copy( + offerTranslation = null, + ), + ) + } + TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> { - state.copyWithTranslationsState(action.tabId) { - it.copy( + state.copy( + translationEngine = state.translationEngine.copy( neverTranslateSites = null, - ) - } + ), + ) } TranslationOperation.TRANSLATE, TranslationOperation.RESTORE -> { // No state change for these operations @@ -400,6 +423,35 @@ internal object TranslationsStateReducer { } } + is TranslationsAction.UpdateLanguageSettingsAction -> { + val languageSettings = state.translationEngine.languageSettings?.toMutableMap() + // Only set when keys are present. + if (languageSettings?.get(action.languageCode) != null) { + languageSettings[action.languageCode] = action.setting + } + state.copy( + translationEngine = state.translationEngine.copy( + languageSettings = languageSettings, + ), + ) + } + + is TranslationsAction.SetGlobalOfferTranslateSettingAction -> { + state.copy( + translationEngine = state.translationEngine.copy( + offerTranslation = action.offerTranslation, + ), + ) + } + + is TranslationsAction.UpdateGlobalOfferTranslateSettingAction -> { + state.copy( + translationEngine = state.translationEngine.copy( + offerTranslation = action.offerTranslation, + ), + ) + } + is TranslationsAction.SetEngineSupportedAction -> { state.copy( translationEngine = state.translationEngine.copy( diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsBrowserState.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsBrowserState.kt index 2fb937f9f3..c34e1bc062 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsBrowserState.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsBrowserState.kt @@ -13,17 +13,21 @@ import mozilla.components.concept.engine.translate.TranslationSupport * Value type that represents the state of the translations engine within a [BrowserState]. * * @property isEngineSupported Whether the translations engine supports the device architecture. + * @property offerTranslation Whether to offer translations or not to the user. * @property supportedLanguages Set of languages the translation engine supports. * @property languageModels Set of language machine learning translation models the translation engine has available. * @property languageSettings A map containing a key of BCP 47 language code and its * [LanguageSetting] to represent the automatic language settings. + * @property neverTranslateSites List of sites the user has opted to never translate. * @property engineError Holds the error state of the translations engine. * See [TranslationsState.translationError] for session level errors. */ data class TranslationsBrowserState( val isEngineSupported: Boolean? = null, + val offerTranslation: Boolean? = null, val supportedLanguages: TranslationSupport? = null, val languageModels: List? = null, val languageSettings: Map? = null, + val neverTranslateSites: List? = null, val engineError: TranslationError? = null, ) diff --git a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsState.kt b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsState.kt index 8c05340928..d70d6e1492 100644 --- a/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsState.kt +++ b/mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsState.kt @@ -23,7 +23,6 @@ import mozilla.components.concept.engine.translate.TranslationPageSettings * translation engine requires the pair's ML models to be present on the device to complete a * translation. * @property pageSettings The translation engine settings that relate to the current page. - * @property neverTranslateSites List of sites the user has opted to never translate. * @property translationError Type of error that occurred when acquiring resources, translating, or * restoring a translation. * @property settingsError Type of error that occurred when acquiring resources or setting preferences. @@ -37,7 +36,6 @@ data class TranslationsState( val isRestoreProcessing: Boolean = false, val translationDownloadSize: TranslationDownloadSize? = null, val pageSettings: TranslationPageSettings? = null, - val neverTranslateSites: List? = null, val translationError: TranslationError? = null, val settingsError: TranslationError? = null, ) diff --git a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/action/TranslationsActionTest.kt b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/action/TranslationsActionTest.kt index 2a12cda264..ecbbae9c61 100644 --- a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/action/TranslationsActionTest.kt +++ b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/action/TranslationsActionTest.kt @@ -95,6 +95,7 @@ class TranslationsActionTest { error = null, isEngineReady = true, requestedTranslationPair = TranslationPair(fromLanguage = "es", toLanguage = "en"), + hasVisibleChange = true, ) store.dispatch(TranslationsAction.TranslateStateChangeAction(tabId = tab.id, translationEngineState = translatedEngineState)) @@ -110,6 +111,7 @@ class TranslationsActionTest { error = null, isEngineReady = true, requestedTranslationPair = TranslationPair(fromLanguage = null, toLanguage = null), + hasVisibleChange = false, ) store.dispatch(TranslationsAction.TranslateStateChangeAction(tabId = tab.id, nonTranslatedEngineState)) @@ -264,7 +266,6 @@ class TranslationsActionTest { store.dispatch(TranslationsAction.TranslateSuccessAction(tabId = tab.id, operation = TranslationOperation.TRANSLATE)) .joinBlocking() assertEquals(false, tabState().translationsState.isTranslateProcessing) - assertEquals(true, tabState().translationsState.isTranslated) assertEquals(null, tabState().translationsState.translationError) } @@ -347,44 +348,41 @@ class TranslationsActionTest { @Test fun `WHEN a SetNeverTranslateSitesAction is dispatched AND successful THEN update neverTranslateSites`() { // Initial - assertEquals(null, tabState().translationsState.neverTranslateSites) + assertNull(store.state.translationEngine.neverTranslateSites) // Action started val neverTranslateSites = listOf("google.com") store.dispatch( TranslationsAction.SetNeverTranslateSitesAction( - tabId = tab.id, neverTranslateSites = neverTranslateSites, ), ).joinBlocking() // Action success - assertEquals(neverTranslateSites, tabState().translationsState.neverTranslateSites) + assertEquals(neverTranslateSites, store.state.translationEngine.neverTranslateSites) } @Test fun `WHEN a RemoveNeverTranslateSiteAction is dispatched AND successful THEN update neverTranslateSites`() { // Initial add to neverTranslateSites - assertEquals(null, tabState().translationsState.neverTranslateSites) + assertNull(store.state.translationEngine.neverTranslateSites) val neverTranslateSites = listOf("google.com") store.dispatch( TranslationsAction.SetNeverTranslateSitesAction( - tabId = tab.id, neverTranslateSites = neverTranslateSites, ), ).joinBlocking() - assertEquals(neverTranslateSites, tabState().translationsState.neverTranslateSites) + assertEquals(neverTranslateSites, store.state.translationEngine.neverTranslateSites) // Action started store.dispatch( TranslationsAction.RemoveNeverTranslateSiteAction( - tabId = tab.id, origin = "google.com", ), ).joinBlocking() // Action success - assertEquals(listOf(), tabState().translationsState.neverTranslateSites) + assertEquals(listOf(), store.state.translationEngine.neverTranslateSites) } @Test @@ -451,7 +449,6 @@ class TranslationsActionTest { ), ).joinBlocking() assertEquals(null, tabState().translationsState.translationError) - assertEquals(true, tabState().translationsState.isTranslated) assertEquals(false, tabState().translationsState.isTranslateProcessing) // RESTORE usage @@ -900,4 +897,88 @@ class TranslationsActionTest { // Final state assertEquals(languageModels, store.state.translationEngine.languageModels) } + + @Test + fun `WHEN SetOfferTranslateSettingAction is called then set offerToTranslate`() { + // Initial State + assertNull(store.state.translationEngine.offerTranslation) + + // Action started + store.dispatch( + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = false, + ), + ).joinBlocking() + + // Action success + assertFalse(store.state.translationEngine.offerTranslation!!) + } + + @Test + fun `WHEN UpdateOfferTranslateSettingAction is called then set offerToTranslate`() { + // Initial State + assertNull(store.state.translationEngine.offerTranslation) + + // Action started + store.dispatch( + TranslationsAction.UpdateGlobalOfferTranslateSettingAction( + offerTranslation = false, + ), + ).joinBlocking() + + // Action success + assertFalse(store.state.translationEngine.offerTranslation!!) + } + + @Test + fun `WHEN UpdateGlobalLanguageSettingAction is called then update languageSettings`() { + // Initial State + assertNull(store.state.translationEngine.languageSettings) + + // No-op null test + store.dispatch( + TranslationsAction.UpdateLanguageSettingsAction( + languageCode = "fr", + setting = LanguageSetting.ALWAYS, + ), + ).joinBlocking() + + assertNull(store.state.translationEngine.languageSettings) + + // Setting Initial State + val languageSettings = mapOf( + "en" to LanguageSetting.OFFER, + "es" to LanguageSetting.NEVER, + "de" to LanguageSetting.ALWAYS, + ) + + store.dispatch( + TranslationsAction.SetLanguageSettingsAction( + languageSettings = languageSettings, + ), + ).joinBlocking() + + assertEquals(languageSettings, store.state.translationEngine.languageSettings) + + // No-op update test + store.dispatch( + TranslationsAction.UpdateLanguageSettingsAction( + languageCode = "fr", + setting = LanguageSetting.ALWAYS, + ), + ).joinBlocking() + + assertEquals(languageSettings, store.state.translationEngine.languageSettings) + + // Main action started + store.dispatch( + TranslationsAction.UpdateLanguageSettingsAction( + languageCode = "es", + setting = LanguageSetting.ALWAYS, + ), + ).joinBlocking() + + // Action success + assertEquals(LanguageSetting.ALWAYS, store.state.translationEngine.languageSettings!!["es"]) + } } diff --git a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt index 5eaeb328ee..13365b57a7 100644 --- a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt +++ b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/EngineDelegateMiddlewareTest.kt @@ -263,6 +263,7 @@ class EngineDelegateMiddlewareTest { EngineAction.LoadUrlAction( "test-tab", "https://www.firefox.com", + includeParent = true, ), ).joinBlocking() diff --git a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt index 89939b71a2..c24f838c02 100644 --- a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt +++ b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/LinkingMiddlewareTest.kt @@ -96,7 +96,9 @@ class LinkingMiddlewareTest { store.dispatch(EngineAction.LinkEngineSessionAction(parent.id, parentEngineSession)).joinBlocking() val childEngineSession: EngineSession = mock() - store.dispatch(EngineAction.LinkEngineSessionAction(child.id, childEngineSession)).joinBlocking() + store.dispatch( + EngineAction.LinkEngineSessionAction(child.id, childEngineSession, includeParent = true), + ).joinBlocking() dispatcher.scheduler.advanceUntilIdle() diff --git a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddlewareTest.kt b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddlewareTest.kt index cf30f7060e..dadc88de52 100644 --- a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddlewareTest.kt +++ b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddlewareTest.kt @@ -326,6 +326,35 @@ class TranslationsMiddlewareTest { ) } + @Test + fun `WHEN InitTranslationsBrowserState is dispatched AND the engine is supported THEN SetNeverTranslateSitesAction is also dispatched`() = runTest { + // Send Action + translationsMiddleware.invoke(context = context, next = {}, action = TranslationsAction.InitTranslationsBrowserState) + + // Set the engine to support + val engineSupportedCallback = argumentCaptor<((Boolean) -> Unit)>() + // At least once, since InitAction also will trigger this + verify(engine, atLeastOnce()).isTranslationsEngineSupported( + onSuccess = engineSupportedCallback.capture(), + onError = any(), + ) + engineSupportedCallback.value.invoke(true) + + val neverTranslateSitesCallBack = argumentCaptor<((List) -> Unit)>() + verify(engine, atLeastOnce()).getNeverTranslateSiteList(onSuccess = neverTranslateSitesCallBack.capture(), onError = any()) + val mockNeverTranslate = listOf("www.mozilla.org") + neverTranslateSitesCallBack.value.invoke(mockNeverTranslate) + + waitForIdle() + + // Verifying at least once + verify(store, atLeastOnce()).dispatch( + TranslationsAction.SetNeverTranslateSitesAction( + neverTranslateSites = mockNeverTranslate, + ), + ) + } + @Test fun `WHEN InitTranslationsBrowserState is dispatched AND has an issue with the engine THEN EngineExceptionAction is dispatched`() = runTest() { // Send Action @@ -726,7 +755,6 @@ class TranslationsMiddlewareTest { verify(context.store).dispatch( TranslationsAction.SetNeverTranslateSitesAction( - tabId = tab.id, neverTranslateSites = neverTranslateSites, ), ) @@ -825,7 +853,7 @@ class TranslationsMiddlewareTest { } @Test - fun `WHEN RemoveNeverTranslateSiteAction is dispatched AND removing is unsuccessful THEN FETCH_NEVER_TRANSLATE_SITES is dispatched`() = runTest { + fun `WHEN RemoveNeverTranslateSiteAction is dispatched AND removing is unsuccessful THEN SetNeverTranslateSitesAction is dispatched`() = runTest { val errorCallback = argumentCaptor<((Throwable) -> Unit)>() whenever( engine.setNeverTranslateSpecifiedSite( @@ -838,45 +866,20 @@ class TranslationsMiddlewareTest { val action = TranslationsAction.RemoveNeverTranslateSiteAction( - tabId = tab.id, origin = "google.com", ) translationsMiddleware.invoke(context, {}, action) waitForIdle() - // Verify Dispatch - verify(store).dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tab.id, - operation = TranslationOperation.FETCH_NEVER_TRANSLATE_SITES, - ), - ) - waitForIdle() - } - - @Test - fun `WHEN RemoveNeverTranslateSiteAction is dispatched AND removing is successful THEN FETCH_PAGE_SETTINGS is dispatched`() = runTest { - val sitesCallback = argumentCaptor<(() -> Unit)>() - val action = - TranslationsAction.RemoveNeverTranslateSiteAction( - tabId = tab.id, - origin = "google.com", - ) - translationsMiddleware.invoke(context, {}, action) - verify(engine).setNeverTranslateSpecifiedSite( - origin = any(), - setting = anyBoolean(), - onSuccess = sitesCallback.capture(), - onError = any(), - ) - sitesCallback.value.invoke() - waitForIdle() + val neverTranslateSitesCallBack = argumentCaptor<((List) -> Unit)>() + verify(engine, atLeastOnce()).getNeverTranslateSiteList(onSuccess = neverTranslateSitesCallBack.capture(), onError = any()) + val mockNeverTranslate = listOf("www.mozilla.org") + neverTranslateSitesCallBack.value.invoke(mockNeverTranslate) // Verify Dispatch verify(store).dispatch( - TranslationsAction.OperationRequestedAction( - tabId = tab.id, - operation = TranslationOperation.FETCH_PAGE_SETTINGS, + TranslationsAction.SetNeverTranslateSitesAction( + neverTranslateSites = mockNeverTranslate, ), ) waitForIdle() @@ -942,4 +945,116 @@ class TranslationsMiddlewareTest { waitForIdle() } + + @Test + fun `WHEN InitTranslationsBrowserState is dispatched AND the engine is supported THEN SetOfferTranslateSettingAction is also dispatched`() = runTest { + // Send Action + translationsMiddleware.invoke(context = context, next = {}, action = TranslationsAction.InitTranslationsBrowserState) + + // Set the engine to support + val engineSupportedCallback = argumentCaptor<((Boolean) -> Unit)>() + // At least once, since InitAction also will trigger this + verify(engine, atLeastOnce()).isTranslationsEngineSupported( + onSuccess = engineSupportedCallback.capture(), + onError = any(), + ) + engineSupportedCallback.value.invoke(true) + + // Verify results for offer + verify(engine, atLeastOnce()).getTranslationsOfferPopup() + waitForIdle() + + // Verifying at least once + verify(store, atLeastOnce()).dispatch( + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = false, + ), + ) + + waitForIdle() + } + + @Test + fun `WHEN FETCH_OFFER_SETTING is dispatched with a tab id THEN SetOfferTranslateSettingAction and SetPageSettingsAction are also dispatched`() = runTest { + // Set the mock offer value + whenever( + engine.getTranslationsOfferPopup(), + ).thenAnswer { true } + + // Send Action + val action = + TranslationsAction.OperationRequestedAction( + tabId = tab.id, + operation = TranslationOperation.FETCH_OFFER_SETTING, + ) + translationsMiddleware.invoke(context, {}, action) + waitForIdle() + + // Verify Dispatch + verify(store, atLeastOnce()).dispatch( + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = true, + ), + ) + + // Since we had a tabId, this call will also happen + verify(store, atLeastOnce()).dispatch( + TranslationsAction.SetPageSettingsAction( + tabId = tab.id, + pageSettings = any(), + ), + ) + + waitForIdle() + } + + @Test + fun `WHEN UpdateOfferTranslateSettingAction is called then setTranslationsOfferPopup is called on the engine`() = runTest { + // Send Action + val action = + TranslationsAction.UpdateGlobalOfferTranslateSettingAction( + offerTranslation = true, + ) + translationsMiddleware.invoke(context, {}, action) + waitForIdle() + + // Verify offer was set + verify(engine, atLeastOnce()).setTranslationsOfferPopup(offer = true) + waitForIdle() + } + + @Test + fun `WHEN UpdateLanguageSettingsAction is dispatched and fails THEN SetLanguageSettingsAction is dispatched`() = runTest { + // Send Action + val action = + TranslationsAction.UpdateLanguageSettingsAction( + languageCode = "es", + setting = LanguageSetting.ALWAYS, + ) + translationsMiddleware.invoke(context, {}, action) + + waitForIdle() + + // Mock engine error + val updateLanguagesErrorCallback = argumentCaptor<((Throwable) -> Unit)>() + verify(engine).setLanguageSetting( + languageCode = any(), + languageSetting = any(), + onSuccess = any(), + onError = updateLanguagesErrorCallback.capture(), + ) + updateLanguagesErrorCallback.value.invoke(Throwable()) + + waitForIdle() + + // Verify Dispatch + val languageSettingsCallback = argumentCaptor<((Map) -> Unit)>() + verify(engine, atLeastOnce()).getLanguageSettings( + onSuccess = languageSettingsCallback.capture(), + onError = any(), + ) + val mockLanguageSetting = mapOf("en" to LanguageSetting.OFFER) + languageSettingsCallback.value.invoke(mockLanguageSetting) + waitForIdle() + } } diff --git a/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/ext/TabSessionStateTest.kt b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/ext/TabSessionStateTest.kt new file mode 100644 index 0000000000..1202afb1c6 --- /dev/null +++ b/mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/ext/TabSessionStateTest.kt @@ -0,0 +1,34 @@ +/* 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.state.ext + +import mozilla.components.browser.state.state.ReaderState +import mozilla.components.browser.state.state.createTab +import org.junit.Assert.assertEquals +import org.junit.Test + +class TabSessionStateTest { + + @Test + fun `GIVEN reader mode is active WHEN get url extension property is fetched THEN return the active url`() { + val readerUrl = "moz-extension://1234" + val activeUrl = "https://mozilla.org" + val readerTab = createTab( + url = readerUrl, + readerState = ReaderState(active = true, activeUrl = activeUrl), + title = "Mozilla", + ) + + assertEquals(activeUrl, readerTab.getUrl()) + } + + @Test + fun `WHEN get url extension property is fetched THEN return the content url`() { + val url = "https://mozilla.org" + val tab = createTab(url = url) + + assertEquals(url, tab.getUrl()) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/browser/toolbar/src/main/res/values-nb-rNO/strings.xml index f9cb99c55d..9d9bcbfc13 100644 --- a/mobile/android/android-components/components/browser/toolbar/src/main/res/values-nb-rNO/strings.xml +++ b/mobile/android/android-components/components/browser/toolbar/src/main/res/values-nb-rNO/strings.xml @@ -2,6 +2,7 @@ Meny + Tøm Sporingsbeskyttelse er på @@ -14,5 +15,5 @@ Laster - Noe av innholdet er blokkert av autoavspillings-innstillingene + Noe av innholdet er blokkert av autoavspillings-innstillingene diff --git a/mobile/android/android-components/components/browser/toolbar/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/browser/toolbar/src/main/res/values-sc/strings.xml index 5194a4765e..f0b375f172 100644 --- a/mobile/android/android-components/components/browser/toolbar/src/main/res/values-sc/strings.xml +++ b/mobile/android/android-components/components/browser/toolbar/src/main/res/values-sc/strings.xml @@ -2,6 +2,7 @@ Menù + Isbòida S’amparu contra sa sighidura est ativu @@ -13,4 +14,6 @@ Informatziones de su situ Carrighende - + + Sa funtzionalidade de riprodutzione automàtica at blocadu cuntenutos + diff --git a/mobile/android/android-components/components/browser/toolbar/src/main/res/values/strings.xml b/mobile/android/android-components/components/browser/toolbar/src/main/res/values/strings.xml index 3c2d7dcf1b..c845e189e7 100644 --- a/mobile/android/android-components/components/browser/toolbar/src/main/res/values/strings.xml +++ b/mobile/android/android-components/components/browser/toolbar/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Menu + Clear Tracking Protection is on diff --git a/mobile/android/android-components/components/browser/toolbar2/README.md b/mobile/android/android-components/components/browser/toolbar2/README.md new file mode 100644 index 0000000000..a0cfe1d6ce --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/README.md @@ -0,0 +1,37 @@ +# [Android Components](../../../README.md) > Browser > Toolbar2 + +A customizable toolbar for browsers. + +## 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-toolbar2:{latest-version}" +``` + +## Facts + +This component emits the following [Facts](../../support/base/README.md#Facts): + +| Action | Item | Extras | Description | +|--------|---------|----------------|------------------------------------| +| CLICK | menu | `menuExtras` | The user opened the overflow menu. | +| COMMIT | toolbar | `commitExtras` | The user has edited the URL. | + +`menuExtras` are additional extras set on the `BrowserMenuBuilder` passed to the `BrowserToolbar` (see [browser-menu](../menu/README.md)). + +#### `commitExtras` + +| Key | Type | Value | +|--------------|---------|-----------------------------------| +| autocomplete | Boolean | Whether the URL was autocompleted | +| source | String? | Which autocomplete list was used | + +## 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/ diff --git a/mobile/android/android-components/components/browser/toolbar2/build.gradle b/mobile/android/android-components/components/browser/toolbar2/build.gradle new file mode 100644 index 0000000000..60d3930430 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/build.gradle @@ -0,0 +1,56 @@ +/* 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 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + namespace 'mozilla.components.browser.toolbar2' +} + +dependencies { + api project(':concept-toolbar') + api project(':ui-autocomplete') + api project(':support-base') + + implementation project(':concept-engine') + implementation project(':concept-menu') + implementation project(':browser-menu') + implementation project(':browser-menu2') + implementation project(':ui-icons') + implementation project(':ui-colors') + implementation project(':ui-widgets') + implementation project(':support-ktx') + + implementation ComponentsDependencies.androidx_appcompat + implementation ComponentsDependencies.androidx_constraintlayout + implementation ComponentsDependencies.androidx_core_ktx + implementation ComponentsDependencies.google_material + + implementation ComponentsDependencies.kotlin_coroutines + + testImplementation project(':support-test') + + testImplementation ComponentsDependencies.androidx_test_core + 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) diff --git a/mobile/android/android-components/components/browser/toolbar2/proguard-rules.pro b/mobile/android/android-components/components/browser/toolbar2/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/proguard-rules.pro @@ -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 diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/AndroidManifest.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e16cda1d34 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/BrowserToolbar.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/BrowserToolbar.kt new file mode 100644 index 0000000000..e740455385 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/BrowserToolbar.kt @@ -0,0 +1,691 @@ +/* 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.toolbar2 + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.View.NO_ID +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PRIVATE +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.content.ContextCompat +import androidx.core.view.forEach +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import mozilla.components.browser.toolbar2.display.DisplayToolbar +import mozilla.components.browser.toolbar2.edit.EditToolbar +import mozilla.components.concept.toolbar.AutocompleteDelegate +import mozilla.components.concept.toolbar.AutocompleteResult +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.concept.toolbar.Toolbar.Highlight +import mozilla.components.support.base.android.Padding +import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.kotlin.trimmed +import mozilla.components.ui.autocomplete.AutocompleteView +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText +import mozilla.components.ui.autocomplete.OnFilterListener +import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior +import kotlin.coroutines.CoroutineContext + +internal fun ImageView.setTintResource(@ColorRes tintColorResource: Int) { + if (tintColorResource != NO_ID) { + imageTintList = ContextCompat.getColorStateList(context, tintColorResource) + } +} + +/** + * A customizable toolbar for browsers. + * + * The toolbar can switch between two modes: display and edit. The display mode displays the current + * URL and controls for navigation. In edit mode the current URL can be edited. Those two modes are + * implemented by the DisplayToolbar and EditToolbar classes. + * + * ``` + * +----------------+ + * | BrowserToolbar | + * +--------+-------+ + * + + * +-------+-------+ + * | | + * +---------v------+ +-------v--------+ + * | DisplayToolbar | | EditToolbar | + * +----------------+ +----------------+ + * ``` + */ +@Suppress("TooManyFunctions") +class BrowserToolbar @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : ViewGroup(context, attrs, defStyleAttr), Toolbar { + private var state: State = State.DISPLAY + + @VisibleForTesting + internal var searchTerms: String = "" + private var urlCommitListener: ((String) -> Boolean)? = null + var isNavBarEnabled: Boolean = false + + /** + * Toolbar in "display mode". + */ + var display = DisplayToolbar( + context, + this, + LayoutInflater.from(context).inflate( + R.layout.mozac_browser_toolbar_displaytoolbar, + this, + false, + ), + ) + @VisibleForTesting(otherwise = PRIVATE) + internal set + + /** + * Toolbar in "edit mode". + */ + var edit = EditToolbar( + context, + this, + LayoutInflater.from(context).inflate( + R.layout.mozac_browser_toolbar_edittoolbar, + this, + false, + ), + ) + @VisibleForTesting(otherwise = PRIVATE) + internal set + + override var title: String + get() = display.title + set(value) { display.title = value } + + override var url: CharSequence + get() = display.url.toString() + set(value) { + // We update the display toolbar immediately. We do not do that for the edit toolbar to not + // mess with what the user is entering. Instead we will remember the value and update the + // edit toolbar whenever we switch to it. + display.url = (value as String).trimmed() + } + + override var siteSecure: Toolbar.SiteSecurity + get() = display.siteSecurity + set(value) { display.siteSecurity = value } + + override var highlight: Highlight = Highlight.NONE + set(value) { + if (field != value) { + display.setHighlight(value) + field = value + } + } + + override var siteTrackingProtection: Toolbar.SiteTrackingProtection = + Toolbar.SiteTrackingProtection.OFF_GLOBALLY + set(value) { + if (field != value) { + display.setTrackingProtectionState(value) + field = value + } + } + + override var private: Boolean + get() = edit.private + set(value) { edit.private = value } + + /** + * Registers the given listener to be invoked when the user edits the URL. + */ + override fun setOnEditListener(listener: Toolbar.OnEditListener) { + edit.editListener = listener + } + + /** + * Registers the given function to be invoked when users changes text in the toolbar. + * + * @param filter A function which will perform autocompletion and send results to [AutocompleteDelegate]. + */ + override fun setAutocompleteListener(filter: suspend (String, AutocompleteDelegate) -> Unit) { + // Our 'filter' knows how to autocomplete, and the 'urlView' knows how to apply results of + // autocompletion. Which gives us a lovely delegate chain! + // urlView decides when it's appropriate to ask for autocompletion, and in turn we invoke + // our 'filter' and send results back to 'urlView'. + edit.setAutocompleteListener(filter) + } + + override fun refreshAutocomplete() { + edit.refreshAutocompleteSuggestion() + } + + init { + addView(display.rootView) + addView(edit.rootView) + + updateState(State.DISPLAY) + } + + // We layout the toolbar ourselves to avoid the overhead from using complex ViewGroup implementations + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + forEach { child -> + child.layout( + 0 + paddingLeft, + 0 + paddingTop, + paddingLeft + child.measuredWidth, + paddingTop + child.measuredHeight, + ) + } + } + + // We measure the views manually to avoid overhead by using complex ViewGroup implementations + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // Our toolbar will always use the full width and a fixed height (default) or the provided + // height if it's an exact value. + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { + MeasureSpec.getSize(heightMeasureSpec) + } else { + resources.getDimensionPixelSize(R.dimen.mozac_browser_toolbar_default_toolbar_height) + } + + setMeasuredDimension(width, height) + + // Let the children measure themselves using our fixed size (with padding subtracted) + val childWidth = width - paddingLeft - paddingRight + val childHeight = height - paddingTop - paddingBottom + + val childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY) + val childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY) + + forEach { child -> child.measure(childWidthSpec, childHeightSpec) } + } + + override fun onBackPressed(): Boolean { + if (state == State.EDIT) { + displayMode() + return true + } + return false + } + + override fun onStop() { + display.onStop() + } + + override fun setSearchTerms(searchTerms: String) { + this.searchTerms = searchTerms.trimmed() + + if (state == State.EDIT) { + edit.editSuggestion(this.searchTerms) + } + } + + override fun displayProgress(progress: Int) { + display.updateProgress(progress) + } + + override fun setOnUrlCommitListener(listener: (String) -> Boolean) { + this.urlCommitListener = listener + } + + /** + * Declare that the actions (navigation actions, browser actions, page actions) have changed and + * should be updated if needed. + * + * The toolbar will call the visible lambda of every action to determine whether a + * view for this action should be added or removed. Additionally bind will be + * called on every visible action to update its view. + */ + override fun invalidateActions() { + display.invalidateActions() + edit.invalidateActions() + } + + /** + * Adds an action to be displayed on the right side of the toolbar (outside of the URL bounding + * box) in display mode. + * + * If there is not enough room to show all icons then some icons may be moved to an overflow + * menu. + * + * Related: + * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action + */ + override fun addBrowserAction(action: Toolbar.Action) { + display.addBrowserAction(action) + } + + /** + * Removes a previously added browser action (see [addBrowserAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + override fun removeBrowserAction(action: Toolbar.Action) { + display.removeBrowserAction(action) + } + + /** + * Removes a previously added page action (see [addPageAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + override fun removePageAction(action: Toolbar.Action) { + display.removePageAction(action) + } + + /** + * Adds an action to be displayed on the right side of the URL in display mode. + * + * Related: + * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Page_actions + */ + override fun addPageAction(action: Toolbar.Action) { + display.addPageAction(action) + } + + /** + * Adds an action to be display on the far left side of the toolbar. This area is usually used + * on larger devices for navigation actions like "back" and "forward". + */ + override fun addNavigationAction(action: Toolbar.Action) { + display.addNavigationAction(action) + } + + /** + * Removes a previously added navigation action (see [addNavigationAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + override fun removeNavigationAction(action: Toolbar.Action) { + display.removeNavigationAction(action) + } + + /** + * Adds an action to be displayed at the start of the URL in edit mode. + */ + override fun addEditActionStart(action: Toolbar.Action) { + edit.addEditActionStart(action) + } + + /** + * Adds an action to be displayed at the end of the URL in edit mode. + */ + override fun addEditActionEnd(action: Toolbar.Action) { + edit.addEditActionEnd(action) + } + + /** + * Removes an action end of the URL in edit mode. + */ + override fun removeEditActionEnd(action: Toolbar.Action) { + edit.removeEditActionEnd(action) + } + + /** + * Hides the menu button in display mode. + */ + override fun hideMenuButton() { + display.hideMenuButton() + } + + /** + * Shows the menu button in display mode. + */ + override fun showMenuButton() { + display.showMenuButton() + } + + /** + * Sets the horizontal padding in display mode. + */ + override fun setDisplayHorizontalPadding(horizontalPadding: Int) { + display.setHorizontalPadding(horizontalPadding) + } + + /** + * Hides the page action separator in display/edit mode. + */ + override fun hidePageActionSeparator() { + display.hidePageActionSeparator() + edit.hidePageActionSeparator() + } + + /** + * Shows the page action separator in display/edit mode. + */ + override fun showPageActionSeparator() { + display.showPageActionSeparator() + edit.showPageActionSeparator() + } + + /** + * Switches to URL editing mode. + * + * @param cursorPlacement Where the cursor should be placed after focusing on the URL input field. + */ + override fun editMode(cursorPlacement: Toolbar.CursorPlacement) { + val urlValue = if (searchTerms.isEmpty()) url else searchTerms + edit.updateUrl(urlValue.toString(), false) + updateState(State.EDIT) + edit.focus() + + when (cursorPlacement) { + Toolbar.CursorPlacement.ALL -> { + edit.selectAll() + } + Toolbar.CursorPlacement.END -> { + edit.selectEnd() + } + } + } + + /** + * Switches to URL displaying mode. + */ + override fun displayMode() { + updateState(State.DISPLAY) + } + + /** + * Dismisses the display toolbar popup menu. + */ + override fun dismissMenu() { + display.views.menu.dismissMenu() + } + + override fun enableScrolling() { + // Behavior can be changed without us knowing. Not safe to use a memoized value. + (layoutParams as? CoordinatorLayout.LayoutParams)?.apply { + (behavior as? EngineViewScrollingBehavior)?.enableScrolling() + } + } + + override fun disableScrolling() { + // Behavior can be changed without us knowing. Not safe to use a memoized value. + (layoutParams as? CoordinatorLayout.LayoutParams)?.apply { + (behavior as? EngineViewScrollingBehavior)?.disableScrolling() + } + } + + override fun expand() { + (layoutParams as? CoordinatorLayout.LayoutParams)?.apply { + (behavior as? EngineViewScrollingBehavior)?.forceExpand(this@BrowserToolbar) + } + } + + override fun collapse() { + (layoutParams as? CoordinatorLayout.LayoutParams)?.apply { + (behavior as? EngineViewScrollingBehavior)?.forceCollapse(this@BrowserToolbar) + } + } + + internal fun onUrlEntered(url: String) { + if (urlCommitListener?.invoke(url) != false) { + // Return to display mode if there's no urlCommitListener or if it returned true. This lets + // the app control whether we should switch to display mode automatically. + displayMode() + } + } + + private fun updateState(state: State) { + this.state = state + + val (show, hide) = when (state) { + State.DISPLAY -> { + edit.stopEditing() + Pair(display.rootView, edit.rootView) + } + State.EDIT -> { + edit.startEditing() + Pair(edit.rootView, display.rootView) + } + } + + show.visibility = View.VISIBLE + hide.visibility = View.GONE + } + + private enum class State { + DISPLAY, + EDIT, + } + + /** + * An action button to be added to the toolbar. + * + * @param imageDrawable The drawable to be shown. + * @param contentDescription The content description to use. + * @param visible Lambda that returns true or false to indicate whether this button should be shown. + * @param autoHide Lambda that returns true or false to indicate whether this button should auto hide. + * @param weight Lambda that returns an integer to indicate weight of an action. The lesser the weight, + * the closer it is to the url. A default weight -1 indicates, the position is not cared for + * and action will be appended at the end. + * @param background A custom (stateful) background drawable resource to be used. + * @param padding a custom [Padding] for this Button. + * @param iconTintColorResource Optional ID of color resource to tint the icon. + * @param longClickListener Callback that will be invoked whenever the button is long-pressed. + * @param listener Callback that will be invoked whenever the button is pressed + */ + open class Button( + imageDrawable: Drawable, + contentDescription: String, + visible: () -> Boolean = { true }, + autoHide: () -> Boolean = { false }, + weight: () -> Int = { -1 }, + @DrawableRes background: Int = 0, + val padding: Padding = DEFAULT_PADDING, + @ColorRes iconTintColorResource: Int = NO_ID, + longClickListener: (() -> Unit)? = null, + listener: () -> Unit, + ) : Toolbar.ActionButton( + imageDrawable, + contentDescription, + visible, + autoHide, + weight, + background, + padding, + iconTintColorResource, + longClickListener, + listener, + ) + + /** + * An action button with two states, selected and unselected. When the button is pressed, the + * state changes automatically. + * + * @param image The drawable to be shown if the button is in unselected state. + * @param imageSelected The drawable to be shown if the button is in selected state. + * @param contentDescription The content description to use if the button is in unselected state. + * @param contentDescriptionSelected The content description to use if the button is in selected state. + * @param visible Lambda that returns true or false to indicate whether this button should be shown. + * @param weight Lambda that returns an integer to indicate weight of an action. The lesser the weight, + * the closer it is to the url. A default weight -1 indicates, the position is not cared for + * and action will be appended at the end. + * @param selected Sets whether this button should be selected initially. + * @param background A custom (stateful) background drawable resource to be used. + * @param padding a custom [Padding] for this Button. + * @param listener Callback that will be invoked whenever the checked state changes. + */ + open class ToggleButton( + image: Drawable, + imageSelected: Drawable, + contentDescription: String, + contentDescriptionSelected: String, + visible: () -> Boolean = { true }, + weight: () -> Int = { -1 }, + selected: Boolean = false, + @DrawableRes background: Int = 0, + val padding: Padding = DEFAULT_PADDING, + listener: (Boolean) -> Unit, + ) : Toolbar.ActionToggleButton( + image, + imageSelected, + contentDescription, + contentDescriptionSelected, + visible, + weight, + selected, + background, + padding, + listener, + ) + + /** + * An action that either shows an active button or an inactive button based on the provided + * isInPrimaryState lambda. All secondary characteristics default to their + * corresponding primary. + * + * @param primaryImage: The drawable to be shown if the button is in the primary/enabled state + * @param primaryContentDescription: The content description to use if the button is in the primary state. + * @param secondaryImage: The drawable to be shown if the button is in the secondary/disabled state. + * @param secondaryContentDescription: The content description to use if the button is in the secondary state. + * @param isInPrimaryState: Lambda that returns whether this button should be in the primary or secondary state. + * @param primaryImageTintResource: Optional ID of color resource to tint the icon in the primary state. + * @param secondaryImageTintResource: ID of color resource to tint the icon in the secondary state. + * @param disableInSecondaryState: Disable the button entirely when in the secondary state? + * @param weight Lambda that returns an integer to indicate weight of an action. The lesser the weight, + * the closer it is to the url. A default weight -1 indicates, the position is not cared for + * and action will be appended at the end. + * @param background A custom (stateful) background drawable resource to be used. + * @param longClickListener Callback that will be invoked whenever the button is long-pressed. + * @param listener Callback that will be invoked whenever the button is pressed. + */ + open class TwoStateButton( + val primaryImage: Drawable, + val primaryContentDescription: String, + val secondaryImage: Drawable = primaryImage, + val secondaryContentDescription: String = primaryContentDescription, + val isInPrimaryState: () -> Boolean = { true }, + @ColorRes val primaryImageTintResource: Int = NO_ID, + @ColorRes val secondaryImageTintResource: Int = primaryImageTintResource, + val disableInSecondaryState: Boolean = true, + override val weight: () -> Int = { -1 }, + background: Int = 0, + longClickListener: (() -> Unit)? = null, + listener: () -> Unit, + ) : Button( + primaryImage, + primaryContentDescription, + weight = weight, + background = background, + longClickListener = longClickListener, + listener = listener, + ) { + var enabled: Boolean = false + private set + + override fun bind(view: View) { + enabled = isInPrimaryState.invoke() + + val button = view as ImageButton + if (enabled) { + button.setImageDrawable(primaryImage) + button.contentDescription = primaryContentDescription + button.setTintResource(primaryImageTintResource) + button.isEnabled = true + } else { + button.setImageDrawable(secondaryImage) + button.contentDescription = secondaryContentDescription + button.setTintResource(secondaryImageTintResource) + button.isEnabled = !disableInSecondaryState + } + } + } + + companion object { + internal const val ACTION_PADDING_DP = 16 + internal val DEFAULT_PADDING = + Padding(ACTION_PADDING_DP, ACTION_PADDING_DP, ACTION_PADDING_DP, ACTION_PADDING_DP) + } +} + +/** + * Wraps [filter] execution in a coroutine context, cancelling prior executions on every invocation. + * [coroutineContext] must be of type that doesn't propagate cancellation of its children upwards. + */ +class AsyncFilterListener( + private val urlView: AutocompleteView, + override val coroutineContext: CoroutineContext, + private val filter: suspend (String, AutocompleteDelegate) -> Unit, + private val uiContext: CoroutineContext = Dispatchers.Main, +) : OnFilterListener, CoroutineScope { + override fun invoke(text: String) { + // We got a new input, so whatever past autocomplete queries we still have running are + // irrelevant. We cancel them, but do not depend on cancellation to take place. + coroutineContext.cancelChildren() + + CoroutineScope(coroutineContext).launch { + filter(text, AsyncAutocompleteDelegate(urlView, this, uiContext)) + } + } +} + +/** + * An autocomplete delegate which is aware of its parent scope (to check for cancellations). + * Responsible for processing autocompletion results and discarding stale results when [urlView] moved on. + */ +private class AsyncAutocompleteDelegate( + private val urlView: AutocompleteView, + private val parentScope: CoroutineScope, + override val coroutineContext: CoroutineContext, + private val logger: Logger = Logger("AsyncAutocompleteDelegate"), +) : AutocompleteDelegate, CoroutineScope { + override fun applyAutocompleteResult(result: AutocompleteResult, onApplied: () -> Unit) { + // Bail out if we were cancelled already. + if (!parentScope.isActive) { + logger.debug("Autocomplete request cancelled. Discarding results.") + return + } + + // Process results on the UI dispatcher. + CoroutineScope(coroutineContext).launch { + // Ignore this result if the query is stale. + if (result.input == urlView.originalText.lowercase()) { + urlView.applyAutocompleteResult( + InlineAutocompleteEditText.AutocompleteResult( + text = result.text, + source = result.source, + totalItems = result.totalItems, + ), + ) + onApplied() + } else { + logger.debug("Discarding stale autocomplete result.") + } + } + } + + override fun noAutocompleteResult(input: String) { + // Bail out if we were cancelled already. + if (!parentScope.isActive) { + logger.debug("Autocomplete request cancelled. Discarding 'noAutocompleteResult'.") + return + } + + // Process results on the UI thread. + CoroutineScope(coroutineContext).launch { + // Ignore this result if the query is stale. + if (input == urlView.originalText) { + urlView.noAutocompleteResult() + } else { + logger.debug("Discarding stale lack of autocomplete results.") + } + } + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbar.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbar.kt new file mode 100644 index 0000000000..66218febbb --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbar.kt @@ -0,0 +1,711 @@ +/* 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.toolbar2.display + +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.TypedValue +import android.view.View +import android.view.accessibility.AccessibilityEvent +import android.widget.ImageView +import android.widget.ProgressBar +import androidx.annotation.ColorInt +import androidx.appcompat.content.res.AppCompatResources.getDrawable +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import mozilla.components.browser.menu.BrowserMenuBuilder +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.browser.toolbar2.R +import mozilla.components.browser.toolbar2.internal.ActionContainer +import mozilla.components.concept.menu.MenuController +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.support.ktx.android.content.isScreenReaderEnabled +import mozilla.components.ui.colors.R.color as photonColors + +/** + * Sub-component of the browser toolbar responsible for displaying the URL and related controls ("display mode"). + * + * Structure: + * ``` + * +-------------+------------+-----------------------+----------+------+ + * | navigation | indicators | url [ page ] | browser | menu | + * | actions | | [ actions ] | actions | | + * +-------------+------------+-----------------------+----------+------+ + * +------------------------progress------------------------------------+ + * ``` + * + * Navigation actions (optional): + * A dynamic list of clickable icons usually used for navigation on larger devices + * (e.g. “back”/”forward” buttons.) + * + * Indicators (optional): + * Tracking protection indicator icon (e.g. “shield” icon) that may show a doorhanger when clicked. + * Separator icon: a vertical line that separate the above and below icons. + * Site security indicator icon (e.g. “Lock” icon) that may show a doorhanger when clicked. + * Empty indicator: Icon that will be displayed if the URL is empty. + * + * URL: + * Section that displays the current URL (read-only) + * + * Page actions (optional): + * A dynamic list of clickable icons inside the URL section (e.g. “reader mode” icon) + * + * Browser actions (optional): + * A list of dynamic clickable icons on the toolbar (e.g. tabs tray button) + * + * Menu (optional): + * A button that shows an overflow menu when clicked (constructed using the browser-menu + * component) + * + * Progress (optional): + * A horizontal progress bar showing the loading progress (at the top or bottom of the toolbar). + */ +@Suppress("LargeClass") +class DisplayToolbar internal constructor( + private val context: Context, + private val toolbar: BrowserToolbar, + internal val rootView: View, +) { + /** + * Enum of indicators that can be displayed in the toolbar. + */ + enum class Indicators { + SECURITY, + TRACKING_PROTECTION, + EMPTY, + HIGHLIGHT, + } + + /** + * Data class holding the customizable colors in "display mode". + * + * @property securityIconSecure Color tint for the "secure connection" icon (lock). + * @property securityIconInsecure Color tint for the "insecure connection" icon (broken lock). + * @property emptyIcon Color tint for the icon shown when the URL is empty. + * @property menu Color tint for the menu icon. + * @property hint Text color of the hint shown when the URL is empty. + * @property title Text color of the website title. + * @property text Text color of the URL. + * @property trackingProtection Color tint for the tracking protection icons. + * @property separator Color tint for the separator shown between indicators. + * @property pageActionSeparator Color tint of separator dividing url and page actions. + * @property highlight Color tint for the highlight icon. + * + * Set/Get the site security icon colours. It uses a pair of color integers which represent the + * insecure and secure colours respectively. + */ + data class Colors( + @ColorInt val securityIconSecure: Int, + @ColorInt val securityIconInsecure: Int, + @ColorInt val emptyIcon: Int, + @ColorInt val menu: Int, + @ColorInt val hint: Int, + @ColorInt val title: Int, + @ColorInt val text: Int, + @ColorInt val trackingProtection: Int?, + @ColorInt val separator: Int, + @ColorInt val pageActionSeparator: Int, + @ColorInt val highlight: Int?, + ) + + /** + * Data class holding the customizable icons in "display mode". + * + * @property emptyIcon An icon that is shown in front of the URL if the URL is empty. + * @property trackingProtectionTrackersBlocked An icon that is shown if tracking protection is + * enabled and trackers have been blocked. + * @property trackingProtectionNothingBlocked An icon that is shown if tracking protection is + * enabled and no trackers have been blocked. + * @property trackingProtectionException An icon that is shown if tracking protection is enabled + * but the current page is in the "exception list". + * @property highlight An icon that is shown if any event needs to be brought + * to the user's attention. Like the autoplay permission been blocked. + */ + data class Icons( + val emptyIcon: Drawable?, + val trackingProtectionTrackersBlocked: Drawable, + val trackingProtectionNothingBlocked: Drawable, + val trackingProtectionException: Drawable, + val highlight: Drawable, + ) + + /** + * Gravity enum for positioning the progress bar. + */ + enum class Gravity { + TOP, + BOTTOM, + } + + internal val views = DisplayToolbarViews( + browserActions = rootView.findViewById(R.id.mozac_browser_toolbar_browser_actions), + pageActions = rootView.findViewById(R.id.mozac_browser_toolbar_page_actions), + navigationActions = rootView.findViewById(R.id.mozac_browser_toolbar_navigation_actions), + background = rootView.findViewById(R.id.mozac_browser_toolbar_background), + separator = rootView.findViewById(R.id.mozac_browser_toolbar_separator), + pageActionSeparator = rootView.findViewById(R.id.mozac_browser_toolbar_action_separator), + emptyIndicator = rootView.findViewById(R.id.mozac_browser_toolbar_empty_indicator), + menu = MenuButton(rootView.findViewById(R.id.mozac_browser_toolbar_menu)), + securityIndicator = rootView.findViewById(R.id.mozac_browser_toolbar_security_indicator), + trackingProtectionIndicator = rootView.findViewById( + R.id.mozac_browser_toolbar_tracking_protection_indicator, + ), + origin = rootView.findViewById(R.id.mozac_browser_toolbar_origin_view).also { + it.toolbar = toolbar + }, + progress = rootView.findViewById(R.id.mozac_browser_toolbar_progress), + highlight = rootView.findViewById(R.id.mozac_browser_toolbar_permission_indicator), + ) + + /** + * Customizable colors in "display mode". + */ + var colors: Colors = Colors( + securityIconSecure = ContextCompat.getColor(context, photonColors.photonWhite), + securityIconInsecure = ContextCompat.getColor(context, photonColors.photonWhite), + emptyIcon = ContextCompat.getColor(context, photonColors.photonWhite), + menu = ContextCompat.getColor(context, photonColors.photonWhite), + hint = views.origin.hintColor, + title = views.origin.titleColor, + text = views.origin.textColor, + trackingProtection = null, + separator = ContextCompat.getColor(context, photonColors.photonGrey80), + pageActionSeparator = ContextCompat.getColor(context, photonColors.photonGrey80), + highlight = null, + ) + set(value) { + field = value + + updateSiteSecurityIcon() + views.emptyIndicator.setColorFilter(value.emptyIcon) + views.menu.setColorFilter(value.menu) + views.origin.hintColor = value.hint + views.origin.titleColor = value.title + views.origin.textColor = value.text + views.separator.setColorFilter(value.separator) + views.pageActionSeparator.setBackgroundColor(value.pageActionSeparator) + + if (value.trackingProtection != null) { + views.trackingProtectionIndicator.setTint(value.trackingProtection) + views.trackingProtectionIndicator.setColorFilter(value.trackingProtection) + } + + if (value.highlight != null) { + views.highlight.setTint(value.highlight) + } + } + + /** + * Customizable icons in "edit mode". + */ + var icons: Icons = Icons( + emptyIcon = null, + trackingProtectionTrackersBlocked = requireNotNull( + getDrawable(context, TrackingProtectionIconView.DEFAULT_ICON_ON_TRACKERS_BLOCKED), + ), + trackingProtectionNothingBlocked = requireNotNull( + getDrawable(context, TrackingProtectionIconView.DEFAULT_ICON_ON_NO_TRACKERS_BLOCKED), + ), + trackingProtectionException = requireNotNull( + getDrawable(context, TrackingProtectionIconView.DEFAULT_ICON_OFF_FOR_A_SITE), + ), + highlight = requireNotNull( + getDrawable(context, R.drawable.mozac_dot_notification), + ), + ) + set(value) { + field = value + + views.emptyIndicator.setImageDrawable(value.emptyIcon) + + views.trackingProtectionIndicator.setIcons( + value.trackingProtectionNothingBlocked, + value.trackingProtectionTrackersBlocked, + value.trackingProtectionException, + ) + views.highlight.setIcon(value.highlight) + } + + /** + * Allows customization of URL for display purposes. + */ + var urlFormatter: ((CharSequence) -> CharSequence)? = null + + /** + * Sets a listener to be invoked when the site security indicator icon is clicked. + */ + fun setOnSiteSecurityClickedListener(listener: (() -> Unit)?) { + if (listener == null) { + views.securityIndicator.setOnClickListener(null) + views.securityIndicator.background = null + } else { + views.securityIndicator.setOnClickListener { + listener.invoke() + } + + val outValue = TypedValue() + context.theme.resolveAttribute( + android.R.attr.selectableItemBackgroundBorderless, + outValue, + true, + ) + + views.securityIndicator.setBackgroundResource(outValue.resourceId) + } + } + + /** + * Sets a listener to be invoked when the site tracking protection indicator icon is clicked. + */ + fun setOnTrackingProtectionClickedListener(listener: (() -> Unit)?) { + if (listener == null) { + views.trackingProtectionIndicator.setOnClickListener(null) + views.trackingProtectionIndicator.background = null + } else { + views.trackingProtectionIndicator.setOnClickListener { + listener.invoke() + } + + val outValue = TypedValue() + context.theme.resolveAttribute( + android.R.attr.selectableItemBackgroundBorderless, + outValue, + true, + ) + + views.trackingProtectionIndicator.setBackgroundResource(outValue.resourceId) + } + } + + /** + * Sets a lambda to be invoked when the menu is dismissed + */ + fun setMenuDismissAction(onDismiss: () -> Unit) { + views.menu.setMenuDismissAction(onDismiss) + } + + /** + * List of indicators that should be displayed next to the URL. + */ + var indicators: List = listOf(Indicators.SECURITY) + set(value) { + field = value + + updateIndicatorVisibility() + } + + var displayIndicatorSeparator: Boolean = true + set(value) { + field = value + updateIndicatorVisibility() + } + + /** + * Sets the background that should be drawn behind the URL, page actions an indicators. + */ + fun setUrlBackground(background: Drawable?) { + views.background.setImageDrawable(background) + } + + /** + * Whether the progress bar should be drawn at the top or bottom of the toolbar. + */ + var progressGravity: Gravity = Gravity.BOTTOM + set(value) { + field = value + + val layout = rootView as ConstraintLayout + layout.hashCode() + + val constraintSet = ConstraintSet() + constraintSet.clone(layout) + constraintSet.clear(views.progress.id, ConstraintSet.TOP) + constraintSet.clear(views.progress.id, ConstraintSet.BOTTOM) + constraintSet.connect( + views.progress.id, + if (value == Gravity.TOP) ConstraintSet.TOP else ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + if (value == Gravity.TOP) ConstraintSet.TOP else ConstraintSet.BOTTOM, + ) + constraintSet.applyTo(layout) + } + + /** + * Sets a lambda that will be invoked whenever the URL in display mode was clicked. Only if this + * lambda returns true the toolbar will switch to editing mode. Return + * false to not switch to editing mode and handle the click manually. + */ + var onUrlClicked: () -> Boolean + get() = views.origin.onUrlClicked + set(value) { + views.origin.onUrlClicked = value + } + + /** + * Sets the text to be displayed when the URL of the toolbar is empty. + */ + var hint: String + get() = views.origin.hint + set(value) { + views.origin.hint = value + } + + /** + * Sets the size of the text for the title displayed in the toolbar. + */ + var titleTextSize: Float + get() = views.origin.titleTextSize + set(value) { + views.origin.titleTextSize = value + } + + /** + * Sets the size of the text for the URL/search term displayed in the toolbar. + */ + var textSize: Float + get() = views.origin.textSize + set(value) { + views.origin.textSize = value + } + + /** + * Sets the typeface of the text for the URL/search term displayed in the toolbar. + */ + var typeface: Typeface + get() = views.origin.typeface + set(value) { + views.origin.typeface = value + } + + /** + * Sets a [BrowserMenuBuilder] that will be used to create a menu when the menu button is clicked. + * The menu button will only be visible if a builder or controller has been set. + */ + var menuBuilder: BrowserMenuBuilder? + get() = views.menu.menuBuilder + set(value) { + views.menu.menuBuilder = value + } + + /** + * Sets a [MenuController] that will be used to create a menu when the menu button is clicked. + * The menu button will only be visible if a builder or controller has been set. + * If both a [menuBuilder] and controller are present, only the controller will be used. + */ + var menuController: MenuController? + get() = views.menu.menuController + set(value) { + views.menu.menuController = value + } + + /** + * Set a LongClickListener to the urlView of the toolbar. + */ + fun setOnUrlLongClickListener(handler: ((View) -> Boolean)?) = views.origin.setOnUrlLongClickListener(handler) + + private fun updateIndicatorVisibility() { + val urlEmpty = url.isEmpty() + + views.securityIndicator.visibility = if (!urlEmpty && indicators.contains(Indicators.SECURITY)) { + View.VISIBLE + } else { + View.GONE + } + + views.trackingProtectionIndicator.visibility = if ( + !urlEmpty && indicators.contains(Indicators.TRACKING_PROTECTION) + ) { + View.VISIBLE + } else { + View.GONE + } + + views.emptyIndicator.visibility = if (urlEmpty && indicators.contains(Indicators.EMPTY)) { + View.VISIBLE + } else { + View.GONE + } + + views.highlight.visibility = if (!urlEmpty && indicators.contains(Indicators.HIGHLIGHT)) { + setHighlight(toolbar.highlight) + } else { + View.GONE + } + + updateSeparatorVisibility() + } + + private fun updateSeparatorVisibility() { + views.separator.visibility = if ( + displayIndicatorSeparator && + views.trackingProtectionIndicator.isVisible && + views.securityIndicator.isVisible + ) { + View.VISIBLE + } else { + View.GONE + } + + // In Fenix (which is using a beta release of ConstraintLayout) we are seeing issues after + // early visibility changes. Children of the ConstraintLayout are not visible and have a + // size of 0x0 (even though they have a fixed size in the layout XML). Explicitly requesting + // to layout the ConstraintLayout fixes that issue. This may be a bug in the beta of + // ConstraintLayout and in the future we may be able to just remove this call. + rootView.requestLayout() + } + + /** + * Updates the title to be displayed. + */ + internal var title: String + get() = views.origin.title + set(value) { + views.origin.title = value + } + + /** + * Updates the URL to be displayed. + */ + internal var url: CharSequence = "" + set(value) { + field = value + views.origin.url = urlFormatter?.invoke(value) ?: value + updateIndicatorVisibility() + } + + /** + * Sets the site's security icon as secure if true, else the regular globe. + */ + internal var siteSecurity: Toolbar.SiteSecurity = Toolbar.SiteSecurity.INSECURE + set(value) { + field = value + updateSiteSecurityIcon() + } + + private fun updateSiteSecurityIcon() { + @ColorInt val color = when (siteSecurity) { + Toolbar.SiteSecurity.INSECURE -> colors.securityIconInsecure + Toolbar.SiteSecurity.SECURE -> colors.securityIconSecure + } + if (color == Color.TRANSPARENT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + views.securityIndicator.clearColorFilter() + } else { + views.securityIndicator.setColorFilter(color) + } + + views.securityIndicator.siteSecurity = siteSecurity + } + + internal fun setTrackingProtectionState(state: Toolbar.SiteTrackingProtection) { + views.trackingProtectionIndicator.siteTrackingProtection = state + updateSeparatorVisibility() + } + + internal fun setHighlight(state: Toolbar.Highlight): Int { + if (!indicators.contains(Indicators.HIGHLIGHT)) { + return views.highlight.visibility + } + + views.highlight.state = state + + return views.highlight.visibility + } + + internal fun onStop() { + views.menu.dismissMenu() + } + + /** + * Updates the progress to be displayed. + * + * Accessibility note: + * ProgressBars can be made accessible to TalkBack by setting `android:accessibilityLiveRegion`. + * They will emit TYPE_VIEW_SELECTED events. TalkBack will format those events into percentage + * announcements along with a pitch-change earcon. We are not using that feature here for + * several reasons: + * 1. They are dispatched via a 200ms timeout. Since loading a page can be a short process, + * and since we only update the bar a handful of times, these events often never fire and + * they don't give the user a true sense of the progress. + * 2. The last 100% event is dispatched after the view is hidden. This prevents the event + * from being fired, so the user never gets a "complete" event. + * 3. Live regions in TalkBack have their role announced, so the user will hear + * "Progress bar, 25%". For a common feature like page load this is very chatty and unintuitive. + * 4. We can provide custom strings instead of the less useful percentage utterance, but + * TalkBack will not play an earcon if an event has its own text. + * + * For all those reasons, we are going another route here with a "loading" announcement + * when the progress bar first appears along with scroll events that have the same + * pitch-change earcon in TalkBack (although they are a bit louder). This gives a concise and + * consistent feedback to the user that they can depend on. + * + */ + internal fun updateProgress(progress: Int) { + if (!views.progress.isVisible && progress > 0) { + // Loading has just started, make visible. + views.progress.visibility = View.VISIBLE + + // Announce "loading" for accessibility if it has not been completed + if (progress < views.progress.max) { + views.progress.announceForAccessibility( + context.getString(R.string.mozac_browser_toolbar_progress_loading), + ) + } + } + + views.progress.progress = progress + val event = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED) + } else { + @Suppress("DEPRECATION") + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED) + }.apply { + scrollY = progress + maxScrollY = views.progress.max + } + + if (context.isScreenReaderEnabled) { + views.progress.parent.requestSendAccessibilityEvent(views.progress, event) + } + + if (progress >= views.progress.max) { + // Loading is done, hide progress bar. + views.progress.visibility = View.GONE + } + } + + /** + * Declare that the actions (navigation actions, browser actions, page actions) have changed and + * should be updated if needed. + */ + internal fun invalidateActions() { + views.menu.invalidateMenu() + + views.browserActions.invalidateActions() + views.pageActions.invalidateActions() + views.navigationActions.invalidateActions() + } + + /** + * Adds an action to be displayed on the right side of the toolbar (outside of the URL bounding + * box) in display mode. + * + * If there is not enough room to show all icons then some icons may be moved to an overflow + * menu. + * + * Related: + * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action + */ + internal fun addBrowserAction(action: Toolbar.Action) { + views.browserActions.addAction(action) + } + + /** + * Removes a previously added browser action (see [addBrowserAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + internal fun removeBrowserAction(action: Toolbar.Action) { + views.browserActions.removeAction(action) + } + + /** + * Removes a previously added page action (see [addBrowserAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + internal fun removePageAction(action: Toolbar.Action) { + views.pageActions.removeAction(action) + } + + /** + * Adds an action to be displayed on the right side of the URL in display mode. + * + * Related: + * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Page_actions + */ + internal fun addPageAction(action: Toolbar.Action) { + views.pageActions.addAction(action) + } + + /** + * Adds an action to be display on the far left side of the toolbar. This area is usually used + * on larger devices for navigation actions like "back" and "forward". + */ + internal fun addNavigationAction(action: Toolbar.Action) { + views.navigationActions.addAction(action) + } + + /** + * Removes a previously added navigation action (see [addNavigationAction]). If the provided + * action was never added, this method has no effect. + * + * @param action the action to remove. + */ + internal fun removeNavigationAction(action: Toolbar.Action) { + views.navigationActions.removeAction(action) + } + + /** + * Hides the menu button in display mode. + */ + fun hideMenuButton() { + views.menu.setShouldBeHidden(true) + } + + /** + * Shows the menu button in display mode. + */ + internal fun showMenuButton() { + views.menu.setShouldBeHidden(false) + } + + /** + * Sets the horizontal padding. + */ + fun setHorizontalPadding(horizontalPadding: Int) { + rootView.setPadding(horizontalPadding, 0, horizontalPadding, 0) + } + + /** + * Hides the page action separator in display mode. + */ + fun hidePageActionSeparator() { + views.pageActionSeparator.isVisible = false + } + + /** + * Shows the page action separator in display mode. + */ + internal fun showPageActionSeparator() { + views.pageActionSeparator.isVisible = true + } +} + +/** + * Internal holder for view references. + */ +@Suppress("LongParameterList") +internal class DisplayToolbarViews( + val browserActions: ActionContainer, + val pageActions: ActionContainer, + val navigationActions: ActionContainer, + val background: ImageView, + val separator: ImageView, + val pageActionSeparator: View, + val emptyIndicator: ImageView, + val menu: MenuButton, + val securityIndicator: SiteSecurityIconView, + val trackingProtectionIndicator: TrackingProtectionIconView, + val origin: OriginView, + val progress: ProgressBar, + val highlight: HighlightView, +) diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbarView.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbarView.kt new file mode 100644 index 0000000000..5bd684a07e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/DisplayToolbarView.kt @@ -0,0 +1,51 @@ +/* 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.toolbar2.display + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import mozilla.components.browser.toolbar2.R + +/** + * Custom ConstraintLayout for DisplayToolbar that allows us to draw ripple backgrounds on the toolbar + * by setting a background to transparent. + */ +class DisplayToolbarView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : ConstraintLayout(context, attrs, defStyleAttr) { + init { + // Forcing transparent background so that draw methods will get called and ripple effect + // for children will be drawn on this layout. + setBackgroundColor(0x00000000) + } + + lateinit var backgroundView: ImageView + + override fun onFinishInflate() { + backgroundView = findViewById(R.id.mozac_browser_toolbar_background) + backgroundView.visibility = View.INVISIBLE + + super.onFinishInflate() + } + + // Overriding draw instead of onDraw since we want to draw the background before the actual + // (transparent) background (with a ripple effect) is drawn. + override fun draw(canvas: Canvas) { + canvas.save() + canvas.translate(backgroundView.x, backgroundView.y) + + backgroundView.drawable?.draw(canvas) + + canvas.restore() + + super.draw(canvas) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/HighlightView.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/HighlightView.kt new file mode 100644 index 0000000000..240da038ec --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/HighlightView.kt @@ -0,0 +1,91 @@ +/* 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.toolbar2.display + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.VisibleForTesting +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.Toolbar.Highlight +import mozilla.components.concept.toolbar.Toolbar.Highlight.NONE +import mozilla.components.concept.toolbar.Toolbar.Highlight.PERMISSIONS_CHANGED + +/** + * Internal widget to display a dot notification. + */ +internal class HighlightView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : AppCompatImageView(context, attrs, defStyleAttr) { + + init { + visibility = GONE + } + + var state: Highlight = NONE + set(value) { + if (value != field) { + field = value + updateIcon() + } + } + + @VisibleForTesting + internal var highlightTint: Int? = null + + private var highlightIcon: Drawable = + requireNotNull(AppCompatResources.getDrawable(context, DEFAULT_ICON)) + + fun setTint(tint: Int) { + highlightTint = tint + setColorFilter(tint) + } + + fun setIcon(icons: Drawable) { + this.highlightIcon = icons + + updateIcon() + } + + @Synchronized + @VisibleForTesting + internal fun updateIcon() { + val update = state.toUpdate() + + isVisible = update.visible + + contentDescription = if (update.contentDescription != null) { + context.getString(update.contentDescription) + } else { + null + } + + highlightTint?.let { setColorFilter(it) } + setImageDrawable(update.drawable) + } + + companion object { + val DEFAULT_ICON = R.drawable.mozac_dot_notification + } + + private fun Highlight.toUpdate(): Update = when (this) { + PERMISSIONS_CHANGED -> Update( + highlightIcon, + R.string.mozac_browser_toolbar_content_description_autoplay_blocked, + true, + ) + + NONE -> Update( + null, + null, + false, + ) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/MenuButton.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/MenuButton.kt new file mode 100644 index 0000000000..6c2a919d00 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/MenuButton.kt @@ -0,0 +1,106 @@ +/* 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.toolbar2.display + +import androidx.annotation.ColorInt +import androidx.annotation.VisibleForTesting +import androidx.core.view.isVisible +import mozilla.components.browser.menu.BrowserMenuBuilder +import mozilla.components.browser.menu.ext.asCandidateList +import mozilla.components.browser.menu.ext.getHighlight +import mozilla.components.browser.toolbar2.facts.emitOpenMenuFact +import mozilla.components.concept.menu.MenuController + +internal class MenuButton( + @get:VisibleForTesting internal val impl: mozilla.components.browser.menu.view.MenuButton, +) { + + init { + impl.isVisible = false + impl.register( + object : mozilla.components.concept.menu.MenuButton.Observer { + override fun onShow() { + emitOpenMenuFact(impl.menuBuilder?.extras) + } + }, + ) + } + + /** + * Reference to the [MenuController]. + * If present, [menuBuilder] will be ignored. + */ + var menuController: MenuController? + get() = impl.menuController + set(value) { + impl.menuController = value + impl.isVisible = shouldBeVisible() + } + + /** + * Legacy [BrowserMenuBuilder] reference. + * Used to build the menu. + */ + var menuBuilder: BrowserMenuBuilder? + get() = impl.menuBuilder + set(value) { + impl.menuBuilder = value + impl.isVisible = shouldBeVisible() + } + + /** + * Declare that the menu items should be updated if needed. + * This should only be used once a [menuBuilder] is set. + * To update items in the [menuController], call [MenuController.submitList] directly. + */ + fun invalidateMenu() { + val menuController = menuController + if (menuController != null) { + // Convert the menu builder items into a menu candidate list, + // if the menu builder is present + menuBuilder?.items?.let { items -> + val list = items.asCandidateList(impl.context) + menuController.submitList(list) + } + } else { + // Invalidate the BrowserMenu + impl.invalidateBrowserMenu() + impl.setHighlight(menuBuilder?.items?.getHighlight()) + } + } + + fun dismissMenu() { + val menuController = menuController + if (menuController != null) { + // Use the controller to dismiss the menu + menuController.dismiss() + } else { + // Use the button to dismiss the legacy menu + impl.dismissMenu() + } + } + + /** + * Sets a lambda to be invoked when the menu is dismissed + */ + @Suppress("Deprecation") + fun setMenuDismissAction(onDismiss: () -> Unit) { + impl.onDismiss = onDismiss + } + + fun setColorFilter(@ColorInt color: Int) = impl.setColorFilter(color) + + /** + * Hides the menu button. + * + * @param shouldBeHidden A [Boolean] that determines the visibility of the menu button. + */ + fun setShouldBeHidden(shouldBeHidden: Boolean) { + impl.isVisible = !shouldBeHidden && shouldBeVisible() + } + + @VisibleForTesting + internal fun shouldBeVisible() = impl.menuBuilder != null || impl.menuController != null +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/OriginView.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/OriginView.kt new file mode 100644 index 0000000000..3cd70d0dc9 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/OriginView.kt @@ -0,0 +1,198 @@ +/* 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.toolbar2.display + +import android.animation.LayoutTransition +import android.content.Context +import android.graphics.Typeface +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.VisibleForTesting +import androidx.core.view.isVisible +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.browser.toolbar2.R + +private const val TITLE_VIEW_WEIGHT = 5.7f +private const val URL_VIEW_WEIGHT = 4.3f + +/** + * View displaying the URL and optionally the title of a website. + */ +internal class OriginView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : LinearLayout(context, attrs, defStyleAttr) { + internal lateinit var toolbar: BrowserToolbar + + private val textSizeUrlNormal = context.resources.getDimension( + R.dimen.mozac_browser_toolbar_url_textsize, + ) + private val textSizeUrlWithTitle = context.resources.getDimension( + R.dimen.mozac_browser_toolbar_url_with_title_textsize, + ) + private val textSizeTitle = context.resources.getDimension( + R.dimen.mozac_browser_toolbar_title_textsize, + ) + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val urlView = TextView(context).apply { + id = R.id.mozac_browser_toolbar_url_view + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeUrlNormal) + gravity = Gravity.CENTER_VERTICAL + + setSingleLine() + isClickable = true + isFocusable = true + + setOnClickListener { + if (onUrlClicked()) { + toolbar.editMode() + } + } + + val fadingEdgeSize = resources.getDimensionPixelSize( + R.dimen.mozac_browser_toolbar_url_fading_edge_size, + ) + + setFadingEdgeLength(fadingEdgeSize) + isHorizontalFadingEdgeEnabled = fadingEdgeSize > 0 + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val titleView = TextView(context).apply { + id = R.id.mozac_browser_toolbar_title_view + visibility = View.GONE + + setTextSize( + TypedValue.COMPLEX_UNIT_PX, + textSizeTitle, + ) + gravity = Gravity.CENTER_VERTICAL + + setSingleLine() + + val fadingEdgeSize = resources.getDimensionPixelSize( + R.dimen.mozac_browser_toolbar_url_fading_edge_size, + ) + + setFadingEdgeLength(fadingEdgeSize) + isHorizontalFadingEdgeEnabled = fadingEdgeSize > 0 + } + + init { + orientation = VERTICAL + + addView( + titleView, + LayoutParams( + LayoutParams.MATCH_PARENT, + 0, + TITLE_VIEW_WEIGHT, + ), + ) + + addView( + urlView, + LayoutParams( + LayoutParams.MATCH_PARENT, + 0, + URL_VIEW_WEIGHT, + ), + ) + + layoutTransition = LayoutTransition() + } + + internal var title: String + get() = titleView.text.toString() + set(value) { + titleView.text = value + + titleView.isVisible = value.isNotEmpty() + + urlView.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + if (value.isNotEmpty()) { + textSizeUrlWithTitle + } else { + textSizeUrlNormal + }, + ) + } + + internal var onUrlClicked: () -> Boolean = { true } + + fun setOnUrlLongClickListener(handler: ((View) -> Boolean)?) { + urlView.isLongClickable = true + titleView.isLongClickable = true + + urlView.setOnLongClickListener(handler) + titleView.setOnLongClickListener(handler) + } + + internal var url: CharSequence + get() = urlView.text + set(value) { urlView.text = value } + + /** + * Sets the colour of the text to be displayed when the URL of the toolbar is empty. + */ + var hintColor: Int + get() = urlView.currentHintTextColor + set(value) { + urlView.setHintTextColor(value) + } + + /** + * Sets the text to be displayed when the URL of the toolbar is empty. + */ + var hint: String + get() = urlView.hint.toString() + set(value) { urlView.hint = value } + + /** + * Sets the colour of the text for title displayed in the toolbar. + */ + var titleColor: Int + get() = urlView.currentTextColor + set(value) { titleView.setTextColor(value) } + + /** + * Sets the colour of the text for the URL/search term displayed in the toolbar. + */ + var textColor: Int + get() = urlView.currentTextColor + set(value) { urlView.setTextColor(value) } + + /** + * Sets the size of the text for the title displayed in the toolbar. + */ + var titleTextSize: Float + get() = titleView.textSize + set(value) { titleView.textSize = value } + + /** + * Sets the size of the text for the URL/search term displayed in the toolbar. + */ + var textSize: Float + get() = urlView.textSize + set(value) { + urlView.textSize = value + } + + /** + * Sets the typeface of the text for the URL/search term displayed in the toolbar. + */ + var typeface: Typeface + get() = urlView.typeface + set(value) { + urlView.typeface = value + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/SiteSecurityIconView.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/SiteSecurityIconView.kt new file mode 100644 index 0000000000..6ebde5a885 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/SiteSecurityIconView.kt @@ -0,0 +1,48 @@ +/* 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.toolbar2.display + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.Toolbar.SiteSecurity + +/** + * Internal widget to display the different icons of site security, relies on the + * [SiteSecurity] state of each page. + */ +internal class SiteSecurityIconView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : AppCompatImageView(context, attrs, defStyleAttr) { + + // We allow null here because in some situations, onCreateDrawableState is getting called from + // the super() constructor on the View class, way before this class properties get + // initialized causing a null pointer exception. + // See for more details: https://github.com/mozilla-mobile/android-components/issues/4058 + var siteSecurity: SiteSecurity? = SiteSecurity.INSECURE + set(value) { + if (value != field) { + field = value + refreshDrawableState() + } + + field = value + } + + override fun onCreateDrawableState(extraSpace: Int): IntArray { + return when (siteSecurity) { + SiteSecurity.INSECURE, null -> super.onCreateDrawableState(extraSpace) + SiteSecurity.SECURE -> { + val drawableState = super.onCreateDrawableState(extraSpace + 1) + View.mergeDrawableStates(drawableState, intArrayOf(R.attr.state_site_secure)) + drawableState + } + } + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconView.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconView.kt new file mode 100644 index 0000000000..654f4edadb --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconView.kt @@ -0,0 +1,135 @@ +/* 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.toolbar2.display + +import android.content.Context +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.StringRes +import androidx.annotation.VisibleForTesting +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection.OFF_FOR_A_SITE +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection.OFF_GLOBALLY +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection.ON_TRACKERS_BLOCKED + +/** + * Internal widget to display the different icons of tracking protection, relies on the + * [SiteTrackingProtection] state of each page. + */ +internal class TrackingProtectionIconView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : AppCompatImageView(context, attrs, defStyleAttr) { + var siteTrackingProtection: SiteTrackingProtection? = null + set(value) { + if (value != field) { + field = value + updateIcon() + } + } + + @VisibleForTesting + internal var trackingProtectionTint: Int? = null + + private var iconOnNoTrackersBlocked: Drawable = + requireNotNull(AppCompatResources.getDrawable(context, DEFAULT_ICON_ON_NO_TRACKERS_BLOCKED)) + private var iconOnTrackersBlocked: Drawable = + requireNotNull(AppCompatResources.getDrawable(context, DEFAULT_ICON_ON_TRACKERS_BLOCKED)) + private var disabledForSite: Drawable = + requireNotNull(AppCompatResources.getDrawable(context, DEFAULT_ICON_OFF_FOR_A_SITE)) + + fun setTint(tint: Int) { + trackingProtectionTint = tint + } + + fun setIcons( + iconOnNoTrackersBlocked: Drawable, + iconOnTrackersBlocked: Drawable, + disabledForSite: Drawable, + ) { + this.iconOnNoTrackersBlocked = iconOnNoTrackersBlocked + this.iconOnTrackersBlocked = iconOnTrackersBlocked + this.disabledForSite = disabledForSite + + updateIcon() + } + + @Synchronized + private fun updateIcon() { + val update = siteTrackingProtection?.toUpdate() ?: return + + isVisible = update.visible + + contentDescription = if (update.contentDescription != null) { + context.getString(update.contentDescription) + } else { + null + } + + setOrClearColorFilter(update.drawable) + setImageDrawable(update.drawable) + + if (update.drawable is Animatable) { + update.drawable.start() + } + } + + @VisibleForTesting + internal fun setOrClearColorFilter(drawable: Drawable?) { + if (drawable is Animatable) { + clearColorFilter() + } else { + trackingProtectionTint?.let { setColorFilter(it) } + } + } + + companion object { + val DEFAULT_ICON_ON_NO_TRACKERS_BLOCKED = + R.drawable.mozac_ic_tracking_protection_on_no_trackers_blocked + val DEFAULT_ICON_ON_TRACKERS_BLOCKED = + R.drawable.mozac_ic_tracking_protection_on_trackers_blocked + val DEFAULT_ICON_OFF_FOR_A_SITE = + R.drawable.mozac_ic_tracking_protection_off_for_a_site + } + + private fun SiteTrackingProtection.toUpdate(): Update = when (this) { + ON_NO_TRACKERS_BLOCKED -> Update( + iconOnNoTrackersBlocked, + R.string.mozac_browser_toolbar_content_description_tracking_protection_on_no_trackers_blocked, + true, + ) + + ON_TRACKERS_BLOCKED -> Update( + iconOnTrackersBlocked, + R.string.mozac_browser_toolbar_content_description_tracking_protection_on_trackers_blocked1, + true, + ) + + OFF_FOR_A_SITE -> Update( + disabledForSite, + R.string.mozac_browser_toolbar_content_description_tracking_protection_off_for_a_site1, + true, + ) + + OFF_GLOBALLY -> Update( + null, + null, + false, + ) + } +} + +internal class Update( + val drawable: Drawable?, + @StringRes val contentDescription: Int?, + val visible: Boolean, +) diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/edit/EditToolbar.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/edit/EditToolbar.kt new file mode 100644 index 0000000000..2595ab66e2 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/edit/EditToolbar.kt @@ -0,0 +1,415 @@ +/* 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.toolbar2.edit + +import android.content.Context +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Build +import android.view.KeyEvent +import android.view.View +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PRIVATE +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.ContextCompat +import androidx.core.view.inputmethod.EditorInfoCompat +import androidx.core.view.isVisible +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import mozilla.components.browser.toolbar2.AsyncFilterListener +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.browser.toolbar2.R +import mozilla.components.browser.toolbar2.facts.emitCommitFact +import mozilla.components.browser.toolbar2.internal.ActionContainer +import mozilla.components.concept.toolbar.AutocompleteDelegate +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.base.utils.NamedThreadFactory +import mozilla.components.support.ktx.android.view.showKeyboard +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText +import java.util.concurrent.Executors +import mozilla.components.ui.colors.R as colorsR + +private const val AUTOCOMPLETE_QUERY_THREADS = 3 + +/** + * Sub-component of the browser toolbar responsible for allowing the user to edit the URL ("edit mode"). + * + * Structure: + * +------+--------------------+---------------------------+------------------+------+ + * | icon | edit actions start | url | edit actions end | exit | + * +------+--------------------+---------------------------+------------------+------+ + * + * - icon: Optional icon that will be shown in front of the URL. + * - edit actions start: Optional action icons injected by other components in front of the URL + * (e.g. search engines). + * - url: Editable URL of the currently displayed website. + * - edit actions end: Optional action icons injected by other components after the URL + * (e.g. barcode scanner). + * - exit: Button that switches back to display mode or invoke an app-defined callback. + */ +@Suppress("LargeClass") +class EditToolbar internal constructor( + context: Context, + private val toolbar: BrowserToolbar, + internal val rootView: View, +) { + private val logger = Logger("EditToolbar") + + /** + * Data class holding the customizable colors in "edit mode". + * + * @property clear Color tint used for the "cancel" icon to leave "edit mode". + * @property icon Color tint of the icon displayed in front of the URL. + * @property hint Text color of the hint shown when the URL field is empty. + * @property text Text color of the URL. + * @property suggestionBackground The background color used for autocomplete suggestions. + * @property suggestionForeground The foreground color used for autocomplete suggestions. + * @property pageActionSeparator Color tint of separator dividing page actions. + */ + data class Colors( + @ColorInt val clear: Int, + @ColorInt val erase: Int, + @ColorInt val icon: Int?, + @ColorInt val hint: Int, + @ColorInt val text: Int, + @ColorInt val suggestionBackground: Int, + @ColorInt val suggestionForeground: Int?, + @ColorInt val pageActionSeparator: Int, + ) + + private val autocompleteDispatcher = SupervisorJob() + + Executors.newFixedThreadPool( + AUTOCOMPLETE_QUERY_THREADS, + NamedThreadFactory("EditToolbar"), + ).asCoroutineDispatcher() + + CoroutineExceptionHandler { _, throwable -> + logger.error("Error while processing autocomplete input", throwable) + } + + @VisibleForTesting(otherwise = PRIVATE) + internal val views = EditToolbarViews( + background = rootView.findViewById(R.id.mozac_browser_toolbar_background), + icon = rootView.findViewById(R.id.mozac_browser_toolbar_edit_icon), + editActionsStart = rootView.findViewById(R.id.mozac_browser_toolbar_edit_actions_start), + editActionsEnd = rootView.findViewById(R.id.mozac_browser_toolbar_edit_actions_end), + clear = rootView.findViewById(R.id.mozac_browser_toolbar_clear_view).apply { + setOnClickListener { + onClear() + } + }, + erase = rootView.findViewById(R.id.mozac_browser_toolbar_erase_view).apply { + setOnClickListener { + onClear() + } + }, + url = rootView.findViewById( + R.id.mozac_browser_toolbar_edit_url_view, + ).apply { + setOnCommitListener { + // We emit the fact before notifying the listener because otherwise the listener may cause a focus + // change which may reset the autocomplete state that we want to report here. + emitCommitFact(autocompleteResult) + + toolbar.onUrlEntered(text.toString()) + } + + setOnTextChangeListener { text, _ -> + onTextChanged(text) + } + + setUrlGoneMargin( + ConstraintSet.END, + context.resources.getDimensionPixelSize(R.dimen.mozac_browser_toolbar_url_gone_margin_end), + ) + + setOnDispatchKeyEventPreImeListener { event -> + if (event?.keyCode == KeyEvent.KEYCODE_BACK && editListener?.onCancelEditing() != false) { + toolbar.displayMode() + } + false + } + }, + pageActionSeparator = rootView.findViewById(R.id.mozac_browser_action_separator), + ) + + /** + * Customizable colors in "edit mode". + */ + var colors: Colors = Colors( + clear = ContextCompat.getColor(context, colorsR.color.photonWhite), + erase = ContextCompat.getColor(context, colorsR.color.photonWhite), + icon = null, + hint = views.url.currentHintTextColor, + text = views.url.currentTextColor, + suggestionBackground = views.url.autoCompleteBackgroundColor, + suggestionForeground = views.url.autoCompleteForegroundColor, + pageActionSeparator = ContextCompat.getColor(context, colorsR.color.photonGrey80), + ) + set(value) { + field = value + + views.clear.setColorFilter(value.clear) + + views.erase.setColorFilter(value.erase) + + if (value.icon != null) { + views.icon.setColorFilter(value.icon) + } + + views.url.setHintTextColor(value.hint) + views.url.setTextColor(value.text) + views.url.autoCompleteBackgroundColor = value.suggestionBackground + views.url.autoCompleteForegroundColor = value.suggestionForeground + views.pageActionSeparator.setBackgroundColor(value.pageActionSeparator) + } + + /** + * Sets the background that will be drawn behind the URL, icon and edit actions. + */ + fun setUrlBackground(background: Drawable?) { + views.background.setImageDrawable(background) + } + + /** + * Sets an icon that will be drawn in front of the URL. + */ + fun setIcon(icon: Drawable, contentDescription: String) { + views.icon.setImageDrawable(icon) + views.icon.contentDescription = contentDescription + views.icon.visibility = View.VISIBLE + } + + /** + * Sets a click listener on the icon view + */ + fun setIconClickListener(listener: ((View) -> Unit)?) { + views.icon.setOnClickListener(listener) + } + + /** + * Sets the text to be displayed when the URL of the toolbar is empty. + */ + var hint: String + get() = views.url.hint.toString() + set(value) { views.url.hint = value } + + /** + * Sets the size of the text for the URL/search term displayed in the toolbar. + */ + var textSize: Float + get() = views.url.textSize + set(value) { + views.url.textSize = value + } + + /** + * Sets the typeface of the text for the URL/search term displayed in the toolbar. + */ + var typeface: Typeface + get() = views.url.typeface + set(value) { views.url.typeface = value } + + /** + * Sets a listener to be invoked when focus of the URL input view (in edit mode) changed. + */ + fun setOnEditFocusChangeListener(listener: (Boolean) -> Unit) { + views.url.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> + listener.invoke(hasFocus) + } + } + + /** + * Focuses the url input field and shows the virtual keyboard if needed. + */ + fun focus() { + views.url.run { + if (!hasFocus()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // On Android 14 this needs to be called before requestFocus() in order to receive focus. + isFocusableInTouchMode = true + } + requestFocus() + showKeyboard() + } + } + } + + internal fun stopEditing() { + editListener?.onStopEditing() + } + + internal fun startEditing() { + editListener?.onStartEditing() + } + + internal var editListener: Toolbar.OnEditListener? = null + + internal fun setAutocompleteListener(filter: suspend (String, AutocompleteDelegate) -> Unit) { + views.url.setOnFilterListener( + AsyncFilterListener(views.url, autocompleteDispatcher, filter), + ) + } + + /** + * Attempt to restart the autocomplete functionality with the current user input. + */ + internal fun refreshAutocompleteSuggestion() { + views.url.refreshAutocompleteSuggestions() + } + + internal fun invalidateActions() { + views.editActionsStart.invalidateActions() + views.editActionsEnd.invalidateActions() + } + + internal fun addEditActionStart(action: Toolbar.Action) { + views.editActionsStart.addAction(action) + } + + internal fun addEditActionEnd(action: Toolbar.Action) { + views.editActionsEnd.addAction(action) + } + + internal fun removeEditActionEnd(action: Toolbar.Action) { + views.editActionsEnd.removeAction(action) + } + + /** + * Updates the text of the URL input field. Note: this does *not* affect the value of url itself + * and is only a visual change + */ + fun updateUrl( + url: String, + shouldAutoComplete: Boolean = false, + shouldHighlight: Boolean = false, + shouldAppend: Boolean = false, + ): String { + if (shouldAppend) { + views.url.appendText(url, shouldAutoComplete) + } else { + views.url.setText(url, shouldAutoComplete) + } + views.clear.isVisible = url.isNotBlank() && !toolbar.isNavBarEnabled + views.erase.isVisible = url.isNotBlank() && toolbar.isNavBarEnabled + + if (shouldHighlight) { + views.url.setSelection(views.url.text.length - url.length, views.url.text.length) + } + return views.url.text.toString() + } + + /** + * Select the entire text in the URL input field. + */ + internal fun selectAll() { + views.url.selectAll() + } + + /** + * Places the cursor at the end of the URL input field. + */ + internal fun selectEnd() { + views.url.setSelection(views.url.text.length) + } + + /** + * Applies the given search terms for further editing, requesting new suggestions along the way. + */ + internal fun editSuggestion(searchTerms: String) { + updateUrl(searchTerms) + views.url.setSelection(views.url.text.length) + focus() + + editListener?.onTextChanged(searchTerms) + } + + /** + * Sets/gets private mode. + * + * In private mode the IME should not update any personalized data such as typing history and personalized language + * model based on what the user typed. + */ + internal var private: Boolean + get() = (views.url.imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING) != 0 + set(value) { + views.url.imeOptions = if (value) { + views.url.imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + views.url.imeOptions and (EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()) + } + } + + private fun onClear() { + // We set text to an empty string instead of using clear to avoid #3612. + views.url.setText("") + editListener?.onInputCleared() + } + + private fun setUrlGoneMargin(anchor: Int, dimen: Int) { + val set = ConstraintSet() + val container = rootView.findViewById( + R.id.mozac_browser_toolbar_container, + ) + set.clone(container) + set.setGoneMargin(R.id.mozac_browser_toolbar_edit_url_view, anchor, dimen) + set.applyTo(container) + } + + private fun onTextChanged(text: String) { + views.clear.isVisible = text.isNotBlank() && !toolbar.isNavBarEnabled + views.erase.isVisible = text.isNotBlank() && toolbar.isNavBarEnabled + views.editActionsEnd.autoHideAction(text.isEmpty()) + + /* + We use margin_gone instead of margin to take into account both the actionContainer(which in + most cases is gone) and the clear button. + */ + if (text.isNotBlank()) { + setUrlGoneMargin(ConstraintSet.END, 0) + } else { + setUrlGoneMargin( + ConstraintSet.END, + rootView.resources.getDimensionPixelSize( + R.dimen.mozac_browser_toolbar_url_gone_margin_end, + ), + ) + } + editListener?.onTextChanged(text) + } + + /** + * Hides the page action separator in edit mode. + */ + fun hidePageActionSeparator() { + views.pageActionSeparator.isVisible = false + } + + /** + * Shows the page action separator in edit mode. + */ + fun showPageActionSeparator() { + views.pageActionSeparator.isVisible = true + } +} + +/** + * Internal holder for view references. + */ +@Suppress("LongParameterList") +internal class EditToolbarViews( + val background: ImageView, + val icon: ImageView, + val editActionsStart: ActionContainer, + val editActionsEnd: ActionContainer, + val clear: ImageView, + val erase: ImageView, + val url: InlineAutocompleteEditText, + val pageActionSeparator: View, +) diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/facts/ToolbarFacts.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/facts/ToolbarFacts.kt new file mode 100644 index 0000000000..06f09ea18d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/facts/ToolbarFacts.kt @@ -0,0 +1,60 @@ +/* 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.toolbar2.facts + +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.Fact +import mozilla.components.support.base.facts.collect +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText + +/** + * Facts emitted for telemetry related to [ToolbarFeature] + */ +class ToolbarFacts { + /** + * Items that specify which portion of the [ToolbarFeature] was interacted with + */ + object Items { + const val TOOLBAR = "toolbar" + const val MENU = "menu" + } +} + +private fun emitToolbarFact( + action: Action, + item: String, + value: String? = null, + metadata: Map? = null, +) { + Fact( + Component.BROWSER_TOOLBAR, + action, + item, + value, + metadata, + ).collect() +} + +internal fun emitOpenMenuFact(extras: Map?) { + emitToolbarFact(Action.CLICK, ToolbarFacts.Items.MENU, metadata = extras) +} + +internal fun emitCommitFact( + autocompleteResult: InlineAutocompleteEditText.AutocompleteResult?, +) { + val metadata = if (autocompleteResult == null) { + mapOf( + "autocomplete" to false, + ) + } else { + mapOf( + "autocomplete" to true, + "source" to autocompleteResult.source, + ) + } + + emitToolbarFact(Action.COMMIT, ToolbarFacts.Items.TOOLBAR, metadata = metadata) +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionContainer.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionContainer.kt new file mode 100644 index 0000000000..7907dba520 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionContainer.kt @@ -0,0 +1,134 @@ +/* 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.toolbar2.internal + +import android.content.Context +import android.transition.TransitionManager +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import androidx.annotation.VisibleForTesting +import androidx.core.view.isVisible +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.Toolbar + +/** + * A container [View] for displaying [Toolbar.Action] objects. + */ +internal class ActionContainer @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : LinearLayout(context, attrs, defStyleAttr) { + private val actions = mutableListOf() + private var actionSize: Int? = null + + init { + gravity = Gravity.CENTER_VERTICAL + orientation = HORIZONTAL + visibility = View.GONE + + context.obtainStyledAttributes( + attrs, + R.styleable.ActionContainer, + defStyleAttr, + 0, + ).run { + actionSize = attrs?.let { + getDimensionPixelSize(R.styleable.ActionContainer_actionContainerItemSize, 0) + } + + recycle() + } + } + + fun addAction(action: Toolbar.Action) { + val wrapper = ActionWrapper(action) + + if (action.visible()) { + visibility = View.VISIBLE + + action.createView(this).let { + wrapper.view = it + val insertionIndex = calculateInsertionIndex(action) + addActionView(it, insertionIndex) + } + } + + actions.add(wrapper) + } + + /** + * Essentially calculates the index of an action on toolbar based on a + * map [visibleActionIndicesWithWeights] that holds the order + * of visible action indices to their weights sorted by weights. + * An index is now calculated by finding the immediate next larger weight + * compared to the new action's weight. Index of this find becomes the index of the new action. + * If not found, action is appended at the end. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun calculateInsertionIndex(newAction: Toolbar.Action): Int { + if (newAction.weight() == -1) { + return -1 + } + val visibleActionIndicesWithWeights = actions.filter { it.actual.visible() } + .mapNotNull { actionWrapper -> + val index = indexOfChild(actionWrapper.view) + if (index != -1) index to actionWrapper.actual.weight() else null + }.sortedBy { it.second } + + val insertionIndex = visibleActionIndicesWithWeights.firstOrNull { it.second > newAction.weight() }?.first + + return insertionIndex ?: childCount + } + + fun removeAction(action: Toolbar.Action) { + actions.find { it.actual == action }?.let { + actions.remove(it) + removeView(it.view) + } + } + + fun invalidateActions() { + TransitionManager.beginDelayedTransition(this) + var updatedVisibility = View.GONE + + for (action in actions) { + val visible = action.actual.visible() + + if (visible) { + updatedVisibility = View.VISIBLE + } + + if (!visible && action.view != null) { + removeView(action.view) + action.view = null + } else if (visible && action.view == null) { + action.actual.createView(this).let { + action.view = it + val insertionIndex = calculateInsertionIndex(action.actual) + addActionView(it, insertionIndex) + } + } + + action.view?.let { action.actual.bind(it) } + } + + visibility = updatedVisibility + } + + fun autoHideAction(isVisible: Boolean) { + for (action in actions) { + if (action.actual.autoHide()) { + action.view?.isVisible = isVisible + } + } + } + + private fun addActionView(view: View, index: Int) { + addView(view, index, LayoutParams(actionSize ?: 0, actionSize ?: 0)) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionWrapper.kt b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionWrapper.kt new file mode 100644 index 0000000000..8600372f1e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/java/mozilla/components/browser/toolbar2/internal/ActionWrapper.kt @@ -0,0 +1,16 @@ +/* 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.toolbar2.internal + +import android.view.View +import mozilla.components.concept.toolbar.Toolbar + +/** + * A wrapper helper to pair a Toolbar.Action with an optional View. + */ +internal class ActionWrapper( + var actual: Toolbar.Action, + var view: View? = null, +) diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_browser_toolbar_icons_vertical_separator.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_browser_toolbar_icons_vertical_separator.xml new file mode 100644 index 0000000000..2e366640df --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_browser_toolbar_icons_vertical_separator.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_dot_notification.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_dot_notification.xml new file mode 100644 index 0000000000..5469b822af --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_dot_notification.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_site_security.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_site_security.xml new file mode 100644 index 0000000000..ab2b8d0e37 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_site_security.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_off_for_a_site.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_off_for_a_site.xml new file mode 100644 index 0000000000..250bfbfc4c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_off_for_a_site.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_no_trackers_blocked.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_no_trackers_blocked.xml new file mode 100644 index 0000000000..4f81a5602b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_no_trackers_blocked.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_trackers_blocked.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_trackers_blocked.xml new file mode 100644 index 0000000000..4f81a5602b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/drawable/mozac_ic_tracking_protection_on_trackers_blocked.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_displaytoolbar.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_displaytoolbar.xml new file mode 100644 index 0000000000..5a5f779a70 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_displaytoolbar.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_edittoolbar.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_edittoolbar.xml new file mode 100644 index 0000000000..3325cd099f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/layout/mozac_browser_toolbar_edittoolbar.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-am/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-am/strings.xml new file mode 100644 index 0000000000..eda99f879b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-am/strings.xml @@ -0,0 +1,18 @@ + + + + ምናሌ + አጽዳ + + የመከታተያ ጥበቃ በርቷል + + የክትትል ጥበቃ መከታተያዎችን አግዷል + + የክትትል ጥበቃ ለዚህ ድረ-ገፅ ጠፍቷል + + የድረ-ገፅ መረጃ + + በመጫን ላይ + + አንዳንድ ይዘቶች በራስ አጫውት ቅንብር ታግደዋል + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-an/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-an/strings.xml new file mode 100644 index 0000000000..14b40de38e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-an/strings.xml @@ -0,0 +1,16 @@ + + + + Menú + Borrar + + La protección contra seguimiento ye activada + + Las protección contra seguimiento ha blocau elementos de seguimiento + + La protección de seguimiento ye desactivada en este puesto + + Información d’o puesto + + Se ye cargando + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ar/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..a4a01aa836 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ar/strings.xml @@ -0,0 +1,18 @@ + + + + القائمة + امسح + + الحماية من التعقّب مفعّلة + + حجبت الحماية من التعقّب بعض المتعقّبات + + عُطّلت الحماية من التعقب في هذا الموقع + + معلومات الموقع + + يُحمّل + + حجب إعداد التشغيل التلقائي بعض المحتوى + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ast/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ast/strings.xml new file mode 100644 index 0000000000..6cb4e96a9a --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ast/strings.xml @@ -0,0 +1,18 @@ + + + + Menú + Borrar + + La proteición antirrastrexu ta activada + + La proteición antirrastrexu bloquió rastrexadores + + La proteición antirrastrexu ta desactivada nesti sitiu + + Información del sitiu + + Cargando + + La configuración de la reproducción automática bloquió parte del conteníu + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-az/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-az/strings.xml new file mode 100644 index 0000000000..0ccb692a99 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-az/strings.xml @@ -0,0 +1,16 @@ + + + + Menyu + Təmizlə + + İzlənmə Qoruması açıqdır + + İzlənmə Qoruması izləyiciləri əngəllədi + + İzlənmə Qoruması bu sayt üçün sönülüdür + + Sayt məlumatları + + Yüklənir + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-azb/strings.xml new file mode 100644 index 0000000000..d2901fff35 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-azb/strings.xml @@ -0,0 +1,19 @@ + + + + منو + + پوز + + ایزله‌مه قورونماسی آچیق‌دیر + + تعقیب قوروماسی تعقیب‌چی‌لری مسدود ائدیپ‌دیر + + بو سایت اوچون ایزله‌مه قورونماسی باغلیدیر. + + سایت بیلگی‌لری + + دولور + + بعضی محتوا اتوْماتیک‌چال تنظیمی ایله بلوْکلانیب + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ban/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ban/strings.xml new file mode 100644 index 0000000000..037114093f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ban/strings.xml @@ -0,0 +1,8 @@ + + + + Menu + Puyung + + Jantosang dumun + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-be/strings.xml new file mode 100644 index 0000000000..5042e0d03d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-be/strings.xml @@ -0,0 +1,19 @@ + + + + Меню + + Ачысціць + + Ахова ад сачэння ўключана + + Ахова ад сачэння заблакавала трэкеры + + Ахова ад сачэння выключана на гэтым сайце + + Інфармацыя пра сайт + + Загрузка + + Некаторае змесціва было заблакавана наладамі аўтапрайгравання + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bg/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..6f6d6ecbe0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bg/strings.xml @@ -0,0 +1,19 @@ + + + + Меню + + Изчистване + + Защита от проследяване включена + + Защитата от проследяване е спряла проследяване + + Защитата от проследяване е изключена за сайта + + Показване на информация за сайта + + Зареждане + + Част от съдържанието е спряно от настройките за автоматично възпроизвеждане + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bn/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bn/strings.xml new file mode 100644 index 0000000000..8c93a8c6e7 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bn/strings.xml @@ -0,0 +1,18 @@ + + + + মেনু + পরিষ্কার করুন + + ট্র্যাকিং সুরক্ষা চালু আছে + + ট্র্যাকিং সুরক্ষা ট্র্যাকারদের অবরুদ্ধ করেছে + + এই সাইটের জন্য ট্র্যাকিং সুরক্ষা বন্ধ + + সাইটের তথ্য + + লোড হচ্ছে + + কিছু বিষয়বস্তু অটোপ্লে সেটিং দ্বারা ব্লক করা হয়েছে + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-br/strings.xml new file mode 100644 index 0000000000..c5f9c203fd --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-br/strings.xml @@ -0,0 +1,19 @@ + + + + Lañser + + Skarzhañ + + Gweredekaet eo ar gware heuliañ + + Stanket ez eus bet heulierien gant ar gwarez heuliañ + + Diweredekaet eo bet ar gwarez heuliañ evit al lecʼhienn-mañ + + Titouroù al lecʼhienn + + O kargañ + + Elfennoù ’zo a zo bet stanket gant arventenn al lenn emgefreek + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bs/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bs/strings.xml new file mode 100644 index 0000000000..473bf58af2 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-bs/strings.xml @@ -0,0 +1,19 @@ + + + + Meni + + Očisti + + Zaštita od praćenja uključena + + Zaštita od praćenja je blokirala pratioce + + Zaštita od praćenja je isključena za ovu stranicu + + Informacije o stranici + + Učitavanje + + Neki sadržaj je blokiran postavkom automatske reprodukcije + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ca/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000000..44f350dcf2 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ca/strings.xml @@ -0,0 +1,18 @@ + + + + Menú + Esborra + + La protecció contra el seguiment està activada + + La protecció contra el seguiment ha blocat elements de seguiment coneguts + + S’ha desactivat la protecció contra el seguiment per a aquest lloc + + Informació del lloc + + S’està carregant + + Una part del contingut s’ha blocat per la configuració de la reproducció automàtica + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cak/strings.xml new file mode 100644 index 0000000000..ae15567dc0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cak/strings.xml @@ -0,0 +1,19 @@ + + + + K\'utsamaj + + Tijosq\'ïx + + Tzijïl ri Chajinïk Chuwäch Ojqanïk + + Eruq\'aton ojqanela\' ri i Chajinïk chuwäch Ojqanïk + + Chupül ri Chajinïk chuwäch Ojqanem pa re ruxaq re\' + + Rutzijol ruxaq + + Nusamajij + + Jun peraj chi re ri rupam xq\'at ruma ri runuk\'ulem ri ruyon nitzij + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ceb/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ceb/strings.xml new file mode 100644 index 0000000000..4a03205a32 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ceb/strings.xml @@ -0,0 +1,18 @@ + + + + Menu + Panas + + Ang Tracking Protection on + + Ang Tracking Protection nibara ug tracker + + Wala\'y Tracking Protection ani nga site + + Detalye sa site + + Loading + + Ang uban content gibara sa setting sa autoplay + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ckb/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ckb/strings.xml new file mode 100644 index 0000000000..95e99bea91 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ckb/strings.xml @@ -0,0 +1,18 @@ + + + + پێڕست + پاککردنەوە + + پارێزگاری لە چاودێری کارایە + + پارێزگاری چاودێری توانی چەند چاودێریکەرێک بلۆک بکات + + پارێزگاری لە چاودێری ناکارایە بۆ ئەم ماڵپەڕە + + زانیاری ماڵپەڕ + + باردەکرێت + + هەندێک ناوەڕۆک بلۆک کران لە لایەن ڕێکخستنی خۆپێکردنەوە + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-co/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-co/strings.xml new file mode 100644 index 0000000000..2a309bb0b3 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-co/strings.xml @@ -0,0 +1,19 @@ + + + + Listinu + + Squassà + + A prutezzione contr’à u spiunagiu hè attivata + + A prutezzione contr’à u spiunagiu hà bluccatu perseguitatori + + A prutezzione contr’à u spiunagiu hè disattivata per stu situ + + Infurmazioni nant’à u situ + + Caricamentu in corsu + + Certi cuntenuti sò stati bluccati da a preferenza di lettura autumatica + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cs/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000000..a2c3e0d17a --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cs/strings.xml @@ -0,0 +1,19 @@ + + + + Nabídka + + Vymazat + + Ochrana proti sledování je zapnuta + + Ochrana proti sledování zablokovala sledovací prvky + + Ochrana proti sledování je pro tento web vypnuta + + Informace o stránce + + Načítání + + Část obsahu byla zablokována nastavením automatického přehrávání + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cy/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cy/strings.xml new file mode 100644 index 0000000000..3689bb4004 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-cy/strings.xml @@ -0,0 +1,19 @@ + + + + Dewislen + + Clirio + + Mae Diogelwch rhag Tracio ymlaen + + Mae Diogelwch rhag Tracio wedi rhwystro tracwyr + + Mae Diogelwch rhag Tracio wedi ei ddiffodd ar gyfer y wefan hon + + Manylion y wefan + + Llwytho + + Mae rhywfaint o gynnwys wedi’i rwystro gan osodiadau awtochwarae + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-da/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-da/strings.xml new file mode 100644 index 0000000000..b8d4ba146f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-da/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Ryd + + Beskyttelse mod sporing er slået til + + Beskyttelse mod sporing har blokeret sporings-tjenester + + Beskyttelse mod sporing er slået fra for dette websted + + Information om webstedet + + Indlæser + + Noget indhold er blevet blokeret af indstillingen for automatisk afspilning + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-de/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-de/strings.xml new file mode 100644 index 0000000000..2db37ddfd5 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-de/strings.xml @@ -0,0 +1,19 @@ + + + + Menü + + Leeren + + Schutz vor Aktivitätenverfolgung ist an + + Der Tracking-Schutz hat Tracker blockiert + + Der Tracking-Schutz ist für diese Website deaktiviert + + Seiteninformation + + Wird geladen… + + Einige Inhalte wurden durch die Einstellung zur automatischen Wiedergabe blockiert + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-dsb/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-dsb/strings.xml new file mode 100644 index 0000000000..5f13b6571d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-dsb/strings.xml @@ -0,0 +1,19 @@ + + + + Meni + + Lašowaś + + Slědowański šćit jo zašaltowany + + Slědowański šćit jo blokěrował pśeslědowaki + + Slědowański šćit jo něnto znjemóžnjony za toś to sedło + + Sedłowe informacije + + Zacytujo se + + Někake wopśimjeśe jo se pśez wótgrawańske nastajenje zablokěrował + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-el/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-el/strings.xml new file mode 100644 index 0000000000..8960320249 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-el/strings.xml @@ -0,0 +1,19 @@ + + + + Μενού + + Απαλοιφή + + Η προστασία από καταγραφή είναι ενεργή + + Η προστασία από καταγραφή έχει αποκλείσει ιχνηλάτες + + Η προστασία από καταγραφή είναι ανενεργή για τον ιστότοπο + + Πληροφορίες ιστοτόπου + + Φόρτωση + + Ορισμένο περιεχόμενο έχει αποκλειστεί από τη ρύθμιση αυτόματης αναπαραγωγής + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rCA/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rCA/strings.xml new file mode 100644 index 0000000000..29866e8096 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rCA/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Clear + + Tracking Protection is on + + Tracking Protection has blocked trackers + + Tracking Protection is off for this site + + Site information + + Loading + + Some content has been blocked by the autoplay setting + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rGB/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..29866e8096 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Clear + + Tracking Protection is on + + Tracking Protection has blocked trackers + + Tracking Protection is off for this site + + Site information + + Loading + + Some content has been blocked by the autoplay setting + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eo/strings.xml new file mode 100644 index 0000000000..5f31034648 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eo/strings.xml @@ -0,0 +1,19 @@ + + + + Menuo + + Viŝi + + Protekto kontraŭ spurado ŝaltita + + La protekto kontraŭ spurado blokis spurilojn + + Protekto kontraŭ spurado malŝaltita por tiu ĉi retejo + + Informo pri retejo + + Ŝargado + + Parto de la enhavo estis blokita de la agordo pri aŭtomata ludado + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rAR/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rAR/strings.xml new file mode 100644 index 0000000000..d5fa1f30f0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rAR/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Limpiar + + La protección contra rastreo está habilitada + + La protección contra rastreo bloqueó los rastreadores + + La protección de rastreo está deshabilitada para este sitio + + Información del sitio + + Cargando + + Se bloquearon algunos contenidos debido a la configuración de reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rCL/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rCL/strings.xml new file mode 100644 index 0000000000..107843b536 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rCL/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Limpiar + + Protección de seguimiento activada + + La Protección de seguimiento ha bloqueado rastreadores + + Protección de seguimiento desactivada para este sitio + + Información del sitio + + Cargando + + Algunos contenidos han sido bloqueados por los ajustes de reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rES/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rES/strings.xml new file mode 100644 index 0000000000..9c4263b840 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rES/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Limpiar + + La protección contra rastreo está activada + + La protección contra rastreo ha bloqueado rastreadores + + La protección contra rastreo está desactivada para este sitio web + + Información del sitio + + Cargando + + Algunos contenidos han sido bloqueados por los ajustes de reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rMX/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rMX/strings.xml new file mode 100644 index 0000000000..cd4388769d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es-rMX/strings.xml @@ -0,0 +1,18 @@ + + + + Menú + Limpiar + + La protección contra rastreo está activada + + La protección contra rastreo ha bloqueado rastreadores + + La protección contra rastreo está desactivada para este sitio + + Información del sitio + + Cargando + + Parte del contenido ha sido bloqueado por la configuración de reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..9c4263b840 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-es/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Limpiar + + La protección contra rastreo está activada + + La protección contra rastreo ha bloqueado rastreadores + + La protección contra rastreo está desactivada para este sitio web + + Información del sitio + + Cargando + + Algunos contenidos han sido bloqueados por los ajustes de reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-et/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-et/strings.xml new file mode 100644 index 0000000000..7fb1096fd7 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-et/strings.xml @@ -0,0 +1,18 @@ + + + + Menüü + Tühjenda + + Jälitamisvastane kaitse on sees + + Jälitamisvastane kaitse on jälitajaid blokkinud + + Täiustatud jälitamisvastane kaitse on sellel saidil väljas + + Saidi teave + + Laadimine + + Osa sisu on automaatse esitamise sättega blokitud + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eu/strings.xml new file mode 100644 index 0000000000..fb0520f183 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-eu/strings.xml @@ -0,0 +1,19 @@ + + + + Menua + + Garbitu + + Jarraipenaren babesa gaituta dago + + Jarraipenaren babesak jarraipen-elementuak blokeatu ditu + + Jarraipenaren babesa desgaituta dago webgune honetarako + + Gunearen informazioa + + Kargatzen + + Eduki batzuk blokeatu egin dira erreprodukzio automatikoko ezarpenetan oinarrituta + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fa/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000000..f381a2069f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fa/strings.xml @@ -0,0 +1,18 @@ + + + + منو + پاک کردن + + حفاظت در برابر ردیابی روشن است + + حفاظت در برابر ردیابی، ردیاب‌ها را مسدود کرده است + + حفاظت در برابر ردیابی برای این پایگاه خاموش است + + اطلاعات پایگاه + + در حال بار کردن + + برخی از محتواها توسط تنظیمات پخش خودکار مسدود شده‌اند + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ff/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ff/strings.xml new file mode 100644 index 0000000000..b774316dfb --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ff/strings.xml @@ -0,0 +1,16 @@ + + + + Dosol + Momtu + + Ndeenka Dewindol nani e + + Ndeenka Dewindol faliima rewindotooɓe + + Ndeenka Dewindol ko ko ñifi e ndee lowre + + Humpito lowre + + Nana loowa + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fi/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000..b47d8af7bc --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fi/strings.xml @@ -0,0 +1,19 @@ + + + + Valikko + + Tyhjennä + + Seurannan suojaus on päällä + + Seurannan suojaus on estänyt seuraimia + + Seurannan suojaus ei ole käytössä tällä sivustolla + + Sivustotiedot + + Ladataan + + Automaattisen toiston asetus on estänyt osan sisällöstä + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..072941bc03 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fr/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Effacer + + La protection contre le pistage est activée + + La protection contre le pistage a bloqué des traqueurs + + La protection contre le pistage est désactivée pour ce site + + Informations sur le site + + Chargement en cours + + Certains contenus ont été bloqués par le paramètre de lecture automatique + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fur/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fur/strings.xml new file mode 100644 index 0000000000..19900e6bc0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fur/strings.xml @@ -0,0 +1,19 @@ + + + + Menù + + Nete + + La protezion da lis spiis e je ative + + La protezion da lis spiis e à blocât spiis + + La protezion da lis spiis e je disativade par chest sît + + Informazions sît + + Daûr a cjamâ + + Cualchi contignût al è stât blocât de impostazion pe riproduzion automatiche + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fy-rNL/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fy-rNL/strings.xml new file mode 100644 index 0000000000..9aca189901 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-fy-rNL/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Wiskje + + Beskerming tsjin folgjen is ynskeakele + + Beskerming tsjin folgjen hat trackers blokkearre + + Beskerming tsjin folgjen is út foar dizze website + + Website-ynformaasje + + Lade + + Guon ynhâld is blokkearre troch de ynstelling foar automatysk ôfspyljen + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ga-rIE/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ga-rIE/strings.xml new file mode 100644 index 0000000000..ba4f41d62f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ga-rIE/strings.xml @@ -0,0 +1,16 @@ + + + + Roghchlár + Bánaigh + + Tá Cosaint ar Lorgaireacht ar siúl + + Chuir Cosaint ar Lorgaireacht cosc ar lorgairí + + Tá Cosaint ar Lorgaireacht múchta don suíomh seo + + Eolas faoin suíomh + + Á lódáil + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gd/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gd/strings.xml new file mode 100644 index 0000000000..d694698b3d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gd/strings.xml @@ -0,0 +1,18 @@ + + + + Clàr-taice + Falamhaich + + Tha an dìon o thracadh air + + Bhac gleus an dìon o thracadh tracaichean + + Tha an dìon o thracadh dheth air an làrach seo + + Fiosrachadh mun làrach + + Ga luchdadh + + Chaidh cuid dhen t-susbaint a bhacadh an cois roghainn na fèin-chluich + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gl/strings.xml new file mode 100644 index 0000000000..990d910d08 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gl/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Borrar + + Protección contra o rastrexo activada + + A protección contra o rastrexo bloqueou rastrexadores + + A protección contra o rastrexo está desactivada para este sitio + + Información sobre o sitio + + Cargando + + A configuración de reprodución automática bloqueou algúns contidos + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gn/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gn/strings.xml new file mode 100644 index 0000000000..7798e540da --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gn/strings.xml @@ -0,0 +1,19 @@ + + + + Poravorã + + Mopotĩ + + Tapykuehoha ñemo’ã oñemyandy + + Ñemo’ã jehekaha ojoko tapykuehohápe + + Ñemo’ã jehekaha ndoikovéima ko tendápe g̃uarã + + Marandu tenda rehegua + + Henyhẽhína + + Ojejoko ndahetái tetepy ñemboheta ijeheguíva ñemboheko rupive + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gu-rIN/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000000..a6ff2d26b0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-gu-rIN/strings.xml @@ -0,0 +1,16 @@ + + + + મેનુ + સાફ કરો + + ટ્રેકિંગ સુરક્ષા ચાલુ છે + + ટ્રેકિંગ સુરક્ષા દ્વારા ટ્રેકર્સને અવરોધિત કરવામાં આવ્યા છે + + આ સાઇટ માટે ટ્રેકિંગ સુરક્ષા બંધ છે + + સાઇટ માહિતી + + લોડ કરી રહ્યું છે + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hi-rIN/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hi-rIN/strings.xml new file mode 100644 index 0000000000..2d3c7d9b42 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hi-rIN/strings.xml @@ -0,0 +1,16 @@ + + + + मेन्यू + साफ करें + + ट्रैकिंग सुरक्षा चालू है + + ट्रैकिंग सुरक्षा ने ट्रैकरों को अवरुद्ध कर दिया है + + इस साइट के लिए ट्रैकिंग सुरक्षा बंद है + + साइट सूचना + + लोड हो रहा है + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hil/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hil/strings.xml new file mode 100644 index 0000000000..b02bc03a94 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hil/strings.xml @@ -0,0 +1,8 @@ + + + + Menu + Klaro + + Loading + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000000..420583468b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hr/strings.xml @@ -0,0 +1,18 @@ + + + + Izbornik + Izbriši + + Zaštita od praćenja je uključena + + Zaštita od praćenja je blokirala programe za praćenje + + Zaštita od praćenja je isključena za ovu stranicu + + Informacije o web mjestu + + Učitavanje + + Neki sadržaji su blokirani zbog postavki automatske reprodukcije + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hsb/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hsb/strings.xml new file mode 100644 index 0000000000..2c03305e16 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hsb/strings.xml @@ -0,0 +1,19 @@ + + + + Meni + + Zhašeć + + Slědowanski škit je zmóžnjeny + + Slědowanski škit je přesćěhowaki blokował + + Slědowanski škit je znjemóžnjeny za tute sydło + + Sydłowe informacije + + Začituje so + + Někajki wobsah je so přez wothrawanske nastajenje zablokował + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hu/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..eef3c81920 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hu/strings.xml @@ -0,0 +1,19 @@ + + + + Menü + + Törlés + + Követés elleni védelem bekapcsolva + + A követés elleni védelem nyomkövetőket blokkolt + + A követés elleni védelem le van tiltva ezen az oldalon + + Oldalinformációk + + Betöltés + + Bizonyos tartalmakat letiltott az automatikus lejátszási beállítás + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000000..668ae3425c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,19 @@ + + + + Ցանկ + + Մաքրել + + Հետագծման պաշտպանությունը միաց. է + + Հետագծման պաշտպանությունն արգելափակել է հետագծիչները + + Հետագծման պաշտպանությունն անջ. է այս կայքի համար + + Կայքի տեղեկատվություն + + Բեռնում + + Որոշ բովանդակություն արգելափակվել է ինքնանվագարկման կարգավորումով + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ia/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ia/strings.xml new file mode 100644 index 0000000000..8a27cf8a5a --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ia/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Vacuar + + Protection contra le traciamento active + + Le protection contra le traciamento ha blocate traciatores + + Le protection contra le traciamento es disactivate pro iste sito + + Informationes del sito + + Cargamento + + Alcun contento ha essite blocate per le parametros del reproduction automatic + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-in/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-in/strings.xml new file mode 100644 index 0000000000..2d7074c6ba --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-in/strings.xml @@ -0,0 +1,18 @@ + + + + Menu + Bersihkan + + Perlindungan Pelacakan aktif + + Perlindungan Pelacakan telah memblokir pelacak + + Perlindungan Pelacakan dinonaktifkan untuk situs ini + + Informasi situs + + Memuat + + Beberapa konten telah diblokir dengan pengaturan putar-otomatis + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-is/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-is/strings.xml new file mode 100644 index 0000000000..653bc2c4d0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-is/strings.xml @@ -0,0 +1,19 @@ + + + + Valmynd + + Hreinsa + + Vörn gegn gagnasöfnun virk + + Vörn gegn gagnasöfnun hefur lokað á rekjara + + Vörn gegn gagnasöfnun er ekki virk fyrir þetta svæði + + Upplýsingar um vefsvæði + + Hleður + + Lokað hefur verið sumt efni með stillingum fyrir sjálfvirka afspilun + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-it/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..33d9f28220 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-it/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Cancella + + La protezione antitracciamento è attiva + + La protezione antitracciamento ha bloccato contenuti traccianti + + La protezione antitracciamento è disattivata per questo sito + + Informazioni sito + + Caricamento… + + Alcuni contenuti sono stati bloccati dall’impostazione per la riproduzione automatica + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-iw/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000000..51477ead53 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-iw/strings.xml @@ -0,0 +1,19 @@ + + + + תפריט + + ניקוי + + הגנת מעקב פעילה + + הגנת מעקב חסמה רכיבי מעקב + + הגנת מעקב כבויה עבור אתר זה + + פרטי האתר + + בטעינה + + תוכן מסויים נחסם על־ידי ההגדרה של הניגון האוטומטי + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ja/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000000..4707063495 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ja/strings.xml @@ -0,0 +1,19 @@ + + + + メニュー + + 消去 + + トラッキング防止はオンです + + トラッキング防止によりトラッカーをブロックしました + + このサイトではトラッキング防止がオフです + + サイト情報 + + 読み込み中 + + 一部のコンテンツは自動再生設定によってブロックされています + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ka/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ka/strings.xml new file mode 100644 index 0000000000..bf438a8ed7 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ka/strings.xml @@ -0,0 +1,18 @@ + + + + მენიუ + გასუფთავება + + თვალთვალისგან დაცვა ჩართულია + + თვალთვალისგან დაცვამ შეზღუდა მეთვალყურეები + + თვალთვალისგან დაცვა გამორთულია ამ საიტზე + + საიტის მონაცემები + + იტვირთება + + ზოგიერთი მასალა შეიზღუდა თვითგაშვების პარამეტრებით + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kaa/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kaa/strings.xml new file mode 100644 index 0000000000..e4ad1073b0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kaa/strings.xml @@ -0,0 +1,18 @@ + + + + Menyu + Tazalaw + + Baqlaw qorǵanıwı qosılǵan + + Baqlaw qorǵanıw funkciyası trekkerlerdi blokladı + + Bul sayt ushın baqlaw qorǵanıwı óshirilgen + + Sayt maǵlıwmatları + + Júklenbekte + + Ayrım kontentler avtomatik sazlawlar tárepinen bloklanǵan + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kab/strings.xml new file mode 100644 index 0000000000..034ec09580 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kab/strings.xml @@ -0,0 +1,19 @@ + + + + Umuɣ + + Sfeḍ + + Ammesten mgal aḍfaṛ yermed + + Ammesten mgal aḍfaṛ yessewḥel ineḍfaṛen + + Ammesten mgal aḍfaṛ insa akka tura i usmel-a + + Asmel n telɣut + + Asali + + Yettusewḥel kra n yigburen s aɣewwar n tɣuri tawurmant + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kk/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kk/strings.xml new file mode 100644 index 0000000000..17b7710fac --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kk/strings.xml @@ -0,0 +1,19 @@ + + + + Мәзір + + Тазарту + + Бақылаудан қорғаныс іске қосулы + + Бақылаудан қорғаныс трекерлерді бұғаттады + + Бақылаудан қорғаныс бұл сайт үшін сөндірілген + + Сайт ақпараты + + Жүктелуде + + Кейбір құрама автоойнату баптауларымен бұғатталған + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kmr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kmr/strings.xml new file mode 100644 index 0000000000..29542924a0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kmr/strings.xml @@ -0,0 +1,18 @@ + + + + Menû + Paqij bike + + Parastina ji Şopandinê vekirî ye + + Parastina ji şopandinê şopdar asteng kirin + + Parastina ji şopadinê, ji bo vê malperê girtî ye + + Agahiyên malperê + + Tê barkirin + + Hin naverok ji aliyê eyara lêdana-otomatîk ve hatin astengkirin + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kn/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kn/strings.xml new file mode 100644 index 0000000000..ec07a646a6 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-kn/strings.xml @@ -0,0 +1,16 @@ + + + + ಪರಿವಿಡಿ + ಅಳಿಸು + + ಜಾಡು ಇರಿಸುವಿಕೆ ಇಂದ ರಕ್ಷಣೆ ಶುರುವಾಗಿದೆ + + ಟ್ರ್ಯಾಕಿಂಗ್ ಪ್ರೊಟೆಕ್ಷನ್ ಟ್ರ್ಯಾಕರ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿದೆ + + ಈ ಸೈಟ್‌ಗಾಗಿ ಟ್ರ್ಯಾಕಿಂಗ್ ಪ್ರೊಟೆಕ್ಷನ್ ಆಫ್ ಆಗಿದೆ + + ತಾಣದ ಮಾಹಿತಿ + + ಲೋಡ್ ಆಗುತ್ತಿದೆ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ko/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000000..ff3fcf3bcc --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ko/strings.xml @@ -0,0 +1,19 @@ + + + + 메뉴 + + 지우기 + + 추적 방지 기능이 켜져 있음 + + 추적 방지 기능이 추적기를 차단함 + + 이 사이트에 추적 방지 기능이 꺼져 있음 + + 사이트 정보 + + 로드 중 + + 자동 재생 설정에 의해 일부 콘텐츠가 차단되었습니다. + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ldrtl/dimens.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ldrtl/dimens.xml new file mode 100644 index 0000000000..a36c7c4366 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ldrtl/dimens.xml @@ -0,0 +1,8 @@ + + + + + 16dp + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lij/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lij/strings.xml new file mode 100644 index 0000000000..31fd025526 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lij/strings.xml @@ -0,0 +1,16 @@ + + + + Menû + Scancella + + Proteçion anti-traciamento açeiza + + A proteçion anti-traciamento a l\'à blocou di traciatoî + + A proteçion anti-traciamento a l\'é asmortâ pe sto scito + + Informaçioin do scito + + Carego + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lo/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lo/strings.xml new file mode 100644 index 0000000000..d9327a731d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lo/strings.xml @@ -0,0 +1,18 @@ + + + + ເມນູ + ລົບລ້າງ + + ການປ້ອງກັນການຕິດຕາມກຳລັງເປີດຢູ່ + + ການປ້ອງກັນການຕິດຕາມໄດ້ບັອກຕົວຕິດຕາມ + + ການປ້ອງກັນການຕິດຕາມໄດ້ປິດສຳລັບເວັບໄຊທນີ້ + + ຂໍ້ມູນເວັບໄຊ + + ກຳລັງໂຫລດ + + ເນື້ອຫາບາງອັນຖືກບລັອກໂດຍການຕັ້ງຄ່າການຫຼີ້ນອັດຕະໂນມັດ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lt/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000000..433b57f18e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-lt/strings.xml @@ -0,0 +1,18 @@ + + + + Meniu + Išvalyti + + Apsauga nuo stebėjimo įjungta + + Apsaugo nuo stebėjimo užblokavo stebėjimo elementus + + Apsauga nuo stebėjimo šioje svetainėje išjungta + + Svetainės informacija + + Įkeliama + + Dalį turinio užblokavo automatinio grojimo nuostatos + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mix/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mix/strings.xml new file mode 100644 index 0000000000..1636f653d5 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mix/strings.xml @@ -0,0 +1,18 @@ + + + + Katsi + Ku^un + + Chika va^a ña sau + + Chika va^a ña sau + + Chika va^a ña sau nu pagina yo^o + + Tu^tu sitio yo^o + + Sachuin + + Ma ku kunu ña ku reproducción automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ml/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ml/strings.xml new file mode 100644 index 0000000000..9f376452a4 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ml/strings.xml @@ -0,0 +1,16 @@ + + + + മെനു + മായ്ക്കുക + + ട്രാക്കിങ്ങ് സംരക്ഷണം ഓൺ ആണ് + + ട്രാക്കിംഗ് സംരക്ഷണം ട്രാക്കറുകളെ തടഞ്ഞു + + ഈ സൈറ്റിന് ട്രാക്കിംഗ് പരിരക്ഷ ഇപ്പോൾ ഓഫാണ് + + സൈറ്റ് വിവരങ്ങള്‍ + + ലഭ്യമാക്കുന്നു + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mr/strings.xml new file mode 100644 index 0000000000..5430ce7327 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-mr/strings.xml @@ -0,0 +1,16 @@ + + + + मेनू + साफ करा + + ट्रॅकिंग संरक्षण चालू आहे + + ट्रॅकिंग संरक्षणने ट्रॅकर्स अवरोधित केले आहेत + + या साइटसाठी ट्रॅकिंग संरक्षण बंद आहे + + साईट माहिती + + लोड होत आहे + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-my/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-my/strings.xml new file mode 100644 index 0000000000..006be61ee2 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-my/strings.xml @@ -0,0 +1,18 @@ + + + + စာရင်း + ရှင်းလင်းပါ + + ခြေရာခံကာကွယ်မှုကို ဖွင့်ထားသည် + + ခြေရာခံကာကွယ်မှုသည် ခြေရာခံသူများကိုပိတ်ဆို့ထားသည်။ + + ဒီ site အတွက်အကာအကွယ်ပေးမှုကိုပိတ်ထားသည် + + ဆိုက်အချက်အလက် + + အလုပ်လုပ်နေတယ် + + အလိုအလျောက်ဖွင့်ခြင်း ဆက်တင်မှ အကြောင်းအရာအချို့အား ပိတ်ထားသည်။ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 0000000000..9d9bcbfc13 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,19 @@ + + + + Meny + + Tøm + + Sporingsbeskyttelse er på + + Sporingsbeskyttelse har blokkert sporere + + Sporingsbeskyttelse er slått av for dette nettstedet + + Informasjon om nettstedet + + Laster + + Noe av innholdet er blokkert av autoavspillings-innstillingene + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ne-rNP/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000000..6a4341aafd --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,18 @@ + + + + मेनु + खाली गर्नुहोस् + + ट्र्याकिंग संरक्षण सक्रिय छ + + ट्र्याकिङ्ग सुरक्षाले ट्रयाकरहरुलाई रोकेको छ + + हाल यस साइटको लागी ट्रयाकिङ् सुरक्षा बन्द गरिएको छ। + + साइट जानकारी + + लोड हुँदैछ + + केहि सामग्री स्वतः प्ले सेटिङ्ग द्वारा रोकिएको छ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..fee481b12f --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nl/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Wissen + + Bescherming tegen volgen is ingeschakeld + + Bescherming tegen volgen heeft trackers geblokkeerd + + Bescherming tegen volgen is uit voor deze website + + Website-informatie + + Laden + + Sommige inhoud is geblokkeerd door de instelling voor automatisch afspelen + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nn-rNO/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nn-rNO/strings.xml new file mode 100644 index 0000000000..c77b3373d0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-nn-rNO/strings.xml @@ -0,0 +1,19 @@ + + + + Meny + + Tøm + + Sporingsvern er på + + Sporingsvern har blokkert sporarar + + Sporingsvern er slått av for denne nettstaden + + Informasjon om nettstaden + + Lastar + + Noko innhald har vorte blokkert av autoavspelings-innstillingane + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-oc/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-oc/strings.xml new file mode 100644 index 0000000000..d2a72f26c1 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-oc/strings.xml @@ -0,0 +1,19 @@ + + + + Menú + + Escafar + + La proteccion contra lo seguiment es activada + + La proteccion contra lo seguiment a blocat de traçadors + + La proteccion contra lo seguiment es desactivada per aqueste site + + Informacions del site + + Cargament + + Una part del contengut es estada blocada per la configuracion de la lectura automatica + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-or/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-or/strings.xml new file mode 100644 index 0000000000..9ca67e8959 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-or/strings.xml @@ -0,0 +1,16 @@ + + + + ମେନୁ + ଖାଲି କରନ୍ତୁ + + ଟ୍ରାକିଂ ସୁରକ୍ଷା ଚାଲୁଛି + + ଟ୍ରାକିଂ ସୁରକ୍ଷା ଟ୍ରାକରଗୁଡ଼ିକୁ ରୋକି ଦେଇଛି + + ଟ୍ରାକିଂ ସୁରକ୍ଷା ଏହି ସାଇଟ ପାଇଁ ବନ୍ଦ ଅଛି + + ସାଇଟ ସୂଚନା + + ଧାରଣ କରୁଅଛି + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rIN/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000000..10a68745b3 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,19 @@ + + + + ਮੇਨੂ + + ਸਾਫ਼ ਕਰੋ + + ਟਰੈਕ ਕਰਨ ਤੋਂ ਸੁਰੱਖਿਆ ਚਾਲੂ ਹੈ + + ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ ਟਰੈਕਾਂ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਉਂਦੀ ਹੈ + + ਇਸ ਸਾਈਟ ਲਈ ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ ਬੰਦ ਹੈ + + ਸਾਈਟ ਜਾਣਕਾਰੀ + + ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ + + ਕੁਝ ਸਮੱਗਰੀ ਨੂੰ ਆਪੇ-ਪਲੇਅ ਦੀ ਸੈਟਿੰਗ ਰਾਹੀਂ ਪਾਬੰਦੀ ਲਾਈ ਹੈ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rPK/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rPK/strings.xml new file mode 100644 index 0000000000..f48af403b2 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pa-rPK/strings.xml @@ -0,0 +1,18 @@ + + + + مینو + صاف کرو + + ٹوہ لاوݨ توں سرکھیا چالو اے + + ٹوہ لاوݨ توں کجھ روک لاۓ گئے ہن + + ٹوہ لاوݨ توں سرکھیا بند ہو گیا + + سائٹ جاݨکاری + + لوڈ کیتا جا رہا اے + + خود بخود چلݨ نال کجھ وستوآں روکے گئے + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..083dfd9aa9 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pl/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Wyczyść + + Ochrona przed śledzeniem jest włączona + + Ochrona przed śledzeniem zablokowała elementy śledzące + + Ochrona przed śledzeniem jest wyłączona na tej witrynie + + Informacje o witrynie + + Wczytywanie + + Część treści została zablokowana przez ustawienie automatycznego odtwarzania + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rBR/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..135bacdc26 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Limpar + + Proteção contra rastreamento ativada + + A proteção contra rastreamento bloqueou rastreadores + + A proteção contra rastreamento está desativada neste site + + Informações do site + + Carregando + + Algum conteúdo foi bloqueado pela configuração de reprodução automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rPT/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..bcd168a481 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Limpar + + Proteção contra monitorização está ativada + + A proteção contra a monitorização bloqueou rastreadores + + A proteção contra monitorização está desativada para este site + + Informação do site + + A carregar + + Algum conteúdo foi bloqueado pela configuração de reprodução automática + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-rm/strings.xml new file mode 100644 index 0000000000..d583fd3c77 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-rm/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Stizzar + + La protecziun cunter il fastizar è activa + + La protecziun cunter il fastizar ha bloccà fastizaders + + La protecziun cunter il fastizar è deactivada per questa website + + Infurmaziuns davart la website + + Chargiar + + Tschert cuntegn è vegnì bloccà dal parameter da la reproducziun automatica + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ro/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000000..72dee0c902 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ro/strings.xml @@ -0,0 +1,16 @@ + + + + Meniu + Șterge + + Protecția împotriva urmăririi este activată + + Protecția împotriva urmăririi a blocat elementele de urmărire + + Protecția împotriva urmăririi este dezactivată pentru acest site + + Informații despre site + + Se încarcă + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ru/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..f722325b3a --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ru/strings.xml @@ -0,0 +1,19 @@ + + + + Меню + + Очистить + + Защита от отслеживания включена + + Защита от отслеживания заблокировала трекеры + + Защита от отслеживания отключена для этого сайта + + Сведения о сайте + + Загрузка + + Некоторое содержимое было заблокировано настройками автовоспроизведения + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sat/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sat/strings.xml new file mode 100644 index 0000000000..f363d29490 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sat/strings.xml @@ -0,0 +1,19 @@ + + + + ᱢᱮᱱᱩ + + ᱯᱷᱟᱨᱪᱟ + + ᱯᱟᱸᱡᱟ ᱟᱰ ᱪᱟᱹᱞᱩ ᱢᱮᱱᱟᱜ-ᱟ + + ᱯᱟᱸᱡᱟ ᱨᱚᱯᱷᱟ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱠᱚ ᱟᱠᱚᱴ ᱠᱮᱜᱼᱟᱭ + + ᱯᱟᱸᱡᱟ ᱨᱚᱯᱷᱟ ᱵᱚᱸᱫᱚ ᱢᱮᱱᱟᱜ-ᱟ + + ᱥᱟᱭᱤᱴ ᱨᱮᱭᱟᱜ ᱠᱷᱚᱵᱚᱨ + + ᱞᱟᱫᱮᱜ ᱠᱟᱱᱟ + + ᱟᱡ ᱛᱮ ᱮᱛᱦᱚᱵ ᱥᱟᱡᱟᱣ ᱠᱚ ᱠᱷᱟᱹᱛᱤᱨ ᱛᱮ ᱠᱤᱪᱷᱤ ᱡᱤᱱᱤᱥ ᱠᱚ ᱵᱞᱚᱠ ᱠᱟᱱᱟ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sc/strings.xml new file mode 100644 index 0000000000..f0b375f172 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sc/strings.xml @@ -0,0 +1,19 @@ + + + + Menù + + Isbòida + + S’amparu contra sa sighidura est ativu + + S’amparu contra sa sighidura at blocadu sighidores + + Sa protetzione contra sa sighidura est disativada pro custu situ + + Informatziones de su situ + + Carrighende + + Sa funtzionalidade de riprodutzione automàtica at blocadu cuntenutos + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-si/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-si/strings.xml new file mode 100644 index 0000000000..52a9db1744 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-si/strings.xml @@ -0,0 +1,19 @@ + + + + වට්ටෝරුව + + මකන්න + + ලුහුබැඳීමේ රැකවරණය සක්‍රියයි + + ලුහුබැඳීමේ රැකවරණයෙන් අවහිරයි + + අඩවිය සඳහා ලුහුබැඳීමේ රැකවරණය අබලයි + + අඩවියේ තොරතුරු + + පූරණය වෙමින් + + ස්වයං වාදන සැකසුම මගින් ඇතැම් අන්තර්ගත අවහිර වී ඇත + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sk/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..d60ecd8152 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sk/strings.xml @@ -0,0 +1,19 @@ + + + + Ponuka + + Vymazať + + Ochrana pred sledovaním je zapnutá + + Ochrana pred sledovaním zablokovala sledovacie prvky + + Ochrana pred sledovaním je na tejto stránke vypnutá + + Informácie o stránke + + Načítava sa + + Niektorý obsah bol zablokovaný nastavením automatického prehrávania + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-skr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-skr/strings.xml new file mode 100644 index 0000000000..ccf404cabd --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-skr/strings.xml @@ -0,0 +1,19 @@ + + + + مینیو + + صاف کرو + + سراغ کاری تحفظ چالو ہے + + سراغ کاری تحفظ نے سُراغ رساں کوں بلاک کر ݙتا ہے + + سراغ کاری تحفظ ایں سائٹ کیتے بند ہے + + سائٹ ڄاݨکاری + + لوڈ تھیندا پئے + + کجھ مواد کوں آٹو پلے ترتیباں نال بلاک کر ݙتا ڳئے + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000000..0d7a545930 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sl/strings.xml @@ -0,0 +1,19 @@ + + + + Meni + + Počisti + + Zaščita pred sledenjem je vključena + + Zaščita pred sledenjem je zavrnila sledilce + + Zaščita pred sledenjem je za to spletno mesto izključena + + Podatki o strani + + Nalaganje + + Nastavitev samodejnega predvajanja je zavrnila nekaj vsebine + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sq/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sq/strings.xml new file mode 100644 index 0000000000..33b69257a5 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sq/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Spastroje + + Mbrojtje Nga Gjurmimet është aktive + + Mbrojtja Nga Gjurmimet ka bllokuar gjurmues + + Mbrojtja Nga Gjurmimet është e çaktivizuar për këtë sajt + + Hollësi sajti + + Po ngarkohet + + Është bllokuar lëndë nga rregullimi i vetëluajtjes + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..5e70cab0c1 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sr/strings.xml @@ -0,0 +1,18 @@ + + + + Мени + Обриши + + Заштита од праћења је укључена + + Заштита од праћења је блокирала пратиоце + + Заштита од праћења је искључена за ову страницу + + Информације о страници + + Учитавање + + Неки садржај је блокиран због подешавања аутоматске репродукције + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-su/strings.xml new file mode 100644 index 0000000000..af9bfdc6b0 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-su/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Beresihan + + Kilung Palacakan keur hurung + + Kilung Palacakan geus meungpeuk palacak + + Kilung Palacakan pareum pikeun ieu loka + + Émbaran loka + + Ngamuat + + Sababaraha kontén dipeungpeuk ku setélan otoplay + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sv-rSE/strings.xml new file mode 100644 index 0000000000..8922e20f5e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-sv-rSE/strings.xml @@ -0,0 +1,19 @@ + + + + Meny + + Rensa + + Spårningsskydd är på + + Spårningsskydd har blockerat spårare + + Spårningsskydd är avstängt för den här webbplatsen + + Webbplatsinformation + + Laddar + + En del innehåll har blockerats av inställningen för automatisk uppspelning + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-szl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-szl/strings.xml new file mode 100644 index 0000000000..e6537d32b1 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-szl/strings.xml @@ -0,0 +1,18 @@ + + + + Myni + Wypucuj + + Ôchrōna ôd śledzynio je załōnczōno + + Ôchrōna ôd śledzynio szperuje śledzōnce elymynty + + Na tyj strōnie ôchrōna ôd śledzynio je wyłōnczōno + + Informacyje ô strōnie + + Ladowanie + + Nasztalowanie autōmatycznego puszczanio zaszperowało kōnsek zawartości + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ta/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000000..9b6e317f6c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ta/strings.xml @@ -0,0 +1,18 @@ + + + + பட்டி + துடை + + தடமறியல் பாதுகாப்பு இயக்கத்தில் + + தடமறியல் பாதுகாப்பு தடமறிவான்களை முடக்கியது + + தடமறியல் பாதுகாப்பு இத்தளத்தில் அணைக்கப்பட்டுள்ளது + + தளத்தகவல்கள் + + ஏற்றுகிறது + + தன்னியக்க அமைப்பால் சில உள்ளடக்கம் தடுக்கப்பட்டுள்ளது + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-te/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-te/strings.xml new file mode 100644 index 0000000000..504c84b58d --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-te/strings.xml @@ -0,0 +1,18 @@ + + + + మెనూ + తుడిచివేయి + + ట్రాకింగ్ సంరక్షణ చేతనంగా ఉంది + + ట్రాకింగ్ సంరక్షణ ట్రాకర్లను నిరోధించింది + + ఈ సైటుకి ట్రాకింగ్ సంరక్షణ ఆపివేయబడింది + + సైటు సమాచారం + + వస్తోంది + + ఆటోప్లే అమరిక ద్వారా కొంత కంటెంట్ నిరోధించబడింది + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tg/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tg/strings.xml new file mode 100644 index 0000000000..0fed273e55 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tg/strings.xml @@ -0,0 +1,19 @@ + + + + Меню + + Пок кардан + + Муҳофизат аз пайгирӣ фаъол аст + + Муҳофизат аз пайгирӣ васоити пайгириро манъ кард + + Муҳофизат аз пайгирӣ барои ин сомона хомӯш аст + + Маълумот дар бораи сомона + + Бор шуда истодааст + + Баъзеи муҳтаво тавассути танзими пахши худкор манъ карда шуд + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-th/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..55f06924fa --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-th/strings.xml @@ -0,0 +1,18 @@ + + + + เมนู + ล้าง + + การป้องกันการติดตามเปิดอยู่ + + การป้องกันการติดตามได้ปิดกั้นตัวติดตาม + + การป้องกันการติดตามปิดอยู่สำหรับไซต์นี้ + + ข้อมูลไซต์ + + กำลังโหลด + + เนื้อหาบางส่วนถูกปิดกั้นด้วยการตั้งค่าเล่นอัตโนมัติ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tl/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000000..4e5e6b3300 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tl/strings.xml @@ -0,0 +1,18 @@ + + + + Menu + Burahin + + Nakabukas ang Tracking Protection + + May naharang na mga tracker ang Tracking Protection + + Nakasara ang Tracking Protection sa site na ito + + Impormasyon sa site + + Naglo-load + + May ilang content na naharang dahil sa autoplay setting + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tok/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tok/strings.xml new file mode 100644 index 0000000000..d9f3149d1b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tok/strings.xml @@ -0,0 +1,14 @@ + + + o weka ale + + lipu li ken ala lukin e sona sina + + lipu li weka e lipu lukin + + lipu ni la weka pi lipu lukin li lon ala + + sona lipu + + o awen + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tr/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000000..904675f6e9 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tr/strings.xml @@ -0,0 +1,19 @@ + + + + Menü + + Temizle + + İzlenme koruması açık + + İzlenme koruması, takip kodlarını engelledi + + Bu sitede izlenme koruması kapalı + + Site bilgileri + + Yükleniyor + + Otomatik oynatma ayarınız bazı içerikleri engelledi + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-trs/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-trs/strings.xml new file mode 100644 index 0000000000..ef86e00be9 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-trs/strings.xml @@ -0,0 +1,18 @@ + + + + Menû + Nā\'nïn\' + + Sa narrán riña sa naga\'nāj a + + Narrán sa dugumî ñù\' riña nej sa naga\'nāj a + + Nitāj si \'iaj sun sa narán riña sa naga\'nāj a riña sitiô nan + + Nuguan\' huā rayi\'î sitiô nan + + Hìaj ayì\'ij + + Huā da’āj nej sa mà riñaj narán gi’iaj guendâ nù sa duguachín man’an sa ni’io’ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tt/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tt/strings.xml new file mode 100644 index 0000000000..3a463ee5cf --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tt/strings.xml @@ -0,0 +1,18 @@ + + + + Меню + Чистарту + + Күзәтелүдән Саклау кабызылган + + Күзәтүдән саклау күзәтеп торучыларны блоклады + + Бу сайт өчен Күзәтелүдән Саклау сүндерелгән + + Сайт турында мәгълүмат + + Йөкләү + + Автоуйнату көйләүләре аркасында кайбер эчтәлекләр блокланды + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tzm/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tzm/strings.xml new file mode 100644 index 0000000000..d451c5972c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-tzm/strings.xml @@ -0,0 +1,10 @@ + + + + Umuɣ + Sfeḍ + + Asmel n wasit + + Asali + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ug/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ug/strings.xml new file mode 100644 index 0000000000..89e5173df3 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ug/strings.xml @@ -0,0 +1,19 @@ + + + + تىزىملىك + + تازىلا + + ئىزلاشتىن توسۇش ئىقتىدارى ئوچۇق + + ئىز قوغلاش قوغدىغۇچىسى ئىز قوغلىغۇچىلارنى توستى + + بۇ توربېكەتكە نىسبەتەن ئىزلاشتىن قوغداش تاقاق + + بېكەت ئۇچۇرى + + يۈكلەۋاتىدۇ + + بەزى مەزمۇنلارنى ئاپتوماتىك قويۇش تەڭشىكى توستى + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uk/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000000..1460d015dd --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uk/strings.xml @@ -0,0 +1,19 @@ + + + + Меню + + Очистити + + Захист від стеження увімкнено + + Захист від стеження заблокував стеження + + Захист від стеження вимкнено для цього сайту + + Інформація про сайт + + Завантаження + + Деякий вміст заблоковано налаштуванням автовідтворення + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ur/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ur/strings.xml new file mode 100644 index 0000000000..78470d254c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-ur/strings.xml @@ -0,0 +1,18 @@ + + + + مینیو + صاف کریں + + سراغ کاری تحفظ چالو ہے + + سراغ کاری تحفظ نے سُراغ رساں کو مسدود کردیا ہے + + سراغ کاری تحفظ اس سائٹ کے لیئے بند ہے + + سائٹ کی معلومات + + لوڈ کر رہا ہے + + کچھ مشمولات کو آٹو پلے سیٹنگ سے مسدود کردیا گیا ہے + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uz/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uz/strings.xml new file mode 100644 index 0000000000..ec69af71f6 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-uz/strings.xml @@ -0,0 +1,18 @@ + + + + Menyu + Tozalash + + Kuzatuvdan himoya yoniq + + Kuzatuvdan himoya funksiyasi kuzatuvchilarni blokladi + + Bu sayt uchun kuzatuvdan himoya funksiyasi oʻchirilgan + + Sayt maʼlumoti + + Yuklanmoqda + + Avtomatik ishga tushirish sozlamasi tufayli ayrim kontentlar bloklandi + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vec/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vec/strings.xml new file mode 100644 index 0000000000..15a625bcbc --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vec/strings.xml @@ -0,0 +1,16 @@ + + + + Menu + Pulisi + + Ƚa protesion antitraciamento ƚa xe ativa + + Ƚa protesion antitraciamento ƚa ga blocà contenudi tracianti + + Ƚa protesion antitraciamento ƚa xe disativà par sto sito + + Informasioni sito + + Cargamento + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vi/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000000..c3c54456ed --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-vi/strings.xml @@ -0,0 +1,19 @@ + + + + Menu + + Xóa + + Trình chống theo dõi đang bật + + Trình chống theo dõi đã chặn trình theo dõi + + Đã tắt Trình chống theo dõi cho trang web này + + Thông tin về trang web + + Đang tải + + Một số nội dung đã bị chặn bởi cài đặt tự động phát + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-yo/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-yo/strings.xml new file mode 100644 index 0000000000..6f46be1286 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-yo/strings.xml @@ -0,0 +1,18 @@ + + + + Mẹ́nù + Paárẹ́ + + Ìtọpinpin Ìdàábòbò wà ní títàn + + Ìtọpinpin ìdàábòbò ti dènà atọpinpin + + Ìtọpinpin ìdàábòbò ti di pípa fún ìkànnì yìí + + Ìfitóniléti ìkànnì + + Ó ń gbáradì + + Àwọn àkòónú kan ti di dídénà ààtò ìfi-ara-ẹni-ṣisẹ́ + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..3cb765dc23 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,19 @@ + + + + 菜单 + + 清除 + + 已开启跟踪保护 + + 跟踪保护已拦截跟踪器 + + 已关闭对此网站的跟踪保护 + + 网站信息 + + 正在加载 + + 某些内容已被自动播放设置阻止 + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rTW/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..d1d023f3db --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,19 @@ + + + + 選單 + + 清除 + + 追蹤保護功能已開啟 + + 追蹤保護功能已封鎖追蹤器 + + 已關閉針對此網站的追蹤保護功能 + + 網站資訊 + + 載入中 + + 某些內容已由自動播放設定封鎖 + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/attrs_browser_toolbar.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/attrs_browser_toolbar.xml new file mode 100644 index 0000000000..431bb6b080 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/attrs_browser_toolbar.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/dimens.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..23fd755686 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/dimens.xml @@ -0,0 +1,31 @@ + + + + 56dp + + + 3dp + 24dp + 1dp + 4dp + 24dp + 12dp + 24dp + 16dp + 0dp + + + 8dp + 0dp + 8dp + 16dp + + 15sp + 15sp + 12sp + + 48dp + 48dp + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/ids.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/ids.xml new file mode 100644 index 0000000000..20e28a0c58 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/ids.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/strings.xml b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/strings.xml new file mode 100644 index 0000000000..c845e189e7 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + Menu + + Clear + + Tracking Protection is on + + Tracking Protection has blocked trackers + + Tracking Protection is off for this site + + Site information + + Loading + + Some content has been blocked by the autoplay setting + diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/AsyncFilterListenerTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/AsyncFilterListenerTest.kt new file mode 100644 index 0000000000..6c95c8f243 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/AsyncFilterListenerTest.kt @@ -0,0 +1,350 @@ +/* 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.toolbar2 + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.isActive +import kotlinx.coroutines.test.runTest +import mozilla.components.concept.toolbar.AutocompleteDelegate +import mozilla.components.concept.toolbar.AutocompleteResult +import mozilla.components.support.test.mock +import mozilla.components.ui.autocomplete.AutocompleteView +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Test +import org.mockito.Mockito.atLeast +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import java.util.concurrent.Executor + +@ExperimentalCoroutinesApi // for runTest +class AsyncFilterListenerTest { + @Test + fun `filter listener cancels prior filter executions`() = runTest { + val urlView: AutocompleteView = mock() + val filter: suspend (String, AutocompleteDelegate) -> Unit = mock() + + val dispatcher = spy( + Executor { + it.run() + }.asCoroutineDispatcher(), + ) + + val listener = AsyncFilterListener(urlView, dispatcher, filter) + + verify(dispatcher, never()).cancelChildren() + + listener("test") + + verify(dispatcher, atLeastOnce()).cancelChildren() + } + + @Test + fun `filter delegate checks for cancellations before it runs, passes results to autocomplete view`() = runTest { + var filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + assertEquals("test", query) + delegate.applyAutocompleteResult( + AutocompleteResult( + input = "test", + text = "testing.com", + url = "http://www.testing.com", + source = "asyncTest", + totalItems = 1, + ), + ) + } + + val dispatcher = spy( + Executor { + it.run() + }.asCoroutineDispatcher(), + ) + + var didCallApply = 0 + + var listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "test" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + assertEquals("asyncTest", result.source) + assertEquals("testing.com", result.text) + assertEquals(1, result.totalItems) + didCallApply += 1 + } + + override fun noAutocompleteResult() { + fail() + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + verify(dispatcher, never()).isActive + + async { listener("test") }.await() + + // Checked if parent scope is still active. Somehow, each access to 'isActive' registers as 4? + verify(dispatcher, atLeast(4)).isActive + // Passed the result to the view's apply method exactly once. + assertEquals(1, didCallApply) + + filter = { query, delegate -> + assertEquals("moz", query) + delegate.applyAutocompleteResult( + AutocompleteResult( + input = "moz", + text = "mozilla.com", + url = "http://www.mozilla.com", + source = "asyncTestTwo", + totalItems = 2, + ), + ) + } + listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "moz" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + assertEquals("asyncTestTwo", result.source) + assertEquals("mozilla.com", result.text) + assertEquals(2, result.totalItems) + didCallApply += 1 + } + + override fun noAutocompleteResult() { + fail() + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + async { listener("moz") }.await() + + verify(dispatcher, atLeast(8)).isActive + assertEquals(2, didCallApply) + } + + @Test + fun `delegate discards stale results`() = runTest { + val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + assertEquals("test", query) + delegate.applyAutocompleteResult( + AutocompleteResult( + input = "test", + text = "testing.com", + url = "http://www.testing.com", + source = "asyncTest", + totalItems = 1, + ), + ) + } + + val dispatcher = Executor { + it.run() + }.asCoroutineDispatcher() + + val listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "nolongertest" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + fail() + } + + override fun noAutocompleteResult() { + fail() + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + listener("test") + } + + @Test + fun `delegate discards stale lack of results`() = runTest { + val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + assertEquals("test", query) + delegate.noAutocompleteResult("test") + } + + val dispatcher = Executor { + it.run() + }.asCoroutineDispatcher() + + val listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "nolongertest" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + fail() + } + + override fun noAutocompleteResult() { + fail() + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + listener("test") + } + + @Test + fun `delegate passes through non-stale lack of results`() = runTest { + val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + assertEquals("test", query) + delegate.noAutocompleteResult("test") + } + + val dispatcher = Executor { + it.run() + }.asCoroutineDispatcher() + + var calledNoResults = 0 + val listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "test" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + fail() + } + + override fun noAutocompleteResult() { + calledNoResults += 1 + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + async { listener("test") }.await() + + assertEquals(1, calledNoResults) + } + + @Test + fun `delegate discards results if parent scope was cancelled`() = runTest { + var preservedDelegate: AutocompleteDelegate? = null + + val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + preservedDelegate = delegate + assertEquals("test", query) + delegate.applyAutocompleteResult( + AutocompleteResult( + input = "test", + text = "testing.com", + url = "http://www.testing.com", + source = "asyncTest", + totalItems = 1, + ), + ) + } + + val dispatcher = Executor { + it.run() + }.asCoroutineDispatcher() + + var calledResults = 0 + val listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "test" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + assertEquals("asyncTest", result.source) + assertEquals("testing.com", result.text) + assertEquals(1, result.totalItems) + calledResults += 1 + } + + override fun noAutocompleteResult() { + fail() + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + async { + listener("test") + listener("test") + }.await() + + // This result application should be discarded, because scope has been cancelled by the second + // 'listener' call above. + preservedDelegate!!.applyAutocompleteResult( + AutocompleteResult( + input = "test", + text = "testing.com", + url = "http://www.testing.com", + source = "asyncCancelled", + totalItems = 1, + ), + ) + + assertEquals(2, calledResults) + } + + @Test + fun `delegate discards lack of results if parent scope was cancelled`() = runTest { + var preservedDelegate: AutocompleteDelegate? = null + + val filter: suspend (String, AutocompleteDelegate) -> Unit = { query, delegate -> + preservedDelegate = delegate + assertEquals("test", query) + delegate.noAutocompleteResult("test") + } + + val dispatcher = Executor { + it.run() + }.asCoroutineDispatcher() + + var calledResults = 0 + val listener = AsyncFilterListener( + object : AutocompleteView { + override val originalText: String = "test" + + override fun applyAutocompleteResult(result: InlineAutocompleteEditText.AutocompleteResult) { + fail() + } + + override fun noAutocompleteResult() { + calledResults += 1 + } + }, + dispatcher, + filter, + this.coroutineContext, + ) + + async { + listener("test") + listener("test") + }.await() + + // This "no results" call should be discarded, because scope has been cancelled by the second + // 'listener' call above. + preservedDelegate!!.noAutocompleteResult("test") + + assertEquals(2, calledResults) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/BrowserToolbarTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/BrowserToolbarTest.kt new file mode 100644 index 0000000000..d1a499e30c --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/BrowserToolbarTest.kt @@ -0,0 +1,1044 @@ +/* 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.toolbar2 + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import android.view.ViewParent +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityManager +import android.widget.ImageButton +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.inputmethod.EditorInfoCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.toolbar2.display.DisplayToolbar +import mozilla.components.browser.toolbar2.display.DisplayToolbarViews +import mozilla.components.browser.toolbar2.display.MenuButton +import mozilla.components.browser.toolbar2.edit.EditToolbar +import mozilla.components.concept.toolbar.AutocompleteDelegate +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.concept.toolbar.Toolbar.SiteSecurity +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection +import mozilla.components.support.ktx.kotlin.MAX_URI_LENGTH +import mozilla.components.support.test.argumentCaptor +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.whenever +import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior +import mozilla.components.ui.widgets.behavior.ViewPosition +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` +import org.robolectric.Robolectric +import org.robolectric.Shadows.shadowOf + +@RunWith(AndroidJUnit4::class) +class BrowserToolbarTest { + + @Test + fun `display toolbar is visible by default`() { + val toolbar = BrowserToolbar(testContext) + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + } + + @Test + fun `calling editMode() makes edit toolbar visible`() { + val toolbar = BrowserToolbar(testContext) + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + + toolbar.editMode() + + assertTrue(toolbar.display.rootView.visibility == View.GONE) + assertTrue(toolbar.edit.rootView.visibility == View.VISIBLE) + } + + @Test + fun `calling displayMode() makes display toolbar visible`() { + val toolbar = BrowserToolbar(testContext) + toolbar.editMode() + + assertTrue(toolbar.display.rootView.visibility == View.GONE) + assertTrue(toolbar.edit.rootView.visibility == View.VISIBLE) + + toolbar.displayMode() + + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + } + + @Test + fun `back presses will not be handled in display mode`() { + val toolbar = BrowserToolbar(testContext) + toolbar.displayMode() + + assertFalse(toolbar.onBackPressed()) + + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + } + + @Test + fun `back presses will switch from edit mode to display mode`() { + val toolbar = BrowserToolbar(testContext) + toolbar.editMode() + + assertTrue(toolbar.display.rootView.visibility == View.GONE) + assertTrue(toolbar.edit.rootView.visibility == View.VISIBLE) + + assertTrue(toolbar.onBackPressed()) + + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + } + + @Test + fun `displayUrl will be forwarded to display toolbar immediately`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + val edit: EditToolbar = mock() + + toolbar.display = display + toolbar.edit = edit + + toolbar.url = "https://www.mozilla.org" + + verify(display).url = "https://www.mozilla.org" + verify(edit, never()).updateUrl(ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) + } + + @Test + fun `displayUrl is truncated to prevent extreme cases from slowing down the UI`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + val edit: EditToolbar = mock() + + toolbar.display = display + toolbar.edit = edit + + toolbar.url = "a".repeat(MAX_URI_LENGTH + 1) + toolbar.url = "b".repeat(MAX_URI_LENGTH) + toolbar.url = "c".repeat(MAX_URI_LENGTH - 1) + + val urlCaptor = argumentCaptor() + verify(display, times(3)).url = urlCaptor.capture() + + val capturedValues = urlCaptor.allValues + // Value was too long and should've been truncated + assertEquals("a".repeat(MAX_URI_LENGTH), capturedValues[0]) + // Values should be the same as before + assertEquals("b".repeat(MAX_URI_LENGTH), capturedValues[1]) + assertEquals("c".repeat(MAX_URI_LENGTH - 1), capturedValues[2]) + } + + @Test + fun `searchTerms is truncated in case it is greater than MAX_URI_LENGTH`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = spy(toolbar.edit) + toolbar.editMode() + + toolbar.setSearchTerms("a".repeat(MAX_URI_LENGTH + 1)) + + // Value was too long and should've been truncated + assertEquals(toolbar.searchTerms.length, MAX_URI_LENGTH) + verify(toolbar.edit).editSuggestion("a".repeat(MAX_URI_LENGTH)) + } + + @Test + fun `searchTerms is not truncated in case it is equal or less than MAX_URI_LENGTH`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = spy(toolbar.edit) + toolbar.editMode() + + toolbar.setSearchTerms("b".repeat(MAX_URI_LENGTH)) + + // Value should be the same as before + assertEquals(toolbar.searchTerms.length, MAX_URI_LENGTH) + verify(toolbar.edit).editSuggestion("b".repeat(MAX_URI_LENGTH)) + + toolbar.setSearchTerms("c".repeat(MAX_URI_LENGTH - 1)) + + // Value should be the same as before + assertEquals(toolbar.searchTerms.length, MAX_URI_LENGTH - 1) + verify(toolbar.edit).editSuggestion("c".repeat(MAX_URI_LENGTH - 1)) + } + + @Test + fun `last URL will be forwarded to edit toolbar when switching mode`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = spy(toolbar.edit) + + toolbar.url = "https://www.mozilla.org" + verify(toolbar.edit, never()).updateUrl("https://www.mozilla.org", false) + + toolbar.editMode() + + verify(toolbar.edit).updateUrl("https://www.mozilla.org", false) + } + + @Test + fun `displayProgress will send accessibility events`() { + val toolbar = BrowserToolbar(testContext) + val root = mock(ViewParent::class.java) + shadowOf(toolbar).setMyParent(root) + `when`(root.requestSendAccessibilityEvent(any(), any())).thenReturn(false) + + val shadowAccessibilityManager = shadowOf(testContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager) + shadowAccessibilityManager.setEnabled(true) + shadowAccessibilityManager.setTouchExplorationEnabled(true) + + toolbar.displayProgress(10) + toolbar.displayProgress(50) + toolbar.displayProgress(100) + + // make sure multiple calls to 100% does not trigger "loading" announcement + toolbar.displayProgress(100) + + val captor = ArgumentCaptor.forClass(AccessibilityEvent::class.java) + + verify(root, times(5)).requestSendAccessibilityEvent(any(), captor.capture()) + + assertEquals(AccessibilityEvent.TYPE_ANNOUNCEMENT, captor.allValues[0].eventType) + assertEquals(testContext.getString(R.string.mozac_browser_toolbar_progress_loading), captor.allValues[0].text[0]) + + assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[1].eventType) + assertEquals(10, captor.allValues[1].scrollY) + assertEquals(100, captor.allValues[1].maxScrollY) + + assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[2].eventType) + assertEquals(50, captor.allValues[2].scrollY) + assertEquals(100, captor.allValues[2].maxScrollY) + + assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[3].eventType) + assertEquals(100, captor.allValues[3].scrollY) + assertEquals(100, captor.allValues[3].maxScrollY) + + assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[4].eventType) + assertEquals(100, captor.allValues[3].scrollY) + assertEquals(100, captor.allValues[3].maxScrollY) + } + + @Test + fun `displayProgress will not send send view scrolled accessibility events if touch exploration is disabled`() { + val toolbar = BrowserToolbar(testContext) + val root = mock(ViewParent::class.java) + shadowOf(toolbar).setMyParent(root) + `when`(root.requestSendAccessibilityEvent(any(), any())).thenReturn(false) + + val shadowAccessibilityManager = shadowOf(testContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager) + shadowAccessibilityManager.setEnabled(true) + shadowAccessibilityManager.setTouchExplorationEnabled(false) + + toolbar.displayProgress(10) + toolbar.displayProgress(50) + toolbar.displayProgress(100) + + // make sure multiple calls to 100% does not trigger "loading" announcement + toolbar.displayProgress(100) + + val captor = ArgumentCaptor.forClass(AccessibilityEvent::class.java) + + verify(root, times(1)).requestSendAccessibilityEvent(any(), captor.capture()) + + assertEquals(AccessibilityEvent.TYPE_ANNOUNCEMENT, captor.allValues[0].eventType) + assertEquals(testContext.getString(R.string.mozac_browser_toolbar_progress_loading), captor.allValues[0].text[0]) + } + + @Test + fun `displayProgress will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + + toolbar.display = display + + toolbar.displayProgress(10) + toolbar.displayProgress(50) + toolbar.displayProgress(75) + toolbar.displayProgress(100) + + verify(display).updateProgress(10) + verify(display).updateProgress(50) + verify(display).updateProgress(75) + verify(display).updateProgress(100) + + verifyNoMoreInteractions(display) + } + + @Test + fun `internal onUrlEntered callback will be forwarded to urlChangeListener`() { + val toolbar = BrowserToolbar(testContext) + + val mockedListener = object { + var called = false + var url: String? = null + + fun invoke(url: String): Boolean { + this.called = true + this.url = url + return true + } + } + + toolbar.setOnUrlCommitListener(mockedListener::invoke) + toolbar.onUrlEntered("https://www.mozilla.org") + + assertTrue(mockedListener.called) + assertEquals("https://www.mozilla.org", mockedListener.url) + } + + /* + @Test + fun `internal onEditCancelled callback will be forwarded to editListener`() { + val toolbar = BrowserToolbar(testContext) + val listener: Toolbar.OnEditListener = mock() + toolbar.setOnEditListener(listener) + assertEquals(toolbar.edit.editListener, listener) + + toolbar.edit.views.url.onKeyPreIme( + KeyEvent.KEYCODE_BACK, + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK) + ) + verify(listener, times(1)).onCancelEditing() + }*/ + + @Test + fun `toolbar measure will use full width and fixed 56dp height`() { + val toolbar = BrowserToolbar(testContext) + + val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST) + val heightSpec = View.MeasureSpec.makeMeasureSpec(800, View.MeasureSpec.AT_MOST) + + toolbar.measure(widthSpec, heightSpec) + + assertEquals(1024, toolbar.measuredWidth) + assertEquals(56, toolbar.measuredHeight) + } + + @Test + fun `toolbar will use provided height with EXACTLY measure spec`() { + val toolbar = BrowserToolbar(testContext) + + val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST) + val heightSpec = View.MeasureSpec.makeMeasureSpec(800, View.MeasureSpec.EXACTLY) + + toolbar.measure(widthSpec, heightSpec) + + assertEquals(1024, toolbar.measuredWidth) + assertEquals(800, toolbar.measuredHeight) + } + + @Test + fun `display and edit toolbar will use full size of browser toolbar`() { + val toolbar = BrowserToolbar(testContext) + + assertEquals(0, toolbar.display.rootView.measuredWidth) + assertEquals(0, toolbar.display.rootView.measuredHeight) + assertEquals(0, toolbar.edit.rootView.measuredWidth) + assertEquals(0, toolbar.edit.rootView.measuredHeight) + + val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST) + val heightSpec = View.MeasureSpec.makeMeasureSpec(800, View.MeasureSpec.AT_MOST) + + toolbar.measure(widthSpec, heightSpec) + + assertEquals(1024, toolbar.display.rootView.measuredWidth) + assertEquals(56, toolbar.display.rootView.measuredHeight) + assertEquals(1024, toolbar.edit.rootView.measuredWidth) + assertEquals(56, toolbar.edit.rootView.measuredHeight) + } + + @Test + fun `toolbar will switch back to display mode after an URL has been entered`() { + val toolbar = BrowserToolbar(testContext) + toolbar.editMode() + + assertTrue(toolbar.display.rootView.visibility == View.GONE) + assertTrue(toolbar.edit.rootView.visibility == View.VISIBLE) + + toolbar.onUrlEntered("https://www.mozilla.org") + + assertTrue(toolbar.display.rootView.visibility == View.VISIBLE) + assertTrue(toolbar.edit.rootView.visibility == View.GONE) + } + + @Test + fun `toolbar will switch back to display mode if URL commit listener returns true`() { + val toolbar = BrowserToolbar(testContext) + toolbar.setOnUrlCommitListener { true } + toolbar.editMode() + + assertTrue(toolbar.display.rootView.isGone) + assertTrue(toolbar.edit.rootView.isVisible) + + toolbar.onUrlEntered("https://www.mozilla.org") + + assertTrue(toolbar.display.rootView.isVisible) + assertTrue(toolbar.edit.rootView.isGone) + } + + @Test + fun `toolbar will stay in edit mode if URL commit listener returns false`() { + val toolbar = BrowserToolbar(testContext) + toolbar.setOnUrlCommitListener { false } + toolbar.editMode() + + assertTrue(toolbar.display.rootView.isGone) + assertTrue(toolbar.edit.rootView.isVisible) + + toolbar.onUrlEntered("https://www.mozilla.org") + + assertTrue(toolbar.display.rootView.isGone) + assertTrue(toolbar.edit.rootView.isVisible) + } + + @Test + fun `add browser action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "Hello") { + // Do nothing + } + + toolbar.addBrowserAction(action) + + verify(display).addBrowserAction(action) + } + + @Test + fun `remove browser action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "Hello") { + // Do nothing + } + + toolbar.removeBrowserAction(action) + + verify(display).removeBrowserAction(action) + } + + @Test + fun `remove navigation action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "Hello") { + // Do nothing + } + + toolbar.removeNavigationAction(action) + + verify(display).removeNavigationAction(action) + } + + @Test + fun `remove page action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "Hello") { + // Do nothing + } + + toolbar.removePageAction(action) + + verify(display).removePageAction(action) + } + + @Test + fun `add page action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "World") { + // Do nothing + } + + toolbar.addPageAction(action) + + verify(display).addPageAction(action) + } + + @Test + fun `add edit action start will be forwarded to edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + + val edit: EditToolbar = mock() + toolbar.edit = edit + + val action = BrowserToolbar.Button(mock(), "QR code scanner") { + // Do nothing + } + + toolbar.addEditActionStart(action) + + verify(edit).addEditActionStart(action) + } + + @Test + fun `add edit action end will be forwarded to edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + + val edit: EditToolbar = mock() + toolbar.edit = edit + + val action = BrowserToolbar.Button(mock(), "QR code scanner") { + // Do nothing + } + + toolbar.addEditActionEnd(action) + + verify(edit).addEditActionEnd(action) + } + + @Test + fun `WHEN removing action end THEN it will be forwarded to the edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + + val edit: EditToolbar = mock() + toolbar.edit = edit + + val action = BrowserToolbar.Button(mock(), "QR code scanner") { + // Do nothing + } + + toolbar.removeEditActionEnd(action) + + verify(edit).removeEditActionEnd(action) + } + + @Test + fun `WHEN hideMenuButton is sent to BrowserToolbar THEN it will be forwarded to the DisplayToolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + toolbar.display = display + + toolbar.hideMenuButton() + + verify(display).hideMenuButton() + } + + @Test + fun `WHEN showMenuButton is sent to BrowserToolbar THEN it will be forwarded to the DisplayToolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + toolbar.display = display + + toolbar.showMenuButton() + + verify(display).showMenuButton() + } + + @Test + fun `WHEN showPageActionSeparator is sent to BrowserToolbar THEN it will be forwarded to the DisplayToolbar and EditToolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + val edit: EditToolbar = mock() + toolbar.display = display + toolbar.edit = edit + + toolbar.showPageActionSeparator() + + verify(display).showPageActionSeparator() + verify(edit).showPageActionSeparator() + } + + @Test + fun `WHEN hidePageActionSeparator is sent to BrowserToolbar THEN it will be forwarded to the DisplayToolbar and EditToolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + val edit: EditToolbar = mock() + toolbar.display = display + toolbar.edit = edit + + toolbar.hidePageActionSeparator() + + verify(display).hidePageActionSeparator() + verify(edit).hidePageActionSeparator() + } + + @Test + fun `WHEN setDisplayHorizontalPadding is sent to BrowserToolbar THEN it will be forwarded to the DisplayToolbar`() { + val toolbar = BrowserToolbar(testContext) + + val display: DisplayToolbar = mock() + toolbar.display = display + toolbar.edit = mock() + + toolbar.setDisplayHorizontalPadding(123) + verify(display).setHorizontalPadding(123) + + toolbar.setDisplayHorizontalPadding(0) + verify(display).setHorizontalPadding(0) + } + + @Test + fun `cast to view`() { + // Given + val toolbar = BrowserToolbar(testContext) + + // When + val view = toolbar.asView() + + // Then + assertNotNull(view) + } + + @Test + fun `URL update does not override search terms in edit mode`() { + val toolbar = BrowserToolbar(testContext) + + toolbar.display = spy(toolbar.display) + toolbar.edit = spy(toolbar.edit) + + toolbar.setSearchTerms("mozilla android") + toolbar.url = "https://www.mozilla.com" + toolbar.editMode() + verify(toolbar.display).url = "https://www.mozilla.com" + verify(toolbar.edit).updateUrl("mozilla android", false) + + toolbar.setSearchTerms("") + verify(toolbar.edit).updateUrl("", false) + + toolbar.url = "https://www.mozilla.org" + toolbar.editMode() + verify(toolbar.display).url = "https://www.mozilla.org" + verify(toolbar.edit).updateUrl("https://www.mozilla.org", false) + } + + @Test + fun `add navigation action will be forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + toolbar.display = display + + val action = BrowserToolbar.Button(mock(), "Back") { + // Do nothing + } + + toolbar.addNavigationAction(action) + + verify(display).addNavigationAction(action) + } + + @Test + fun `invalidate actions is forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + val display: DisplayToolbar = mock() + toolbar.display = display + + verify(display, never()).invalidateActions() + + toolbar.invalidateActions() + + verify(display).invalidateActions() + } + + @Test + fun `invalidate actions is forwarded to edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + val edit: EditToolbar = mock() + toolbar.edit = edit + + verify(edit, never()).invalidateActions() + + toolbar.invalidateActions() + + verify(edit).invalidateActions() + } + + @Test + fun `search terms (if set) are forwarded to edit toolbar instead of URL`() { + val toolbar = BrowserToolbar(testContext) + + toolbar.edit = spy(toolbar.edit) + + toolbar.url = "https://www.mozilla.org" + toolbar.setSearchTerms("Mozilla Firefox") + + verify(toolbar.edit, never()).updateUrl("https://www.mozilla.org") + verify(toolbar.edit, never()).updateUrl("Mozilla Firefox") + + toolbar.editMode() + + verify(toolbar.edit, never()).updateUrl("https://www.mozilla.org") + verify(toolbar.edit).updateUrl("Mozilla Firefox") + } + + @Test + fun `search terms are forwarded to edit toolbar when it is active`() { + val toolbar = BrowserToolbar(testContext) + + toolbar.edit = spy(toolbar.edit) + + toolbar.editMode() + + toolbar.setSearchTerms("Mozilla Firefox") + + verify(toolbar.edit).editSuggestion("Mozilla Firefox") + } + + @Test + fun `editListener is set on edit`() { + val toolbar = BrowserToolbar(testContext) + assertNull(toolbar.edit.editListener) + + val listener: Toolbar.OnEditListener = mock() + toolbar.setOnEditListener(listener) + + assertEquals(listener, toolbar.edit.editListener) + } + + @Test + fun `editListener is invoked when switching between modes`() { + val toolbar = BrowserToolbar(testContext) + + val listener: Toolbar.OnEditListener = mock() + toolbar.setOnEditListener(listener) + + toolbar.editMode() + + verify(listener).onStartEditing() + verifyNoMoreInteractions(listener) + + toolbar.displayMode() + + verify(listener).onStopEditing() + verifyNoMoreInteractions(listener) + } + + @Test + fun `editListener is invoked when text changes`() { + val toolbar = BrowserToolbar(testContext) + + val listener: Toolbar.OnEditListener = mock() + toolbar.setOnEditListener(listener) + + toolbar.edit.views.url.onAttachedToWindow() + + toolbar.editMode() + + toolbar.edit.views.url.setText("Hello") + toolbar.edit.views.url.setText("Hello World") + + verify(listener).onStartEditing() + verify(listener).onTextChanged("Hello") + verify(listener).onTextChanged("Hello World") + } + + @Test + fun `titleView visibility is based on being set`() { + val toolbar = BrowserToolbar(testContext) + + assertEquals(toolbar.display.views.origin.titleView.visibility, View.GONE) + toolbar.title = "Mozilla" + assertEquals(toolbar.display.views.origin.titleView.visibility, View.VISIBLE) + toolbar.title = "" + assertEquals(toolbar.display.views.origin.titleView.visibility, View.GONE) + } + + @Test + fun `titleView text is set properly`() { + val toolbar = BrowserToolbar(testContext) + + toolbar.title = "Mozilla" + assertEquals("Mozilla", toolbar.display.views.origin.titleView.text) + assertEquals("Mozilla", toolbar.title) + } + + @Test + fun `titleView fading is set properly with non-null attrs`() { + val attributeSet: AttributeSet = Robolectric.buildAttributeSet().build() + + val toolbar = BrowserToolbar(testContext, attributeSet) + val titleView = toolbar.display.views.origin.titleView + val edgeLength = testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_toolbar_url_fading_edge_size) + + assertTrue(titleView.isHorizontalFadingEdgeEnabled) + assertEquals(edgeLength, titleView.horizontalFadingEdgeLength) + } + + @Test + fun `Button constructor with drawable`() { + val buttonDefault = BrowserToolbar.Button(mock(), "imageDrawable") {} + + assertEquals(true, buttonDefault.visible()) + assertEquals(BrowserToolbar.DEFAULT_PADDING, buttonDefault.padding) + assertEquals("imageDrawable", buttonDefault.contentDescription) + + val button = BrowserToolbar.Button(mock(), "imageDrawable", visible = { false }) {} + + assertEquals(false, button.visible()) + } + + @Test + fun `ToggleButton constructor with drawable`() { + val buttonDefault = + BrowserToolbar.ToggleButton(mock(), mock(), "imageDrawable", "imageSelectedDrawable") {} + + assertEquals(true, buttonDefault.visible()) + assertEquals(BrowserToolbar.DEFAULT_PADDING, buttonDefault.padding) + + val button = BrowserToolbar.ToggleButton( + mock(), + mock(), + "imageDrawable", + "imageSelectedDrawable", + visible = { false }, + ) {} + + assertEquals(false, button.visible()) + } + + @Test + fun `ReloadPageAction visibility changes update image`() { + val reloadImage: Drawable = mock() + val stopImage: Drawable = mock() + val view: ImageButton = mock() + var reloadPageAction = BrowserToolbar.TwoStateButton(reloadImage, "reload", stopImage, "stop") {} + assertFalse(reloadPageAction.enabled) + reloadPageAction.bind(view) + verify(view).setImageDrawable(reloadImage) + verify(view).contentDescription = "reload" + + reloadPageAction = BrowserToolbar.TwoStateButton(reloadImage, "reload", stopImage, "stop", { false }) {} + reloadPageAction.bind(view) + verify(view).setImageDrawable(stopImage) + verify(view).contentDescription = "stop" + } + + @Test + fun `siteSecure updates the display`() { + val toolbar = BrowserToolbar(testContext) + toolbar.display = spy(toolbar.display) + assertEquals(SiteSecurity.INSECURE, toolbar.siteSecure) + + toolbar.siteSecure = SiteSecurity.SECURE + + verify(toolbar.display).siteSecurity = SiteSecurity.SECURE + } + + @Test + fun `siteTrackingProtection updates the display`() { + val toolbar = BrowserToolbar(testContext) + toolbar.display = spy(toolbar.display) + assertEquals(SiteTrackingProtection.OFF_GLOBALLY, toolbar.siteTrackingProtection) + + toolbar.siteTrackingProtection = SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED + + verify(toolbar.display).setTrackingProtectionState(SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED) + + toolbar.siteTrackingProtection = SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED + verifyNoMoreInteractions(toolbar.display) + } + + @Test + fun `private flag sets IME_FLAG_NO_PERSONALIZED_LEARNING on url edit view`() { + val toolbar = BrowserToolbar(testContext) + val edit = toolbar.edit + + // By default "private mode" is off. + assertEquals( + 0, + edit.views.url.imeOptions and + EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING, + ) + assertEquals(false, toolbar.private) + + // Turning on private mode sets flag + toolbar.private = true + assertNotEquals( + 0, + edit.views.url.imeOptions and + EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING, + ) + assertTrue(toolbar.private) + + // Turning private mode off again - should remove flag + toolbar.private = false + assertEquals( + 0, + edit.views.url.imeOptions and + EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING, + ) + assertEquals(false, toolbar.private) + } + + @Test + fun `setAutocompleteListener is forwarded to edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = mock() + + val filter: suspend (String, AutocompleteDelegate) -> Unit = { _, _ -> + // Do nothing + } + + toolbar.setAutocompleteListener(filter) + + verify(toolbar.edit).setAutocompleteListener(filter) + } + + @Test + fun `WHEN an attempt to refresh autocomplete suggestions is made THEN forward the call to edit toolbar`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = mock() + toolbar.setAutocompleteListener { _, _ -> } + + toolbar.refreshAutocomplete() + + verify(toolbar.edit).refreshAutocompleteSuggestion() + } + + @Test + fun `onStop is forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + toolbar.display = mock() + + toolbar.onStop() + + verify(toolbar.display).onStop() + } + + @Test + fun `dismiss menu is forwarded to display toolbar`() { + val toolbar = BrowserToolbar(testContext) + toolbar.display = mock() + val displayToolbarViews: DisplayToolbarViews = mock() + val menuButton: MenuButton = mock() + + whenever(toolbar.display.views).thenReturn(displayToolbarViews) + whenever(displayToolbarViews.menu).thenReturn(menuButton) + + toolbar.dismissMenu() + verify(menuButton).dismissMenu() + } + + @Test + fun `enable scrolling is forwarded to the toolbar behavior`() { + // Seems like real instances are needed for things to be set properly + val toolbar = BrowserToolbar(testContext) + val behavior = spy(EngineViewScrollingBehavior(testContext, null, ViewPosition.BOTTOM)) + val params = CoordinatorLayout.LayoutParams(10, 10).apply { + this.behavior = behavior + } + toolbar.layoutParams = params + + toolbar.enableScrolling() + + verify(behavior).enableScrolling() + } + + @Test + fun `disable scrolling is forwarded to the toolbar behavior`() { + // Seems like real instances are needed for things to be set properly + val toolbar = BrowserToolbar(testContext) + val behavior = spy(EngineViewScrollingBehavior(testContext, null, ViewPosition.BOTTOM)) + val params = CoordinatorLayout.LayoutParams(10, 10).apply { + this.behavior = behavior + } + toolbar.layoutParams = params + + toolbar.disableScrolling() + + verify(behavior).disableScrolling() + } + + @Test + fun `expand is forwarded to the toolbar behavior`() { + // Seems like real instances are needed for things to be set properly + val toolbar = BrowserToolbar(testContext) + val behavior = spy(EngineViewScrollingBehavior(testContext, null, ViewPosition.BOTTOM)) + val params = CoordinatorLayout.LayoutParams(10, 10).apply { + this.behavior = behavior + } + toolbar.layoutParams = params + + toolbar.expand() + + verify(behavior).forceExpand(toolbar) + } + + @Test + fun `collapse is forwarded to the toolbar behavior`() { + // Seems like real instances are needed for things to be set properly + val toolbar = BrowserToolbar(testContext) + val behavior = spy(EngineViewScrollingBehavior(testContext, null, ViewPosition.BOTTOM)) + val params = CoordinatorLayout.LayoutParams(10, 10).apply { + this.behavior = behavior + } + toolbar.layoutParams = params + + toolbar.collapse() + + verify(behavior).forceCollapse(toolbar) + } + + @Test + fun `WHEN search terms changes THEN edit listener is notified`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = spy(toolbar.edit) + toolbar.edit.editListener = mock() + + toolbar.setSearchTerms("") + toolbar.editMode() + + toolbar.setSearchTerms("test") + verify(toolbar.edit.editListener)?.onTextChanged("test") + + toolbar.setSearchTerms("") + verify(toolbar.edit.editListener)?.onTextChanged("") + } + + @Test + fun `WHEN switching to edit mode AND the cursor placement parameter is specified THEN call the correct method to place the cursor`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit = spy(toolbar.edit) + + toolbar.editMode(Toolbar.CursorPlacement.ALL) + + verify(toolbar.edit).selectAll() + + toolbar.editMode(Toolbar.CursorPlacement.END) + + verify(toolbar.edit).selectEnd() + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/DisplayToolbarTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/DisplayToolbarTest.kt new file mode 100644 index 0000000000..6a8ba9e478 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/DisplayToolbarTest.kt @@ -0,0 +1,824 @@ +/* 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.toolbar2.display + +import android.graphics.Color +import android.os.Build +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.menu.BrowserMenu +import mozilla.components.browser.menu.BrowserMenuBuilder +import mozilla.components.browser.menu.item.SimpleBrowserMenuItem +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.menu.MenuButton +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.concept.toolbar.Toolbar.SiteSecurity +import mozilla.components.concept.toolbar.Toolbar.SiteTrackingProtection +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.processor.CollectionProcessor +import mozilla.components.support.test.any +import mozilla.components.support.test.eq +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.robolectric.util.ReflectionHelpers +import mozilla.components.ui.icons.R as iconsR + +@RunWith(AndroidJUnit4::class) +class DisplayToolbarTest { + private fun createDisplayToolbar(): Pair { + val toolbar: BrowserToolbar = mock() + val displayToolbar = DisplayToolbar( + testContext, + toolbar, + View.inflate(testContext, R.layout.mozac_browser_toolbar_displaytoolbar, null), + ) + return Pair(toolbar, displayToolbar) + } + + @Test + fun `clicking on the URL switches the toolbar to editing mode`() { + val (toolbar, displayToolbar) = createDisplayToolbar() + + val urlView = displayToolbar.views.origin.urlView + assertTrue(urlView.performClick()) + + verify(toolbar).editMode() + } + + @Test + fun `progress is forwarded to progress bar`() { + val (_, displayToolbar) = createDisplayToolbar() + + val progressView = displayToolbar.views.progress + + displayToolbar.updateProgress(0) + assertEquals(0, progressView.progress) + assertEquals(View.GONE, progressView.visibility) + + displayToolbar.updateProgress(10) + assertEquals(10, progressView.progress) + assertEquals(View.VISIBLE, progressView.visibility) + + displayToolbar.updateProgress(50) + assertEquals(50, progressView.progress) + assertEquals(View.VISIBLE, progressView.visibility) + + displayToolbar.updateProgress(75) + assertEquals(75, progressView.progress) + assertEquals(View.VISIBLE, progressView.visibility) + + displayToolbar.updateProgress(100) + assertEquals(100, progressView.progress) + assertEquals(View.GONE, progressView.visibility) + } + + @Test + fun `trackingProtectionViewColor will change the color of the trackingProtectionIconView`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.trackingProtectionIndicator.colorFilter) + + displayToolbar.colors = displayToolbar.colors.copy( + trackingProtection = Color.BLUE, + ) + + assertNotNull(displayToolbar.views.trackingProtectionIndicator.colorFilter) + assertNotNull(displayToolbar.views.trackingProtectionIndicator.trackingProtectionTint) + } + + @Test + fun `highlightView will change the color of the dot`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.highlight.colorFilter) + + displayToolbar.colors = displayToolbar.colors.copy(highlight = Color.BLUE) + + assertNotNull(displayToolbar.views.highlight.colorFilter) + assertNotNull(displayToolbar.views.highlight.highlightTint) + } + + @Test + fun `tracking protection and separator views become visible when states ON OR ACTIVE are set to siteTrackingProtection`() { + val (_, displayToolbar) = createDisplayToolbar() + + val trackingView = displayToolbar.views.trackingProtectionIndicator + val separatorView = displayToolbar.views.separator + + assertTrue(trackingView.visibility == View.GONE) + assertTrue(separatorView.visibility == View.GONE) + + displayToolbar.indicators = listOf( + DisplayToolbar.Indicators.SECURITY, + DisplayToolbar.Indicators.TRACKING_PROTECTION, + ) + displayToolbar.url = "https://www.mozilla.org" + displayToolbar.displayIndicatorSeparator = true + displayToolbar.setTrackingProtectionState(SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED) + + assertTrue(trackingView.isVisible) + assertTrue(separatorView.isVisible) + + displayToolbar.setTrackingProtectionState(SiteTrackingProtection.OFF_GLOBALLY) + + assertTrue(trackingView.visibility == View.GONE) + assertTrue(separatorView.visibility == View.GONE) + + displayToolbar.setTrackingProtectionState(SiteTrackingProtection.ON_TRACKERS_BLOCKED) + + assertTrue(trackingView.isVisible) + assertTrue(separatorView.isVisible) + } + + @Test + fun `setTrackingProtectionIcons will forward to TrackingProtectionIconView`() { + val (_, displayToolbar) = createDisplayToolbar() + + displayToolbar.indicators = listOf(DisplayToolbar.Indicators.TRACKING_PROTECTION) + displayToolbar.setTrackingProtectionState(SiteTrackingProtection.ON_NO_TRACKERS_BLOCKED) + + val oldTrackingProtectionIcon = displayToolbar.views.trackingProtectionIndicator.drawable + assertNotNull(oldTrackingProtectionIcon) + + val drawable1 = + testContext.getDrawable(TrackingProtectionIconView.DEFAULT_ICON_ON_NO_TRACKERS_BLOCKED)!! + val drawable2 = + testContext.getDrawable(TrackingProtectionIconView.DEFAULT_ICON_ON_TRACKERS_BLOCKED)!! + val drawable3 = + testContext.getDrawable(TrackingProtectionIconView.DEFAULT_ICON_OFF_FOR_A_SITE)!! + + displayToolbar.icons = displayToolbar.icons.copy( + trackingProtectionTrackersBlocked = drawable1, + trackingProtectionNothingBlocked = drawable2, + trackingProtectionException = drawable3, + ) + + assertNotEquals( + oldTrackingProtectionIcon, + displayToolbar.views.trackingProtectionIndicator.drawable, + ) + + assertEquals(drawable2, displayToolbar.views.trackingProtectionIndicator.drawable) + + displayToolbar.setTrackingProtectionState(SiteTrackingProtection.ON_TRACKERS_BLOCKED) + + assertNotEquals( + oldTrackingProtectionIcon, + displayToolbar.views.trackingProtectionIndicator.drawable, + ) + + assertEquals( + drawable1, + displayToolbar.views.trackingProtectionIndicator.drawable, + ) + } + + @Test + fun `setHighlight will forward to HighlightView`() { + val (_, displayToolbar) = createDisplayToolbar() + + val oldPermissionIcon = displayToolbar.views.highlight.drawable + assertNotNull(oldPermissionIcon) + + val drawable1 = testContext.getDrawable(HighlightView.DEFAULT_ICON)!! + + displayToolbar.indicators = listOf(DisplayToolbar.Indicators.HIGHLIGHT) + displayToolbar.icons = displayToolbar.icons.copy( + highlight = drawable1, + ) + + assertNotEquals( + oldPermissionIcon, + displayToolbar.views.highlight.drawable, + ) + + displayToolbar.setHighlight(Toolbar.Highlight.PERMISSIONS_CHANGED) + + assertNotEquals( + oldPermissionIcon, + displayToolbar.views.highlight.drawable, + ) + } + + @Test + fun `menu view is gone by default`() { + val (_, displayToolbar) = createDisplayToolbar() + + val menuView = displayToolbar.views.menu + + assertNotNull(menuView) + assertEquals(View.GONE, menuView.impl.visibility) + } + + @Test + fun `menu view becomes visible once a menu builder is set`() { + val (_, displayToolbar) = createDisplayToolbar() + + val menuView = displayToolbar.views.menu + + assertNotNull(menuView) + + assertEquals(View.GONE, menuView.impl.visibility) + + displayToolbar.menuBuilder = BrowserMenuBuilder(emptyList()) + + assertEquals(View.VISIBLE, menuView.impl.visibility) + + displayToolbar.menuBuilder = null + + assertEquals(View.GONE, menuView.impl.visibility) + } + + @Test + fun `no menu builder is set by default`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.menuBuilder) + } + + @Test + fun `menu builder will be used to create and show menu when button is clicked`() { + val (_, displayToolbar) = createDisplayToolbar() + val menuView = displayToolbar.views.menu + + val menuBuilder = mock(BrowserMenuBuilder::class.java) + val menu = mock(BrowserMenu::class.java) + doReturn(menu).`when`(menuBuilder).build(testContext) + + displayToolbar.menuBuilder = menuBuilder + + verify(menuBuilder, never()).build(testContext) + verify(menu, never()).show(menuView.impl) + + menuView.impl.performClick() + + verify(menuBuilder).build(testContext) + verify(menu).show(eq(menuView.impl), any(), any(), anyBoolean(), any()) + verify(menu, never()).invalidate() + + displayToolbar.invalidateActions() + + verify(menu).invalidate() + } + + @Test + fun `browser action gets added as view to toolbar`() { + val contentDescription = "Mozilla" + + val (_, displayToolbar) = createDisplayToolbar() + + assertEquals(0, displayToolbar.views.browserActions.childCount) + + val action = BrowserToolbar.Button(mock(), contentDescription) {} + displayToolbar.addBrowserAction(action) + + assertEquals(1, displayToolbar.views.browserActions.childCount) + + val view = displayToolbar.views.browserActions.getChildAt(0) + assertEquals(contentDescription, view.contentDescription) + } + + @Test + fun `clicking browser action view triggers listener of action`() { + var callbackExecuted = false + + val action = BrowserToolbar.Button(mock(), "Button") { + callbackExecuted = true + } + + val (_, displayToolbar) = createDisplayToolbar() + displayToolbar.addBrowserAction(action) + + assertEquals(1, displayToolbar.views.browserActions.childCount) + val view = displayToolbar.views.browserActions.getChildAt(0) + + assertNotNull(view) + + assertFalse(callbackExecuted) + + view?.performClick() + + assertTrue(callbackExecuted) + } + + @Test + fun `browser action can be removed`() { + val contentDescription = "to-be-removed" + + val (_, displayToolbar) = createDisplayToolbar() + + val action = BrowserToolbar.Button(mock(), contentDescription) {} + // Removing action which was never added has no effect + displayToolbar.removeBrowserAction(action) + + displayToolbar.addBrowserAction(action) + assertEquals(1, displayToolbar.views.browserActions.childCount) + + displayToolbar.removeBrowserAction(action) + assertEquals(0, displayToolbar.views.browserActions.childCount) + } + + @Test + fun `navigation action can be removed`() { + val contentDescription = "to-be-removed" + + val (_, displayToolbar) = createDisplayToolbar() + + val action = BrowserToolbar.Button(mock(), contentDescription) {} + // Removing action which was never added has no effect + displayToolbar.removeNavigationAction(action) + + displayToolbar.addNavigationAction(action) + assertEquals(1, displayToolbar.views.navigationActions.childCount) + + displayToolbar.removeNavigationAction(action) + assertEquals(0, displayToolbar.views.navigationActions.childCount) + } + + @Test + fun `page action can be removed`() { + val contentDescription = "to-be-removed" + + val (_, displayToolbar) = createDisplayToolbar() + + val action = BrowserToolbar.Button(mock(), contentDescription) {} + // Removing action which was never added has no effect + displayToolbar.removePageAction(action) + + displayToolbar.addPageAction(action) + assertEquals(1, displayToolbar.views.pageActions.childCount) + + displayToolbar.removePageAction(action) + assertEquals(0, displayToolbar.views.pageActions.childCount) + } + + @Test + fun `page actions will be added as view to the toolbar`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertEquals(0, displayToolbar.views.pageActions.childCount) + + val action = BrowserToolbar.Button(mock(), "Reader Mode") {} + displayToolbar.addPageAction(action) + + assertEquals(1, displayToolbar.views.pageActions.childCount) + val view = displayToolbar.views.pageActions.getChildAt(0) + assertEquals("Reader Mode", view.contentDescription) + } + + @Test + fun `clicking a page action view will execute the listener of the action`() { + var listenerExecuted = false + + val action = BrowserToolbar.Button(mock(), "Reload") { + listenerExecuted = true + } + + val (_, displayToolbar) = createDisplayToolbar() + displayToolbar.addPageAction(action) + + assertFalse(listenerExecuted) + + assertEquals(1, displayToolbar.views.pageActions.childCount) + val view = displayToolbar.views.pageActions.getChildAt(0) + + assertNotNull(view) + view!!.performClick() + + assertTrue(listenerExecuted) + } + + @Test + fun `navigation actions will be added as view to the toolbar`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertEquals(0, displayToolbar.views.navigationActions.childCount) + + displayToolbar.addNavigationAction(BrowserToolbar.Button(mock(), "Back") {}) + displayToolbar.addNavigationAction(BrowserToolbar.Button(mock(), "Forward") {}) + + assertEquals(2, displayToolbar.views.navigationActions.childCount) + } + + @Test + fun `clicking on navigation action will execute listener of the action`() { + val (_, displayToolbar) = createDisplayToolbar() + + var listenerExecuted = false + val action = BrowserToolbar.Button(mock(), "Back") { + listenerExecuted = true + } + + displayToolbar.addNavigationAction(action) + + assertFalse(listenerExecuted) + + assertEquals(1, displayToolbar.views.navigationActions.childCount) + val view = displayToolbar.views.navigationActions.getChildAt(0) + view.performClick() + + assertTrue(listenerExecuted) + } + + @Test + fun `view of not visible navigation action gets removed after invalidating`() { + val (_, displayToolbar) = createDisplayToolbar() + + var shouldActionBeDisplayed = true + + val action = BrowserToolbar.Button( + mock(), + "Back", + visible = { shouldActionBeDisplayed }, + ) { /* Do nothing */ } + + displayToolbar.addNavigationAction(action) + + assertEquals(1, displayToolbar.views.navigationActions.childCount) + + shouldActionBeDisplayed = false + displayToolbar.invalidateActions() + + assertEquals(0, displayToolbar.views.navigationActions.childCount) + + shouldActionBeDisplayed = true + displayToolbar.invalidateActions() + + assertEquals(1, displayToolbar.views.navigationActions.childCount) + } + + @Test + fun `toolbar should call bind with view argument on action after invalidating`() { + val (_, displayToolbar) = createDisplayToolbar() + + val action = spy(BrowserToolbar.Button(mock(), "Reload") {}) + + displayToolbar.addPageAction(action) + + assertEquals(1, displayToolbar.views.pageActions.childCount) + val view = displayToolbar.views.pageActions.getChildAt(0) + + verify(action, never()).bind(view!!) + + displayToolbar.invalidateActions() + + verify(action).bind(view) + } + + @Test + fun `page action will not be added if visible lambda of action returns false`() { + val (_, displayToolbar) = createDisplayToolbar() + + val visibleAction = BrowserToolbar.Button(mock(), "Reload") {} + val invisibleAction = BrowserToolbar.Button( + mock(), + "Reader Mode", + visible = { false }, + ) {} + + displayToolbar.addPageAction(visibleAction) + displayToolbar.addPageAction(invisibleAction) + + assertEquals(1, displayToolbar.views.pageActions.childCount) + + val view = displayToolbar.views.pageActions.getChildAt(0) + assertEquals("Reload", view.contentDescription) + } + + @Test + fun `browser action will not be added if visible lambda of action returns false`() { + val (_, displayToolbar) = createDisplayToolbar() + + val visibleAction = BrowserToolbar.Button(mock(), "Tabs") {} + val invisibleAction = BrowserToolbar.Button( + mock(), + "Settings", + visible = { false }, + ) {} + + displayToolbar.addBrowserAction(visibleAction) + displayToolbar.addBrowserAction(invisibleAction) + + assertEquals(1, displayToolbar.views.browserActions.childCount) + + val view = displayToolbar.views.browserActions.getChildAt(0) + assertEquals("Tabs", view.contentDescription) + } + + @Test + fun `navigation action will not be added if visible lambda of action returns false`() { + val (_, displayToolbar) = createDisplayToolbar() + + val visibleAction = BrowserToolbar.Button(mock(), "Forward") {} + val invisibleAction = BrowserToolbar.Button( + mock(), + "Back", + visible = { false }, + ) {} + + displayToolbar.addNavigationAction(visibleAction) + displayToolbar.addNavigationAction(invisibleAction) + + assertEquals(1, displayToolbar.views.navigationActions.childCount) + + val view = displayToolbar.views.navigationActions.getChildAt(0) + assertEquals("Forward", view.contentDescription) + } + + @Test + fun `url background will be added and removed from display layout`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.background.drawable) + + displayToolbar.setUrlBackground( + ContextCompat.getDrawable( + testContext, + iconsR.drawable.mozac_ic_broken_lock, + ), + ) + + assertNotNull(displayToolbar.views.background.drawable) + + displayToolbar.setUrlBackground(null) + + assertNull(displayToolbar.views.background.drawable) + } + + @Test + fun `titleView does not display when there is no title text`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertTrue(displayToolbar.views.origin.titleView.isGone) + + displayToolbar.title = "Hello World" + + assertTrue(displayToolbar.views.origin.titleView.isVisible) + } + + @Test + fun `toolbar only switches to editing mode if onUrlClicked returns true`() { + val (toolbar, displayToolbar) = createDisplayToolbar() + + displayToolbar.views.origin.urlView.performClick() + + verify(toolbar).editMode() + + reset(toolbar) + displayToolbar.onUrlClicked = { false } + displayToolbar.views.origin.urlView.performClick() + + verify(toolbar, never()).editMode() + + reset(toolbar) + displayToolbar.onUrlClicked = { true } + displayToolbar.views.origin.urlView.performClick() + + verify(toolbar).editMode() + } + + @Test + fun `urlView delegates long click when set`() { + val (_, displayToolbar) = createDisplayToolbar() + + var longUrlClicked = false + + displayToolbar.setOnUrlLongClickListener { + longUrlClicked = true + false + } + + assertFalse(longUrlClicked) + displayToolbar.views.origin.urlView.performLongClick() + assertTrue(longUrlClicked) + } + + @Test + fun `urlView longClickListener can be unset`() { + val (_, displayToolbar) = createDisplayToolbar() + + var longClicked = false + displayToolbar.setOnUrlLongClickListener { + longClicked = true + true + } + + displayToolbar.views.origin.urlView.performLongClick() + assertTrue(longClicked) + longClicked = false + + displayToolbar.setOnUrlLongClickListener(null) + displayToolbar.views.origin.urlView.performLongClick() + + assertFalse(longClicked) + } + + @Test + fun `iconView changes site secure state when site security changes`() { + val (_, displayToolbar) = createDisplayToolbar() + assertEquals(SiteSecurity.INSECURE, displayToolbar.views.securityIndicator.siteSecurity) + + displayToolbar.siteSecurity = SiteSecurity.SECURE + + assertEquals(SiteSecurity.SECURE, displayToolbar.views.securityIndicator.siteSecurity) + + displayToolbar.siteSecurity = SiteSecurity.INSECURE + + assertEquals(SiteSecurity.INSECURE, displayToolbar.views.securityIndicator.siteSecurity) + } + + @Test + fun `securityIconColor is set when securityIconColor changes`() { + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.securityIndicator.colorFilter) + + displayToolbar.colors = displayToolbar.colors.copy( + securityIconSecure = Color.BLUE, + securityIconInsecure = Color.BLUE, + ) + + assertNotNull(displayToolbar.views.securityIndicator.colorFilter) + } + + @Test + fun `color filter is set with transparent when securityIconColor changes to transparent and api version is lower than 23`() { + ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 22) + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.securityIndicator.colorFilter) + + displayToolbar.colors = displayToolbar.colors.copy( + securityIconSecure = Color.TRANSPARENT, + securityIconInsecure = Color.TRANSPARENT, + ) + + assertNotNull(displayToolbar.views.securityIndicator.colorFilter) + } + + @Test + fun `color filter is cleared when securityIconColor changes to transparent and api version is bigger than 22`() { + ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 23) + + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.securityIndicator.colorFilter) + + displayToolbar.colors = displayToolbar.colors.copy( + securityIconSecure = Color.TRANSPARENT, + securityIconInsecure = Color.TRANSPARENT, + ) + + assertNull(displayToolbar.views.securityIndicator.colorFilter) + } + + @Test + fun `clicking menu button emits facts with additional extras from builder set`() { + CollectionProcessor.withFactCollection { facts -> + val (_, displayToolbar) = createDisplayToolbar() + val menuView = displayToolbar.views.menu + + val menuBuilder = BrowserMenuBuilder( + listOf(SimpleBrowserMenuItem("Mozilla")), + mapOf( + "customTab" to true, + "test" to "23", + ), + ) + displayToolbar.menuBuilder = menuBuilder + + assertEquals(0, facts.size) + + menuView.impl.performClick() + + assertEquals(1, facts.size) + + val fact = facts[0] + + assertEquals(Component.BROWSER_TOOLBAR, fact.component) + assertEquals(Action.CLICK, fact.action) + assertEquals("menu", fact.item) + assertNull(fact.value) + + assertNotNull(fact.metadata) + + val metadata = fact.metadata!! + assertEquals(2, metadata.size) + assertTrue(metadata.containsKey("customTab")) + assertTrue(metadata.containsKey("test")) + assertEquals(true, metadata["customTab"]) + assertEquals("23", metadata["test"]) + } + } + + @Test + fun `clicking on site security indicator invokes listener`() { + var listenerInvoked = false + + val (_, displayToolbar) = createDisplayToolbar() + + assertNull(displayToolbar.views.securityIndicator.background) + + displayToolbar.setOnSiteSecurityClickedListener { + listenerInvoked = true + } + + assertNotNull(displayToolbar.views.securityIndicator.background) + + displayToolbar.views.securityIndicator.performClick() + + assertTrue(listenerInvoked) + + listenerInvoked = false + + displayToolbar.setOnSiteSecurityClickedListener { } + + assertNotNull(displayToolbar.views.securityIndicator.background) + + displayToolbar.views.securityIndicator.performClick() + + assertFalse(listenerInvoked) + + displayToolbar.setOnSiteSecurityClickedListener(null) + + assertNull(displayToolbar.views.securityIndicator.background) + } + + @Test + fun `Security icon has proper content description`() { + val (_, displayToolbar) = createDisplayToolbar() + val siteSecurityIconView = displayToolbar.views.securityIndicator + + assertNotNull(siteSecurityIconView.contentDescription) + assertEquals( + testContext.getString(R.string.mozac_browser_toolbar_content_description_site_info), + siteSecurityIconView.contentDescription, + ) + } + + @Test + fun `Backgrounding the app dismisses menu if already open`() { + var wasDismissed = false + val (_, displayToolbar) = createDisplayToolbar() + val menuView = displayToolbar.views.menu + menuView.impl.register( + object : MenuButton.Observer { + override fun onDismiss() { + wasDismissed = true + } + }, + ) + menuView.menuBuilder = BrowserMenuBuilder(emptyList()) + menuView.impl.performClick() + + displayToolbar.onStop() + + assertTrue(wasDismissed) + } + + @Test + fun `set a dismiss lambda on the menu button`() { + var wasDismissed = false + val (_, displayToolbar) = createDisplayToolbar() + displayToolbar.setMenuDismissAction { wasDismissed = true } + val menuView = displayToolbar.views.menu + menuView.menuBuilder = BrowserMenuBuilder(emptyList()) + menuView.impl.performClick() + + menuView.dismissMenu() + assertTrue(wasDismissed) + } + + @Test + fun `url formatter used if provided`() { + val (_, displayToolbar) = createDisplayToolbar() + displayToolbar.url = "https://mozilla.org" + assertEquals(displayToolbar.url, displayToolbar.views.origin.url) + + displayToolbar.urlFormatter = { it.replace("https://".toRegex(), "") } + displayToolbar.url = "https://mozilla.org" + assertEquals("mozilla.org", displayToolbar.views.origin.url) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/HighlightViewTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/HighlightViewTest.kt new file mode 100644 index 0000000000..e339b98538 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/HighlightViewTest.kt @@ -0,0 +1,67 @@ +/* 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.toolbar2.display + +import androidx.core.view.isVisible +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.Toolbar.Highlight.NONE +import mozilla.components.concept.toolbar.Toolbar.Highlight.PERMISSIONS_CHANGED +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class HighlightViewTest { + + @Test + fun `after setting tint, can get trackingProtectionTint`() { + val view = HighlightView(testContext) + view.setTint(android.R.color.black) + assertEquals(android.R.color.black, view.highlightTint) + } + + @Test + fun `setting status will trigger an icon updated`() { + val view = HighlightView(testContext) + + view.state = PERMISSIONS_CHANGED + + assertEquals(PERMISSIONS_CHANGED, view.state) + assertTrue(view.isVisible) + assertNotNull(view.drawable) + assertEquals( + view.contentDescription, + testContext.getString(R.string.mozac_browser_toolbar_content_description_autoplay_blocked), + ) + + view.state = NONE + + assertEquals(NONE, view.state) + assertNull(view.drawable) + assertFalse(view.isVisible) + assertNull(view.contentDescription) + } + + @Test + fun `setIcons will trigger an icon updated`() { + val view = spy(HighlightView(testContext)) + + view.setIcon( + testContext.getDrawable( + TrackingProtectionIconView.DEFAULT_ICON_ON_NO_TRACKERS_BLOCKED, + )!!, + ) + + verify(view).updateIcon() + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/MenuButtonTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/MenuButtonTest.kt new file mode 100644 index 0000000000..ada49fd88b --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/MenuButtonTest.kt @@ -0,0 +1,156 @@ +/* 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.toolbar2.display + +import android.graphics.Color +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.menu.BrowserMenu +import mozilla.components.browser.menu.BrowserMenuBuilder +import mozilla.components.browser.menu.BrowserMenuHighlight +import mozilla.components.browser.menu.ext.getHighlight +import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem +import mozilla.components.browser.menu.item.SimpleBrowserMenuItem +import mozilla.components.concept.menu.MenuController +import mozilla.components.concept.menu.candidate.DecorativeTextMenuCandidate +import mozilla.components.concept.menu.candidate.TextMenuCandidate +import mozilla.components.support.test.any +import mozilla.components.support.test.mock +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.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@RunWith(AndroidJUnit4::class) +class MenuButtonTest { + @Mock private lateinit var menuBuilder: BrowserMenuBuilder + + @Mock private lateinit var menuController: MenuController + + @Mock private lateinit var menu: BrowserMenu + + @Mock private lateinit var menuButtonInternal: mozilla.components.browser.menu.view.MenuButton + private lateinit var menuButton: MenuButton + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + `when`(menuBuilder.build(testContext)).thenReturn(menu) + `when`(menuButtonInternal.context).thenReturn(testContext) + + menuButton = MenuButton(menuButtonInternal) + } + + @Test + fun `menu button is visible only if menu builder attached`() { + verify(menuButtonInternal).visibility = View.GONE + + `when`(menuButtonInternal.menuBuilder).thenReturn(mock()) + assertTrue(menuButton.shouldBeVisible()) + + `when`(menuButtonInternal.menuBuilder).thenReturn(null) + assertFalse(menuButton.shouldBeVisible()) + } + + @Suppress("Deprecation") + @Test + fun `menu button sets onDismiss action`() { + val action = {} + menuButton.setMenuDismissAction(action) + + verify(menuButtonInternal).onDismiss = action + } + + @Test + fun `icon displays dot if low highlighted item is present in menu`() { + verify(menuButtonInternal, never()).invalidateBrowserMenu() + verify(menuButtonInternal, never()).setHighlight(any()) + + var isHighlighted = false + val highlight = BrowserMenuHighlight.LowPriority(Color.YELLOW) + val highlightMenuBuilder = spy( + BrowserMenuBuilder( + listOf( + BrowserMenuHighlightableItem( + label = "Test", + startImageResource = 0, + highlight = highlight, + isHighlighted = { isHighlighted }, + ), + ), + ), + ) + doReturn(menu).`when`(highlightMenuBuilder).build(testContext) + + menuButton.menuBuilder = highlightMenuBuilder + `when`(menuButtonInternal.menuBuilder).thenReturn(highlightMenuBuilder) + menuButton.invalidateMenu() + + verify(menuButtonInternal).setHighlight(null) + + isHighlighted = true + menuButton.invalidateMenu() + + assertEquals(highlight, highlightMenuBuilder.items.getHighlight()) + verify(menuButtonInternal).setHighlight(highlight) + } + + @Test + fun `invalidateMenu should invalidate the internal menu`() { + `when`(menuButtonInternal.menuController).thenReturn(null) + `when`(menuButtonInternal.menuBuilder).thenReturn(mock()) + verify(menuButtonInternal, never()).invalidateBrowserMenu() + + menuButton.invalidateMenu() + + verify(menuButtonInternal).invalidateBrowserMenu() + } + + @Test + fun `invalidateMenu should do nothing if using the menu controller`() { + `when`(menuButtonInternal.menuController).thenReturn(menuController) + `when`(menuButtonInternal.menuBuilder).thenReturn(null) + verify(menuButtonInternal, never()).invalidateBrowserMenu() + + menuButton.invalidateMenu() + + verify(menuButtonInternal, never()).invalidateBrowserMenu() + } + + @Test + fun `invalidateMenu should automatically upgrade menu items if both builder and controller are present`() { + val onClick = {} + `when`(menuButtonInternal.menuController).thenReturn(menuController) + `when`(menuButtonInternal.menuBuilder).thenReturn( + BrowserMenuBuilder( + listOf( + SimpleBrowserMenuItem("Item 1", listener = onClick), + SimpleBrowserMenuItem("Item 2"), + ), + ), + ) + verify(menuButtonInternal, never()).invalidateBrowserMenu() + + menuButton.invalidateMenu() + + verify(menuButtonInternal, never()).invalidateBrowserMenu() + verify(menuController).submitList( + listOf( + TextMenuCandidate("Item 1", onClick = onClick), + DecorativeTextMenuCandidate("Item 2"), + ), + ) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconViewTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconViewTest.kt new file mode 100644 index 0000000000..1386c17e29 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/display/TrackingProtectionIconViewTest.kt @@ -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.toolbar2.display + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TrackingProtectionIconViewTest { + + @Test + fun `After setting tint, can get trackingProtectionTint`() { + val view = TrackingProtectionIconView(testContext) + view.setTint(android.R.color.black) + assertEquals(android.R.color.black, view.trackingProtectionTint) + } + + @Test + fun `colorFilter is cleared on animatable drawables`() { + val view = TrackingProtectionIconView(testContext) + view.trackingProtectionTint = android.R.color.black + + val drawable = mock() + val animatedDrawable = mock() + + view.setOrClearColorFilter(drawable) + assertEquals(PorterDuffColorFilter(android.R.color.black, PorterDuff.Mode.SRC_ATOP), view.colorFilter) + + view.setOrClearColorFilter(animatedDrawable) + assertNotEquals(PorterDuffColorFilter(android.R.color.black, PorterDuff.Mode.SRC_ATOP), view.colorFilter) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/edit/EditToolbarTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/edit/EditToolbarTest.kt new file mode 100644 index 0000000000..a371126a16 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/edit/EditToolbarTest.kt @@ -0,0 +1,290 @@ +/* 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.toolbar2.edit + +import android.view.KeyEvent +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.browser.toolbar2.R +import mozilla.components.concept.toolbar.AutocompleteDelegate +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.processor.CollectionProcessor +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.ui.autocomplete.InlineAutocompleteEditText +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch + +@ExperimentalCoroutinesApi // for runTest +@RunWith(AndroidJUnit4::class) +class EditToolbarTest { + private fun createEditToolbar(): Pair { + val toolbar: BrowserToolbar = mock() + val displayToolbar = EditToolbar( + testContext, + toolbar, + View.inflate(testContext, R.layout.mozac_browser_toolbar_edittoolbar, null), + ) + return Pair(toolbar, displayToolbar) + } + + @Test + fun `entered text is forwarded to async autocomplete filter`() = runTest { + val toolbar = BrowserToolbar(testContext) + + toolbar.edit.views.url.onAttachedToWindow() + + val latch = CountDownLatch(1) + var invokedWithParams: List? = null + toolbar.setAutocompleteListener { p1, p2 -> + invokedWithParams = listOf(p1, p2) + latch.countDown() + } + + toolbar.edit.views.url.setText("Hello") + + // Autocomplete filter will be invoked on a worker thread. + // Serialize here for the sake of tests. + latch.await() + + assertEquals("Hello", invokedWithParams!![0]) + assertTrue(invokedWithParams!![1] is AutocompleteDelegate) + } + + @Test + fun `GIVEN existing user input WHEN a call to refresh autocomplete suggestions is made THEN retart the autocomplete functionality with the current text`() { + val toolbar = BrowserToolbar(testContext) + toolbar.edit.views.url.onAttachedToWindow() + // Fake existing user input. + toolbar.edit.views.url.setText("Test") + val latch = CountDownLatch(1) + var invokedWithParams: List? = null + // Only now enable the autocomplete functionality. + toolbar.setAutocompleteListener { p1, p2 -> + invokedWithParams = listOf(p1, p2) + latch.countDown() + } + + toolbar.refreshAutocomplete() + + // Autocomplete filter will be invoked on a worker thread. + // Serialize here for the sake of tests. + latch.await() + assertEquals("Test", invokedWithParams!![0]) + assertTrue(invokedWithParams!![1] is AutocompleteDelegate) + } + + @Test + fun `focus change is forwarded to listener`() { + var listenerInvoked = false + var value = false + + val toolbar = BrowserToolbar(testContext) + toolbar.edit.setOnEditFocusChangeListener { hasFocus -> + listenerInvoked = true + value = hasFocus + } + + // Switch to editing mode and focus view. + toolbar.editMode() + toolbar.edit.views.url.requestFocus() + + assertTrue(listenerInvoked) + assertTrue(value) + + // Switch back to display mode + listenerInvoked = false + toolbar.displayMode() + + assertTrue(listenerInvoked) + assertFalse(value) + } + + @Test + fun `entering text emits facts`() { + CollectionProcessor.withFactCollection { facts -> + val toolbar = BrowserToolbar(testContext) + toolbar.edit.views.url.onAttachedToWindow() + + assertEquals(0, facts.size) + + toolbar.edit.views.url.setText("https://www.mozilla.org") + toolbar.edit.views.url.dispatchKeyEvent( + KeyEvent( + System.currentTimeMillis(), + System.currentTimeMillis(), + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_ENTER, + 0, + ), + ) + + assertEquals(2, facts.size) + + val factDetail = facts[0] + assertEquals(Component.UI_AUTOCOMPLETE, factDetail.component) + assertEquals(Action.IMPLEMENTATION_DETAIL, factDetail.action) + assertEquals("onTextChanged", factDetail.item) + assertEquals("InlineAutocompleteEditText", factDetail.value) + + val fact = facts[1] + assertEquals(Component.BROWSER_TOOLBAR, fact.component) + assertEquals(Action.COMMIT, fact.action) + assertEquals("toolbar", fact.item) + assertNull(fact.value) + + val metadata = fact.metadata + assertNotNull(metadata!!) + assertEquals(1, metadata.size) + assertTrue(metadata.contains("autocomplete")) + assertTrue(metadata["autocomplete"] is Boolean) + assertFalse(metadata["autocomplete"] as Boolean) + } + } + + @Test + fun `entering text emits facts with autocomplete metadata`() { + CollectionProcessor.withFactCollection { facts -> + val toolbar = BrowserToolbar(testContext) + toolbar.edit.views.url.onAttachedToWindow() + + assertEquals(0, facts.size) + + toolbar.edit.views.url.setText("https://www.mozilla.org") + + // Fake autocomplete + toolbar.edit.views.url.autocompleteResult = InlineAutocompleteEditText.AutocompleteResult( + text = "hello world", + source = "test-source", + totalItems = 100, + ) + + toolbar.edit.views.url.dispatchKeyEvent( + KeyEvent( + System.currentTimeMillis(), + System.currentTimeMillis(), + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_ENTER, + 0, + ), + ) + + assertEquals(2, facts.size) + + val factDetail = facts[0] + assertEquals(Component.UI_AUTOCOMPLETE, factDetail.component) + assertEquals(Action.IMPLEMENTATION_DETAIL, factDetail.action) + assertEquals("onTextChanged", factDetail.item) + assertEquals("InlineAutocompleteEditText", factDetail.value) + + val factCommit = facts[1] + assertEquals(Component.BROWSER_TOOLBAR, factCommit.component) + assertEquals(Action.COMMIT, factCommit.action) + assertEquals("toolbar", factCommit.item) + assertNull(factCommit.value) + + val metadata = factCommit.metadata + assertNotNull(metadata!!) + assertEquals(2, metadata.size) + + assertTrue(metadata.contains("autocomplete")) + assertTrue(metadata["autocomplete"] is Boolean) + assertTrue(metadata["autocomplete"] as Boolean) + + assertTrue(metadata.contains("source")) + assertEquals("test-source", metadata["source"]) + } + } + + @Test + fun `clearView gone on init`() { + val (_, editToolbar) = createEditToolbar() + val clearView = editToolbar.views.clear + assertTrue(clearView.visibility == View.GONE) + } + + @Test + fun `clearView visible on updateUrl`() { + val (_, editToolbar) = createEditToolbar() + val clearView = editToolbar.views.clear + + editToolbar.updateUrl("TestUrl", false) + assertTrue(clearView.visibility == View.VISIBLE) + } + + @Test + fun `WHEN shouldAppend is set to true updateUrl should append text`() { + val (_, editToolbar) = createEditToolbar() + + // Initial state + editToolbar.updateUrl(url = "what ") + + // Simulate text update with voice input + val actual = editToolbar.updateUrl(url = "is this", shouldAppend = true, shouldHighlight = true) + val expected = "what is this" + + assertEquals(expected, actual) + assertEquals(expected, editToolbar.views.url.text.toString()) + assertEquals(5, editToolbar.views.url.selectionStart) + assertEquals(12, editToolbar.views.url.selectionEnd) + } + + @Test + fun `setIconClickListener sets a click listener on the icon view`() { + val (_, editToolbar) = createEditToolbar() + val iconView = editToolbar.views.icon + assertFalse(iconView.hasOnClickListeners()) + editToolbar.setIconClickListener { /* noop */ } + assertTrue(iconView.hasOnClickListeners()) + } + + @Test + fun `clearView clears text in urlView`() { + val (_, editToolbar) = createEditToolbar() + val clearView = editToolbar.views.clear + + editToolbar.views.url.setText("https://www.mozilla.org") + assertTrue(editToolbar.views.url.text.isNotBlank()) + + assertNotNull(clearView) + clearView.performClick() + assertTrue(editToolbar.views.url.text.isBlank()) + } + + @Test + fun `editSuggestion sets text in urlView`() { + val (_, editToolbar) = createEditToolbar() + val url = editToolbar.views.url + + url.setText("https://www.mozilla.org") + assertEquals("https://www.mozilla.org", url.text.toString()) + + var callbackCalled = false + + editToolbar.editListener = object : Toolbar.OnEditListener { + override fun onTextChanged(text: String) { + callbackCalled = true + } + } + + editToolbar.editSuggestion("firefox") + + assertEquals("firefox", url.text.toString()) + assertTrue(callbackCalled) + assertEquals("firefox".length, url.selectionStart) + assertTrue(url.hasFocus()) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/internal/ActionContainerTest.kt b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/internal/ActionContainerTest.kt new file mode 100644 index 0000000000..e79c804f9e --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/java/mozilla/components/browser/toolbar2/internal/ActionContainerTest.kt @@ -0,0 +1,99 @@ +/* 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.toolbar2.internal + +import android.view.View +import mozilla.components.browser.toolbar2.BrowserToolbar +import mozilla.components.concept.toolbar.Toolbar +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 +import org.mockito.Mockito.mock +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ActionContainerTest { + private lateinit var actionContainer: ActionContainer + private lateinit var browserAction: Toolbar.Action + + @Before + fun setUp() { + browserAction = BrowserToolbar.Button( + imageDrawable = mock(), + contentDescription = "Test", + visible = { true }, + autoHide = { true }, + weight = { 2 }, + listener = mock(), + ) + actionContainer = ActionContainer(testContext) + } + + @Test + fun `GIVEN multiple actions with different weights WHEN calculateInsertionIndex is called THEN action is placed at right index`() { + actionContainer.addAction( + BrowserToolbar.Button( + imageDrawable = mock(), + contentDescription = "Share", + visible = { true }, + weight = { 1 }, + listener = mock(), + ), + ) + actionContainer.addAction( + BrowserToolbar.Button( + imageDrawable = mock(), + contentDescription = "Reload", + visible = { true }, + weight = { 3 }, + listener = mock(), + ), + ) + val newAction = + BrowserToolbar.Button( + imageDrawable = mock(), + contentDescription = "Translation", + visible = { true }, + weight = { 2 }, + listener = mock(), + ) + + val insertionIndex = actionContainer.calculateInsertionIndex(newAction) + + assertEquals("The insertion index should be", 1, insertionIndex) + } + + @Test + fun `WHEN addAction is called THEN child views are increased`() { + actionContainer.addAction(browserAction) + + assertEquals(1, actionContainer.childCount) + } + + @Test + fun `WHEN removeAction is called THEN child views are decreased`() { + actionContainer.addAction(browserAction) + actionContainer.removeAction(browserAction) + + assertEquals(0, actionContainer.childCount) + } + + @Test + fun `WHEN invalidateAction is called THEN action visibility is reconsidered`() { + val browserToolbarAction = BrowserToolbar.Button( + imageDrawable = mock(), + contentDescription = "Translation", + visible = { false }, + weight = { 2 }, + listener = mock(), + ) + actionContainer.addAction(browserToolbarAction) + actionContainer.invalidateActions() + + assertEquals(View.GONE, actionContainer.visibility) + } +} diff --git a/mobile/android/android-components/components/browser/toolbar2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/mobile/android/android-components/components/browser/toolbar2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..cf1c399ea8 --- /dev/null +++ b/mobile/android/android-components/components/browser/toolbar2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1,2 @@ +mock-maker-inline +// This allows mocking final classes (classes are final by default in Kotlin) diff --git a/mobile/android/android-components/components/compose/cfr/src/main/java/mozilla/components/compose/cfr/CFRPopupLayout.kt b/mobile/android/android-components/components/compose/cfr/src/main/java/mozilla/components/compose/cfr/CFRPopupLayout.kt new file mode 100644 index 0000000000..4585c0303d --- /dev/null +++ b/mobile/android/android-components/components/compose/cfr/src/main/java/mozilla/components/compose/cfr/CFRPopupLayout.kt @@ -0,0 +1,160 @@ +/* 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.compose.cfr + +import android.view.View +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView + +typealias DismissAction = () -> Unit + +/** + * A layout for displaying a [CFRPopup] anchored by [anchorContent]. + * + * @param showCFR Whether to display the CFR. + * @param properties [CFRPopupProperties] allowing to customize the popup appearance and behavior. + * @param onCFRShown Invoked when the CFR is displayed. + * @param onDismiss Invoked when the CFR is dismissed. Returns true if the dismissal was + * explicit (e.g. clicked via the "X" button). + * @param text [Text] block containing the CFR's message. + * @param action Optional Composable displayed below [text]. Provides a [DismissAction] if the CFR needs + * to be dismissed after the action is invoked. + * @param anchorContent The Composable to anchor the CFR to. + */ +@Composable +fun CFRPopupLayout( + showCFR: Boolean, + properties: CFRPopupProperties, + onCFRShown: () -> Unit, + onDismiss: (Boolean) -> Unit, + text: @Composable () -> Unit, + action: @Composable (dismissCFR: DismissAction) -> Unit = {}, + anchorContent: @Composable () -> Unit, +) { + var hasDismissedCFR by rememberSaveable { mutableStateOf(false) } + + Box( + modifier = Modifier.height(intrinsicSize = IntrinsicSize.Min), + ) { + if (showCFR && !hasDismissedCFR) { + LaunchedEffect(Unit) { + onCFRShown() + } + + var popup: CFRPopup? = null + + val invokeDismiss: DismissAction = { + if (!hasDismissedCFR) { + popup?.dismiss() + } + popup = null + } + + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + View(context).also { + popup = CFRPopup( + anchor = it, + properties = properties, + onDismiss = { dismissFromButton -> + onDismiss(dismissFromButton) + hasDismissedCFR = true + }, + text = text, + action = { + action(invokeDismiss) + }, + ) + } + }, + onRelease = { + invokeDismiss() + }, + update = { + popup?.dismiss() + popup?.show() + }, + ) + } + + anchorContent() + } +} + +/** + * This is to validate the sizing of the underlying AndroidView. The current implementation of CFRs + * via [CFRPopupFullscreenLayout] do not render in previews. + */ +@Preview +@Composable +private fun CFRPopupLayoutPreview() { + var cfrVisible by remember { mutableStateOf(true) } + + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.LightGray), + ) { + CFRPopupLayout( + showCFR = cfrVisible, + properties = CFRPopupProperties(), + onCFRShown = {}, + onDismiss = { cfrVisible = false }, + text = { + Text(text = "This is a CFR in Compose") + }, + action = { + TextButton(onClick = { cfrVisible = false }) { + Text(text = "Dismiss") + } + }, + ) { + Box(modifier = Modifier.size(300.dp)) { + Text( + text = "This is the thing the CFR is anchored to", + modifier = Modifier.align(alignment = Alignment.Center), + ) + } + } + + Spacer(modifier = Modifier.height(60.dp)) + + Box(modifier = Modifier.size(100.dp)) { + Text( + text = "This is just another element", + modifier = Modifier.align(alignment = Alignment.Center), + ) + } + + Spacer(modifier = Modifier.height(60.dp)) + + Button(onClick = { cfrVisible = true }) { + Text(text = "Show CFR") + } + } +} diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationEngineState.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationEngineState.kt index 1885c650a4..faa1513ec9 100644 --- a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationEngineState.kt +++ b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationEngineState.kt @@ -10,6 +10,7 @@ package mozilla.components.concept.engine.translate * @property detectedLanguages Detected information about preferences and page information. * @property error If an error state occurred or an error was reported. * @property isEngineReady If the translation engine is primed for use or will need to be loaded. +* @property hasVisibleChange If the browser has visibly started showing the translation. * @property requestedTranslationPair The language pair to translate. Usually populated after first request. */ @@ -17,6 +18,7 @@ data class TranslationEngineState( val detectedLanguages: DetectedLanguages? = null, val error: String? = null, val isEngineReady: Boolean? = false, + val hasVisibleChange: Boolean? = false, val requestedTranslationPair: TranslationPair? = null, ) diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationOperation.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationOperation.kt index 0f9b62029f..28dfd8b80c 100644 --- a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationOperation.kt +++ b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationOperation.kt @@ -35,6 +35,13 @@ enum class TranslationOperation { */ FETCH_PAGE_SETTINGS, + /** + * Fetch the translations offer setting. + * Note: this request is also encompassed in [FETCH_PAGE_SETTINGS], but intended for checking + * fetching for global settings or when only this setting is needed. + */ + FETCH_OFFER_SETTING, + /** * Fetch the user preference on whether to offer, always translate, or never translate for * all supported language settings. diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtensionDelegate.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtensionDelegate.kt index fce18e3863..a2e8b18699 100644 --- a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtensionDelegate.kt +++ b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtensionDelegate.kt @@ -48,6 +48,13 @@ interface WebExtensionDelegate { */ fun onReady(extension: WebExtension) = Unit + /** + * Invoked when optional permissions for a web extension have changed. + * + * @param extension The [WebExtension] for which permissions have changed. + */ + fun onOptionalPermissionsChanged(extension: WebExtension) = Unit + /** * Invoked when a web extension in private browsing allowed is set. * diff --git a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/example_mdn.json b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/example_mdn.json index d08b78f9b7..4e6b1d6a98 100644 --- a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/example_mdn.json +++ b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/example_mdn.json @@ -5,33 +5,42 @@ "display": "standalone", "background_color": "#ffffff", "description": "A simply readable Hacker News app.", - "icons": [{ - "src": "images/touch/homescreen48.png", - "sizes": "48x48", - "type": "image/png" - }, { - "src": "images/touch/homescreen72.png", - "sizes": "72x72", - "type": "image/png" - }, { - "src": "images/touch/homescreen96.png", - "sizes": "96x96", - "type": "image/png" - }, { - "src": "images/touch/homescreen144.png", - "sizes": "144x144", - "type": "image/png" - }, { - "src": "images/touch/homescreen168.png", - "sizes": "168x168", - "type": "image/png" - }, { - "src": "images/touch/homescreen192.png", - "sizes": "192x192", - "type": "image/png" - }], - "related_applications": [{ - "platform": "play", - "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb" - }] + "icons": [ + { + "src": "images/touch/homescreen48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "images/touch/homescreen72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "images/touch/homescreen96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "images/touch/homescreen144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "images/touch/homescreen168.png", + "sizes": "168x168", + "type": "image/png" + }, + { + "src": "images/touch/homescreen192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "related_applications": [ + { + "platform": "play", + "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb" + } + ] } diff --git a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/spec_typical.json b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/spec_typical.json index 3f180353eb..82aeb2c95f 100644 --- a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/spec_typical.json +++ b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/spec_typical.json @@ -4,17 +4,21 @@ "name": "Super Racer 3000", "description": "The ultimate futuristic racing game from the future!", "short_name": "Racer3K", - "icons": [{ - "src": "icon/lowres.webp", - "sizes": "64x64", - "type": "image/webp" - },{ - "src": "icon/lowres.png", - "sizes": "64x64" - }, { - "src": "icon/hd_hi", - "sizes": "128x128" - }], + "icons": [ + { + "src": "icon/lowres.webp", + "sizes": "64x64", + "type": "image/webp" + }, + { + "src": "icon/lowres.png", + "sizes": "64x64" + }, + { + "src": "icon/hd_hi", + "sizes": "128x128" + } + ], "scope": "/racer/", "start_url": "/racer/start.html", "display": "fullscreen", @@ -26,26 +30,34 @@ "scope": "/racer/", "update_via_cache": "none" }, - "screenshots": [{ - "src": "screenshots/in-game-1x.jpg", - "sizes": "640x480", - "type": "image/jpeg" - },{ - "src": "screenshots/in-game-2x.jpg", - "sizes": "1280x920", - "type": "image/jpeg" - }], - "related_applications": [{ - "platform": "play", - "url": "https://play.google.com/store/apps/details?id=com.example.app1", - "id": "com.example.app1", - "min_version": "2", - "fingerprints": [{ - "type": "sha256_cert", - "value": "92:5A:39:05:C5:B9:EA:BC:71:48:5F:F2" - }] - }, { - "platform": "itunes", - "url": "https://itunes.apple.com/app/example-app1/id123456789" - }] + "screenshots": [ + { + "src": "screenshots/in-game-1x.jpg", + "sizes": "640x480", + "type": "image/jpeg" + }, + { + "src": "screenshots/in-game-2x.jpg", + "sizes": "1280x920", + "type": "image/jpeg" + } + ], + "related_applications": [ + { + "platform": "play", + "url": "https://play.google.com/store/apps/details?id=com.example.app1", + "id": "com.example.app1", + "min_version": "2", + "fingerprints": [ + { + "type": "sha256_cert", + "value": "92:5A:39:05:C5:B9:EA:BC:71:48:5F:F2" + } + ] + }, + { + "platform": "itunes", + "url": "https://itunes.apple.com/app/example-app1/id123456789" + } + ] } diff --git a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/twitter_mobile.json b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/twitter_mobile.json index 142ce0317e..2f661ffc34 100644 --- a/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/twitter_mobile.json +++ b/mobile/android/android-components/components/concept/engine/src/test/resources/manifests/twitter_mobile.json @@ -1 +1,28 @@ -{"background_color":"#ffffff","description":"It's what's happening. From breaking news and entertainment, sports and politics, to big events and everyday interests.","display":"standalone","gcm_sender_id":"49625052041","gcm_user_visible_only":true,"icons":[{"src":"https://abs.twimg.com/responsive-web/web/icon-default.604e2486a34a2f6e1.png","sizes":"192x192","type":"image/png"},{"src":"https://abs.twimg.com/responsive-web/web/icon-default.604e2486a34a2f6e1.png","sizes":"512x512","type":"image/png"}],"name":"Twitter","share_target":{"action":"compose/tweet","params":{"title":"title","text":"text","url":"url"}},"short_name":"Twitter","start_url":"/","theme_color":"#ffffff","scope":"/"} +{ + "background_color": "#ffffff", + "description": "It's what's happening. From breaking news and entertainment, sports and politics, to big events and everyday interests.", + "display": "standalone", + "gcm_sender_id": "49625052041", + "gcm_user_visible_only": true, + "icons": [ + { + "src": "https://abs.twimg.com/responsive-web/web/icon-default.604e2486a34a2f6e1.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "https://abs.twimg.com/responsive-web/web/icon-default.604e2486a34a2f6e1.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "name": "Twitter", + "share_target": { + "action": "compose/tweet", + "params": { "title": "title", "text": "text", "url": "url" } + }, + "short_name": "Twitter", + "start_url": "/", + "theme_color": "#ffffff", + "scope": "/" +} diff --git a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt index fe46cc5b90..e7c0b1650e 100644 --- a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt +++ b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt @@ -46,6 +46,9 @@ sealed class AccountEvent { sealed class DeviceCommandIncoming { /** A command to open a list of tabs on the current device */ class TabReceived(val from: Device?, val entries: List) : DeviceCommandIncoming() + + /** A command to close one or more tabs that are open on the current device */ + class TabsClosed(val from: Device?, val urls: List) : DeviceCommandIncoming() } /** @@ -54,6 +57,9 @@ sealed class DeviceCommandIncoming { sealed class DeviceCommandOutgoing { /** A command to open a tab on another device */ class SendTab(val title: String, val url: String) : DeviceCommandOutgoing() + + /** A command to close one or more tabs that are open on another device */ + class CloseTab(val urls: List) : DeviceCommandOutgoing() } /** diff --git a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/Devices.kt b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/Devices.kt index 94b022ce20..876ab86eb6 100644 --- a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/Devices.kt +++ b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/Devices.kt @@ -158,6 +158,7 @@ data class DeviceConfig( */ enum class DeviceCapability { SEND_TAB, + CLOSE_TABS, } /** diff --git a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/OAuthAccount.kt b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/OAuthAccount.kt index 7737d4bc36..7ef087c86e 100644 --- a/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/OAuthAccount.kt +++ b/mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/OAuthAccount.kt @@ -39,6 +39,17 @@ data class MigratingAccountInfo( val kXCS: String, ) +/** + * User data provided by the web content as a means of delivering the session token to the + * application + */ +data class UserData( + val sessionToken: String, + val email: String, + val uid: String, + val verified: Boolean, +) + /** * Representing all the possible entry points into FxA * @@ -110,6 +121,14 @@ interface OAuthAccount : AutoCloseable { */ suspend fun getProfile(ignoreCache: Boolean = false): Profile? + /** + * Sets the user data given by the web content finishing the OAuth flow. + * This should only be used by user agents that need the session token + * + * @param userData: The user data provided by the web content, including the session token + */ + suspend fun setUserData(userData: UserData) + /** * Authenticates the current account using the [code] and [state] parameters obtained via the * OAuth flow initiated by [beginOAuthFlow]. diff --git a/mobile/android/android-components/components/feature/accounts-push/build.gradle b/mobile/android/android-components/components/feature/accounts-push/build.gradle index c87fa7582a..17bc5d8313 100644 --- a/mobile/android/android-components/components/feature/accounts-push/build.gradle +++ b/mobile/android/android-components/components/feature/accounts-push/build.gradle @@ -37,6 +37,7 @@ tasks.withType(KotlinCompile).configureEach { } dependencies { + implementation project(':browser-state') implementation project(':service-firefox-accounts') implementation project(':support-ktx') implementation project(':support-base') @@ -47,7 +48,9 @@ dependencies { implementation ComponentsDependencies.androidx_lifecycle_process implementation ComponentsDependencies.kotlin_coroutines + testImplementation project(':concept-engine') testImplementation project(':support-test') + testImplementation project(':support-test-libstate') testImplementation ComponentsDependencies.androidx_test_core testImplementation ComponentsDependencies.androidx_test_junit diff --git a/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsFeature.kt b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsFeature.kt new file mode 100644 index 0000000000..8767064867 --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsFeature.kt @@ -0,0 +1,93 @@ +/* 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.feature.accounts.push + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import mozilla.components.browser.state.action.TabListAction +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.sync.AccountEvent +import mozilla.components.concept.sync.AccountEventsObserver +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCommandIncoming +import mozilla.components.concept.sync.DeviceConstellation +import mozilla.components.service.fxa.manager.FxaAccountManager + +/** + * A feature for closing tabs on this device from other devices + * in the [DeviceConstellation]. + * + * This feature receives commands to close tabs using the [FxaAccountManager]. + * + * See [CloseTabsUseCases] for the ability to close tabs that are open on + * other devices from this device. + * + * @param browserStore The [BrowserStore] that holds the currently open tabs. + * @param accountManager The account manager. + * @param owner The Android lifecycle owner for the observers. Defaults to + * the [ProcessLifecycleOwner]. + * @param autoPause Whether or not the observer should automatically be + * paused/resumed with the bound lifecycle. + * @param onTabsClosed The callback invoked when one or more tabs are closed. + */ +class CloseTabsFeature( + private val browserStore: BrowserStore, + private val accountManager: FxaAccountManager, + private val owner: LifecycleOwner = ProcessLifecycleOwner.get(), + private val autoPause: Boolean = false, + onTabsClosed: (Device?, List) -> Unit, +) { + @VisibleForTesting internal val observer = TabsClosedEventsObserver { device, urls -> + val tabsToRemove = getTabsToRemove(urls) + if (tabsToRemove.isNotEmpty()) { + browserStore.dispatch(TabListAction.RemoveTabsAction(tabsToRemove.map { it.id })) + onTabsClosed(device, tabsToRemove.map { it.content.url }) + } + } + + /** + * Begins observing the [accountManager] for "tabs closed" events. + */ + fun observe() { + accountManager.registerForAccountEvents(observer, owner, autoPause) + } + + private fun getTabsToRemove(remotelyClosedUrls: List): List { + // The user might have the same URL open in multiple tabs on this device, and might want + // to remotely close some or all of those tabs. Synced tabs don't carry enough + // information to know which duplicates the user meant to close, so we use a heuristic: + // if a URL appears N times in the remotely closed URLs list, we'll close up to + // N instances of that URL. + val countsByUrl = remotelyClosedUrls.groupingBy { it }.eachCount() + return browserStore.state.tabs + .groupBy { it.content.url } + .asSequence() + .mapNotNull { (url, tabs) -> + countsByUrl[url]?.let { count -> tabs.take(count) } + } + .flatten() + .toList() + } +} + +internal class TabsClosedEventsObserver( + internal val onTabsClosed: (Device?, List) -> Unit, +) : AccountEventsObserver { + override fun onEvents(events: List) { + // Group multiple commands from the same device, so that we can close + // more tabs at once. + events.asSequence() + .filterIsInstance() + .map { it.command } + .filterIsInstance() + .groupingBy { it.from } + .fold(emptyList()) { urls, command -> urls + command.urls } + .forEach { (device, urls) -> + onTabsClosed(device, urls) + } + } +} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsUseCases.kt b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsUseCases.kt new file mode 100644 index 0000000000..845ae27a98 --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/CloseTabsUseCases.kt @@ -0,0 +1,63 @@ +/* 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.feature.accounts.push + +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceCommandOutgoing +import mozilla.components.concept.sync.DeviceConstellation +import mozilla.components.service.fxa.manager.FxaAccountManager + +/** + * Use cases for closing tabs that are open on other devices in the [DeviceConstellation]. + * + * The use cases send commands to close tabs using the [FxaAccountManager]. + * + * See [CloseTabsFeature] for the ability to close tabs on this device from + * other devices. + * + * @param accountManager The account manager. + */ +class CloseTabsUseCases(private val accountManager: FxaAccountManager) { + /** + * Closes a tab that's currently open on another device. + * + * @param deviceId The ID of the device on which the tab is currently open. + * @param url The URL of the tab to close. + * @return Whether the command to close the tab was sent to the device. + */ + @WorkerThread + suspend fun close(deviceId: String, url: String): Boolean { + filterCloseTabsDevices(accountManager) { constellation, devices -> + val device = devices.firstOrNull { it.id == deviceId } + device?.let { + return constellation.sendCommandToDevice( + device.id, + DeviceCommandOutgoing.CloseTab(listOf(url)), + ) + } + } + + return false + } +} + +@VisibleForTesting +internal inline fun filterCloseTabsDevices( + accountManager: FxaAccountManager, + block: (DeviceConstellation, Collection) -> Unit, +) { + val constellation = accountManager.authenticatedAccount()?.deviceConstellation() ?: return + + constellation.state()?.let { state -> + state.otherDevices.filter { + it.capabilities.contains(DeviceCapability.CLOSE_TABS) + }.let { devices -> + block(constellation, devices) + } + } +} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/SendTabFeature.kt b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/SendTabFeature.kt index 4f049a790b..931dcf59a6 100644 --- a/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/SendTabFeature.kt +++ b/mobile/android/android-components/components/feature/accounts-push/src/main/java/mozilla/components/feature/accounts/push/SendTabFeature.kt @@ -24,7 +24,7 @@ import mozilla.components.support.base.log.logger.Logger * * See [SendTabUseCases] for the ability to send tabs to other devices. * - * @param accountManager Firefox account manager. + * @param accountManager Account manager. * @param owner Android lifecycle owner for the observers. Defaults to the [ProcessLifecycleOwner] * so that we can always observe events throughout the application lifecycle. * @param autoPause whether or not the observer should automatically be @@ -38,7 +38,7 @@ class SendTabFeature( onTabsReceived: (Device?, List) -> Unit, ) { init { - val observer = EventsObserver(onTabsReceived) + val observer = TabReceivedEventsObserver(onTabsReceived) // Observe the account for all account events, although we'll ignore // non send-tab command events. @@ -46,10 +46,10 @@ class SendTabFeature( } } -internal class EventsObserver( +internal class TabReceivedEventsObserver( private val onTabsReceived: (Device?, List) -> Unit, ) : AccountEventsObserver { - private val logger = Logger("EventsObserver") + private val logger = Logger("TabReceivedEventsObserver") override fun onEvents(events: List) { events.asSequence() diff --git a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsFeatureTest.kt b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsFeatureTest.kt new file mode 100644 index 0000000000..7b18681dce --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsFeatureTest.kt @@ -0,0 +1,136 @@ +/* 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.feature.accounts.push + +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceType +import mozilla.components.support.test.any +import mozilla.components.support.test.eq +import mozilla.components.support.test.libstate.ext.waitUntilIdle +import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +class CloseTabsFeatureTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + private val device123 = Device( + id = "123", + displayName = "Charcoal", + deviceType = DeviceType.DESKTOP, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = listOf(DeviceCapability.CLOSE_TABS), + subscriptionExpired = true, + subscription = null, + ) + + @Test + fun `GIVEN a notification to close multiple URLs WHEN all URLs are open in tabs THEN all tabs are closed and the callback is invoked`() { + val urls = listOf( + "https://mozilla.org", + "https://getfirefox.com", + "https://example.org", + "https://getthunderbird.com", + ) + val browserStore = BrowserStore( + BrowserState( + tabs = urls.map { createTab(it) }, + ), + ) + val callback: (Device?, List) -> Unit = mock() + val feature = CloseTabsFeature( + browserStore, + accountManager = mock(), + owner = mock(), + onTabsClosed = callback, + ) + + feature.observer.onTabsClosed(device123, urls) + + browserStore.waitUntilIdle() + + assertTrue(browserStore.state.tabs.isEmpty()) + verify(callback).invoke(eq(device123), eq(urls)) + } + + @Test + fun `GIVEN a notification to close a URL WHEN the URL is not open in a tab THEN the callback is not invoked`() { + val browserStore = BrowserStore() + val callback: (Device?, List) -> Unit = mock() + val feature = CloseTabsFeature( + browserStore, + accountManager = mock(), + owner = mock(), + onTabsClosed = callback, + ) + + feature.observer.onTabsClosed(device123, listOf("https://mozilla.org")) + + browserStore.waitUntilIdle() + + verify(callback, never()).invoke(any(), any()) + } + + @Test + fun `GIVEN a notification to close duplicate URLs WHEN the duplicate URLs are open in tabs THEN the number of tabs closed matches the number of URLs and the callback is invoked`() { + val browserStore = BrowserStore( + BrowserState( + tabs = listOf( + createTab("https://mozilla.org", id = "1"), + createTab("https://mozilla.org", id = "2"), + createTab("https://getfirefox.com", id = "3"), + createTab("https://getfirefox.com", id = "4"), + createTab("https://getfirefox.com", id = "5"), + createTab("https://getthunderbird.com", id = "6"), + createTab("https://example.org", id = "7"), + ), + ), + ) + val callback: (Device?, List) -> Unit = mock() + val feature = CloseTabsFeature( + browserStore, + accountManager = mock(), + owner = mock(), + onTabsClosed = callback, + ) + + feature.observer.onTabsClosed( + device123, + listOf( + "https://mozilla.org", + "https://getfirefox.com", + "https://getfirefox.com", + "https://example.org", + "https://example.org", + ), + ) + + browserStore.waitUntilIdle() + + assertEquals(listOf("2", "5", "6"), browserStore.state.tabs.map { it.id }) + verify(callback).invoke( + eq(device123), + eq( + listOf( + "https://mozilla.org", + "https://getfirefox.com", + "https://getfirefox.com", + "https://example.org", + ), + ), + ) + } +} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsUseCasesTest.kt b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsUseCasesTest.kt new file mode 100644 index 0000000000..4aae8c84f6 --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/CloseTabsUseCasesTest.kt @@ -0,0 +1,100 @@ +/* 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.feature.accounts.push + +import mozilla.components.concept.sync.ConstellationState +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceConstellation +import mozilla.components.concept.sync.DeviceType +import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.service.fxa.manager.FxaAccountManager +import mozilla.components.support.test.any +import mozilla.components.support.test.mock +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +class CloseTabsUseCasesTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + + private val device123 = Device( + id = "123", + displayName = "Charcoal", + deviceType = DeviceType.DESKTOP, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = listOf(DeviceCapability.CLOSE_TABS), + subscriptionExpired = true, + subscription = null, + ) + + private val device1234 = Device( + id = "1234", + displayName = "Ruby", + deviceType = DeviceType.DESKTOP, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = emptyList(), + subscriptionExpired = true, + subscription = null, + ) + + private val manager: FxaAccountManager = mock() + private val account: OAuthAccount = mock() + private val constellation: DeviceConstellation = mock() + private val state: ConstellationState = mock() + + @Before + fun setUp() { + `when`(manager.authenticatedAccount()).thenReturn(account) + `when`(account.deviceConstellation()).thenReturn(constellation) + `when`(constellation.state()).thenReturn(state) + } + + @Test + fun `GIVEN a list of devices WHEN one device supports the close tabs command THEN filtering returns that device`() { + val deviceIds = mutableListOf() + `when`(state.otherDevices).thenReturn(listOf(device123, device1234)) + filterCloseTabsDevices(manager) { _, devices -> + deviceIds.addAll(devices.map { it.id }) + } + + assertEquals(listOf("123"), deviceIds) + } + + @Test + fun `GIVEN a constellation with one capable device WHEN sending a close tabs command to that device THEN the command is sent`() = runTestOnMain { + val useCases = CloseTabsUseCases(manager) + + `when`(state.otherDevices).thenReturn(listOf(device123)) + `when`(constellation.sendCommandToDevice(any(), any())) + .thenReturn(true) + + useCases.close("123", "http://example.com") + + verify(constellation).sendCommandToDevice(any(), any()) + } + + @Test + fun `GIVEN a constellation with one incapable device WHEN sending a close tabs command to that device THEN the command is not sent`() = runTestOnMain { + val useCases = CloseTabsUseCases(manager) + + `when`(state.otherDevices).thenReturn(listOf(device1234)) + `when`(constellation.sendCommandToDevice(any(), any())) + .thenReturn(false) + + useCases.close("1234", "http://example.com") + + verify(constellation, never()).sendCommandToDevice(any(), any()) + } +} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/EventsObserverTest.kt b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/EventsObserverTest.kt deleted file mode 100644 index 6de8ff42f6..0000000000 --- a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/EventsObserverTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* 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.feature.accounts.push - -import mozilla.components.concept.sync.AccountEvent -import mozilla.components.concept.sync.Device -import mozilla.components.concept.sync.DeviceCommandIncoming -import mozilla.components.concept.sync.TabData -import mozilla.components.support.test.any -import mozilla.components.support.test.eq -import mozilla.components.support.test.mock -import org.junit.Test -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -class EventsObserverTest { - @Test - fun `events are delivered successfully`() { - val callback: (Device?, List) -> Unit = mock() - val observer = EventsObserver(callback) - val events = listOf(AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock()))) - - observer.onEvents(events) - - verify(callback).invoke(any(), any()) - - observer.onEvents(listOf(AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(null, mock())))) - - verify(callback).invoke(eq(null), any()) - } - - @Test - fun `only TabReceived commands are delivered`() { - val callback: (Device?, List) -> Unit = mock() - val observer = EventsObserver(callback) - val events = listOf( - AccountEvent.ProfileUpdated, - AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock())), - AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock())), - ) - - observer.onEvents(events) - - verify(callback, times(2)).invoke(any(), any()) - } -} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabReceivedEventsObserverTest.kt b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabReceivedEventsObserverTest.kt new file mode 100644 index 0000000000..1b8128d4ba --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabReceivedEventsObserverTest.kt @@ -0,0 +1,48 @@ +/* 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.feature.accounts.push + +import mozilla.components.concept.sync.AccountEvent +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCommandIncoming +import mozilla.components.concept.sync.TabData +import mozilla.components.support.test.any +import mozilla.components.support.test.eq +import mozilla.components.support.test.mock +import org.junit.Test +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +class TabReceivedEventsObserverTest { + @Test + fun `events are delivered successfully`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabReceivedEventsObserver(callback) + val events = listOf(AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock()))) + + observer.onEvents(events) + + verify(callback).invoke(any(), any()) + + observer.onEvents(listOf(AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(null, mock())))) + + verify(callback).invoke(eq(null), any()) + } + + @Test + fun `only TabReceived commands are delivered`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabReceivedEventsObserver(callback) + val events = listOf( + AccountEvent.ProfileUpdated, + AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock())), + AccountEvent.DeviceCommandIncoming(command = DeviceCommandIncoming.TabReceived(mock(), mock())), + ) + + observer.onEvents(events) + + verify(callback, times(2)).invoke(any(), any()) + } +} diff --git a/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabsClosedEventsObserverTest.kt b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabsClosedEventsObserverTest.kt new file mode 100644 index 0000000000..8d51d0b812 --- /dev/null +++ b/mobile/android/android-components/components/feature/accounts-push/src/test/java/mozilla/components/feature/accounts/push/TabsClosedEventsObserverTest.kt @@ -0,0 +1,183 @@ +/* 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.feature.accounts.push + +import mozilla.components.concept.sync.AccountEvent +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceCommandIncoming +import mozilla.components.concept.sync.DeviceType +import mozilla.components.support.test.any +import mozilla.components.support.test.eq +import mozilla.components.support.test.mock +import org.junit.Test +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +class TabsClosedEventsObserverTest { + private val device123 = Device( + id = "123", + displayName = "Charcoal", + deviceType = DeviceType.DESKTOP, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = listOf(DeviceCapability.CLOSE_TABS), + subscriptionExpired = true, + subscription = null, + ) + + private val device1234 = Device( + id = "1234", + displayName = "Emerald", + deviceType = DeviceType.MOBILE, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = listOf(DeviceCapability.CLOSE_TABS), + subscriptionExpired = true, + subscription = null, + ) + + private val device12345 = Device( + id = "12345", + displayName = "Sapphire", + deviceType = DeviceType.MOBILE, + isCurrentDevice = false, + lastAccessTime = null, + capabilities = listOf(DeviceCapability.CLOSE_TABS), + subscriptionExpired = true, + subscription = null, + ) + + @Test + fun `GIVEN a tabs closed command WHEN the observer is notified THEN the callback is invoked`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabsClosedEventsObserver(callback) + val events = listOf( + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + null, + listOf("https://mozilla.org"), + ), + ), + ) + + observer.onEvents(events) + + verify(callback).invoke(eq(null), eq(listOf("https://mozilla.org"))) + } + + @Test + fun `GIVEN a tabs closed command from a device WHEN the observer is notified THEN the callback is invoked`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabsClosedEventsObserver(callback) + val events = listOf( + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://mozilla.org"), + ), + ), + ) + + observer.onEvents(events) + + verify(callback).invoke(eq(device123), eq(listOf("https://mozilla.org"))) + } + + @Test + fun `GIVEN multiple commands WHEN the observer is notified THEN the callback is only invoked for the tabs closed commands`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabsClosedEventsObserver(callback) + val events = listOf( + AccountEvent.ProfileUpdated, + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://mozilla.org"), + ), + ), + ) + + observer.onEvents(events) + + verify(callback, times(1)).invoke(eq(device123), eq(listOf("https://mozilla.org"))) + } + + @Test + fun `GIVEN multiple tabs closed commands from the same device WHEN the observer is notified THEN the callback is invoked once`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabsClosedEventsObserver(callback) + val events = listOf( + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://mozilla.org", "https://getfirefox.com"), + ), + ), + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://example.org"), + ), + ), + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://getthunderbird.com"), + ), + ), + ) + + observer.onEvents(events) + + verify(callback, times(1)).invoke( + eq(device123), + eq( + listOf( + "https://mozilla.org", + "https://getfirefox.com", + "https://example.org", + "https://getthunderbird.com", + ), + ), + ) + } + + @Test + fun `GIVEN multiple tabs closed commands from different devices WHEN the observer is notified THEN the callback is invoked once per device`() { + val callback: (Device?, List) -> Unit = mock() + val observer = TabsClosedEventsObserver(callback) + val events = listOf( + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + null, + listOf("https://mozilla.org"), + ), + ), + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device123, + listOf("https://mozilla.org"), + ), + ), + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device1234, + listOf("https://mozilla.org"), + ), + ), + AccountEvent.DeviceCommandIncoming( + command = DeviceCommandIncoming.TabsClosed( + device12345, + listOf("https://mozilla.org"), + ), + ), + ) + + observer.onEvents(events) + + verify(callback, times(4)).invoke(any(), eq(listOf("https://mozilla.org"))) + } +} diff --git a/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/background.js b/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/background.js index b90f57154a..e8cf40ba8d 100644 --- a/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/background.js +++ b/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/background.js @@ -10,12 +10,12 @@ let port = browser.runtime.connectNative(WEB_CHANNEL_BACKGROUND_MESSAGING_ID); /* Handle messages from native application, register content script for specific url. */ -port.onMessage.addListener( event => { - if(event.type == "overrideFxAServer"){ +port.onMessage.addListener(event => { + if (event.type == "overrideFxAServer") { browser.contentScripts.register({ - "matches": [ event.url+"/*" ], - "js": [{file: "fxawebchannel.js"}], - "runAt": "document_start" + matches: [event.url + "/*"], + js: [{ file: "fxawebchannel.js" }], + runAt: "document_start", }); port.disconnect(); } diff --git a/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/fxawebchannel.js b/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/fxawebchannel.js index 2f5934dff1..16614d3069 100644 --- a/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/fxawebchannel.js +++ b/mobile/android/android-components/components/feature/accounts/src/main/assets/extensions/fxawebchannel/fxawebchannel.js @@ -10,16 +10,18 @@ let port = browser.runtime.connectNative("mozacWebchannel"); /* Handle messages from native application, dispatch them to FxA via an event. */ -port.onMessage.addListener((event) => { - window.dispatchEvent(new CustomEvent('WebChannelMessageToContent', { - detail: JSON.stringify(event) - })); +port.onMessage.addListener(event => { + window.dispatchEvent( + new CustomEvent("WebChannelMessageToContent", { + detail: JSON.stringify(event), + }) + ); }); /* Handle messages from FxA. Messages are posted to the native application for processing. */ -window.addEventListener('WebChannelMessageToChrome', function (e) { +window.addEventListener("WebChannelMessageToChrome", function (e) { const detail = JSON.parse(e.detail); port.postMessage(detail); }); diff --git a/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeature.kt b/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeature.kt index 60913282b7..b244eb0de8 100644 --- a/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeature.kt +++ b/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeature.kt @@ -38,10 +38,15 @@ class FirefoxAccountsAuthFeature( * @param context [Context] The application context * @param entrypoint [FxAEntryPoint] The Firefox Accounts feature/entrypoint that is launching * authentication + * @param scopes [Set] The oAuth scopes being requested */ - fun beginAuthentication(context: Context, entrypoint: FxAEntryPoint) { + fun beginAuthentication( + context: Context, + entrypoint: FxAEntryPoint, + scopes: Set = emptySet(), + ) { beginAuthenticationAsync(context) { - accountManager.beginAuthentication(entrypoint = entrypoint) + accountManager.beginAuthentication(entrypoint = entrypoint, authScopes = scopes) } } @@ -50,15 +55,17 @@ class FirefoxAccountsAuthFeature( * @param context [Context] The application context * @param pairingUrl [String] The pairing URL retrieved from the QR scanner * @param entrypoint [FxAEntryPoint] The Firefox Accounts feature/entrypoint that is launching + * @param scopes [Set] The oAuth scopes being requested * authentication */ fun beginPairingAuthentication( context: Context, pairingUrl: String, entrypoint: FxAEntryPoint, + scopes: Set = emptySet(), ) { beginAuthenticationAsync(context) { - accountManager.beginAuthentication(pairingUrl, entrypoint = entrypoint) + accountManager.beginAuthentication(pairingUrl, entrypoint = entrypoint, scopes) } } diff --git a/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FxaWebChannelFeature.kt b/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FxaWebChannelFeature.kt index f5554c99e4..377d6b0d93 100644 --- a/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FxaWebChannelFeature.kt +++ b/mobile/android/android-components/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FxaWebChannelFeature.kt @@ -20,6 +20,7 @@ import mozilla.components.concept.engine.webextension.MessageHandler import mozilla.components.concept.engine.webextension.Port import mozilla.components.concept.engine.webextension.WebExtensionRuntime import mozilla.components.concept.sync.AuthType +import mozilla.components.concept.sync.UserData import mozilla.components.lib.state.ext.flowScoped import mozilla.components.service.fxa.FxaAuthData import mozilla.components.service.fxa.ServerConfig @@ -157,6 +158,7 @@ class FxaWebChannelFeature( WebChannelCommand.CAN_LINK_ACCOUNT -> processCanLinkAccountCommand(messageId) WebChannelCommand.FXA_STATUS -> processFxaStatusCommand(accountManager, messageId, fxaCapabilities) WebChannelCommand.OAUTH_LOGIN -> processOauthLoginCommand(accountManager, payload) + WebChannelCommand.LOGIN -> processLoginCommand(accountManager, payload) } response?.let { port.postMessage(it) } } @@ -195,6 +197,7 @@ class FxaWebChannelFeature( enum class WebChannelCommand { CAN_LINK_ACCOUNT, + LOGIN, OAUTH_LOGIN, FXA_STATUS, } @@ -221,6 +224,12 @@ class FxaWebChannelFeature( */ private const val COMMAND_STATUS = "fxaccounts:fxa_status" + /** + * Gets triggered when the web content is signed in/up, but not necessarily verified + * it passes in its payload the session token the web content is holding on to + */ + private const val COMMAND_LOGIN = "fxaccounts:login" + /** * Handles the [COMMAND_CAN_LINK_ACCOUNT] event from the web-channel. * Currently this always response with 'ok=true'. @@ -328,6 +337,32 @@ class FxaWebChannelFeature( return result } + /** + * Handles the [COMMAND_LOGIN] event from the web-channel + */ + private fun processLoginCommand(accountManager: FxaAccountManager, payload: JSONObject): JSONObject? { + val sessionToken: String + val email: String + val uid: String + val verified: Boolean + + try { + val data = payload.getJSONObject("data") + sessionToken = data.getString("sessionToken") + email = data.getString("email") + uid = data.getString("uid") + verified = data.getBoolean("verified") + } catch (e: JSONException) { + logger.error("Error while processing WebChannel login command", e) + return null + } + val userData = UserData(sessionToken, email, uid, verified) + CoroutineScope(Dispatchers.Main).launch { + accountManager.setUserData(userData) + } + return null + } + /** * Handles the [COMMAND_OAUTH_LOGIN] event from the web-channel. */ @@ -368,6 +403,7 @@ class FxaWebChannelFeature( COMMAND_CAN_LINK_ACCOUNT -> WebChannelCommand.CAN_LINK_ACCOUNT COMMAND_OAUTH_LOGIN -> WebChannelCommand.OAUTH_LOGIN COMMAND_STATUS -> WebChannelCommand.FXA_STATUS + COMMAND_LOGIN -> WebChannelCommand.LOGIN else -> { logger.warn("Unrecognized WebChannel command: $this") null diff --git a/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt b/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt index a32681e8fe..ef7f78b336 100644 --- a/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt +++ b/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FirefoxAccountsAuthFeatureTest.kt @@ -17,8 +17,8 @@ import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.DeviceConfig import mozilla.components.concept.sync.DeviceType import mozilla.components.concept.sync.FxAEntryPoint -import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile +import mozilla.components.service.fxa.FirefoxAccount import mozilla.components.service.fxa.FxaAuthData import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.StorageWrapper @@ -45,13 +45,13 @@ internal class TestableStorageWrapper( manager: FxaAccountManager, accountEventObserverRegistry: ObserverRegistry, serverConfig: ServerConfig, - private val block: () -> OAuthAccount = { - val account: OAuthAccount = mock() + private val block: () -> FirefoxAccount = { + val account: FirefoxAccount = mock() `when`(account.deviceConstellation()).thenReturn(mock()) account }, ) : StorageWrapper(manager, accountEventObserverRegistry, serverConfig) { - override fun obtainAccount(): OAuthAccount = block() + override fun obtainAccount(): FirefoxAccount = block() } // Same as the actual account manager, except we get to control how FirefoxAccountShaped instances @@ -63,7 +63,7 @@ class TestableFxaAccountManager( config: ServerConfig, scopes: Set, coroutineContext: CoroutineContext, - block: () -> OAuthAccount = { mock() }, + block: () -> FirefoxAccount = { mock() }, ) : FxaAccountManager(context, config, DeviceConfig("test", DeviceType.MOBILE, setOf()), null, scopes, null, coroutineContext) { private val testableStorageWrapper = TestableStorageWrapper(this, accountEventObserverRegistry, serverConfig, block) override fun getStorageWrapper(): StorageWrapper { @@ -252,7 +252,7 @@ class FirefoxAccountsAuthFeatureTest { private suspend fun prepareAccountManagerForSuccessfulAuthentication( coroutineContext: CoroutineContext, ): TestableFxaAccountManager { - val mockAccount: OAuthAccount = mock() + val mockAccount: FirefoxAccount = mock() val profile = Profile(uid = "testUID", avatar = null, email = "test@example.com", displayName = "test profile") `when`(mockAccount.deviceConstellation()).thenReturn(mock()) @@ -279,7 +279,7 @@ class FirefoxAccountsAuthFeatureTest { private suspend fun prepareAccountManagerForFailedAuthentication( coroutineContext: CoroutineContext, ): TestableFxaAccountManager { - val mockAccount: OAuthAccount = mock() + val mockAccount: FirefoxAccount = mock() val profile = Profile(uid = "testUID", avatar = null, email = "test@example.com", displayName = "test profile") `when`(mockAccount.getProfile(anyBoolean())).thenReturn(profile) diff --git a/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt b/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt index 809ed7a703..3a49633f61 100644 --- a/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt +++ b/mobile/android/android-components/components/feature/accounts/src/test/java/mozilla/components/feature/accounts/FxaWebChannelFeatureTest.kt @@ -19,6 +19,7 @@ import mozilla.components.concept.engine.webextension.WebExtension import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile +import mozilla.components.concept.sync.UserData import mozilla.components.service.fxa.FxaAuthData import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.SyncEngine @@ -701,6 +702,56 @@ class FxaWebChannelFeatureTest { assertTrue(FxaWebChannelFeature.isCommunicationAllowed("http://localhost", "http://localhost")) } + @Test + fun `COMMAND_LOGIN must be processed and sets the user's data`() = runTest { + val accountManager: FxaAccountManager = mock() // syncConfig is null by default (is not configured) + val engineSession: EngineSession = mock() + val ext: WebExtension = mock() + val port: Port = mock() + val messageHandler = argumentCaptor() + + WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext + + val webchannelFeature = prepareFeatureForTest(ext, port, engineSession, null, emptySet(), accountManager) + webchannelFeature.start() + shadowOf(getMainLooper()).idle() + + verify(ext).registerContentMessageHandler( + eq(engineSession), + eq(FxaWebChannelFeature.WEB_CHANNEL_MESSAGING_ID), + messageHandler.capture(), + ) + messageHandler.value.onPortConnected(port) + + // Action: signin + verifyLogin("sessiontoken123", "foo@bar.com", "uid123", false, messageHandler.value, accountManager) + } + + @Test + fun `COMMAND_LOGIN invalid json sends back`() = runTest { + val accountManager: FxaAccountManager = mock() // syncConfig is null by default (is not configured) + val engineSession: EngineSession = mock() + val ext: WebExtension = mock() + val port: Port = mock() + val messageHandler = argumentCaptor() + + WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext + + val webchannelFeature = prepareFeatureForTest(ext, port, engineSession, null, emptySet(), accountManager) + webchannelFeature.start() + shadowOf(getMainLooper()).idle() + + verify(ext).registerContentMessageHandler( + eq(engineSession), + eq(FxaWebChannelFeature.WEB_CHANNEL_MESSAGING_ID), + messageHandler.capture(), + ) + messageHandler.value.onPortConnected(port) + + // Action: signin + verifyLogin("sessiontoken123", "foo@bar.com", "uid123", false, messageHandler.value, accountManager) + } + private fun JSONObject.getSupportedEngines(): List { val engines = this.getJSONObject("message") .getJSONObject("data") @@ -798,6 +849,41 @@ class FxaWebChannelFeatureTest { ) } + private suspend fun verifyLogin(sessionToken: String, email: String, uid: String, verified: Boolean, messageHandler: MessageHandler, accountManager: FxaAccountManager) { + val jsonToWebChannel = jsonLogin(sessionToken, email, uid, verified) + val port = mock() + whenever(port.senderUrl()).thenReturn("https://foo.bar/email") + messageHandler.onPortMessage(jsonToWebChannel, port) + + val expectedUserData = UserData( + sessionToken = sessionToken, + email = email, + uid = uid, + verified = verified, + ) + shadowOf(getMainLooper()).idle() + + verify(accountManager).setUserData(expectedUserData) + } + + private fun jsonLogin(sessionToken: String, email: String, uid: String, verified: Boolean): JSONObject { + return JSONObject( + """{ + "message":{ + "command": "fxaccounts:login", + "messageId":123, + "data":{ + "email":"$email", + "sessionToken":"$sessionToken", + "uid":"$uid", + "verified":$verified + } + } + } + """.trimIndent(), + ) + } + private fun prepareFeatureForTest( ext: WebExtension = mock(), port: Port = mock(), diff --git a/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/AddonManager.kt b/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/AddonManager.kt index d3e12a0171..723d3e6eb1 100644 --- a/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/AddonManager.kt +++ b/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/AddonManager.kt @@ -252,9 +252,9 @@ class AddonManager( permissions = permissions, origins = origins, onSuccess = { ext -> - val enabledAddon = addon.copy(installedState = toInstalledState(ext)) + val updatedAddon = Addon.newFromWebExtension(ext, toInstalledState(ext)) completePendingAddonAction(pendingAction) - onSuccess(enabledAddon) + onSuccess(updatedAddon) }, onError = { completePendingAddonAction(pendingAction) @@ -296,9 +296,9 @@ class AddonManager( permissions = permissions, origins = origins, onSuccess = { ext -> - val enabledAddon = addon.copy(installedState = toInstalledState(ext)) + val updatedAddon = Addon.newFromWebExtension(ext, toInstalledState(ext)) completePendingAddonAction(pendingAction) - onSuccess(enabledAddon) + onSuccess(updatedAddon) }, onError = { completePendingAddonAction(pendingAction) diff --git a/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt b/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt index 92e555e722..a19a5a61f5 100644 --- a/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt +++ b/mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt @@ -153,9 +153,10 @@ class PermissionsDialogFragment : AddonDialogFragment() { }, addon.translateName(requireContext()), ) - rootView.findViewById(R.id.optional_or_required_text).text = buildOptionalOrRequiredText() - val listPermissions = buildPermissionsList() + rootView.findViewById(R.id.optional_or_required_text).text = + buildOptionalOrRequiredText(listPermissions.isNotEmpty()) + val permissionsRecyclerView = rootView.findViewById(R.id.permissions) val positiveButton = rootView.findViewById - - -

    - + - - + mutedVideo.addEventListener("pause", event => { + document.querySelector(".playbackState").innerHTML = + "Media file is paused"; + }); + + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/password.html b/mobile/android/fenix/app/src/androidTest/assets/pages/password.html index f906d33707..0faf69511f 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/password.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/password.html @@ -1,22 +1,23 @@ - + + + - - - - - - -
    -

    Username:

    -

    Password:

    -

    -
    - - - - + +
    +

    + Username: +

    +

    + Password: +

    +

    + +

    +
    + + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/passwordsubmit.html b/mobile/android/fenix/app/src/androidTest/assets/pages/passwordsubmit.html index 5f55881be2..e3cdfdf9eb 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/passwordsubmit.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/passwordsubmit.html @@ -1,8 +1,8 @@ - - - - -

    Password submitted. Nope just a test.

    - + + + + +

    Password submitted. Nope just a test.

    + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/refresh.html b/mobile/android/fenix/app/src/androidTest/assets/pages/refresh.html index 1b8ff6fe9c..1951f917bb 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/refresh.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/refresh.html @@ -1,44 +1,41 @@ - - - -

    DEFAULT

    - + + +

    DEFAULT

    + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/storage_check.html b/mobile/android/fenix/app/src/androidTest/assets/pages/storage_check.html index 23d030a4f7..79731be911 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/storage_check.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/storage_check.html @@ -1,23 +1,21 @@ - - + + +

    Storage check

    -

    Storage check

    + - - + if (localStorage.getItem("localTest") == "local storage") { + document.write("

    Local storage has value

    "); + } else { + document.write("

    Local storage empty

    "); + } + + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/storage_write.html b/mobile/android/fenix/app/src/androidTest/assets/pages/storage_write.html index a10b469462..e90ebed9e0 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/storage_write.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/storage_write.html @@ -1,28 +1,30 @@ - - + + +

    Storage Write

    -

    Storage Write

    +

    + -

    - + - - + document.write("

    Values written to storage

    "); + + diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/videoMediaPage.html b/mobile/android/fenix/app/src/androidTest/assets/pages/videoMediaPage.html index 50af35c170..3245cb5fa2 100644 --- a/mobile/android/fenix/app/src/androidTest/assets/pages/videoMediaPage.html +++ b/mobile/android/fenix/app/src/androidTest/assets/pages/videoMediaPage.html @@ -1,49 +1,51 @@ - + - + Video_Test_Page - - -

    Page content: video player

    -
    -

    Media file not playing

    -
    -
    - - - -

    - +
    - - + video.addEventListener("pause", event => { + document.querySelector(".playbackState").innerHTML = + "Media file is paused"; + }); + + diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt index ac40972db6..fb8fe67616 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt @@ -46,6 +46,7 @@ import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity import org.mozilla.fenix.helpers.Constants.PackageName.PIXEL_LAUNCHER import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP import org.mozilla.fenix.helpers.Constants.TAG +import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort import org.mozilla.fenix.helpers.TestHelper.appContext @@ -586,4 +587,13 @@ object AppAndSystemHelper { e.printStackTrace() } } + + fun dismissSetAsDefaultBrowserOnboardingDialog() { + Log.i(TAG, "dismissSetAsDefaultBrowserOnboardingDialog: Detected API ${Build.VERSION.SDK_INT}") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Log.i(TAG, "dismissSetAsDefaulltBrowserOnboardingDialog: Trying to click the \"Cancel\" dialog button.") + itemWithResIdContainingText("android:id/button2", "Cancel").click() + Log.i(TAG, "dismissSetAsDefaulltBrowserOnboardingDialog: Clicked the \"Cancel\" dialog button.") + } + } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js index e1210bd24e..766a033b3a 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark.js @@ -8,19 +8,17 @@ */ EnableEngines(["bookmarks"]); -var phases = { "phase1": "profile1" }; - +var phases = { phase1: "profile1" }; // expected bookmark state var bookmarksCreated = { -"mobile": [{ - uri: "http://www.example.com/", - title: "Example Domain"}] + mobile: [ + { + uri: "http://www.example.com/", + title: "Example Domain", + }, + ], }; // sync and verify bookmarks -Phase("phase1", [ - [Sync], - [Bookmarks.add, bookmarksCreated], - [Sync] -]); \ No newline at end of file +Phase("phase1", [[Sync], [Bookmarks.add, bookmarksCreated], [Sync]]); diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark_desktop.js b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark_desktop.js index a6a42aa6d0..69489f84a7 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark_desktop.js +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_bookmark_desktop.js @@ -8,18 +8,17 @@ */ EnableEngines(["bookmarks"]); -var phases = { "phase1": "profile1" }; - +var phases = { phase1: "profile1" }; // expected bookmark state var bookmarksExpected = { -"mobile": [{ - uri: "http://www.example.com/", - title: "Example Domain"}] + mobile: [ + { + uri: "http://www.example.com/", + title: "Example Domain", + }, + ], }; // sync and verify bookmarks -Phase("phase1", [ - [Sync], - [Bookmarks.verify, bookmarksExpected], -]); +Phase("phase1", [[Sync], [Bookmarks.verify, bookmarksExpected]]); diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js index 8b915945e2..9aa96f4aef 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history.js @@ -8,26 +8,18 @@ */ EnableEngines(["history"]); -var phases = { "phase1": "profile1" }; - +var phases = { phase1: "profile1" }; // expected history state var historyCreated = [ - { uri: "http://www.example.com/", - visits: [ - { type: 1 , - date: 0 - }, - { type: 2, - date: -1 - } - ] - } + { + uri: "http://www.example.com/", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, ]; // sync and verify history -Phase("phase1", [ - [Sync], - [History.add, historyCreated], - [Sync] -]); +Phase("phase1", [[Sync], [History.add, historyCreated], [Sync]]); diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history_desktop.js b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history_desktop.js index 5dfdd1aa47..7cf2acaccb 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history_desktop.js +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_history_desktop.js @@ -8,21 +8,12 @@ */ EnableEngines(["history"]); -var phases = { "phase1": "profile1" }; - +var phases = { phase1: "profile1" }; // expected history state var historyExpected = [ - { uri: "http://www.example.com/", - visits: [ - { type: 1 }, - { type: 2 } - ] - } + { uri: "http://www.example.com/", visits: [{ type: 1 }, { type: 2 }] }, ]; // sync and verify history -Phase("phase1", [ - [Sync], - [History.verify, historyExpected] -]); +Phase("phase1", [[Sync], [History.verify, historyExpected]]); diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_logins.js b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_logins.js index 16eb4bcf34..bc1d1e1071 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_logins.js +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/syncintegration/test_logins.js @@ -8,11 +8,11 @@ */ EnableEngines(["passwords"]); -var phases = { "phase1": "profile1" }; - +var phases = { phase1: "profile1" }; // expected tabs state -var password_list = [{ +var password_list = [ + { hostname: "https://accounts.google.com", submitURL: "https://accounts.google.com/signin/challenge/sl/password", realm: null, @@ -20,11 +20,8 @@ var password_list = [{ password: "test15mz", usernameField: "Email", passwordField: "Passwd", - }]; + }, +]; // sync and verify tabs -Phase("phase1", [ - [Sync], - [Passwords.add, password_list], - [Sync] -]); \ No newline at end of file +Phase("phase1", [[Sync], [Passwords.add, password_list], [Sync]]); diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt index 96ca6ad724..d575521a5d 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt @@ -9,7 +9,6 @@ import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.pressBack import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import kotlinx.coroutines.runBlocking -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -237,7 +236,6 @@ class BookmarksTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1919261 - @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268") @Test fun verifyOpenAllInNewTabsOptionTest() { val webPages = listOf( @@ -261,11 +259,6 @@ class BookmarksTest : TestSetup() { createBookmark(webPages[1].url, "root") createBookmark(webPages[2].url, "root") createBookmark(webPages[3].url, "sub") - }.openTabDrawer { - closeTab() - } - - browserScreen { }.openThreeDotMenu { }.openBookmarks { }.openThreeDotMenu("root") { @@ -281,7 +274,6 @@ class BookmarksTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1919262 - @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268") @Test fun verifyOpenAllInPrivateTabsTest() { val webPages = listOf( @@ -301,11 +293,6 @@ class BookmarksTest : TestSetup() { browserScreen { createBookmark(webPages[0].url, "root") createBookmark(webPages[1].url, "sub") - }.openTabDrawer { - closeTab() - } - - browserScreen { }.openThreeDotMenu { }.openBookmarks { }.openThreeDotMenu("root") { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt index 398533830a..995e43f794 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt @@ -233,11 +233,6 @@ class ComposeBookmarksTest : TestSetup() { createBookmark(webPages[1].url, "root") createBookmark(webPages[2].url, "root") createBookmark(webPages[3].url, "sub") - }.openComposeTabDrawer(activityTestRule) { - closeTab() - } - - browserScreen { }.openThreeDotMenu { }.openBookmarks { }.openThreeDotMenu("root") { @@ -272,11 +267,6 @@ class ComposeBookmarksTest : TestSetup() { browserScreen { createBookmark(webPages[0].url, "root") createBookmark(webPages[1].url, "sub") - }.openComposeTabDrawer(activityTestRule) { - closeTab() - } - - browserScreen { }.openThreeDotMenu { }.openBookmarks { }.openThreeDotMenu("root") { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCrashReportingTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCrashReportingTest.kt index e815972900..979824f2f8 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCrashReportingTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCrashReportingTest.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -48,7 +47,6 @@ class ComposeCrashReportingTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2336134 - @Ignore("Test failure caused by: https://github.com/mozilla-mobile/fenix/issues/19964") @Test fun restoreTabFromTabCrashedReporterTest() { val website = TestAssetHelper.getGenericAsset(mockWebServer, 1) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt index d3b17f57d9..2268825d3a 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt @@ -7,7 +7,6 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.pressBack -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -155,7 +154,6 @@ class ComposeHistoryTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/339696 - @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268") @Test fun openMultipleSelectedHistoryItemsInANewTabTest() { val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt index eaa76b1481..30f1ea8dc5 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt @@ -55,7 +55,7 @@ class ComposeSearchTest : TestSetup() { private lateinit var searchMockServer: MockWebServer private val queryString: String = "firefox" private val generalEnginesList = listOf("DuckDuckGo", "Google", "Bing") - private val topicEnginesList = listOf("Amazon.com", "Wikipedia", "eBay") + private val topicEnginesList = listOf("Wikipedia", "eBay") @get:Rule val activityTestRule = AndroidComposeTestRule( @@ -111,7 +111,7 @@ class ComposeSearchTest : TestSetup() { verifySearchToolbar(isDisplayed = true) clickSearchSelectorButton() verifySearchShortcutListContains( - "DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay", + "DuckDuckGo", "Google", "Wikipedia", "Bing", "eBay", "Bookmarks", "Tabs", "History", "Search settings", ) } @@ -153,7 +153,7 @@ class ComposeSearchTest : TestSetup() { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154196 @Test fun verifySearchPlaceholderForTopicSpecificSearchEnginesTest() { - val topicEnginesList = listOf("Amazon.com", "Wikipedia", "eBay") + val topicEnginesList = listOf("Wikipedia", "eBay") topicEnginesList.forEach { homeScreen { @@ -229,7 +229,7 @@ class ComposeSearchTest : TestSetup() { @SmokeTest @Test fun searchEnginesCanBeChangedTemporarilyFromSearchSelectorMenuTest() { - val enginesList = listOf("DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay") + val enginesList = listOf("DuckDuckGo", "Google", "Wikipedia", "Bing", "eBay") enginesList.forEach { homeScreen { @@ -303,13 +303,12 @@ class ComposeSearchTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1592229 - @Ignore("Test run timing out: https://github.com/mozilla-mobile/fenix/issues/27704") @Test fun verifyAPageIsAddedToASearchGroupOnlyOnceTest() { val firstPageUrl = TestAssetHelper.getGenericAsset(searchMockServer, 1).url val secondPageUrl = TestAssetHelper.getGenericAsset(searchMockServer, 2).url val originPageUrl = - "http://localhost:${searchMockServer.port}/pages/searchResults.html?search=test%20search".toUri() + "http://localhost:${searchMockServer.port}/pages/searchResults.html?search=firefox".toUri() val searchEngineName = "TestSearchEngine" // setting our custom mockWebServer search URL setCustomSearchEngine(searchMockServer, searchEngineName) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt index be99ebe8af..224de9a675 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -47,7 +46,6 @@ class CrashReportingTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2336134 - @Ignore("Test failure caused by: https://github.com/mozilla-mobile/fenix/issues/19964") @Test fun restoreTabFromTabCrashedReporterTest() { val website = TestAssetHelper.getGenericAsset(mockWebServer, 1) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt index 2e881e16fe..938c13d857 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt @@ -51,7 +51,7 @@ class FirefoxSuggestTest : TestSetup() { ), "Houzz" to listOf( "Houzz.com - Official Site", - "houzz.com/products?m_refid=us-dsp-mpl-admp-219577_15416306_kwd-353208810&adcid=319104989&mfadid=adm&utm_source=admarketplace&utm_medium=cpc&utm_campaign=Privacy&utm_term=houzz&utm_content=319104989us1287${getSponsoredFxSuggestPlaceHolder()}", + "houzz.com/products?m_refid=us-dsp-mpl-admp-219577_15416306_kwd-353208810&adcid=319104989us1287${getSponsoredFxSuggestPlaceHolder()}&utm_source=admarketplace&utm_medium=cpc&utm_campaign=Privacy&utm_term=houzz&utm_content=319104989&mfadid=adm", ), "Spanx" to listOf( "SPANX® - Official Site", diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt index 317aa9c397..b551ec831a 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt @@ -8,7 +8,6 @@ import android.os.Build import android.view.autofill.AutofillManager import androidx.core.net.toUri import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -538,7 +537,6 @@ class LoginsTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/593768 - @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812995") @Test fun doNotSaveOptionWillNotUpdateALoginTest() { val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html" @@ -547,19 +545,18 @@ class LoginsTest : TestSetup() { navigationToolbar { }.enterURLAndEnterToBrowser(loginPage.toUri()) { setPageObjectText(itemWithResId("username"), "mozilla") + waitForAppWindowToBeUpdated() setPageObjectText(itemWithResId("password"), "firefox") + waitForAppWindowToBeUpdated() clickPageObject(itemWithResId("submit")) verifySaveLoginPromptIsDisplayed() clickPageObject(itemWithText("Save")) - }.openTabDrawer { - closeTab() - } - - navigationToolbar { - }.enterURLAndEnterToBrowser(loginPage.toUri()) { + waitForAppWindowToBeUpdated() clickPageObject(itemWithResId("togglePassword")) setPageObjectText(itemWithResId("username"), "mozilla") + waitForAppWindowToBeUpdated() setPageObjectText(itemWithResId("password"), "fenix") + waitForAppWindowToBeUpdated() clickPageObject(itemWithResId("submit")) verifySaveLoginPromptIsDisplayed() clickPageObject(itemWithText("Don’t update")) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt index 95ced206a2..cad28b1878 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt @@ -84,7 +84,7 @@ class NimbusMessagingHomescreenTest : TestSetup() { HomeScreenSection.POCKET to false, HomeScreenSection.POCKET_SPONSORED_STORIES to false, HomeScreenSection.RECENT_EXPLORATIONS to false, - HomeScreenSection.RECENTLY_SAVED to false, + HomeScreenSection.BOOKMARKS to false, HomeScreenSection.TOP_SITES to false, ), ) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt index 4eead4f63c..d2da41ca11 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt @@ -1,9 +1,12 @@ package org.mozilla.fenix.ui +import android.os.Build import androidx.compose.ui.test.junit4.AndroidComposeTestRule import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest +import org.mozilla.fenix.helpers.AppAndSystemHelper.dismissSetAsDefaultBrowserOnboardingDialog +import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithLauncherIntent import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestSetup @@ -20,9 +23,13 @@ class OnboardingTest : TestSetup() { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2122321 @Test fun verifyFirstOnboardingCardItemsTest() { - runWithLauncherIntent(activityTestRule) { - homeScreen { - verifyFirstOnboardingCard(activityTestRule) + // Run UI test only on devices with Android version lower than 10 + // because on Android 10 and above, the default browser dialog is shown and the first onboarding card is skipped + runWithCondition(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + runWithLauncherIntent(activityTestRule) { + homeScreen { + verifyFirstOnboardingCard(activityTestRule) + } } } } @@ -31,15 +38,19 @@ class OnboardingTest : TestSetup() { @SmokeTest @Test fun verifyFirstOnboardingCardItemsFunctionalityTest() { - runWithLauncherIntent(activityTestRule) { - homeScreen { - clickDefaultCardNotNowOnboardingButton(activityTestRule) - verifySecondOnboardingCard(activityTestRule) - swipeSecondOnboardingCardToRight() - }.clickSetAsDefaultBrowserOnboardingButton(activityTestRule) { - verifyAndroidDefaultAppsMenuAppears() - }.goBackToOnboardingScreen { - verifySecondOnboardingCard(activityTestRule) + // Run UI test only on devices with Android version lower than 10 + // because on Android 10 and above, the default browser dialog is shown and the first onboarding card is skipped + runWithCondition(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + runWithLauncherIntent(activityTestRule) { + homeScreen { + clickDefaultCardNotNowOnboardingButton(activityTestRule) + verifySecondOnboardingCard(activityTestRule) + swipeSecondOnboardingCardToRight() + }.clickSetAsDefaultBrowserOnboardingButton(activityTestRule) { + verifyAndroidDefaultAppsMenuAppears() + }.goBackToOnboardingScreen { + verifySecondOnboardingCard(activityTestRule) + } } } } @@ -49,7 +60,12 @@ class OnboardingTest : TestSetup() { fun verifySecondOnboardingCardItemsTest() { runWithLauncherIntent(activityTestRule) { homeScreen { - clickDefaultCardNotNowOnboardingButton(activityTestRule) + // Check if the device is running on Android version lower than 10 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + // If true, click the "Not Now" button from the first onboarding card + clickDefaultCardNotNowOnboardingButton(activityTestRule) + } + dismissSetAsDefaultBrowserOnboardingDialog() verifySecondOnboardingCard(activityTestRule) } } @@ -61,7 +77,12 @@ class OnboardingTest : TestSetup() { fun verifyThirdOnboardingCardSignInFunctionalityTest() { runWithLauncherIntent(activityTestRule) { homeScreen { - clickDefaultCardNotNowOnboardingButton(activityTestRule) + // Check if the device is running on Android version lower than 10 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + // If true, click the "Not Now" button from the first onboarding card + clickDefaultCardNotNowOnboardingButton(activityTestRule) + } + dismissSetAsDefaultBrowserOnboardingDialog() verifySecondOnboardingCard(activityTestRule) clickAddSearchWidgetNotNowOnboardingButton(activityTestRule) verifyThirdOnboardingCard(activityTestRule) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt index 674924c954..e5d52152e3 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt @@ -2,6 +2,7 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.helpers.Constants @@ -63,6 +64,7 @@ class PocketTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252513 + @Ignore("Bug 1868291 - Intermittent failure in org.mozilla.fenix.ui.PocketTest.openPocketStoryItemTest") @Test fun openPocketStoryItemTest() { activityTestRule.activityRule.applySettingsExceptions { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt index ede7b06b5d..6d2202c3ba 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt @@ -68,7 +68,7 @@ class SearchTest : TestSetup() { private lateinit var searchMockServer: MockWebServer private var queryString = "firefox" private val generalEnginesList = listOf("DuckDuckGo", "Google", "Bing") - private val topicEnginesList = listOf("Amazon.com", "Wikipedia", "eBay") + private val topicEnginesList = listOf("Wikipedia", "eBay") @get:Rule val activityTestRule = AndroidComposeTestRule( @@ -123,7 +123,7 @@ class SearchTest : TestSetup() { verifySearchToolbar(isDisplayed = true) clickSearchSelectorButton() verifySearchShortcutListContains( - "DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay", + "DuckDuckGo", "Google", "Wikipedia", "Bing", "eBay", "Bookmarks", "Tabs", "History", "Search settings", ) } @@ -165,7 +165,7 @@ class SearchTest : TestSetup() { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154196 @Test fun verifySearchPlaceholderForTopicSpecificSearchEnginesTest() { - val topicEnginesList = listOf("Amazon.com", "Wikipedia", "eBay") + val topicEnginesList = listOf("Wikipedia", "eBay") topicEnginesList.forEach { homeScreen { @@ -241,7 +241,7 @@ class SearchTest : TestSetup() { @SmokeTest @Test fun searchEnginesCanBeChangedTemporarilyFromSearchSelectorMenuTest() { - val enginesList = listOf("DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay") + val enginesList = listOf("DuckDuckGo", "Google", "Wikipedia", "Bing", "eBay") enginesList.forEach { homeScreen { @@ -802,7 +802,6 @@ class SearchTest : TestSetup() { verifySearchShortcutListContains( "Google", "Bing", - "Amazon.com", "DuckDuckGo", "ويكيبيديا (ar)", ) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt index 3e37fb5e3b..1144deb9c1 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt @@ -21,7 +21,7 @@ import org.mozilla.fenix.ui.robots.homeScreen class SettingsAboutTest : TestSetup() { @get:Rule - val activityIntentTestRule = HomeActivityIntentTestRule() + val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() @Rule @JvmField @@ -61,10 +61,6 @@ class SettingsAboutTest : TestSetup() { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/246961 @Test fun verifyAboutFirefoxMenuItems() { - activityIntentTestRule.applySettingsExceptions { - it.isJumpBackInCFREnabled = false - it.isTCPCFREnabled = false - } homeScreen { }.openThreeDotMenu { }.openSettings { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt index 8dc31c64e8..0f41e4ac94 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt @@ -124,12 +124,12 @@ class SettingsHomepageTest : TestSetup() { }.openThreeDotMenu { }.bookmarkPage { }.goToHomescreen { - verifyRecentBookmarksSectionIsDisplayed(exists = true) + verifyBookmarksSectionIsDisplayed(exists = true) }.openThreeDotMenu { }.openCustomizeHome { clickRecentBookmarksButton() }.goBackToHomeScreen { - verifyRecentBookmarksSectionIsDisplayed(exists = false) + verifyBookmarksSectionIsDisplayed(exists = false) } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt index 5abbaa8b4a..d12d3a9c8c 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt @@ -9,7 +9,6 @@ import androidx.test.espresso.Espresso.pressBack import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -511,7 +510,6 @@ class SettingsSearchTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/412927 - @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268") @Test fun verifyShowClipboardSuggestionsToggleTest() { val link = "https://www.mozilla.org/en-US/" @@ -599,12 +597,11 @@ class SettingsSearchTest : TestSetup() { verifySearchShortcutChecked( EngineShortcut(name = "Google", checkboxIndex = 1, isChecked = true), EngineShortcut(name = "Bing", checkboxIndex = 4, isChecked = true), - EngineShortcut(name = "Amazon.com", checkboxIndex = 7, isChecked = true), - EngineShortcut(name = "DuckDuckGo", checkboxIndex = 10, isChecked = true), - EngineShortcut(name = "eBay", checkboxIndex = 13, isChecked = true), - EngineShortcut(name = "Wikipedia", checkboxIndex = 16, isChecked = true), - EngineShortcut(name = "Reddit", checkboxIndex = 19, isChecked = false), - EngineShortcut(name = "YouTube", checkboxIndex = 22, isChecked = false), + EngineShortcut(name = "DuckDuckGo", checkboxIndex = 7, isChecked = true), + EngineShortcut(name = "eBay", checkboxIndex = 10, isChecked = true), + EngineShortcut(name = "Wikipedia", checkboxIndex = 13, isChecked = true), + EngineShortcut(name = "Reddit", checkboxIndex = 16, isChecked = false), + EngineShortcut(name = "YouTube", checkboxIndex = 19, isChecked = false), ) } } @@ -619,14 +616,13 @@ class SettingsSearchTest : TestSetup() { }.openSearchSubMenu { openManageShortcutsMenu() selectSearchShortcut(EngineShortcut(name = "Google", checkboxIndex = 1)) - selectSearchShortcut(EngineShortcut(name = "Amazon.com", checkboxIndex = 7)) - selectSearchShortcut(EngineShortcut(name = "Reddit", checkboxIndex = 19)) - selectSearchShortcut(EngineShortcut(name = "YouTube", checkboxIndex = 22)) + selectSearchShortcut(EngineShortcut(name = "Reddit", checkboxIndex = 16)) + selectSearchShortcut(EngineShortcut(name = "YouTube", checkboxIndex = 19)) exitMenu() } searchScreen { clickSearchSelectorButton() - verifySearchShortcutListContains("Google", "Amazon.com", shouldExist = false) + verifySearchShortcutListContains("Google", shouldExist = false) verifySearchShortcutListContains("YouTube", shouldExist = true) verifySearchShortcutListContains("Reddit", shouldExist = true) } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt index 4b3842fc6a..720f90c4c8 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt @@ -8,7 +8,6 @@ import androidx.core.net.toUri import androidx.test.espresso.Espresso.pressBack import androidx.test.filters.SdkSuppress import mozilla.components.concept.engine.mediasession.MediaSession -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -146,7 +145,6 @@ class SettingsSitePermissionsTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2286807 - @Ignore("Failing, see https://bugzilla.mozilla.org/show_bug.cgi?id=1827599") @SmokeTest @Test fun verifyAutoplayBlockAudioOnlySettingOnMutedVideoTest() { @@ -210,7 +208,6 @@ class SettingsSitePermissionsTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2286806 - @Ignore("Failing, see https://bugzilla.mozilla.org/show_bug.cgi?id=1827599") @Test fun verifyAutoplayAllowAudioVideoSettingOnMutedVideoTest() { val mutedVideoTestPage = getMutedVideoPageAsset(mockWebServer) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt index 2732ccbcab..cbbceaff97 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt @@ -430,8 +430,8 @@ class HomeScreenRobot { fun verifyJumpBackInShowAllButton() = assertUIObjectExists(itemContainingText(getStringResource(R.string.recent_tabs_show_all))) fun verifyRecentlyVisitedSectionIsDisplayed(exists: Boolean) = assertUIObjectExists(itemContainingText(getStringResource(R.string.history_metadata_header_2)), exists = exists) - fun verifyRecentBookmarksSectionIsDisplayed(exists: Boolean) = - assertUIObjectExists(itemContainingText(getStringResource(R.string.recently_saved_title)), exists = exists) + fun verifyBookmarksSectionIsDisplayed(exists: Boolean) = + assertUIObjectExists(itemContainingText(getStringResource(R.string.home_bookmarks_title)), exists = exists) fun verifyRecentlyVisitedSearchGroupDisplayed(shouldBeDisplayed: Boolean, searchTerm: String, groupSize: Int) { // checks if the search group exists in the Recently visited section @@ -439,12 +439,12 @@ class HomeScreenRobot { scrollToElementByText("Recently visited") assertUIObjectExists( itemContainingText(searchTerm) - .getFromParent(UiSelector().text("$groupSize sites")), + .getFromParent(UiSelector().text("$groupSize pages")), ) } else { assertUIObjectIsGone( itemContainingText(searchTerm) - .getFromParent(UiSelector().text("$groupSize sites")), + .getFromParent(UiSelector().text("$groupSize pages")), ) } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt index 9d1c919a37..c78994784a 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt @@ -444,7 +444,7 @@ private fun jumpBackInButton() = onView(allOf(withText(R.string.customize_toggle_jump_back_in))) private fun recentBookmarksButton() = - onView(allOf(withText(R.string.customize_toggle_recent_bookmarks))) + onView(allOf(withText(R.string.customize_toggle_bookmarks))) private fun recentlyVisitedButton() = onView(allOf(withText(R.string.customize_toggle_recently_visited))) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index a20a79088a..70317c8937 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -162,23 +162,25 @@ class ThreeDotMenuMainRobot { assertUIObjectExists( normalBrowsingNewTabButton(), bookmarksButton(), + addBookmarkButton(), historyButton(), downloadsButton(), + passwordsButton(), addOnsButton(), syncAndSaveDataButton(), findInPageButton(), + translateButton(), desktopSiteButton(), reportSiteIssueButton(), - addToHomeScreenButton(), - addToShortcutsButton(), - saveToCollectionButton(), - addBookmarkButton(), desktopSiteToggle(isRequestDesktopSiteEnabled), - translateButton(), + ) // Swipe to second part of menu expandMenu() assertUIObjectExists( + addToHomeScreenButton(), + addToShortcutsButton(), + saveToCollectionButton(), settingsButton(), ) if (FxNimbus.features.print.value().browserPrintEnabled) { @@ -197,6 +199,7 @@ class ThreeDotMenuMainRobot { bookmarksButton(), historyButton(), downloadsButton(), + passwordsButton(), addOnsButton(), // Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788 // syncAndSaveDataButton, @@ -717,6 +720,8 @@ private fun historyButton() = itemContainingText(getStringResource(R.string.library_history)) private fun downloadsButton() = itemContainingText(getStringResource(R.string.library_downloads)) +private fun passwordsButton() = + itemContainingText(getStringResource(R.string.browser_menu_passwords)) private fun addOnsButton() = itemContainingText(getStringResource(R.string.browser_menu_extensions)) private fun desktopSiteButton() = diff --git a/mobile/android/fenix/app/src/beta/res/xml/shortcuts.xml b/mobile/android/fenix/app/src/beta/res/xml/shortcuts.xml index dcf89bab7b..2da32f3afe 100644 --- a/mobile/android/fenix/app/src/beta/res/xml/shortcuts.xml +++ b/mobile/android/fenix/app/src/beta/res/xml/shortcuts.xml @@ -8,8 +8,7 @@ android:shortcutId="password_manager" android:enabled="true" android:icon="@drawable/ic_static_password_shortcut" - android:shortcutShortLabel="@string/home_screen_shortcut_passwords" - android:shortcutLongLabel="@string/home_screen_shortcut_passwords"> + android:shortcutShortLabel="@string/home_screen_shortcut_passwords"> + android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"> + android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"> + android:shortcutShortLabel="@string/home_screen_shortcut_passwords"> + android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"> + android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"> window.history.back() ); - } +document.addEventListener("DOMContentLoaded", function () { + if (window.history.length == 1) { + document.getElementById("backButton").style.display = "none"; + } else { + document + .getElementById("backButton") + .addEventListener("click", () => window.history.back()); + } }); parseQuery(document.documentURI); diff --git a/mobile/android/fenix/app/src/main/assets/high_risk_error_pages.html b/mobile/android/fenix/app/src/main/assets/high_risk_error_pages.html index c73012b143..7f0e6e3fc6 100644 --- a/mobile/android/fenix/app/src/main/assets/high_risk_error_pages.html +++ b/mobile/android/fenix/app/src/main/assets/high_risk_error_pages.html @@ -8,7 +8,10 @@ - + diff --git a/mobile/android/fenix/app/src/main/assets/lowMediumErrorPages.js b/mobile/android/fenix/app/src/main/assets/lowMediumErrorPages.js index 760ce65868..23a3fe91b4 100644 --- a/mobile/android/fenix/app/src/main/assets/lowMediumErrorPages.js +++ b/mobile/android/fenix/app/src/main/assets/lowMediumErrorPages.js @@ -6,52 +6,56 @@ * Handles the parsing of the ErrorPages URI and then passes them to injectValues */ function parseQuery(queryString) { - if (queryString[0] === '?') { - queryString = queryString.substr(1); - } - const query = Object.fromEntries(new URLSearchParams(queryString).entries()); - injectValues(query); - updateShowSSL(query); - updateShowHSTS(query); -}; + if (queryString[0] === "?") { + queryString = queryString.substr(1); + } + const query = Object.fromEntries(new URLSearchParams(queryString).entries()); + injectValues(query); + updateShowSSL(query); + updateShowHSTS(query); +} /** * Updates the HTML elements based on the queryMap */ function injectValues(queryMap) { - const tryAgainButton = document.getElementById('errorTryAgain'); - const continueHttpButton = document.getElementById("continueHttp"); - const backFromHttpButton = document.getElementById('backFromHttp'); + const tryAgainButton = document.getElementById("errorTryAgain"); + const continueHttpButton = document.getElementById("continueHttp"); + const backFromHttpButton = document.getElementById("backFromHttp"); - // Go through each element and inject the values - document.title = queryMap.title; - tryAgainButton.innerHTML = queryMap.button; - continueHttpButton.innerHTML = queryMap.continueHttpButton; - backFromHttpButton.innerHTML = queryMap.badCertGoBack; - document.getElementById('errorTitleText').innerHTML = queryMap.title; - document.getElementById('errorShortDesc').innerHTML = queryMap.description; - document.getElementById('advancedButton').innerHTML = queryMap.badCertAdvanced; - document.getElementById('badCertTechnicalInfo').innerHTML = queryMap.badCertTechInfo; - document.getElementById('advancedPanelBackButton').innerHTML = queryMap.badCertGoBack; - document.getElementById('advancedPanelAcceptButton').innerHTML = queryMap.badCertAcceptTemporary; + // Go through each element and inject the values + document.title = queryMap.title; + tryAgainButton.innerHTML = queryMap.button; + continueHttpButton.innerHTML = queryMap.continueHttpButton; + backFromHttpButton.innerHTML = queryMap.badCertGoBack; + document.getElementById("errorTitleText").innerHTML = queryMap.title; + document.getElementById("errorShortDesc").innerHTML = queryMap.description; + document.getElementById("advancedButton").innerHTML = + queryMap.badCertAdvanced; + document.getElementById("badCertTechnicalInfo").innerHTML = + queryMap.badCertTechInfo; + document.getElementById("advancedPanelBackButton").innerHTML = + queryMap.badCertGoBack; + document.getElementById("advancedPanelAcceptButton").innerHTML = + queryMap.badCertAcceptTemporary; - // If no image is passed in, remove the element so as not to leave an empty iframe - const errorImage = document.getElementById('errorImage'); - if (!queryMap.image) { - errorImage.remove(); - } else { - errorImage.src = "resource://android/assets/" + queryMap.image; - } + // If no image is passed in, remove the element so as not to leave an empty iframe + const errorImage = document.getElementById("errorImage"); + if (!queryMap.image) { + errorImage.remove(); + } else { + errorImage.src = "resource://android/assets/" + queryMap.image; + } - if (queryMap.showContinueHttp === "true") { - // On the "HTTPS-Only" error page "Try again" doesn't make sense since reloading the page - // will just show an error page again. - tryAgainButton.style.display = 'none'; - } else { - continueHttpButton.style.display = 'none'; - backFromHttpButton.style.display = 'none'; - } -}; + if (queryMap.showContinueHttp === "true") { + // On the "HTTPS-Only" error page "Try again" doesn't make sense since reloading the page + // will just show an error page again. + tryAgainButton.style.display = "none"; + } else { + continueHttpButton.style.display = "none"; + backFromHttpButton.style.display = "none"; + } +} let advancedVisible = false; @@ -59,88 +63,98 @@ let advancedVisible = false; * Used to show or hide the "accept" button based on the validity of the SSL certificate */ function updateShowSSL(queryMap) { - /** @type {'true' | 'false'} */ - const showSSL = queryMap.showSSL; - if (typeof document.addCertException === 'undefined') { - document.getElementById('advancedButton').style.display='none'; + /** @type {'true' | 'false'} */ + const showSSL = queryMap.showSSL; + if (typeof document.addCertException === "undefined") { + document.getElementById("advancedButton").style.display = "none"; + } else { + if (showSSL === "true") { + document.getElementById("advancedButton").style.display = "block"; } else { - if (showSSL === 'true') { - document.getElementById('advancedButton').style.display='block'; - } else { - document.getElementById('advancedButton').style.display='none'; - } + document.getElementById("advancedButton").style.display = "none"; } -}; + } +} /** * Used to show or hide the "accept" button based for the HSTS error page */ function updateShowHSTS(queryMap) { - const showHSTS = queryMap.showHSTS; - if (showHSTS === 'true') { - document.getElementById('advancedButton').style.display='block'; - document.getElementById('advancedPanelAcceptButton').style.display='none'; - } -}; + const showHSTS = queryMap.showHSTS; + if (showHSTS === "true") { + document.getElementById("advancedButton").style.display = "block"; + document.getElementById("advancedPanelAcceptButton").style.display = "none"; + } +} /** * Used to display information about the SSL certificate in `error_pages.html` */ function toggleAdvancedAndScroll() { - const advancedPanel = document.getElementById('badCertAdvancedPanel'); - if (advancedVisible) { - advancedPanel.style.display='none'; - } else { - advancedPanel.style.display='block'; - } - advancedVisible = !advancedVisible; + const advancedPanel = document.getElementById("badCertAdvancedPanel"); + if (advancedVisible) { + advancedPanel.style.display = "none"; + } else { + advancedPanel.style.display = "block"; + } + advancedVisible = !advancedVisible; - const horizontalLine = document.getElementById("horizontalLine"); - const advancedPanelAcceptButton = document.getElementById( - "advancedPanelAcceptButton" - ); - const badCertAdvancedPanel = document.getElementById( - "badCertAdvancedPanel" - ); + const horizontalLine = document.getElementById("horizontalLine"); + const advancedPanelAcceptButton = document.getElementById( + "advancedPanelAcceptButton" + ); + const badCertAdvancedPanel = document.getElementById("badCertAdvancedPanel"); - // We know that the button is being displayed - if (badCertAdvancedPanel.style.display === "block") { - horizontalLine.hidden = false; - advancedPanelAcceptButton.scrollIntoView({ - behavior: "smooth", - block: "center", - inline: "nearest", - }); - } else { - horizontalLine.hidden = true; - } -}; + // We know that the button is being displayed + if (badCertAdvancedPanel.style.display === "block") { + horizontalLine.hidden = false; + advancedPanelAcceptButton.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "nearest", + }); + } else { + horizontalLine.hidden = true; + } +} /** * Used to bypass an SSL pages in `error_pages.html` */ async function acceptAndContinue(temporary) { - try { - await document.addCertException(temporary); - location.reload(); - } catch (error) { - console.error("Unexpected error: " + error); - } -}; + try { + await document.addCertException(temporary); + location.reload(); + } catch (error) { + console.error("Unexpected error: " + error); + } +} -document.addEventListener('DOMContentLoaded', function () { - if (window.history.length == 1) { - document.getElementById('advancedPanelBackButton').style.display = 'none'; - document.getElementById('backFromHttp').style.display = 'none'; - } else { - document.getElementById('advancedPanelBackButton').addEventListener('click', () => window.history.back()); - document.getElementById('backFromHttp').addEventListener('click', () => window.history.back()); - } +document.addEventListener("DOMContentLoaded", function () { + if (window.history.length == 1) { + document.getElementById("advancedPanelBackButton").style.display = "none"; + document.getElementById("backFromHttp").style.display = "none"; + } else { + document + .getElementById("advancedPanelBackButton") + .addEventListener("click", () => window.history.back()); + document + .getElementById("backFromHttp") + .addEventListener("click", () => window.history.back()); + } - document.getElementById('errorTryAgain').addEventListener('click', () => window.location.reload()); - document.getElementById('advancedButton').addEventListener('click', toggleAdvancedAndScroll); - document.getElementById('advancedPanelAcceptButton').addEventListener('click', () => acceptAndContinue(true)); - document.getElementById('continueHttp').addEventListener('click', () => document.reloadWithHttpsOnlyException()); + document + .getElementById("errorTryAgain") + .addEventListener("click", () => window.location.reload()); + document + .getElementById("advancedButton") + .addEventListener("click", toggleAdvancedAndScroll); + document + .getElementById("advancedPanelAcceptButton") + .addEventListener("click", () => acceptAndContinue(true)); + document + .getElementById("continueHttp") + .addEventListener("click", () => document.reloadWithHttpsOnlyException()); }); parseQuery(document.documentURI); diff --git a/mobile/android/fenix/app/src/main/assets/low_and_medium_risk_error_pages.html b/mobile/android/fenix/app/src/main/assets/low_and_medium_risk_error_pages.html index 62e0c70988..84d8800ea5 100644 --- a/mobile/android/fenix/app/src/main/assets/low_and_medium_risk_error_pages.html +++ b/mobile/android/fenix/app/src/main/assets/low_and_medium_risk_error_pages.html @@ -8,7 +8,10 @@ - + - + @@ -56,9 +56,7 @@ id="advancedPanelBackButtonContainer" class="advancedPanelButtonContainer" > - +
    if (window.history.length == 1) { - document.getElementById('advancedPanelBackButton').style.display = 'none'; + document.getElementById("advancedPanelBackButton").style.display = "none"; } function toggleAdvancedAndScroll() { @@ -104,5 +102,5 @@ } - + diff --git a/mobile/android/fenix/app/src/main/assets/shared_error_style.css b/mobile/android/fenix/app/src/main/assets/shared_error_style.css index 5753b00a78..fc6a109003 100644 --- a/mobile/android/fenix/app/src/main/assets/shared_error_style.css +++ b/mobile/android/fenix/app/src/main/assets/shared_error_style.css @@ -48,7 +48,6 @@ li:not(:last-of-type) { } h1 { - margin: 0; padding: 0; margin: var(--moz-vertical-spacing) 0; color: var(--header-color); diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index aa8a5250a7..4d4ce12330 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -50,7 +50,7 @@ object FeatureFlags { /** * Enables compose on the tabs tray items. */ - val composeTabsTray = Config.channel.isNightlyOrDebug || Config.channel.isBeta + const val composeTabsTray = true /** * Enables compose on the top sites. @@ -87,4 +87,9 @@ object FeatureFlags { * Enables the menu redesign. */ const val menuRedesignEnabled = false + + /** + * Enables microsurveys. + */ + const val microsurveysEnabled = false } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 7c32c02f60..cd367fa467 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -206,10 +206,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider { lazy(LazyThreadSafetyMode.NONE) { components.core.client }, ), enableEventTimestamps = FxNimbus.features.glean.value().enableEventTimestamps, + delayPingLifetimeIo = FxNimbus.features.glean.value().delayPingLifetimeIo, ) // Set the metric configuration from Nimbus. - Glean.setMetricsEnabledConfig(FxNimbus.features.glean.value().metricsEnabled) + Glean.applyServerKnobsConfig(FxNimbus.features.glean.value().metricsEnabled) Glean.initialize( applicationContext = this, @@ -870,7 +871,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { disabledAds.set(!settings.isReviewQualityCheckProductRecommendationsEnabled) } - TabStrip.enabled.set(settings.isTabletAndTabStripEnabled) + TabStrip.enabled.set(settings.isTabStripEnabled) } @VisibleForTesting @@ -991,7 +992,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // We break them out here so they can be recorded when // `nimbus.applyPendingExperiments()` is called. CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature) - CustomizeHome.recentlySaved.set(settings.showRecentBookmarksFeature) + CustomizeHome.bookmarks.set(settings.showBookmarksHomeFeature) CustomizeHome.mostVisitedSites.set(settings.showTopSitesFeature) CustomizeHome.recentlyVisited.set(settings.historyMetadataUIFeature) CustomizeHome.pocket.set(settings.showPocketRecommendationsFeature) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 1b17a97507..bf442fab37 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -115,6 +115,7 @@ import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor import org.mozilla.fenix.home.intent.HomeDeepLinkIntentProcessor import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor import org.mozilla.fenix.home.intent.OpenPasswordManagerIntentProcessor +import org.mozilla.fenix.home.intent.OpenRecentlyClosedIntentProcessor import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor @@ -203,6 +204,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { OpenBrowserIntentProcessor(this, ::getIntentSessionId), OpenSpecificTabIntentProcessor(this), OpenPasswordManagerIntentProcessor(), + OpenRecentlyClosedIntentProcessor(), ReEngagementIntentProcessor(this, settings()), ) } @@ -780,7 +782,16 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private fun handleBackLongPress(): Boolean { supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { - if (it is OnBackLongPressedListener && it.onBackLongPressed()) { + if (it is OnLongPressedListener && it.onBackLongPressed()) { + return true + } + } + return false + } + + private fun handleForwardLongPress(): Boolean { + supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { + if (it is OnLongPressedListener && it.onForwardLongPressed()) { return true } } @@ -805,9 +816,16 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { handleBackLongPress() } } + + if (keyCode == KeyEvent.KEYCODE_FORWARD) { + event?.startTracking() + return true + } + return super.onKeyDown(keyCode, event) } + @Suppress("ReturnCount") final override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { if (shouldUseCustomBackLongPress() && keyCode == KeyEvent.KEYCODE_BACK) { backLongPressJob?.cancel() @@ -821,6 +839,20 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { return true } } + + if (keyCode == KeyEvent.KEYCODE_FORWARD) { + if (navHost.navController.hasTopDestination(TabHistoryDialogFragment.NAME)) { + // returning true avoids further processing of the KeyUp event + return true + } + + supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { + if (it is UserInteractionHandler && it.onForwardPressed()) { + return true + } + } + } + return super.onKeyUp(keyCode, event) } @@ -830,6 +862,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { if (!shouldUseCustomBackLongPress() && keyCode == KeyEvent.KEYCODE_BACK) { return handleBackLongPress() } + + if (keyCode == KeyEvent.KEYCODE_FORWARD) { + return handleForwardLongPress() + } + return super.onKeyLongPress(keyCode, event) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt deleted file mode 100644 index e47a0b71b4..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnBackLongPressedListener.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* 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 org.mozilla.fenix - -/** - * Interface for features and fragments that want to handle long presses of the system back button - */ -interface OnBackLongPressedListener { - - /** - * Called when the system back button is long pressed. - * - * Note: This cannot be called when gesture navigation is enabled on Android 10+ due to system - * limitations. - * - * @return true if the event was handled - */ - fun onBackLongPressed(): Boolean -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnLongPressedListener.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnLongPressedListener.kt new file mode 100644 index 0000000000..711830bf72 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/OnLongPressedListener.kt @@ -0,0 +1,28 @@ +/* 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 org.mozilla.fenix + +/** + * Interface for features and fragments that want to handle long presses of the system back/forward button + */ +interface OnLongPressedListener { + + /** + * Called when the system back button is long pressed. + * + * Note: This cannot be called when gesture navigation is enabled on Android 10+ due to system + * limitations. + * + * @return true if the event was handled + */ + fun onBackLongPressed(): Boolean + + /** + * Called when the system forward button is long pressed. + * + * @return true if the event was handled + */ + fun onForwardLongPressed(): Boolean +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index 5eeeeedb3e..0198cb9653 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -40,6 +40,7 @@ import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.settings.SupportUtils.AMO_HOMEPAGE_FOR_ANDROID import org.mozilla.fenix.theme.ThemeManager /** @@ -264,11 +265,4 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) from = BrowserDirection.FromAddonsManagementFragment, ) } - - companion object { - // This is locale-less on purpose so that the content negotiation happens on the AMO side because the current - // user language might not be supported by AMO and/or the language might not be exactly what AMO is expecting - // (e.g. `en` instead of `en-US`). - private const val AMO_HOMEPAGE_FOR_ANDROID = "${BuildConfig.AMO_BASE_URL}/android/" - } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index d6e93e0043..2567021e7e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -115,25 +115,29 @@ import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.kotlin.getOrigin import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import mozilla.components.support.locale.ActivityContextWrapper +import mozilla.components.support.utils.ext.isLandscape import mozilla.components.ui.widgets.withCenterAlignedButtons import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.GleanMetrics.Events +import org.mozilla.fenix.GleanMetrics.Logins import org.mozilla.fenix.GleanMetrics.MediaState import org.mozilla.fenix.GleanMetrics.NavigationBar import org.mozilla.fenix.GleanMetrics.PullToRefreshInBrowser import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.NavGraphDirections -import org.mozilla.fenix.OnBackLongPressedListener +import org.mozilla.fenix.OnLongPressedListener import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.DefaultReaderModeController import org.mozilla.fenix.browser.tabstrip.TabStrip +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.components.menu.MenuAccessPoint import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.components.toolbar.BrowserFragmentState import org.mozilla.fenix.components.toolbar.BrowserFragmentStore @@ -165,6 +169,7 @@ import org.mozilla.fenix.ext.breadcrumb import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.hideToolbar +import org.mozilla.fenix.ext.isTablet import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.navigateWithBreadcrumb import org.mozilla.fenix.ext.registerForActivityResult @@ -173,6 +178,7 @@ import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.secure import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.tabClosedUndoMessage +import org.mozilla.fenix.ext.updateNavBarForConfigurationChange import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.home.SharedViewModel import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel @@ -201,7 +207,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, ActivityResultHandler, - OnBackLongPressedListener, + OnLongPressedListener, AccessibilityManager.AccessibilityStateChangeListener { private var _binding: FragmentBrowserBinding? = null @@ -212,7 +218,9 @@ abstract class BaseBrowserFragment : private lateinit var startForResult: ActivityResultLauncher private var _browserToolbarInteractor: BrowserToolbarInteractor? = null - protected val browserToolbarInteractor: BrowserToolbarInteractor + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + internal val browserToolbarInteractor: BrowserToolbarInteractor get() = _browserToolbarInteractor!! @VisibleForTesting @@ -269,6 +277,13 @@ abstract class BaseBrowserFragment : private var currentStartDownloadDialog: StartDownloadDialog? = null + private lateinit var savedLoginsLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + savedLoginsLauncher = registerForActivityResult { navigateToSavedLoginsFragment() } + } + @CallSuper override fun onCreateView( inflater: LayoutInflater, @@ -419,6 +434,7 @@ abstract class BaseBrowserFragment : }, ) val browserToolbarMenuController = DefaultBrowserToolbarMenuController( + fragment = this, store = store, activity = activity, navController = findNavController(), @@ -439,7 +455,8 @@ abstract class BaseBrowserFragment : tabCollectionStorage = requireComponents.core.tabCollectionStorage, topSitesStorage = requireComponents.core.topSitesStorage, pinnedSiteStorage = requireComponents.core.pinnedSiteStorage, - browserStore = store, + onShowPinVerification = { intent -> savedLoginsLauncher.launch(intent) }, + onBiometricAuthenticationSuccessful = { navigateToSavedLoginsFragment() }, ) _browserToolbarInteractor = DefaultBrowserToolbarInteractor( @@ -485,7 +502,11 @@ abstract class BaseBrowserFragment : }, ) - if (IncompleteRedesignToolbarFeature(context.settings()).isEnabled) { + // We don't show the navigation bar for tablets and in landscape mode. + val shouldAddNavigationBar = IncompleteRedesignToolbarFeature(context.settings()).isEnabled && + !requireContext().isLandscape() && + !isTablet() + if (shouldAddNavigationBar) { initializeNavBar( browserToolbar = browserToolbarView.view, view = view, @@ -756,6 +777,9 @@ abstract class BaseBrowserFragment : loginValidationDelegate = DefaultLoginValidationDelegate( context.components.core.lazyPasswordsStorage, ), + isLoginAutofillEnabled = { + context.settings().shouldAutofillLogins + }, isSaveLoginEnabled = { context.settings().shouldPromptToSaveLogins }, @@ -836,6 +860,7 @@ abstract class BaseBrowserFragment : feature = SessionFeature( requireComponents.core.store, requireComponents.useCases.sessionUseCases.goBack, + requireComponents.useCases.sessionUseCases.goForward, binding.engineView, customTabSessionId, ), @@ -1005,7 +1030,9 @@ abstract class BaseBrowserFragment : ) initializeEngineView( - topToolbarHeight = context.settings().getTopToolbarHeight(includeTabStrip = customTabSessionId == null), + topToolbarHeight = context.settings().getTopToolbarHeight( + includeTabStrip = customTabSessionId == null && context.isTabStripEnabled(), + ), bottomToolbarHeight = bottomToolbarHeight, ) } @@ -1221,7 +1248,7 @@ abstract class BaseBrowserFragment : topToolbarHeight = topToolbarHeight, ) } else { - val toolbarHeight = if (customTabSessionId == null && context.settings().isTabletAndTabStripEnabled) { + val toolbarHeight = if (customTabSessionId == null && context.isTabStripEnabled()) { resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) + resources.getDimensionPixelSize(R.dimen.tab_strip_height) } else { @@ -1348,7 +1375,9 @@ abstract class BaseBrowserFragment : onMenuButtonClick = { findNavController().nav( R.id.browserFragment, - BrowserFragmentDirections.actionGlobalMenuDialogFragment(), + BrowserFragmentDirections.actionGlobalMenuDialogFragment( + accesspoint = MenuAccessPoint.Browser, + ), ) }, ) @@ -1513,6 +1542,11 @@ abstract class BaseBrowserFragment : removeSessionIfNeeded() } + @CallSuper + override fun onForwardPressed(): Boolean { + return sessionFeature.onForwardPressed() + } + /** * Forwards activity results to the [ActivityResultHandler] features. */ @@ -1523,12 +1557,24 @@ abstract class BaseBrowserFragment : ).any { it.onActivityResult(requestCode, data, resultCode) } } - override fun onBackLongPressed(): Boolean { + /** + * Navigate to GlobalTabHistoryDialogFragment. + */ + private fun navigateToGlobalTabHistoryDialogFragment() { findNavController().navigate( NavGraphDirections.actionGlobalTabHistoryDialogFragment( activeSessionId = customTabSessionId, ), ) + } + + override fun onBackLongPressed(): Boolean { + navigateToGlobalTabHistoryDialogFragment() + return true + } + + override fun onForwardLongPressed(): Boolean { + navigateToGlobalTabHistoryDialogFragment() return true } @@ -1773,7 +1819,7 @@ abstract class BaseBrowserFragment : browserToolbarView.visible() initializeEngineView( topToolbarHeight = requireContext().settings().getTopToolbarHeight( - includeTabStrip = customTabSessionId == null, + includeTabStrip = customTabSessionId == null && requireContext().isTabStripEnabled(), ), bottomToolbarHeight = requireContext().settings().getBottomToolbarHeight(), ) @@ -1787,6 +1833,27 @@ abstract class BaseBrowserFragment : @CallSuper internal open fun onUpdateToolbarForConfigurationChange(toolbar: BrowserToolbarView) { toolbar.dismissMenu() + + // If the navbar feature could be visible, we should update it's state. + val shouldUpdateNavBarState = + IncompleteRedesignToolbarFeature(requireContext().settings()).isEnabled && !isTablet() + if (shouldUpdateNavBarState) { + updateNavBarForConfigurationChange( + parent = binding.browserLayout, + toolbarView = browserToolbarView.view, + bottomToolbarContainerView = _bottomToolbarContainerView?.toolbarContainerView, + reinitializeNavBar = ::reinitializeNavBar, + ) + } + } + + private fun reinitializeNavBar() { + initializeNavBar( + browserToolbar = browserToolbarView.view, + view = requireView(), + context = requireContext(), + activity = requireActivity() as HomeActivity, + ) } /* @@ -1939,4 +2006,13 @@ abstract class BaseBrowserFragment : } } } + + private fun navigateToSavedLoginsFragment() { + val navController = findNavController() + if (navController.currentDestination?.id == R.id.browserFragment) { + Logins.openLogins.record(NoExtras()) + val directions = BrowserFragmentDirections.actionLoginsListFragment() + navController.navigate(directions) + } + } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 325d537220..89929ed7d1 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -34,11 +34,14 @@ import mozilla.components.feature.tabs.WindowFeature import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.support.utils.ext.isLandscape +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.GleanMetrics.AddressToolbar import org.mozilla.fenix.GleanMetrics.ReaderMode import org.mozilla.fenix.GleanMetrics.Shopping import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.appstate.AppAction @@ -51,6 +54,8 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragment +import org.mozilla.fenix.messaging.FenixMessageSurfaceId +import org.mozilla.fenix.messaging.MessagingFeature import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature @@ -72,12 +77,17 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { private val reviewQualityCheckFeature = ViewBoundFeatureWrapper() private val translationsBinding = ViewBoundFeatureWrapper() + @VisibleForTesting + internal val messagingFeature = ViewBoundFeatureWrapper() + private var readerModeAvailable = false private var reviewQualityCheckAvailable = false private var translationsAvailable = false private var pwaOnboardingObserver: PwaOnboardingObserver? = null + @VisibleForTesting + internal var leadingAction: BrowserToolbar.Button? = null private var forwardAction: BrowserToolbar.TwoStateButton? = null private var backAction: BrowserToolbar.TwoStateButton? = null private var refreshAction: BrowserToolbar.TwoStateButton? = null @@ -89,9 +99,8 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { val context = requireContext() val components = context.components - val isTabletAndTabStripEnabled = context.settings().isTabletAndTabStripEnabled - if (!isTabletAndTabStripEnabled && context.settings().isSwipeToolbarToSwitchTabsEnabled) { + if (!context.isTabStripEnabled() && context.settings().isSwipeToolbarToSwitchTabsEnabled) { binding.gestureLayout.addGestureListener( ToolbarGestureHandler( activity = requireActivity(), @@ -107,15 +116,14 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ) } - if (!IncompleteRedesignToolbarFeature(context.settings()).isEnabled) { - val isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate - initLeadingAction( - context = context, - isPrivate = isPrivate, - ) - } - - updateToolbarActions(isTablet = resources.getBoolean(R.bool.tablet)) + updateBrowserToolbarLeadingAndNavigationActions( + context = context, + redesignEnabled = IncompleteRedesignToolbarFeature(context.settings()).isEnabled, + isLandscape = context.isLandscape(), + isTablet = resources.getBoolean(R.bool.tablet), + isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate, + feltPrivateBrowsingEnabled = context.settings().feltPrivateBrowsingEnabled, + ) val readerModeAction = BrowserToolbar.ToggleButton( @@ -210,6 +218,22 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ) setTranslationFragmentResultListener() + + setupMicrosurvey() + } + + @VisibleForTesting + internal fun setupMicrosurvey(isMicrosurveyEnabled: Boolean = FeatureFlags.microsurveysEnabled) { + if (requireContext().settings().isExperimentationEnabled && isMicrosurveyEnabled) { + messagingFeature.set( + feature = MessagingFeature( + appStore = requireComponents.appStore, + surface = FenixMessageSurfaceId.MICROSURVEY, + ), + owner = viewLifecycleOwner, + view = binding.root, + ) + } } private fun setTranslationFragmentResultListener() { @@ -310,7 +334,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } private fun initReloadAction(context: Context) { - if (!IncompleteRedesignToolbarFeature(context.settings()).isEnabled || refreshAction != null) { + if (!IncompleteRedesignToolbarFeature(context.settings()).isEnabled) { return } @@ -412,43 +436,138 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } // Adds a home button to BrowserToolbar or, if FeltPrivateBrowsing is enabled, a clear data button instead. - private fun initLeadingAction( + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun addLeadingAction( context: Context, + feltPrivateBrowsingEnabled: Boolean, isPrivate: Boolean, ) { - val leadingAction = if (isPrivate && context.settings().feltPrivateBrowsingEnabled) { - BrowserToolbar.Button( - imageDrawable = AppCompatResources.getDrawable( - context, - R.drawable.mozac_ic_data_clearance_24, - )!!, - contentDescription = context.getString(R.string.browser_toolbar_erase), - iconTintColorResource = ThemeManager.resolveAttribute(R.attr.textPrimary, context), - listener = browserToolbarInteractor::onEraseButtonClicked, + if (leadingAction == null) { + leadingAction = if (isPrivate && feltPrivateBrowsingEnabled) { + BrowserToolbar.Button( + imageDrawable = AppCompatResources.getDrawable( + context, + R.drawable.mozac_ic_data_clearance_24, + )!!, + contentDescription = context.getString(R.string.browser_toolbar_erase), + iconTintColorResource = ThemeManager.resolveAttribute(R.attr.textPrimary, context), + listener = browserToolbarInteractor::onEraseButtonClicked, + ) + } else { + BrowserToolbar.Button( + imageDrawable = AppCompatResources.getDrawable( + context, + R.drawable.mozac_ic_home_24, + )!!, + contentDescription = context.getString(R.string.browser_toolbar_home), + iconTintColorResource = ThemeManager.resolveAttribute(R.attr.textPrimary, context), + listener = browserToolbarInteractor::onHomeButtonClicked, + ) + }.also { + browserToolbarView.view.addNavigationAction(it) + } + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun removeLeadingAction() { + leadingAction?.let { + browserToolbarView.view.removeNavigationAction(it) + } + leadingAction = null + } + + /** + * This code takes care of the [BrowserToolbar] leading and navigation actions. + * The older design requires a HomeButton followed by navigation buttons for tablets. + * The newer design expects NavigationButtons and a HomeButton in landscape mode for phones and in both modes + * for tablets. + */ + @VisibleForTesting + internal fun updateBrowserToolbarLeadingAndNavigationActions( + context: Context, + redesignEnabled: Boolean, + isLandscape: Boolean, + isTablet: Boolean, + isPrivate: Boolean, + feltPrivateBrowsingEnabled: Boolean, + ) { + if (redesignEnabled) { + updateAddressBarNavigationActions( + isLandscape = isLandscape, + isTablet = isTablet, + context = context, + ) + updateAddressBarLeadingAction( + redesignEnabled = true, + isLandscape = isLandscape, + isTablet = isTablet, + isPrivate = isPrivate, + feltPrivateBrowsingEnabled = feltPrivateBrowsingEnabled, + context = context, ) } else { - BrowserToolbar.Button( - imageDrawable = AppCompatResources.getDrawable( - context, - R.drawable.mozac_ic_home_24, - )!!, - contentDescription = context.getString(R.string.browser_toolbar_home), - iconTintColorResource = ThemeManager.resolveAttribute(R.attr.textPrimary, context), - listener = browserToolbarInteractor::onHomeButtonClicked, + updateAddressBarLeadingAction( + redesignEnabled = false, + isLandscape = isLandscape, + isPrivate = isPrivate, + isTablet = isTablet, + feltPrivateBrowsingEnabled = feltPrivateBrowsingEnabled, + context = context, ) + updateTabletToolbarActions(isTablet = isTablet) } + browserToolbarView.view.invalidateActions() + } - browserToolbarView.view.addNavigationAction(leadingAction) + @VisibleForTesting + internal fun updateAddressBarLeadingAction( + redesignEnabled: Boolean, + isLandscape: Boolean, + isTablet: Boolean, + isPrivate: Boolean, + feltPrivateBrowsingEnabled: Boolean, + context: Context, + ) { + if (!redesignEnabled || isLandscape || isTablet) { + addLeadingAction( + isPrivate = isPrivate, + feltPrivateBrowsingEnabled = feltPrivateBrowsingEnabled, + context = context, + ) + } else { + removeLeadingAction() + } + } + + @VisibleForTesting + internal fun updateAddressBarNavigationActions( + context: Context, + isLandscape: Boolean, + isTablet: Boolean, + ) { + if (isLandscape || isTablet) { + addNavigationActions(context) + } else { + removeNavigationActions() + } } override fun onUpdateToolbarForConfigurationChange(toolbar: BrowserToolbarView) { super.onUpdateToolbarForConfigurationChange(toolbar) - updateToolbarActions(isTablet = resources.getBoolean(R.bool.tablet)) + updateBrowserToolbarLeadingAndNavigationActions( + context = requireContext(), + redesignEnabled = IncompleteRedesignToolbarFeature(requireContext().settings()).isEnabled, + isLandscape = requireContext().isLandscape(), + isTablet = resources.getBoolean(R.bool.tablet), + isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate, + feltPrivateBrowsingEnabled = requireContext().settings().feltPrivateBrowsingEnabled, + ) } @VisibleForTesting - internal fun updateToolbarActions(isTablet: Boolean) { + internal fun updateTabletToolbarActions(isTablet: Boolean) { if (isTablet == this.isTablet) return if (isTablet) { @@ -460,8 +579,8 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { this.isTablet = isTablet } - @Suppress("LongMethod") - private fun addTabletActions(context: Context) { + @VisibleForTesting + internal fun addNavigationActions(context: Context) { val enableTint = ThemeManager.resolveAttribute(R.attr.textPrimary, context) val disableTint = ThemeManager.resolveAttribute(R.attr.textDisabled, context) @@ -486,11 +605,9 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ToolbarMenu.Item.Back(viewHistory = false), ) }, - ) - } - - backAction?.let { - browserToolbarView.view.addNavigationAction(it) + ).also { + browserToolbarView.view.addNavigationAction(it) + } } if (forwardAction == null) { @@ -514,13 +631,17 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ToolbarMenu.Item.Forward(viewHistory = false), ) }, - ) + ).also { + browserToolbarView.view.addNavigationAction(it) + } } + } - forwardAction?.let { - browserToolbarView.view.addNavigationAction(it) - } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun addTabletActions(context: Context) { + addNavigationActions(context) + val enableTint = ThemeManager.resolveAttribute(R.attr.textPrimary, context) if (refreshAction == null) { refreshAction = BrowserToolbar.TwoStateButton( primaryImage = AppCompatResources.getDrawable( @@ -552,28 +673,31 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ) } }, - ) - } - - refreshAction?.let { - browserToolbarView.view.addNavigationAction(it) + ).also { + browserToolbarView.view.addNavigationAction(it) + } } - - browserToolbarView.view.invalidateActions() } - private fun removeTabletActions() { + @VisibleForTesting + internal fun removeNavigationActions() { forwardAction?.let { browserToolbarView.view.removeNavigationAction(it) } + forwardAction = null backAction?.let { browserToolbarView.view.removeNavigationAction(it) } + backAction = null + } + + @VisibleForTesting + internal fun removeTabletActions() { + removeNavigationActions() + refreshAction?.let { browserToolbarView.view.removeNavigationAction(it) } - - browserToolbarView.view.invalidateActions() } override fun onStart() { @@ -607,6 +731,10 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { override fun onDestroyView() { super.onDestroyView() isTablet = false + leadingAction = null + forwardAction = null + backAction = null + refreshAction = null } private fun updateHistoryMetadata() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/StandardSnackbarErrorBinding.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/StandardSnackbarErrorBinding.kt index 80fbcd61b9..c103e22026 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/StandardSnackbarErrorBinding.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/StandardSnackbarErrorBinding.kt @@ -59,7 +59,7 @@ class StandardSnackbarErrorBinding( snackBar.setSnackBarTextColor( ContextCompat.getColor( activity, - R.color.fx_mobile_text_color_warning, + R.color.fx_mobile_text_color_critical, ), ) snackBar.setAction( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt index a661d1ea1c..e2d36bffed 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/SwipeGestureLayout.kt @@ -10,7 +10,6 @@ import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.widget.FrameLayout -import androidx.core.view.GestureDetectorCompat /** * Interface that allows intercepting and handling swipe gestures received in a [SwipeGestureLayout]. @@ -101,7 +100,7 @@ class SwipeGestureLayout @JvmOverloads constructor( } } - private val gestureDetector = GestureDetectorCompat(context, gestureListener) + private val gestureDetector = GestureDetector(context, gestureListener) private val listeners = mutableListOf() private var activeListener: SwipeGestureListener? = null diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index 42b6924955..cc855c9fa2 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -110,7 +110,9 @@ class TabPreview @JvmOverloads constructor( }, ) - removeView(binding.fakeToolbar) + if (!isToolbarAtTop) { + removeView(binding.fakeToolbar) + } } // Change view properties to avoid confusing the UI tests diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripFeatureFlag.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripFeatureFlag.kt new file mode 100644 index 0000000000..faeb5446f5 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripFeatureFlag.kt @@ -0,0 +1,31 @@ +/* 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 org.mozilla.fenix.browser.tabstrip + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import org.mozilla.fenix.Config +import org.mozilla.fenix.ext.isTablet +import org.mozilla.fenix.ext.settings + +/** + * Returns true if the tab strip is enabled. + */ +fun Context.isTabStripEnabled(): Boolean = + isTabStripEligible() && settings().isTabStripEnabled + +/** + * Returns true if the the device has the prerequisites to enable the tab strip. + */ +fun Context.isTabStripEligible(): Boolean = + Config.channel.isNightlyOrDebug && isTablet() && !doesDeviceHaveHinge() + +/** + * Check if the device has a hinge sensor. + */ +private fun Context.doesDeviceHaveHinge(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && + packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index fcd624eec8..099b24a744 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -19,6 +19,7 @@ import mozilla.components.concept.sync.DeviceCapability import mozilla.components.concept.sync.DeviceConfig import mozilla.components.concept.sync.DeviceType import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.feature.accounts.push.CloseTabsFeature import mozilla.components.feature.accounts.push.FxaPushSupportFeature import mozilla.components.feature.accounts.push.SendTabFeature import mozilla.components.feature.syncedtabs.SyncedTabsAutocompleteProvider @@ -84,7 +85,12 @@ class BackgroundServices( // NB: flipping this flag back and worth is currently not well supported and may need hand-holding. // Consult with the android-components peers before changing. // See https://github.com/mozilla/application-services/issues/1308 - capabilities = setOf(DeviceCapability.SEND_TAB), + capabilities = buildSet { + add(DeviceCapability.SEND_TAB) + if (context.settings().enableCloseSyncedTabs) { + add(DeviceCapability.CLOSE_TABS) + } + }, // Enable encryption for account state on supported API levels (23+). // Just on Nightly and local builds for now. @@ -194,6 +200,12 @@ class BackgroundServices( notificationManager.showReceivedTabs(context, device, tabs) } + if (context.settings().enableCloseSyncedTabs) { + CloseTabsFeature(context.components.core.store, accountManager) { _, remotelyClosedUrls -> + notificationManager.showSyncedTabsClosed(context, remotelyClosedUrls.size) + }.observe() + } + SyncedTabsIntegration(context, accountManager).launch() syncStoreSupport = SyncStoreSupport(syncStore, lazyOf(accountManager)).also { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt index 0f6f107df1..35c18e98ea 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -202,7 +202,7 @@ class Components(private val context: Context) { collections = core.tabCollectionStorage.cachedTabCollections, expandedCollections = emptySet(), topSites = core.topSitesStorage.cachedTopSites.sort(), - recentBookmarks = emptyList(), + bookmarks = emptyList(), showCollectionPlaceholder = settings.showCollectionsPlaceholderOnHome, // Provide an initial state for recent tabs to prevent re-rendering on the home screen. // This will otherwise cause a visual jump as the section gets rendered from no state diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/NotificationManager.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/NotificationManager.kt index 426019b8eb..9ed2abdcae 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/NotificationManager.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/NotificationManager.kt @@ -17,11 +17,15 @@ import android.os.Build.VERSION.SDK_INT import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.getSystemService +import androidx.core.os.bundleOf import mozilla.components.concept.sync.Device import mozilla.components.concept.sync.TabData +import mozilla.components.support.base.ids.SharedIdsHelper import mozilla.components.support.base.log.logger.Logger +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.R +import org.mozilla.fenix.home.intent.OpenRecentlyClosedIntentProcessor import org.mozilla.fenix.utils.IntentUtils /** @@ -29,6 +33,9 @@ import org.mozilla.fenix.utils.IntentUtils */ class NotificationManager(private val context: Context) { companion object { + const val TABS_CLOSED_TAG = "TabsClosed" + const val TOTAL_TABS_CLOSED_EXTRA = "org.mozilla.fenix.TOTAL_TABS_CLOSED_EXTRA" + const val TABS_CLOSED_NOTIFICATION_TAG = "org.mozilla.fenix.TABS_CLOSED_NOTIFICATION_TAG" const val RECEIVE_TABS_TAG = "ReceivedTabs" const val RECEIVE_TABS_CHANNEL_ID = "ReceivedTabsChannel" } @@ -51,6 +58,73 @@ class NotificationManager(private val context: Context) { private val logger = Logger("NotificationManager") + /** + * Notifies the user that one or more tabs on this device were closed from another device. + * + * @param context The Android application context. + * @param count The number of tabs that were closed. + */ + fun showSyncedTabsClosed(context: Context, count: Int) { + if (count <= 0) { + return + } + val notificationManagerCompat = NotificationManagerCompat.from(context) + val (notificationId, totalCount) = if (SDK_INT >= Build.VERSION_CODES.M) { + // On Android M (released in 2015) and later, we can retrieve + // the last notification from `allNotifications`. If one exists, + // we'll update its contents in-place with the new total number of + // closed tabs. + val notificationId = SharedIdsHelper.getIdForTag(context, TABS_CLOSED_NOTIFICATION_TAG) + val lastNotification = notificationManagerCompat.activeNotifications.find { + it.tag == TABS_CLOSED_TAG && it.id == notificationId + } + val lastTotalCount = lastNotification?.notification?.extras?.getInt(TOTAL_TABS_CLOSED_EXTRA) ?: 0 + Pair(notificationId, lastTotalCount + count) + } else { + // Pre-M doesn't have `activeNotifications`, so we'll show + // a new notification for each call to `showSyncedTabsClosed`. + val notificationId = SharedIdsHelper.getNextIdForTag(context, TABS_CLOSED_NOTIFICATION_TAG) + Pair(notificationId, count) + } + + val notification = NotificationCompat.Builder(context, RECEIVE_TABS_CHANNEL_ID).apply { + val title = context.resources.getString( + R.string.fxa_tabs_closed_notification_title, + context.resources.getString(R.string.app_name), + totalCount, + ) + setContentTitle(title) + + val text = context.resources.getString(R.string.fxa_tabs_closed_text) + setContentText(text) + + val intent = Intent(context, HomeActivity::class.java).apply { + action = OpenRecentlyClosedIntentProcessor.ACTION_OPEN_RECENTLY_CLOSED + } + val pendingIntent = PendingIntent.getActivity( + context, + 0, + intent, + IntentUtils.defaultIntentPendingFlags or PendingIntent.FLAG_UPDATE_CURRENT, + ) + setContentIntent(pendingIntent) + + val extras = bundleOf(TOTAL_TABS_CLOSED_EXTRA to totalCount) + addExtras(extras) + + setSmallIcon(R.drawable.ic_status_logo) + setWhen(System.currentTimeMillis()) + setAutoCancel(true) + setDefaults(Notification.DEFAULT_VIBRATE or Notification.DEFAULT_SOUND) + + if (SDK_INT >= Build.VERSION_CODES.M) { + setCategory(Notification.CATEGORY_STATUS) + } + }.build() + + notificationManagerCompat.notify(TABS_CLOSED_TAG, notificationId, notification) + } + fun showReceivedTabs(context: Context, device: Device?, tabs: List) { // In the future, experiment with displaying multiple tabs from the same device as as Notification Groups. // For now, a single notification per tab received will suffice. diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt index dbdeb831ed..f813ad33bd 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt @@ -17,9 +17,9 @@ import org.mozilla.fenix.browser.StandardSnackbarError import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.appstate.shopping.ShoppingState +import org.mozilla.fenix.home.bookmarks.Bookmark import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState import org.mozilla.fenix.home.recenttabs.RecentTab @@ -54,7 +54,7 @@ sealed class AppAction : Action { val collections: List, val showCollectionPlaceholder: Boolean, val recentTabs: List, - val recentBookmarks: List, + val bookmarks: List, val recentHistory: List, val recentSyncedTabState: RecentSyncedTabState, ) : @@ -68,8 +68,16 @@ sealed class AppAction : Action { data class TopSitesChange(val topSites: List) : AppAction() data class RecentTabsChange(val recentTabs: List) : AppAction() data class RemoveRecentTab(val recentTab: RecentTab) : AppAction() - data class RecentBookmarksChange(val recentBookmarks: List) : AppAction() - data class RemoveRecentBookmark(val recentBookmark: RecentBookmark) : AppAction() + + /** + * The list of bookmarks displayed on the home screen has changed. + */ + data class BookmarksChange(val bookmarks: List) : AppAction() + + /** + * A bookmark has been removed from the home screen. + */ + data class RemoveBookmark(val bookmark: Bookmark) : AppAction() data class RecentHistoryChange(val recentHistory: List) : AppAction() data class RemoveRecentHistoryHighlight(val highlightUrl: String) : AppAction() data class DisbandSearchGroupAction(val searchTerm: String) : AppAction() diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt index e118dca121..dc3a1b368a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt @@ -16,9 +16,9 @@ import org.mozilla.fenix.browser.StandardSnackbarError import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.appstate.shopping.ShoppingState import org.mozilla.fenix.home.HomeFragment +import org.mozilla.fenix.home.bookmarks.Bookmark import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem @@ -45,7 +45,7 @@ import org.mozilla.fenix.wallpapers.WallpaperState * @property showCollectionPlaceholder If true, shows a placeholder when there are no collections. * @property recentTabs The list of recent [RecentTab] in the [HomeFragment]. * @property recentSyncedTabState The [RecentSyncedTabState] in the [HomeFragment]. - * @property recentBookmarks The list of recently saved [BookmarkNode]s to show on the [HomeFragment]. + * @property bookmarks The list of recently saved [BookmarkNode]s to show on the [HomeFragment]. * @property recentHistory The list of [RecentlyVisitedItem]s. * @property pocketStories The list of currently shown [PocketRecommendedStory]s. * @property pocketStoriesCategories All [PocketRecommendedStory] categories. @@ -75,7 +75,7 @@ data class AppState( val showCollectionPlaceholder: Boolean = false, val recentTabs: List = emptyList(), val recentSyncedTabState: RecentSyncedTabState = RecentSyncedTabState.None, - val recentBookmarks: List = emptyList(), + val bookmarks: List = emptyList(), val recentHistory: List = emptyList(), val pocketStories: List = emptyList(), val pocketStoriesCategories: List = emptyList(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt index a614689982..1092de97f0 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt @@ -43,7 +43,7 @@ internal object AppStoreReducer { collections = action.collections, mode = action.mode, topSites = action.topSites, - recentBookmarks = action.recentBookmarks, + bookmarks = action.bookmarks, recentTabs = action.recentTabs, recentHistory = action.recentHistory, recentSyncedTabState = action.recentSyncedTabState, @@ -81,9 +81,9 @@ internal object AppStoreReducer { recentSyncedTabState = action.state, ) } - is AppAction.RecentBookmarksChange -> state.copy(recentBookmarks = action.recentBookmarks) - is AppAction.RemoveRecentBookmark -> { - state.copy(recentBookmarks = state.recentBookmarks.filterNot { it.url == action.recentBookmark.url }) + is AppAction.BookmarksChange -> state.copy(bookmarks = action.bookmarks) + is AppAction.RemoveBookmark -> { + state.copy(bookmarks = state.bookmarks.filterNot { it.url == action.bookmark.url }) } is AppAction.RecentHistoryChange -> state.copy( recentHistory = action.recentHistory, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt index f8ea604c34..986b4ba3b9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt @@ -9,7 +9,7 @@ import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.uniffi.PlacesApiException import mozilla.components.concept.storage.BookmarksStorage import mozilla.components.concept.storage.HistoryStorage -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark +import org.mozilla.fenix.home.bookmarks.Bookmark import java.util.concurrent.TimeUnit /** @@ -20,12 +20,17 @@ class BookmarksUseCase( historyStorage: HistoryStorage, ) { + /** + * Use case for adding a new bookmark. + * + * @param storage [BookmarksStorage] used to add and retrieve bookmark data. + */ class AddBookmarksUseCase internal constructor(private val storage: BookmarksStorage) { /** * Adds a new bookmark with the provided [url] and [title]. * - * @return The result if the operation was executed or not. A bookmark may not be added if + * @return The guid of the newly added bookmark or null. A bookmark may not be added if * one with the identical [url] already exists. */ @WorkerThread @@ -34,21 +39,22 @@ class BookmarksUseCase( title: String, position: UInt? = null, parentGuid: String? = null, - ): Boolean { + ): String? { return try { val canAdd = storage.getBookmarksWithUrl(url).firstOrNull { it.url == url } == null - if (canAdd) { + return if (canAdd) { storage.addItem( parentGuid ?: BookmarkRoot.Mobile.id, url = url, title = title, position = position, ) + } else { + null } - canAdd } catch (e: PlacesApiException.UrlParseFailed) { - false + null } } } @@ -68,27 +74,26 @@ class BookmarksUseCase( * Retrieves a list of recently added bookmarks, if any, up to maximum. * * @param count The number of recent bookmarks to return. - * @param maxAgeInMs The maximum age (ms) of a recently added bookmark to return. - * @return a list of [RecentBookmark] that were added no older than specify by [maxAgeInMs], - * if any, up to a number specified by [count]. + * @param previewImageMaxAgeMs The maximum age (ms) to search history for preview image URLs. + * @return a list of [Bookmark]s if any, up to a number specified by [count]. */ @WorkerThread suspend operator fun invoke( count: Int = DEFAULT_BOOKMARKS_TO_RETRIEVE, - maxAgeInMs: Long = TimeUnit.DAYS.toMillis(DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE), - ): List { + previewImageMaxAgeMs: Long = TimeUnit.DAYS.toMillis(DEFAULT_BOOKMARKS_LENGTH_DAYS_PREVIEW_IMAGE_SEARCH), + ): List { val currentTime = System.currentTimeMillis() // Fetch visit information within the time range of now and the specified maximum age. val history = historyStorage?.getDetailedVisits( - start = currentTime - maxAgeInMs, + start = currentTime - previewImageMaxAgeMs, end = currentTime, ) return bookmarksStorage - .getRecentBookmarks(count, maxAgeInMs) + .getRecentBookmarks(count) .map { bookmark -> - RecentBookmark( + Bookmark( title = bookmark.title, url = bookmark.url, previewImageUrl = history?.find { bookmark.url == it.url }?.previewImageUrl, @@ -107,9 +112,9 @@ class BookmarksUseCase( companion object { // Number of recent bookmarks to retrieve. - const val DEFAULT_BOOKMARKS_TO_RETRIEVE = 4 + const val DEFAULT_BOOKMARKS_TO_RETRIEVE = 8 // The maximum age in days of a recent bookmarks to retrieve. - const val DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE = 10L + const val DEFAULT_BOOKMARKS_LENGTH_DAYS_PREVIEW_IMAGE_SEARCH = 10L } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/BrowserNavigationParams.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/BrowserNavigationParams.kt new file mode 100644 index 0000000000..8de758f3d7 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/BrowserNavigationParams.kt @@ -0,0 +1,18 @@ +/* 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 org.mozilla.fenix.components.menu + +import org.mozilla.fenix.settings.SupportUtils.SumoTopic + +/** + * Browser navigation parameters of the URL or [SumoTopic] to be loaded. + * + * @property url The URL to be loaded. + * @property sumoTopic The [SumoTopic] to be loaded. + */ +data class BrowserNavigationParams( + val url: String? = null, + val sumoTopic: SumoTopic? = null, +) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuAccessPoint.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuAccessPoint.kt new file mode 100644 index 0000000000..f1d8b3a326 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuAccessPoint.kt @@ -0,0 +1,38 @@ +/* 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 org.mozilla.fenix.components.menu + +import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint + +/** + * The origin access points that was used to navigate to the Menu dialog. + */ +enum class MenuAccessPoint { + /** + * Menu was accessed from the browser. + */ + Browser, + + /** + * Menu was accessed from an external app (e.g. custom tab). + */ + External, + + /** + * Menu was accessed from the home screen. + */ + Home, +} + +/** + * Returns the [FenixFxAEntryPoint] equivalent from the given [MenuAccessPoint]. + */ +internal fun MenuAccessPoint.toFenixFxAEntryPoint(): FenixFxAEntryPoint { + return when (this) { + MenuAccessPoint.Browser -> FenixFxAEntryPoint.BrowserToolbar + MenuAccessPoint.External -> FenixFxAEntryPoint.Unknown + MenuAccessPoint.Home -> FenixFxAEntryPoint.HomeMenu + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt index 05890cd71d..c0d9d6f11d 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt @@ -9,25 +9,42 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.lib.state.ext.observeAsState +import mozilla.components.service.fxa.manager.AccountState.NotAuthenticated import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.components.accounts.AccountState -import org.mozilla.fenix.components.lazyStore -import org.mozilla.fenix.components.menu.compose.MenuDialog +import org.mozilla.fenix.components.components +import org.mozilla.fenix.components.menu.compose.EXTENSIONS_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.ExtensionsSubmenu +import org.mozilla.fenix.components.menu.compose.MAIN_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.MainMenu import org.mozilla.fenix.components.menu.compose.MenuDialogBottomSheet +import org.mozilla.fenix.components.menu.compose.SAVE_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.SaveSubmenu +import org.mozilla.fenix.components.menu.compose.TOOLS_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.ToolsSubmenu +import org.mozilla.fenix.components.menu.middleware.MenuDialogMiddleware import org.mozilla.fenix.components.menu.middleware.MenuNavigationMiddleware +import org.mozilla.fenix.components.menu.store.BrowserMenuState import org.mozilla.fenix.components.menu.store.MenuAction import org.mozilla.fenix.components.menu.store.MenuState import org.mozilla.fenix.components.menu.store.MenuStore import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.SupportUtils.SumoTopic import org.mozilla.fenix.theme.FirefoxTheme /** @@ -35,18 +52,8 @@ import org.mozilla.fenix.theme.FirefoxTheme */ class MenuDialogFragment : BottomSheetDialogFragment() { - private val store by lazyStore { viewModelScope -> - MenuStore( - initialState = MenuState(), - middleware = listOf( - MenuNavigationMiddleware( - navController = findNavController(), - openSumoTopic = ::openSumoTopic, - scope = viewModelScope, - ), - ), - ) - } + private val args by navArgs() + private val browsingModeManager get() = (activity as HomeActivity).browsingModeManager override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = super.onCreateDialog(savedInstanceState).apply { @@ -59,6 +66,7 @@ class MenuDialogFragment : BottomSheetDialogFragment() { } } + @Suppress("LongMethod") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -69,42 +77,193 @@ class MenuDialogFragment : BottomSheetDialogFragment() { setContent { FirefoxTheme { MenuDialogBottomSheet(onRequestDismiss = {}) { - MenuDialog( - account = null, - accountState = AccountState.NO_ACCOUNT, - onSignInButtonClick = {}, - onHelpButtonClick = { - store.dispatch(MenuAction.Navigate.Help) - }, - onSettingsButtonClick = { - store.dispatch(MenuAction.Navigate.Settings) - }, - onBookmarksMenuClick = { - store.dispatch(MenuAction.Navigate.Bookmarks) - }, - onHistoryMenuClick = { - store.dispatch(MenuAction.Navigate.History) - }, - onDownloadsMenuClick = { - store.dispatch(MenuAction.Navigate.Downloads) - }, - onPasswordsMenuClick = { - store.dispatch(MenuAction.Navigate.Passwords) - }, - ) + val browserStore = components.core.store + val syncStore = components.backgroundServices.syncStore + val bookmarksStorage = components.core.bookmarksStorage + val addBookmarkUseCase = components.useCases.bookmarksUseCases.addBookmark + val printContentUseCase = components.useCases.sessionUseCases.printContent + val saveToPdfUseCase = components.useCases.sessionUseCases.saveToPdf + val selectedTab = browserStore.state.selectedTab + + val navHostController = rememberNavController() + val coroutineScope = rememberCoroutineScope() + val store = remember { + MenuStore( + initialState = MenuState( + browserMenuState = if (selectedTab != null) { + BrowserMenuState(selectedTab = selectedTab) + } else { + null + }, + ), + middleware = listOf( + MenuDialogMiddleware( + bookmarksStorage = bookmarksStorage, + addBookmarkUseCase = addBookmarkUseCase, + scope = coroutineScope, + ), + MenuNavigationMiddleware( + navController = findNavController(), + navHostController = navHostController, + browsingModeManager = browsingModeManager, + openToBrowser = ::openToBrowser, + scope = coroutineScope, + ), + ), + ) + } + + val account by syncStore.observeAsState(initialValue = null) { state -> state.account } + val accountState by syncStore.observeAsState(initialValue = NotAuthenticated) { state -> + state.accountState + } + val isBookmarked by store.observeAsState(initialValue = false) { state -> + state.browserMenuState != null && state.browserMenuState.bookmarkState.isBookmarked + } + + NavHost( + navController = navHostController, + startDestination = MAIN_MENU_ROUTE, + ) { + composable(route = MAIN_MENU_ROUTE) { + MainMenu( + accessPoint = args.accesspoint, + account = account, + accountState = accountState, + isPrivate = browsingModeManager.mode.isPrivate, + onMozillaAccountButtonClick = { + store.dispatch( + MenuAction.Navigate.MozillaAccount( + accountState = accountState, + accesspoint = args.accesspoint, + ), + ) + }, + onHelpButtonClick = { + store.dispatch(MenuAction.Navigate.Help) + }, + onSettingsButtonClick = { + store.dispatch(MenuAction.Navigate.Settings) + }, + onNewTabMenuClick = { + store.dispatch(MenuAction.Navigate.NewTab) + }, + onNewPrivateTabMenuClick = { + store.dispatch(MenuAction.Navigate.NewPrivateTab) + }, + onSwitchToDesktopSiteMenuClick = {}, + onFindInPageMenuClick = {}, + onToolsMenuClick = { + store.dispatch(MenuAction.Navigate.Tools) + }, + onSaveMenuClick = { + store.dispatch(MenuAction.Navigate.Save) + }, + onExtensionsMenuClick = { + store.dispatch(MenuAction.Navigate.Extensions) + }, + onBookmarksMenuClick = { + store.dispatch(MenuAction.Navigate.Bookmarks) + }, + onHistoryMenuClick = { + store.dispatch(MenuAction.Navigate.History) + }, + onDownloadsMenuClick = { + store.dispatch(MenuAction.Navigate.Downloads) + }, + onPasswordsMenuClick = { + store.dispatch(MenuAction.Navigate.Passwords) + }, + onCustomizeHomepageMenuClick = { + store.dispatch(MenuAction.Navigate.CustomizeHomepage) + }, + onNewInFirefoxMenuClick = { + store.dispatch(MenuAction.Navigate.ReleaseNotes) + }, + ) + } + + composable(route = TOOLS_MENU_ROUTE) { + ToolsSubmenu( + isReaderViewActive = false, + isTranslated = false, + onBackButtonClick = { + store.dispatch(MenuAction.Navigate.Back) + }, + onReaderViewMenuClick = {}, + onTranslatePageMenuClick = { + selectedTab?.let { + store.dispatch(MenuAction.Navigate.Translate) + } + }, + onPrintMenuClick = { + printContentUseCase() + dismiss() + }, + onShareMenuClick = { + selectedTab?.let { + store.dispatch(MenuAction.Navigate.Share) + } + }, + onOpenInAppMenuClick = {}, + ) + } + + composable(route = SAVE_MENU_ROUTE) { + SaveSubmenu( + isBookmarked = isBookmarked, + onBackButtonClick = { + store.dispatch(MenuAction.Navigate.Back) + }, + onBookmarkPageMenuClick = { + store.dispatch(MenuAction.AddBookmark) + }, + onEditBookmarkButtonClick = { + store.dispatch(MenuAction.Navigate.EditBookmark) + }, + onAddToShortcutsMenuClick = {}, + onAddToHomeScreenMenuClick = {}, + onSaveToCollectionMenuClick = {}, + onSaveAsPDFMenuClick = { + saveToPdfUseCase() + dismiss() + }, + ) + } + + composable(route = EXTENSIONS_MENU_ROUTE) { + ExtensionsSubmenu( + onBackButtonClick = { + store.dispatch(MenuAction.Navigate.Back) + }, + onManageExtensionsMenuClick = { + store.dispatch(MenuAction.Navigate.ManageExtensions) + }, + onDiscoverMoreExtensionsMenuClick = { + store.dispatch(MenuAction.Navigate.DiscoverMoreExtensions) + }, + ) + } + } } } } } - private fun openSumoTopic(topic: SumoTopic) = runIfFragmentIsAttached { - (activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getSumoURLForTopic( + private fun openToBrowser(params: BrowserNavigationParams) = runIfFragmentIsAttached { + val url = params.url ?: params.sumoTopic?.let { + SupportUtils.getSumoURLForTopic( context = requireContext(), - topic = topic, - ), - newTab = true, - from = BrowserDirection.FromMenuDialogFragment, - ) + topic = it, + ) + } + + url?.let { + (activity as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = url, + newTab = true, + from = BrowserDirection.FromMenuDialogFragment, + ) + } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ExtensionsSubmenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ExtensionsSubmenu.kt new file mode 100644 index 0000000000..2bfffb7103 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ExtensionsSubmenu.kt @@ -0,0 +1,101 @@ +/* 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 org.mozilla.fenix.components.menu.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.compose.header.SubmenuHeader +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.list.TextListItem +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +internal const val EXTENSIONS_MENU_ROUTE = "extensions_menu" + +@Composable +internal fun ExtensionsSubmenu( + onBackButtonClick: () -> Unit, + onManageExtensionsMenuClick: () -> Unit, + onDiscoverMoreExtensionsMenuClick: () -> Unit, +) { + Column { + SubmenuHeader( + header = stringResource(id = R.string.browser_menu_extensions), + onClick = onBackButtonClick, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column( + modifier = Modifier + .padding( + start = 16.dp, + top = 12.dp, + end = 16.dp, + bottom = 32.dp, + ), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + MenuGroup { + MenuItem( + label = stringResource(id = R.string.browser_menu_manage_extensions), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_extension_cog_24), + onClick = onManageExtensionsMenuClick, + ) + } + + MenuGroup { + TextListItem( + label = stringResource(id = R.string.browser_menu_discover_more_extensions), + onClick = onDiscoverMoreExtensionsMenuClick, + iconPainter = painterResource(R.drawable.mozac_ic_external_link_24), + iconTint = FirefoxTheme.colors.iconSecondary, + ) + } + } + } +} + +@LightDarkPreview +@Composable +private fun ExtensionsSubmenuPreview() { + FirefoxTheme { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + ExtensionsSubmenu( + onBackButtonClick = {}, + onManageExtensionsMenuClick = {}, + onDiscoverMoreExtensionsMenuClick = {}, + ) + } + } +} + +@LightDarkPreview +@Composable +private fun ExtensionsSubmenuPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + ExtensionsSubmenu( + onBackButtonClick = {}, + onManageExtensionsMenuClick = {}, + onDiscoverMoreExtensionsMenuClick = {}, + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MainMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MainMenu.kt new file mode 100644 index 0000000000..695fbfcbca --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MainMenu.kt @@ -0,0 +1,365 @@ +/* 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 org.mozilla.fenix.components.menu.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import mozilla.components.service.fxa.manager.AccountState +import mozilla.components.service.fxa.manager.AccountState.NotAuthenticated +import mozilla.components.service.fxa.store.Account +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.MenuAccessPoint +import org.mozilla.fenix.components.menu.compose.header.MenuHeader +import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +internal const val MAIN_MENU_ROUTE = "main_menu" + +/** + * Wrapper column containing the main menu items. + * + * @param accessPoint The [MenuAccessPoint] that was used to navigate to the menu dialog. + * @param account [Account] information available for a synced account. + * @param accountState The [AccountState] of a Mozilla account. + * @param isPrivate Whether or not the browsing mode is in private mode. + * @param onMozillaAccountButtonClick Invoked when the user clicks on Mozilla account button. + * @param onHelpButtonClick Invoked when the user clicks on the help button. + * @param onSettingsButtonClick Invoked when the user clicks on the settings button. + * @param onNewTabMenuClick Invoked when the user clicks on the new tab menu item. + * @param onNewPrivateTabMenuClick Invoked when the user clicks on the new private tab menu item. + * @param onSwitchToDesktopSiteMenuClick Invoked when the user clicks on the switch to desktop site + * menu toggle. + * @param onFindInPageMenuClick Invoked when the user clicks on the find in page menu item. + * @param onToolsMenuClick Invoked when the user clicks on the tools menu item. + * @param onSaveMenuClick Invoked when the user clicks on the save menu item. + * @param onExtensionsMenuClick Invoked when the user clicks on the extensions menu item. + * @param onBookmarksMenuClick Invoked when the user clicks on the bookmarks menu item. + * @param onHistoryMenuClick Invoked when the user clicks on the history menu item. + * @param onDownloadsMenuClick Invoked when the user clicks on the downloads menu item. + * @param onPasswordsMenuClick Invoked when the user clicks on the passwords menu item. + * @param onCustomizeHomepageMenuClick Invoked when the user clicks on the customize + * homepage menu item. + * @param onNewInFirefoxMenuClick Invoked when the user clicks on the release note menu item. + */ +@Suppress("LongParameterList") +@Composable +internal fun MainMenu( + accessPoint: MenuAccessPoint, + account: Account?, + accountState: AccountState, + isPrivate: Boolean, + onMozillaAccountButtonClick: () -> Unit, + onHelpButtonClick: () -> Unit, + onSettingsButtonClick: () -> Unit, + onNewTabMenuClick: () -> Unit, + onNewPrivateTabMenuClick: () -> Unit, + onSwitchToDesktopSiteMenuClick: () -> Unit, + onFindInPageMenuClick: () -> Unit, + onToolsMenuClick: () -> Unit, + onSaveMenuClick: () -> Unit, + onExtensionsMenuClick: () -> Unit, + onBookmarksMenuClick: () -> Unit, + onHistoryMenuClick: () -> Unit, + onDownloadsMenuClick: () -> Unit, + onPasswordsMenuClick: () -> Unit, + onCustomizeHomepageMenuClick: () -> Unit, + onNewInFirefoxMenuClick: () -> Unit, +) { + Column { + MenuHeader( + account = account, + accountState = accountState, + onMozillaAccountButtonClick = onMozillaAccountButtonClick, + onHelpButtonClick = onHelpButtonClick, + onSettingsButtonClick = onSettingsButtonClick, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column( + modifier = Modifier + .padding( + start = 16.dp, + top = 12.dp, + end = 16.dp, + bottom = 32.dp, + ), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + NewTabsMenuGroup( + accessPoint = accessPoint, + isPrivate = isPrivate, + onNewTabMenuClick = onNewTabMenuClick, + onNewPrivateTabMenuClick = onNewPrivateTabMenuClick, + ) + + ToolsAndActionsMenuGroup( + accessPoint = accessPoint, + onSwitchToDesktopSiteMenuClick = onSwitchToDesktopSiteMenuClick, + onFindInPageMenuClick = onFindInPageMenuClick, + onToolsMenuClick = onToolsMenuClick, + onSaveMenuClick = onSaveMenuClick, + onExtensionsMenuClick = onExtensionsMenuClick, + ) + + LibraryMenuGroup( + onBookmarksMenuClick = onBookmarksMenuClick, + onHistoryMenuClick = onHistoryMenuClick, + onDownloadsMenuClick = onDownloadsMenuClick, + onPasswordsMenuClick = onPasswordsMenuClick, + ) + + if (accessPoint == MenuAccessPoint.Home) { + HomepageMenuGroup( + onCustomizeHomepageMenuClick = onCustomizeHomepageMenuClick, + onNewInFirefoxMenuClick = onNewInFirefoxMenuClick, + ) + } + } + } +} + +@Composable +private fun NewTabsMenuGroup( + accessPoint: MenuAccessPoint, + isPrivate: Boolean, + onNewTabMenuClick: () -> Unit, + onNewPrivateTabMenuClick: () -> Unit, +) { + val isNewTabMenuEnabled: Boolean + val isNewPrivateTabMenuEnabled: Boolean + + when (accessPoint) { + MenuAccessPoint.Browser, + MenuAccessPoint.External, + -> { + isNewTabMenuEnabled = true + isNewPrivateTabMenuEnabled = true + } + + MenuAccessPoint.Home -> { + isNewTabMenuEnabled = isPrivate + isNewPrivateTabMenuEnabled = !isPrivate + } + } + + MenuGroup { + MenuItem( + label = stringResource(id = R.string.library_new_tab), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_plus_24), + state = if (isNewTabMenuEnabled) MenuItemState.ENABLED else MenuItemState.DISABLED, + onClick = onNewTabMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_new_private_tab), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_private_mode_circle_fill_24), + state = if (isNewPrivateTabMenuEnabled) MenuItemState.ENABLED else MenuItemState.DISABLED, + onClick = onNewPrivateTabMenuClick, + ) + } +} + +@Composable +private fun ToolsAndActionsMenuGroup( + accessPoint: MenuAccessPoint, + onSwitchToDesktopSiteMenuClick: () -> Unit, + onFindInPageMenuClick: () -> Unit, + onToolsMenuClick: () -> Unit, + onSaveMenuClick: () -> Unit, + onExtensionsMenuClick: () -> Unit, +) { + MenuGroup { + if (accessPoint == MenuAccessPoint.Browser) { + MenuItem( + label = stringResource(id = R.string.browser_menu_switch_to_desktop_site), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_device_desktop_24), + onClick = onSwitchToDesktopSiteMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_find_in_page_2), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_search_24), + onClick = onFindInPageMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_tools), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_tool_24), + onClick = onToolsMenuClick, + afterIconPainter = painterResource(id = R.drawable.mozac_ic_chevron_right_24), + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_save), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_save_24), + onClick = onSaveMenuClick, + afterIconPainter = painterResource(id = R.drawable.mozac_ic_chevron_right_24), + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + } + + MenuItem( + label = stringResource(id = R.string.browser_menu_extensions), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_extension_24), + onClick = onExtensionsMenuClick, + afterIconPainter = painterResource(id = R.drawable.mozac_ic_chevron_right_24), + ) + } +} + +@Composable +private fun LibraryMenuGroup( + onBookmarksMenuClick: () -> Unit, + onHistoryMenuClick: () -> Unit, + onDownloadsMenuClick: () -> Unit, + onPasswordsMenuClick: () -> Unit, +) { + MenuGroup { + MenuItem( + label = stringResource(id = R.string.library_bookmarks), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_bookmark_tray_fill_24), + onClick = onBookmarksMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.library_history), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_history_24), + onClick = onHistoryMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.library_downloads), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_download_24), + onClick = onDownloadsMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_passwords), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_login_24), + onClick = onPasswordsMenuClick, + ) + } +} + +@Composable +private fun HomepageMenuGroup( + onCustomizeHomepageMenuClick: () -> Unit, + onNewInFirefoxMenuClick: () -> Unit, +) { + MenuGroup { + MenuItem( + label = stringResource(id = R.string.browser_menu_customize_home_1), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_grid_add_24), + onClick = onCustomizeHomepageMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource( + id = R.string.browser_menu_new_in_firefox, + stringResource(id = R.string.app_name), + ), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_whats_new_24), + onClick = onNewInFirefoxMenuClick, + ) + } +} + +@LightDarkPreview +@Composable +private fun MenuDialogPreview() { + FirefoxTheme { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer3), + ) { + MainMenu( + accessPoint = MenuAccessPoint.Home, + account = null, + accountState = NotAuthenticated, + isPrivate = false, + onMozillaAccountButtonClick = {}, + onHelpButtonClick = {}, + onSettingsButtonClick = {}, + onNewTabMenuClick = {}, + onNewPrivateTabMenuClick = {}, + onSwitchToDesktopSiteMenuClick = {}, + onFindInPageMenuClick = {}, + onToolsMenuClick = {}, + onSaveMenuClick = {}, + onExtensionsMenuClick = {}, + onBookmarksMenuClick = {}, + onHistoryMenuClick = {}, + onDownloadsMenuClick = {}, + onPasswordsMenuClick = {}, + onCustomizeHomepageMenuClick = {}, + onNewInFirefoxMenuClick = {}, + ) + } + } +} + +@Preview +@Composable +private fun MenuDialogPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer3), + ) { + MainMenu( + accessPoint = MenuAccessPoint.Home, + account = null, + accountState = NotAuthenticated, + isPrivate = false, + onMozillaAccountButtonClick = {}, + onHelpButtonClick = {}, + onSettingsButtonClick = {}, + onNewTabMenuClick = {}, + onNewPrivateTabMenuClick = {}, + onSwitchToDesktopSiteMenuClick = {}, + onFindInPageMenuClick = {}, + onToolsMenuClick = {}, + onSaveMenuClick = {}, + onExtensionsMenuClick = {}, + onBookmarksMenuClick = {}, + onHistoryMenuClick = {}, + onDownloadsMenuClick = {}, + onPasswordsMenuClick = {}, + onCustomizeHomepageMenuClick = {}, + onNewInFirefoxMenuClick = {}, + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuDialog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuDialog.kt deleted file mode 100644 index a9747fce84..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuDialog.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* 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 org.mozilla.fenix.components.menu.compose - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import mozilla.components.service.fxa.store.Account -import org.mozilla.fenix.R -import org.mozilla.fenix.components.accounts.AccountState -import org.mozilla.fenix.components.accounts.AccountState.NO_ACCOUNT -import org.mozilla.fenix.components.menu.compose.header.MenuHeader -import org.mozilla.fenix.compose.Divider -import org.mozilla.fenix.compose.annotation.LightDarkPreview -import org.mozilla.fenix.compose.list.IconListItem -import org.mozilla.fenix.theme.FirefoxTheme -import org.mozilla.fenix.theme.Theme - -/** - * The menu bottom sheet dialog. - * - * @param account [Account] information available for a synced account. - * @param accountState The [AccountState] of a synced account. - * @param onSignInButtonClick Invoked when the user clicks on the "Sign in" button. - * @param onHelpButtonClick Invoked when the user clicks on the help button. - * @param onSettingsButtonClick Invoked when the user clicks on the settings button. - * @param onBookmarksMenuClick Invoked when the user clicks on the bookmarks menu item. - * @param onHistoryMenuClick Invoked when the user clicks on the history menu item. - * @param onDownloadsMenuClick Invoked when the user clicks on the downloads menu item. - * @param onPasswordsMenuClick Invoked when the user clicks on the passwords menu item. - */ -@Suppress("LongParameterList") -@Composable -fun MenuDialog( - account: Account?, - accountState: AccountState, - onSignInButtonClick: () -> Unit, - onHelpButtonClick: () -> Unit, - onSettingsButtonClick: () -> Unit, - onBookmarksMenuClick: () -> Unit, - onHistoryMenuClick: () -> Unit, - onDownloadsMenuClick: () -> Unit, - onPasswordsMenuClick: () -> Unit, -) { - Column { - MenuHeader( - account = account, - accountState = accountState, - onSignInButtonClick = onSignInButtonClick, - onHelpButtonClick = onHelpButtonClick, - onSettingsButtonClick = onSettingsButtonClick, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - MainMenu( - onBookmarksMenuClick = onBookmarksMenuClick, - onHistoryMenuClick = onHistoryMenuClick, - onDownloadsMenuClick = onDownloadsMenuClick, - onPasswordsMenuClick = onPasswordsMenuClick, - ) - } -} - -/** - * Wrapper column containing the main menu items. - */ -@Composable -private fun MainMenu( - onBookmarksMenuClick: () -> Unit, - onHistoryMenuClick: () -> Unit, - onDownloadsMenuClick: () -> Unit, - onPasswordsMenuClick: () -> Unit, -) { - Column( - modifier = Modifier - .padding( - start = 16.dp, - top = 12.dp, - end = 16.dp, - bottom = 32.dp, - ), - verticalArrangement = Arrangement.spacedBy(32.dp), - ) { - MenuGroup { - IconListItem( - label = stringResource(id = R.string.library_new_tab), - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_plus_24), - ) - - Divider(color = FirefoxTheme.colors.borderSecondary) - - IconListItem( - label = stringResource(id = R.string.browser_menu_new_private_tab), - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_private_mode_circle_fill_24), - ) - } - - LibraryMenuGroup( - onBookmarksMenuClick = onBookmarksMenuClick, - onHistoryMenuClick = onHistoryMenuClick, - onDownloadsMenuClick = onDownloadsMenuClick, - onPasswordsMenuClick = onPasswordsMenuClick, - ) - } -} - -@Composable -private fun LibraryMenuGroup( - onBookmarksMenuClick: () -> Unit, - onHistoryMenuClick: () -> Unit, - onDownloadsMenuClick: () -> Unit, - onPasswordsMenuClick: () -> Unit, -) { - MenuGroup { - IconListItem( - label = stringResource(id = R.string.library_bookmarks), - onClick = onBookmarksMenuClick, - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_bookmark_tray_fill_24), - ) - - Divider(color = FirefoxTheme.colors.borderSecondary) - - IconListItem( - label = stringResource(id = R.string.library_history), - onClick = onHistoryMenuClick, - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_history_24), - ) - - Divider(color = FirefoxTheme.colors.borderSecondary) - - IconListItem( - label = stringResource(id = R.string.library_downloads), - onClick = onDownloadsMenuClick, - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_download_24), - ) - - Divider(color = FirefoxTheme.colors.borderSecondary) - - IconListItem( - label = stringResource(id = R.string.browser_menu_passwords), - onClick = onPasswordsMenuClick, - beforeIconPainter = painterResource(id = R.drawable.mozac_ic_login_24), - ) - } -} - -@LightDarkPreview -@Composable -private fun MenuDialogPreview() { - FirefoxTheme { - Column( - modifier = Modifier - .background(color = FirefoxTheme.colors.layer3), - ) { - MenuDialog( - account = null, - accountState = NO_ACCOUNT, - onSignInButtonClick = {}, - onHelpButtonClick = {}, - onSettingsButtonClick = {}, - onBookmarksMenuClick = {}, - onHistoryMenuClick = {}, - onDownloadsMenuClick = {}, - onPasswordsMenuClick = {}, - ) - } - } -} - -@Preview -@Composable -private fun MenuDialogPrivatePreview() { - FirefoxTheme(theme = Theme.Private) { - Column( - modifier = Modifier - .background(color = FirefoxTheme.colors.layer3), - ) { - MenuDialog( - account = null, - accountState = NO_ACCOUNT, - onSignInButtonClick = {}, - onHelpButtonClick = {}, - onSettingsButtonClick = {}, - onBookmarksMenuClick = {}, - onHistoryMenuClick = {}, - onDownloadsMenuClick = {}, - onPasswordsMenuClick = {}, - ) - } - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuGroup.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuGroup.kt index 1b2fb0ab2f..811ab69db9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuGroup.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuGroup.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.unit.dp import org.mozilla.fenix.R import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.compose.annotation.LightDarkPreview -import org.mozilla.fenix.compose.list.IconListItem import org.mozilla.fenix.theme.FirefoxTheme private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(12.dp) @@ -60,14 +59,14 @@ private fun MenuGroupPreview() { .padding(16.dp), ) { MenuGroup { - IconListItem( + MenuItem( label = stringResource(id = R.string.browser_menu_add_to_homescreen), beforeIconPainter = painterResource(id = R.drawable.mozac_ic_plus_24), ) Divider(color = FirefoxTheme.colors.borderSecondary) - IconListItem( + MenuItem( label = stringResource(id = R.string.browser_menu_add_to_homescreen), beforeIconPainter = painterResource(id = R.drawable.mozac_ic_plus_24), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuItem.kt new file mode 100644 index 0000000000..41f64670a2 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuItem.kt @@ -0,0 +1,202 @@ +/* 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 org.mozilla.fenix.components.menu.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.list.IconListItem +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * An [IconListItem] wrapper for menu items in a [MenuGroup] with an optional icon at the end. + * + * @param label The label in the menu item. + * @param beforeIconPainter [Painter] used to display an [Icon] before the list item. + * @param beforeIconDescription Content description of the icon. + * @param description An optional description text below the label. + * @param state The state of the menu item to display. + * @param onClick Invoked when the user clicks on the item. + * @param afterIconPainter [Painter] used to display an [IconButton] after the list item. + * @param afterIconDescription Content description of the icon. + */ +@Composable +internal fun MenuItem( + label: String, + beforeIconPainter: Painter, + beforeIconDescription: String? = null, + description: String? = null, + state: MenuItemState = MenuItemState.ENABLED, + onClick: (() -> Unit)? = null, + afterIconPainter: Painter? = null, + afterIconDescription: String? = null, +) { + val labelTextColor = getLabelTextColor(state = state) + val iconTint = getIconTint(state = state) + val enabled = state != MenuItemState.DISABLED + + IconListItem( + label = label, + labelTextColor = labelTextColor, + description = description, + enabled = enabled, + onClick = onClick, + beforeIconPainter = beforeIconPainter, + beforeIconDescription = beforeIconDescription, + beforeIconTint = iconTint, + afterIconPainter = afterIconPainter, + afterIconDescription = afterIconDescription, + afterIconTint = iconTint, + ) +} + +/** + * An [IconListItem] wrapper for menu items in a [MenuGroup] with an optional text button at the end. + * + * @param label The label in the menu item. + * @param beforeIconPainter [Painter] used to display an [Icon] before the list item. + * @param beforeIconDescription Content description of the icon. + * @param description An optional description text below the label. + * @param state The state of the menu item to display. + * @param onClick Invoked when the user clicks on the item. + * @param afterButtonText The button text to be displayed after the list item. + * @param afterButtonTextColor [Color] to apply to [afterButtonText]. + * @param onAfterButtonClick Called when the user clicks on the text button. + */ +@Composable +internal fun MenuItem( + label: String, + beforeIconPainter: Painter, + beforeIconDescription: String? = null, + description: String? = null, + state: MenuItemState = MenuItemState.ENABLED, + onClick: (() -> Unit)? = null, + afterButtonText: String? = null, + afterButtonTextColor: Color = FirefoxTheme.colors.actionPrimary, + onAfterButtonClick: (() -> Unit)? = null, +) { + val labelTextColor = getLabelTextColor(state = state) + val iconTint = getIconTint(state = state) + val enabled = state != MenuItemState.DISABLED + + IconListItem( + label = label, + labelTextColor = labelTextColor, + description = description, + enabled = enabled, + onClick = onClick, + beforeIconPainter = beforeIconPainter, + beforeIconDescription = beforeIconDescription, + beforeIconTint = iconTint, + afterButtonText = afterButtonText, + afterButtonTextColor = afterButtonTextColor, + onAfterButtonClick = onAfterButtonClick, + ) +} + +/** + * Enum containing all the supported state for the menu item. + */ +enum class MenuItemState { + /** + * The menu item is enabled. + */ + ENABLED, + + /** + * The menu item is disabled and is not clickable. + */ + DISABLED, + + /** + * The menu item is highlighted to indicate the feature behind the menu item is active. + */ + ACTIVE, + + /** + * The menu item is highlighted to indicate the feature behind the menu item is destructive. + */ + WARNING, +} + +@Composable +private fun getLabelTextColor(state: MenuItemState): Color { + return when (state) { + MenuItemState.ACTIVE -> FirefoxTheme.colors.textAccent + MenuItemState.WARNING -> FirefoxTheme.colors.textCritical + else -> FirefoxTheme.colors.textPrimary + } +} + +@Composable +private fun getIconTint(state: MenuItemState): Color { + return when (state) { + MenuItemState.ACTIVE -> FirefoxTheme.colors.iconAccentViolet + MenuItemState.WARNING -> FirefoxTheme.colors.iconCritical + else -> FirefoxTheme.colors.iconSecondary + } +} + +@LightDarkPreview +@Composable +private fun MenuItemPreview() { + FirefoxTheme { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer3) + .padding(16.dp), + ) { + MenuGroup { + for (state in MenuItemState.entries) { + MenuItem( + label = stringResource(id = R.string.browser_menu_translations), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_translate_24), + state = state, + onClick = {}, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + } + + for (state in MenuItemState.entries) { + MenuItem( + label = stringResource(id = R.string.browser_menu_extensions), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_extension_24), + state = state, + onClick = {}, + afterIconPainter = painterResource(id = R.drawable.mozac_ic_chevron_right_24), + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + } + + for (state in MenuItemState.entries) { + MenuItem( + label = stringResource(id = R.string.library_bookmarks), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_bookmark_tray_fill_24), + state = state, + onClick = {}, + afterButtonText = stringResource(id = R.string.browser_menu_edit), + onAfterButtonClick = {}, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + } + } + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/SaveSubmenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/SaveSubmenu.kt new file mode 100644 index 0000000000..9ceda75cea --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/SaveSubmenu.kt @@ -0,0 +1,163 @@ +/* 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 org.mozilla.fenix.components.menu.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.compose.header.SubmenuHeader +import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +internal const val SAVE_MENU_ROUTE = "save_menu" + +@Suppress("LongParameterList") +@Composable +internal fun SaveSubmenu( + isBookmarked: Boolean, + onBackButtonClick: () -> Unit, + onBookmarkPageMenuClick: () -> Unit, + onEditBookmarkButtonClick: () -> Unit, + onAddToShortcutsMenuClick: () -> Unit, + onAddToHomeScreenMenuClick: () -> Unit, + onSaveToCollectionMenuClick: () -> Unit, + onSaveAsPDFMenuClick: () -> Unit, +) { + Column { + SubmenuHeader( + header = stringResource(id = R.string.browser_menu_save), + onClick = onBackButtonClick, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column( + modifier = Modifier + .padding( + start = 16.dp, + top = 12.dp, + end = 16.dp, + bottom = 32.dp, + ), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + MenuGroup { + BookmarkMenuItem( + isBookmarked = isBookmarked, + onBookmarkPageMenuClick = onBookmarkPageMenuClick, + onEditBookmarkButtonClick = onEditBookmarkButtonClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_add_to_shortcuts), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_pin_24), + onClick = onAddToShortcutsMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_add_to_homescreen_2), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_add_to_homescreen_24), + onClick = onAddToHomeScreenMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_save_to_collection), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_collection_24), + onClick = onSaveToCollectionMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_save_as_pdf), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_save_file_24), + onClick = onSaveAsPDFMenuClick, + ) + } + } + } +} + +@Composable +private fun BookmarkMenuItem( + isBookmarked: Boolean, + onBookmarkPageMenuClick: () -> Unit, + onEditBookmarkButtonClick: () -> Unit, +) { + if (isBookmarked) { + MenuItem( + label = stringResource(id = R.string.browser_menu_edit_bookmark), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_bookmark_fill_24), + state = MenuItemState.ACTIVE, + onClick = onEditBookmarkButtonClick, + ) + } else { + MenuItem( + label = stringResource(id = R.string.browser_menu_bookmark_this_page), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_bookmark_24), + onClick = onBookmarkPageMenuClick, + ) + } +} + +@LightDarkPreview +@Composable +private fun SaveSubmenuPreview() { + FirefoxTheme { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + SaveSubmenu( + isBookmarked = false, + onBackButtonClick = {}, + onBookmarkPageMenuClick = {}, + onEditBookmarkButtonClick = {}, + onAddToShortcutsMenuClick = {}, + onAddToHomeScreenMenuClick = {}, + onSaveToCollectionMenuClick = {}, + onSaveAsPDFMenuClick = {}, + ) + } + } +} + +@Preview +@Composable +private fun SaveSubmenuPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + SaveSubmenu( + isBookmarked = false, + onBackButtonClick = {}, + onBookmarkPageMenuClick = {}, + onEditBookmarkButtonClick = {}, + onAddToShortcutsMenuClick = {}, + onAddToHomeScreenMenuClick = {}, + onSaveToCollectionMenuClick = {}, + onSaveAsPDFMenuClick = {}, + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ToolsSubmenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ToolsSubmenu.kt new file mode 100644 index 0000000000..7f80d5c3e9 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/ToolsSubmenu.kt @@ -0,0 +1,184 @@ +/* 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 org.mozilla.fenix.components.menu.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.components.menu.compose.header.SubmenuHeader +import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +internal const val TOOLS_MENU_ROUTE = "tools_menu" + +@Suppress("LongParameterList") +@Composable +internal fun ToolsSubmenu( + isReaderViewActive: Boolean, + isTranslated: Boolean, + onBackButtonClick: () -> Unit, + onReaderViewMenuClick: () -> Unit, + onTranslatePageMenuClick: () -> Unit, + onPrintMenuClick: () -> Unit, + onShareMenuClick: () -> Unit, + onOpenInAppMenuClick: () -> Unit, +) { + Column { + SubmenuHeader( + header = stringResource(id = R.string.browser_menu_tools), + onClick = onBackButtonClick, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column( + modifier = Modifier + .padding( + start = 16.dp, + top = 12.dp, + end = 16.dp, + bottom = 32.dp, + ), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + MenuGroup { + ReaderViewMenuItem( + isReaderViewActive = isReaderViewActive, + onClick = onReaderViewMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + TranslationMenuItem( + isTranslated = isTranslated, + onClick = onTranslatePageMenuClick, + ) + } + + MenuGroup { + MenuItem( + label = stringResource(id = R.string.browser_menu_print), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_print_24), + onClick = onPrintMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_share_2), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_share_android_24), + onClick = onShareMenuClick, + ) + + Divider(color = FirefoxTheme.colors.borderSecondary) + + MenuItem( + label = stringResource(id = R.string.browser_menu_open_app_link), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_more_grid_24), + onClick = onOpenInAppMenuClick, + ) + } + } + } +} + +@Composable +private fun ReaderViewMenuItem( + isReaderViewActive: Boolean, + onClick: () -> Unit, +) { + if (isReaderViewActive) { + MenuItem( + label = stringResource(id = R.string.browser_menu_turn_off_reader_view), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_reader_view_fill_24), + state = MenuItemState.ACTIVE, + onClick = onClick, + ) + } else { + MenuItem( + label = stringResource(id = R.string.browser_menu_turn_on_reader_view), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_reader_view_24), + onClick = onClick, + ) + } +} + +@Composable +private fun TranslationMenuItem( + isTranslated: Boolean, + onClick: () -> Unit, +) { + if (isTranslated) { + MenuItem( + label = stringResource( + id = R.string.browser_menu_translated_to, + stringResource(id = R.string.app_name), + ), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_translate_24), + state = MenuItemState.ACTIVE, + onClick = onClick, + ) + } else { + MenuItem( + label = stringResource(id = R.string.browser_menu_translate_page), + beforeIconPainter = painterResource(id = R.drawable.mozac_ic_translate_24), + onClick = onClick, + ) + } +} + +@LightDarkPreview +@Composable +private fun ToolsSubmenuPreview() { + FirefoxTheme { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + ToolsSubmenu( + isReaderViewActive = false, + isTranslated = false, + onBackButtonClick = {}, + onReaderViewMenuClick = {}, + onTranslatePageMenuClick = {}, + onPrintMenuClick = {}, + onShareMenuClick = {}, + onOpenInAppMenuClick = {}, + ) + } + } +} + +@Preview +@Composable +private fun ToolsSubmenuPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + Column( + modifier = Modifier.background(color = FirefoxTheme.colors.layer3), + ) { + ToolsSubmenu( + isReaderViewActive = false, + isTranslated = false, + onBackButtonClick = {}, + onReaderViewMenuClick = {}, + onTranslatePageMenuClick = {}, + onPrintMenuClick = {}, + onShareMenuClick = {}, + onOpenInAppMenuClick = {}, + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MenuHeader.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MenuHeader.kt index 1c31f8b87f..a9391696d9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MenuHeader.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MenuHeader.kt @@ -20,10 +20,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import mozilla.components.service.fxa.manager.AccountState +import mozilla.components.service.fxa.manager.AccountState.NotAuthenticated import mozilla.components.service.fxa.store.Account import org.mozilla.fenix.R -import org.mozilla.fenix.components.accounts.AccountState -import org.mozilla.fenix.components.accounts.AccountState.NO_ACCOUNT import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme @@ -33,7 +33,7 @@ import org.mozilla.fenix.theme.Theme internal fun MenuHeader( account: Account?, accountState: AccountState, - onSignInButtonClick: () -> Unit, + onMozillaAccountButtonClick: () -> Unit, onHelpButtonClick: () -> Unit, onSettingsButtonClick: () -> Unit, ) { @@ -46,7 +46,7 @@ internal fun MenuHeader( MozillaAccountMenuButton( account = account, accountState = accountState, - onSignInButtonClick = onSignInButtonClick, + onClick = onMozillaAccountButtonClick, modifier = Modifier.weight(1f), ) @@ -86,8 +86,8 @@ private fun MenuHeaderPreview() { ) { MenuHeader( account = null, - accountState = NO_ACCOUNT, - onSignInButtonClick = {}, + accountState = NotAuthenticated, + onMozillaAccountButtonClick = {}, onHelpButtonClick = {}, onSettingsButtonClick = {}, ) @@ -105,8 +105,8 @@ private fun MenuHeaderPrivatePreview() { ) { MenuHeader( account = null, - accountState = NO_ACCOUNT, - onSignInButtonClick = {}, + accountState = NotAuthenticated, + onMozillaAccountButtonClick = {}, onHelpButtonClick = {}, onSettingsButtonClick = {}, ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MozillaAccountMenuButton.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MozillaAccountMenuButton.kt index 9cf00e248a..4e8663c81a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MozillaAccountMenuButton.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/MozillaAccountMenuButton.kt @@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.Text @@ -24,24 +26,28 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import mozilla.components.service.fxa.manager.AccountState +import mozilla.components.service.fxa.manager.AccountState.Authenticated +import mozilla.components.service.fxa.manager.AccountState.Authenticating +import mozilla.components.service.fxa.manager.AccountState.AuthenticationProblem +import mozilla.components.service.fxa.manager.AccountState.NotAuthenticated import mozilla.components.service.fxa.store.Account import org.mozilla.fenix.R -import org.mozilla.fenix.components.accounts.AccountState -import org.mozilla.fenix.components.accounts.AccountState.AUTHENTICATED -import org.mozilla.fenix.components.accounts.AccountState.NEEDS_REAUTHENTICATION -import org.mozilla.fenix.components.accounts.AccountState.NO_ACCOUNT +import org.mozilla.fenix.compose.Image import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.Theme private val BUTTON_HEIGHT = 56.dp private val BUTTON_SHAPE = RoundedCornerShape(size = 8.dp) +private val ICON_SHAPE = RoundedCornerShape(size = 24.dp) +private val AVATAR_SIZE = 24.dp @Composable internal fun MozillaAccountMenuButton( account: Account?, accountState: AccountState, - onSignInButtonClick: () -> Unit, + onClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -51,13 +57,13 @@ internal fun MozillaAccountMenuButton( shape = BUTTON_SHAPE, ) .clip(shape = BUTTON_SHAPE) - .clickable { onSignInButtonClick() } + .clickable { onClick() } .defaultMinSize(minHeight = BUTTON_HEIGHT), verticalAlignment = Alignment.CenterVertically, ) { Spacer(modifier = Modifier.width(4.dp)) - AvatarIcon() + AvatarIcon(account) Column( modifier = Modifier @@ -65,7 +71,7 @@ internal fun MozillaAccountMenuButton( .weight(1f), ) { when (accountState) { - NO_ACCOUNT -> { + NotAuthenticated -> { Text( text = stringResource(id = R.string.browser_menu_sign_in), color = FirefoxTheme.colors.textSecondary, @@ -81,7 +87,7 @@ internal fun MozillaAccountMenuButton( ) } - NEEDS_REAUTHENTICATION -> { + AuthenticationProblem -> { Text( text = stringResource(id = R.string.browser_menu_sign_back_in_to_sync), color = FirefoxTheme.colors.textSecondary, @@ -91,13 +97,13 @@ internal fun MozillaAccountMenuButton( Text( text = stringResource(id = R.string.browser_menu_syncing_paused_caption), - color = FirefoxTheme.colors.textWarning, + color = FirefoxTheme.colors.textCritical, maxLines = 2, style = FirefoxTheme.typography.caption, ) } - AUTHENTICATED -> { + Authenticated -> { Text( text = account?.displayName ?: account?.email ?: stringResource(id = R.string.browser_menu_account_settings), @@ -106,14 +112,16 @@ internal fun MozillaAccountMenuButton( style = FirefoxTheme.typography.headline7, ) } + + is Authenticating -> Unit } } - if (accountState == NEEDS_REAUTHENTICATION) { + if (accountState == AuthenticationProblem) { Icon( painter = painterResource(R.drawable.mozac_ic_warning_fill_24), contentDescription = null, - tint = FirefoxTheme.colors.iconWarning, + tint = FirefoxTheme.colors.iconCritical, ) Spacer(modifier = Modifier.width(8.dp)) @@ -122,20 +130,44 @@ internal fun MozillaAccountMenuButton( } @Composable -private fun AvatarIcon() { +private fun FallbackAvatarIcon() { Icon( painter = painterResource(id = R.drawable.mozac_ic_avatar_circle_24), contentDescription = null, modifier = Modifier .background( color = FirefoxTheme.colors.layer2, - shape = RoundedCornerShape(size = 24.dp), + shape = ICON_SHAPE, ) .padding(all = 4.dp), tint = FirefoxTheme.colors.iconSecondary, ) } +@Composable +private fun AvatarIcon(account: Account?) { + val avatarUrl = account?.avatar?.url + + if (avatarUrl != null) { + Image( + url = avatarUrl, + modifier = Modifier + .background( + color = FirefoxTheme.colors.layer2, + shape = ICON_SHAPE, + ) + .padding(all = 4.dp) + .size(AVATAR_SIZE) + .clip(CircleShape), + targetSize = AVATAR_SIZE, + placeholder = { FallbackAvatarIcon() }, + fallback = { FallbackAvatarIcon() }, + ) + } else { + FallbackAvatarIcon() + } +} + @Composable private fun MenuHeaderPreviewContent() { Column( @@ -146,14 +178,14 @@ private fun MenuHeaderPreviewContent() { ) { MozillaAccountMenuButton( account = null, - accountState = NO_ACCOUNT, - onSignInButtonClick = {}, + accountState = NotAuthenticated, + onClick = {}, ) MozillaAccountMenuButton( account = null, - accountState = NEEDS_REAUTHENTICATION, - onSignInButtonClick = {}, + accountState = AuthenticationProblem, + onClick = {}, ) MozillaAccountMenuButton( @@ -165,8 +197,8 @@ private fun MenuHeaderPreviewContent() { currentDeviceId = null, sessionToken = null, ), - accountState = AUTHENTICATED, - onSignInButtonClick = {}, + accountState = Authenticated, + onClick = {}, ) MozillaAccountMenuButton( @@ -178,8 +210,8 @@ private fun MenuHeaderPreviewContent() { currentDeviceId = null, sessionToken = null, ), - accountState = AUTHENTICATED, - onSignInButtonClick = {}, + accountState = Authenticated, + onClick = {}, ) MozillaAccountMenuButton( @@ -191,8 +223,8 @@ private fun MenuHeaderPreviewContent() { currentDeviceId = null, sessionToken = null, ), - accountState = AUTHENTICATED, - onSignInButtonClick = {}, + accountState = Authenticated, + onClick = {}, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/SubmenuHeader.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/SubmenuHeader.kt new file mode 100644 index 0000000000..6b47ea17db --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/header/SubmenuHeader.kt @@ -0,0 +1,94 @@ +/* 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 org.mozilla.fenix.components.menu.compose.header + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +@Composable +internal fun SubmenuHeader( + header: String, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .padding(start = 4.dp, end = 16.dp) + .defaultMinSize(minHeight = 56.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = { onClick() }, + ) { + Icon( + painter = painterResource(id = R.drawable.mozac_ic_back_24), + contentDescription = null, + tint = FirefoxTheme.colors.iconSecondary, + ) + } + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = header, + modifier = Modifier + .weight(1f) + .semantics { heading() }, + color = FirefoxTheme.colors.textSecondary, + style = FirefoxTheme.typography.headline7, + ) + } +} + +@LightDarkPreview +@Composable +private fun SubmenuHeaderPreview() { + FirefoxTheme { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer3), + ) { + SubmenuHeader( + header = "sub-menu header", + onClick = {}, + ) + } + } +} + +@Preview +@Composable +private fun SubmenuMenuHeaderPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer3), + ) { + SubmenuHeader( + header = "sub-menu header", + onClick = {}, + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuDialogMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuDialogMiddleware.kt new file mode 100644 index 0000000000..16df4ed576 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuDialogMiddleware.kt @@ -0,0 +1,93 @@ +/* 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 org.mozilla.fenix.components.menu.middleware + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.browser.state.ext.getUrl +import mozilla.components.concept.storage.BookmarksStorage +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.MiddlewareContext +import mozilla.components.lib.state.Store +import org.mozilla.fenix.components.bookmarks.BookmarksUseCase +import org.mozilla.fenix.components.menu.store.BookmarkState +import org.mozilla.fenix.components.menu.store.MenuAction +import org.mozilla.fenix.components.menu.store.MenuState + +/** + * [Middleware] implementation for handling [MenuAction] and managing the [MenuState] for the menu + * dialog. + * + * @param bookmarksStorage An instance of the [BookmarksStorage] used + * to query matching bookmarks. + * @param addBookmarkUseCase The [BookmarksUseCase.AddBookmarksUseCase] for adding the + * selected tab as a bookmark. + * @param scope [CoroutineScope] used to launch coroutines. + */ +class MenuDialogMiddleware( + private val bookmarksStorage: BookmarksStorage, + private val addBookmarkUseCase: BookmarksUseCase.AddBookmarksUseCase, + private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), +) : Middleware { + + override fun invoke( + context: MiddlewareContext, + next: (MenuAction) -> Unit, + action: MenuAction, + ) { + when (action) { + is MenuAction.InitAction -> initialize(context.store) + is MenuAction.AddBookmark -> addBookmark(context.store) + else -> Unit + } + + next(action) + } + + private fun initialize( + store: Store, + ) = scope.launch { + val url = store.state.browserMenuState?.selectedTab?.content?.url ?: return@launch + val bookmark = + bookmarksStorage.getBookmarksWithUrl(url).firstOrNull { it.url == url } ?: return@launch + + store.dispatch( + MenuAction.UpdateBookmarkState( + bookmarkState = BookmarkState( + guid = bookmark.guid, + isBookmarked = true, + ), + ), + ) + } + + private fun addBookmark( + store: Store, + ) = scope.launch { + val browserMenuState = store.state.browserMenuState ?: return@launch + + if (browserMenuState.bookmarkState.isBookmarked) { + return@launch + } + + val selectedTab = browserMenuState.selectedTab + val url = selectedTab.getUrl() ?: return@launch + + val guid = addBookmarkUseCase( + url = url, + title = selectedTab.content.title, + ) + + store.dispatch( + MenuAction.UpdateBookmarkState( + BookmarkState( + guid = guid, + isBookmarked = true, + ), + ), + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt index 7c0a03d5ab..21a361bd0b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/middleware/MenuNavigationMiddleware.kt @@ -5,18 +5,35 @@ package org.mozilla.fenix.components.menu.middleware import androidx.navigation.NavController +import androidx.navigation.NavHostController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot +import mozilla.components.browser.state.ext.getUrl +import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext +import mozilla.components.service.fxa.manager.AccountState.Authenticated +import mozilla.components.service.fxa.manager.AccountState.Authenticating +import mozilla.components.service.fxa.manager.AccountState.AuthenticationProblem +import mozilla.components.service.fxa.manager.AccountState.NotAuthenticated import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager +import org.mozilla.fenix.components.menu.BrowserNavigationParams import org.mozilla.fenix.components.menu.MenuDialogFragmentDirections +import org.mozilla.fenix.components.menu.compose.EXTENSIONS_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.SAVE_MENU_ROUTE +import org.mozilla.fenix.components.menu.compose.TOOLS_MENU_ROUTE import org.mozilla.fenix.components.menu.store.MenuAction import org.mozilla.fenix.components.menu.store.MenuState import org.mozilla.fenix.components.menu.store.MenuStore +import org.mozilla.fenix.components.menu.toFenixFxAEntryPoint import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.settings.SupportUtils.AMO_HOMEPAGE_FOR_ANDROID import org.mozilla.fenix.settings.SupportUtils.SumoTopic /** @@ -24,25 +41,62 @@ import org.mozilla.fenix.settings.SupportUtils.SumoTopic * dispatched to the [MenuStore]. * * @param navController [NavController] used for navigation. - * @param openSumoTopic Callback to open the provided [SumoTopic] in a new browser tab. + * @param navHostController [NavHostController] used for Compose navigation. + * @param browsingModeManager [BrowsingModeManager] used for setting the browsing mode. + * @param openToBrowser Callback to open the provided [BrowserNavigationParams] + * in a new browser tab. * @param scope [CoroutineScope] used to launch coroutines. */ class MenuNavigationMiddleware( private val navController: NavController, - private val openSumoTopic: (topic: SumoTopic) -> Unit, + private val navHostController: NavHostController, + private val browsingModeManager: BrowsingModeManager, + private val openToBrowser: (params: BrowserNavigationParams) -> Unit, private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main), ) : Middleware { + @Suppress("CyclomaticComplexMethod", "LongMethod") override fun invoke( context: MiddlewareContext, next: (MenuAction) -> Unit, action: MenuAction, ) { + // Get the current state before further processing of the chain of actions. + // This is to ensure that any navigation action will be using correct + // state properties before they are modified due to other actions being + // dispatched and processes. + val currentState = context.state + next(action) scope.launch { when (action) { - is MenuAction.Navigate.Help -> openSumoTopic(SumoTopic.HELP) + is MenuAction.Navigate.MozillaAccount -> { + when (action.accountState) { + Authenticated -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalAccountSettingsFragment(), + ) + + AuthenticationProblem -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalAccountProblemFragment( + entrypoint = action.accesspoint.toFenixFxAEntryPoint(), + ), + ) + + is Authenticating, NotAuthenticated -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalTurnOnSync( + entrypoint = action.accesspoint.toFenixFxAEntryPoint(), + ), + ) + } + } + + is MenuAction.Navigate.Help -> openToBrowser( + BrowserNavigationParams(sumoTopic = SumoTopic.HELP), + ) is MenuAction.Navigate.Settings -> navController.nav( R.id.menuDialogFragment, @@ -69,8 +123,82 @@ class MenuNavigationMiddleware( MenuDialogFragmentDirections.actionGlobalSavedLoginsAuthFragment(), ) + is MenuAction.Navigate.CustomizeHomepage -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalHomeSettingsFragment(), + ) + + is MenuAction.Navigate.ReleaseNotes -> openToBrowser( + BrowserNavigationParams(url = SupportUtils.WHATS_NEW_URL), + ) + + is MenuAction.Navigate.Tools -> navHostController.navigate(route = TOOLS_MENU_ROUTE) + + is MenuAction.Navigate.Save -> navHostController.navigate(route = SAVE_MENU_ROUTE) + + is MenuAction.Navigate.Extensions -> navHostController.navigate(route = EXTENSIONS_MENU_ROUTE) + + is MenuAction.Navigate.Back -> navHostController.popBackStack() + + is MenuAction.Navigate.EditBookmark -> { + currentState.browserMenuState?.bookmarkState?.guid?.let { guidToEdit -> + navController.nav( + R.id.menuDialogFragment, + BrowserFragmentDirections.actionGlobalBookmarkEditFragment( + guidToEdit = guidToEdit, + requiresSnackbarPaddingForToolbar = true, + ), + ) + } + } + + is MenuAction.Navigate.Translate -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionMenuDialogFragmentToTranslationsDialogFragment(), + ) + + is MenuAction.Navigate.Share -> { + currentState.browserMenuState?.selectedTab?.let { selectedTab -> + navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalShareFragment( + sessionId = selectedTab.id, + data = arrayOf( + ShareData( + url = selectedTab.getUrl(), + title = selectedTab.content.title, + ), + ), + showPage = true, + ), + ) + } + } + + is MenuAction.Navigate.ManageExtensions -> navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalAddonsManagementFragment(), + ) + + is MenuAction.Navigate.DiscoverMoreExtensions -> openToBrowser( + BrowserNavigationParams(url = AMO_HOMEPAGE_FOR_ANDROID), + ) + + is MenuAction.Navigate.NewTab -> openNewTab(isPrivate = false) + + is MenuAction.Navigate.NewPrivateTab -> openNewTab(isPrivate = true) + else -> Unit } } } + + private fun openNewTab(isPrivate: Boolean) { + browsingModeManager.mode = BrowsingMode.fromBoolean(isPrivate) + + navController.nav( + R.id.menuDialogFragment, + MenuDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true), + ) + } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt index 1400deeae1..021bb63687 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuAction.kt @@ -5,6 +5,8 @@ package org.mozilla.fenix.components.menu.store import mozilla.components.lib.state.Action +import mozilla.components.service.fxa.manager.AccountState +import org.mozilla.fenix.components.menu.MenuAccessPoint /** * Actions to dispatch through the [MenuStore] to modify the [MenuState]. @@ -12,17 +14,41 @@ import mozilla.components.lib.state.Action sealed class MenuAction : Action { /** - * Updates whether or not the current selected tab is bookmarked. + * [MenuAction] dispatched to indicate that the store is initialized and + * ready to use. This action is dispatched automatically before any other + * action is processed. Its main purpose is to trigger initialization logic + * in middlewares. + */ + data object InitAction : MenuAction() + + /** + * [MenuAction] dispatched when a bookmark is to be added. + */ + data object AddBookmark : MenuAction() + + /** + * [MenuAction] dispatched when a bookmark state is updated. * - * @property isBookmarked Whether or not the current selected is bookmarked. + * @property bookmarkState The new [BookmarkState] to be updated. */ - data class UpdateBookmarked(val isBookmarked: Boolean) : MenuAction() + data class UpdateBookmarkState(val bookmarkState: BookmarkState) : MenuAction() /** * [MenuAction] dispatched when a navigation event occurs for a specific destination. */ sealed class Navigate : MenuAction() { + /** + * [Navigate] action dispatched when navigating to Mozilla account. + * + * @property accountState The [AccountState] of a Mozilla account. + * @property accesspoint The access point that was used to navigate to the menu. + */ + data class MozillaAccount( + val accountState: AccountState, + val accesspoint: MenuAccessPoint, + ) : Navigate() + /** * [Navigate] action dispatched when navigating to the help SUMO article. */ @@ -52,5 +78,70 @@ sealed class MenuAction : Action { * [Navigate] action dispatched when navigating to passwords. */ data object Passwords : Navigate() + + /** + * [Navigate] action dispatched when navigating to customize homepage. + */ + data object CustomizeHomepage : Navigate() + + /** + * [Navigate] action dispatched when navigating to release notes. + */ + data object ReleaseNotes : Navigate() + + /** + * [Navigate] action dispatched when navigating to the tools submenu. + */ + data object Tools : Navigate() + + /** + * [Navigate] action dispatched when navigating to the save submenu. + */ + data object Save : Navigate() + + /** + * [Navigate] action dispatched when navigating to the extensions submenu. + */ + data object Extensions : Navigate() + + /** + * [Navigate] action dispatched when a back navigation event occurs. + */ + data object Back : Navigate() + + /** + * [Navigate] action dispatched when navigating to edit the existing bookmark. + */ + data object EditBookmark : Navigate() + + /** + * [Navigate] action dispatched when navigating to translations dialog. + */ + data object Translate : Navigate() + + /** + * [Navigate] action dispatched when navigating to the share sheet. + */ + data object Share : Navigate() + + /** + * [Navigate] action dispatched when navigating to the extensions manager. + */ + data object ManageExtensions : Navigate() + + /** + * [Navigate] action dispatched when navigating to the AMO page. + */ + data object DiscoverMoreExtensions : Navigate() + + /** + * [Navigate] action dispatched when navigating to the new tab. + */ + data object NewTab : Navigate() + + /** + * [Navigate] action dispatched when navigating to the new private tab. + */ + data object NewPrivateTab : Navigate() } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt index 0f5b7a61e3..7635eebaae 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuState.kt @@ -4,13 +4,36 @@ package org.mozilla.fenix.components.menu.store +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.lib.state.State /** * Value type that represents the state of the menu. * - * @property isBookmarked Whether or not the current selected tab is bookmarked. + * @property browserMenuState The [BrowserMenuState] of the current browser session if any. */ data class MenuState( - val isBookmarked: Boolean = false, + val browserMenuState: BrowserMenuState? = null, ) : State + +/** + * Value type that represents the state of the browser menu. + * + * @property selectedTab The current selected [TabSessionState]. + * @property bookmarkState The [BookmarkState] of the selected tab. + */ +data class BrowserMenuState( + val selectedTab: TabSessionState, + val bookmarkState: BookmarkState = BookmarkState(), +) + +/** + * Value type that represents the bookmark state of a tab. + * + * @property guid The id of the bookmark. + * @property isBookmarked Whether or not the selected tab is bookmarked. + */ +data class BookmarkState( + val guid: String? = null, + val isBookmarked: Boolean = false, +) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt index 14cca54e61..bc2c5b9037 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/store/MenuStore.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.components.menu.store +import androidx.annotation.VisibleForTesting import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.Store @@ -13,16 +14,32 @@ import mozilla.components.lib.state.Store class MenuStore( initialState: MenuState, middleware: List> = listOf(), -) : - Store( - initialState = initialState, - reducer = ::reducer, - middleware = middleware, - ) +) : Store( + initialState = initialState, + reducer = ::reducer, + middleware = middleware, +) { + init { + dispatch(MenuAction.InitAction) + } +} private fun reducer(state: MenuState, action: MenuAction): MenuState { return when (action) { - is MenuAction.UpdateBookmarked -> state.copy(isBookmarked = action.isBookmarked) - is MenuAction.Navigate -> state + is MenuAction.InitAction, + is MenuAction.AddBookmark, + is MenuAction.Navigate, + -> state + + is MenuAction.UpdateBookmarkState -> state.copyWithBrowserMenuState { + it.copy(bookmarkState = action.bookmarkState) + } } } + +@VisibleForTesting +internal inline fun MenuState.copyWithBrowserMenuState( + crossinline update: (BrowserMenuState) -> BrowserMenuState, +): MenuState { + return this.copy(browserMenuState = this.browserMenuState?.let { update(it) }) +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 63cec2f8d7..78e3a7c44c 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.components.toolbar import androidx.navigation.NavController import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.ext.getUrl import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.getNormalOrPrivateTabs @@ -233,18 +234,20 @@ class DefaultBrowserToolbarController( override fun handleTranslationsButtonClick() { Translations.action.record(Translations.ActionExtra("main_flow_toolbar")) val directions = - BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment( - sessionId = currentSession?.id, - ) + BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment() navController.navigateSafe(R.id.browserFragment, directions) } override fun onShareActionClicked() { + val sessionId = currentSession?.id + val url = sessionId?.let { + store.state.findTab(it)?.getUrl() + } val directions = NavGraphDirections.actionGlobalShareFragment( - sessionId = currentSession?.id, + sessionId = sessionId, data = arrayOf( ShareData( - url = getProperUrl(currentSession), + url = url, title = currentSession?.content?.title, ), ), @@ -253,17 +256,6 @@ class DefaultBrowserToolbarController( navController.navigate(directions) } - private fun getProperUrl(currentSession: SessionState?): String? { - return currentSession?.id?.let { - val currentTab = store.state.findTab(it) - if (currentTab?.readerState?.active == true) { - currentTab.readerState.activeUrl - } else { - currentSession.content.url - } - } - } - companion object { internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt index 56d65453fe..a214718ba1 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt @@ -8,6 +8,7 @@ import android.content.Intent import android.view.ViewGroup import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment import androidx.navigation.NavController import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope @@ -16,10 +17,10 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.state.action.EngineAction +import mozilla.components.browser.state.ext.getUrl import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.selectedTab -import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.EngineSession.LoadUrlFlags import mozilla.components.concept.engine.prompt.ShareData @@ -51,6 +52,7 @@ import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.openSetDefaultBrowserOption +import org.mozilla.fenix.settings.biometric.bindBiometricsCredentialsPromptOrShowWarning import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit import org.mozilla.fenix.utils.Settings @@ -63,6 +65,7 @@ interface BrowserToolbarMenuController { @Suppress("LargeClass", "ForbiddenComment", "LongParameterList") class DefaultBrowserToolbarMenuController( + private val fragment: Fragment, private val store: BrowserStore, private val activity: HomeActivity, private val navController: NavController, @@ -79,7 +82,8 @@ class DefaultBrowserToolbarMenuController( private val tabCollectionStorage: TabCollectionStorage, private val topSitesStorage: DefaultTopSitesStorage, private val pinnedSiteStorage: PinnedSiteStorage, - private val browserStore: BrowserStore, + private val onShowPinVerification: (Intent) -> Unit, + private val onBiometricAuthenticationSuccessful: () -> Unit, ) : BrowserToolbarMenuController { private val currentSession @@ -214,11 +218,15 @@ class DefaultBrowserToolbarMenuController( } } is ToolbarMenu.Item.Share -> { + val sessionId = currentSession?.id + val url = sessionId?.let { + store.state.findTab(it)?.getUrl() + } val directions = NavGraphDirections.actionGlobalShareFragment( - sessionId = currentSession?.id, + sessionId = sessionId, data = arrayOf( ShareData( - url = getProperUrl(currentSession), + url = url, title = currentSession?.content?.title, ), ), @@ -257,9 +265,9 @@ class DefaultBrowserToolbarMenuController( } } is ToolbarMenu.Item.OpenInRegularTab -> { - currentSession?.let { session -> - getProperUrl(session)?.let { url -> - tabsUseCases.migratePrivateTabUseCase.invoke(session.id, url) + currentSession?.id?.let { sessionId -> + store.state.findTab(sessionId)?.getUrl()?.let { url -> + tabsUseCases.migratePrivateTabUseCase.invoke(sessionId, url) } } } @@ -350,7 +358,7 @@ class DefaultBrowserToolbarMenuController( } is ToolbarMenu.Item.Bookmark -> { store.state.selectedTab?.let { - getProperUrl(it)?.let { url -> bookmarkTapped(url, it.content.title) } + it.getUrl()?.let { url -> bookmarkTapped(url, it.content.title) } } } is ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically { @@ -365,7 +373,15 @@ class DefaultBrowserToolbarMenuController( BrowserFragmentDirections.actionGlobalHistoryFragment(), ) } - + is ToolbarMenu.Item.Passwords -> browserAnimator.captureEngineViewAndDrawStatically { + fragment.view?.let { view -> + bindBiometricsCredentialsPromptOrShowWarning( + view = view, + onShowPinVerification = onShowPinVerification, + onAuthSuccess = onBiometricAuthenticationSuccessful, + ) + } + } is ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically { navController.nav( R.id.browserFragment, @@ -411,25 +427,12 @@ class DefaultBrowserToolbarMenuController( ToolbarMenu.Item.Translate -> { Translations.action.record(Translations.ActionExtra("main_flow_browser")) val directions = - BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment( - sessionId = currentSession?.id, - ) + BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment() navController.navigateSafe(R.id.browserFragment, directions) } } } - private fun getProperUrl(currentSession: SessionState?): String? { - return currentSession?.id?.let { - val currentTab = browserStore.state.findTab(it) - if (currentTab?.readerState?.active == true) { - currentTab.readerState.activeUrl - } else { - currentSession.content.url - } - } - } - @Suppress("ComplexMethod", "LongMethod") private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) { when (item) { @@ -494,6 +497,8 @@ class DefaultBrowserToolbarMenuController( Events.browserMenuAction.record(Events.BrowserMenuActionExtra("bookmarks")) is ToolbarMenu.Item.History -> Events.browserMenuAction.record(Events.BrowserMenuActionExtra("history")) + is ToolbarMenu.Item.Passwords -> + Events.browserMenuAction.record(Events.BrowserMenuActionExtra("passwords")) is ToolbarMenu.Item.Downloads -> Events.browserMenuAction.record(Events.BrowserMenuActionExtra("downloads")) is ToolbarMenu.Item.NewTab -> diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index 2c93538953..199bfb3bcc 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.components.toolbar import android.content.Context import android.graphics.Color +import android.net.Uri import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View @@ -29,6 +30,7 @@ import mozilla.components.concept.toolbar.ScrollableToolbar import mozilla.components.support.ktx.util.URLStringUtils import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor import org.mozilla.fenix.customtabs.CustomTabToolbarIntegration import org.mozilla.fenix.customtabs.CustomTabToolbarMenu @@ -43,7 +45,7 @@ import mozilla.components.ui.widgets.behavior.ViewPosition as MozacToolbarPositi @SuppressWarnings("LargeClass", "LongParameterList") class BrowserToolbarView( - context: Context, + private val context: Context, container: ViewGroup, private val settings: Settings, private val interactor: BrowserToolbarInteractor, @@ -70,6 +72,8 @@ class BrowserToolbarView( private val tabStripView: ComposeView by lazy { layout.findViewById(R.id.tabStripView) } + private val isNavBarEnabled = IncompleteRedesignToolbarFeature(context.settings()).isEnabled + val toolbarIntegration: ToolbarIntegration val menuToolbar: ToolbarMenu @@ -101,9 +105,11 @@ class BrowserToolbarView( true } + view.isNavBarEnabled = isNavBarEnabled + with(context) { val isPinningSupported = components.useCases.webAppUseCases.isPinningSupported() - val searchUrlBackground = if (IncompleteRedesignToolbarFeature(context.settings()).isEnabled) { + val searchUrlBackground = if (isNavBarEnabled) { R.drawable.search_url_background } else { R.drawable.search_old_url_background @@ -150,7 +156,13 @@ class BrowserToolbarView( ThemeManager.resolveAttribute(R.attr.borderToolbarDivider, context), ) - display.urlFormatter = { url -> URLStringUtils.toDisplayUrl(url) } + display.urlFormatter = { url -> + if (isNavBarEnabled) { + Uri.parse(url.toString()).host ?: url + } else { + URLStringUtils.toDisplayUrl(url) + } + } display.colors = display.colors.copy( text = primaryTextColor, @@ -211,8 +223,6 @@ class BrowserToolbarView( isPrivate = customTabSession.content.private, ) } else { - val isNavBarEnabled = IncompleteRedesignToolbarFeature(context.settings()).isEnabled - DefaultToolbarIntegration( this, view, @@ -322,5 +332,5 @@ class BrowserToolbarView( } private fun shouldShowTabStrip() = - customTabSession == null && settings.isTabletAndTabStripEnabled + customTabSession == null && context.isTabStripEnabled() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 6e28294734..0af1430417 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -232,6 +232,14 @@ open class DefaultToolbarMenu( onItemTapped.invoke(ToolbarMenu.Item.Downloads) } + private val passwordsItem = BrowserMenuImageText( + context.getString(R.string.preferences_sync_logins_2), + R.drawable.mozac_ic_login_24, + primaryTextColor(), + ) { + onItemTapped.invoke(ToolbarMenu.Item.Passwords) + } + private val extensionsItem = WebExtensionPlaceholderMenuItem( id = WebExtensionPlaceholderMenuItem.MAIN_EXTENSIONS_MENU_ID, ) @@ -409,6 +417,7 @@ open class DefaultToolbarMenu( bookmarksItem, historyItem, downloadsItem, + passwordsItem, extensionsItem, syncMenuItem(), BrowserMenuDivider(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index 179d30db5c..ebffb04909 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -48,6 +48,11 @@ interface ToolbarMenu { object CustomizeReaderView : Item() object Bookmarks : Item() object History : Item() + + /** + * The Passwords menu item + */ + object Passwords : Item() object Downloads : Item() object NewTab : Item() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/DismissibleItemBackground.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/DismissibleItemBackground.kt index a4ad6da918..98448f626b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/DismissibleItemBackground.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/DismissibleItemBackground.kt @@ -59,7 +59,7 @@ fun DismissibleItemBackground( Alignment.CenterStart }, ), - tint = FirefoxTheme.colors.iconWarning, + tint = FirefoxTheme.colors.iconCritical, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/Button.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/Button.kt index 2cc4421ac7..ad65c37d17 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/Button.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/Button.kt @@ -232,7 +232,7 @@ fun DestructiveButton( text: String, modifier: Modifier = Modifier.fillMaxWidth(), enabled: Boolean = true, - textColor: Color = FirefoxTheme.colors.textWarningButton, + textColor: Color = FirefoxTheme.colors.textCriticalButton, backgroundColor: Color = FirefoxTheme.colors.actionSecondary, icon: Painter? = null, iconModifier: Modifier = Modifier, @@ -246,7 +246,7 @@ fun DestructiveButton( enabled = enabled, icon = icon, iconModifier = iconModifier, - tint = FirefoxTheme.colors.iconWarningButton, + tint = FirefoxTheme.colors.iconCriticalButton, onClick = onClick, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/TextButton.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/TextButton.kt index 018b6722ba..0d09bc6a22 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/TextButton.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/button/TextButton.kt @@ -5,7 +5,7 @@ package org.mozilla.fenix.compose.button import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -20,6 +20,8 @@ import java.util.Locale * @param text The button text to be displayed. * @param onClick Invoked when the user clicks on the button. * @param modifier [Modifier] Used to shape and position the underlying [androidx.compose.material.TextButton]. + * @param enabled Controls the enabled state of the button. When `false`, this button will not + * be clickable. * @param textColor [Color] to apply to the button text. * @param upperCaseText If the button text should be in uppercase letters. */ @@ -28,12 +30,14 @@ fun TextButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, + enabled: Boolean = true, textColor: Color = FirefoxTheme.colors.textAccent, upperCaseText: Boolean = true, ) { androidx.compose.material.TextButton( onClick = onClick, modifier = modifier, + enabled = enabled, ) { Text( text = if (upperCaseText) { @@ -41,7 +45,7 @@ fun TextButton( } else { text }, - color = textColor, + color = if (enabled) textColor else FirefoxTheme.colors.textDisabled, style = FirefoxTheme.typography.button, maxLines = 1, ) @@ -52,11 +56,17 @@ fun TextButton( @LightDarkPreview private fun TextButtonPreview() { FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { + Column(Modifier.background(FirefoxTheme.colors.layer1)) { TextButton( text = "label", onClick = {}, ) + + TextButton( + text = "disabled", + onClick = {}, + enabled = false, + ) } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt index 0a8e88b1ea..48c6e903b2 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role @@ -36,6 +37,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.compose.Favicon import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.button.RadioButton +import org.mozilla.fenix.compose.button.TextButton import org.mozilla.fenix.theme.FirefoxTheme private val LIST_ITEM_HEIGHT = 56.dp @@ -43,8 +45,8 @@ private val LIST_ITEM_HEIGHT = 56.dp private val ICON_SIZE = 24.dp /** - * List item used to display a label with an optional description text and - * an optional [IconButton] at the end. + * List item used to display a label with an optional description text and an optional + * [IconButton] or [Icon] at the end. * * @param label The label in the list item. * @param modifier [Modifier] to be applied to the layout. @@ -52,9 +54,11 @@ private val ICON_SIZE = 24.dp * @param description An optional description text below the label. * @param maxDescriptionLines An optional maximum number of lines for the description text to span. * @param onClick Called when the user clicks on the item. - * @param iconPainter [Painter] used to display an [IconButton] after the list item. + * @param iconPainter [Painter] used to display an icon after the list item. * @param iconDescription Content description of the icon. - * @param onIconClick Called when the user clicks on the icon. + * @param iconTint Tint applied to [iconPainter]. + * @param onIconClick Called when the user clicks on the icon. An [IconButton] will be + * displayed if this is provided. Otherwise, an [Icon] will be displayed. */ @Composable fun TextListItem( @@ -66,6 +70,7 @@ fun TextListItem( onClick: (() -> Unit)? = null, iconPainter: Painter? = null, iconDescription: String? = null, + iconTint: Color = FirefoxTheme.colors.iconPrimary, onIconClick: (() -> Unit)? = null, ) { ListItem( @@ -87,9 +92,16 @@ fun TextListItem( Icon( painter = iconPainter, contentDescription = iconDescription, - tint = FirefoxTheme.colors.iconPrimary, + tint = iconTint, ) } + } else if (iconPainter != null) { + Icon( + painter = iconPainter, + contentDescription = iconDescription, + modifier = Modifier.padding(end = 16.dp), + tint = iconTint, + ) } } } @@ -160,54 +172,135 @@ fun FaviconListItem( /** * List item used to display a label and an icon at the beginning with an optional description - * text and an optional [IconButton] at the end. + * text and an optional [IconButton] or [Icon] at the end. * * @param label The label in the list item. + * @param labelTextColor [Color] to be applied to the label. * @param description An optional description text below the label. + * @param enabled Controls the enabled state of the list item. When `false`, the list item will not + * be clickable. * @param onClick Called when the user clicks on the item. * @param beforeIconPainter [Painter] used to display an [Icon] before the list item. * @param beforeIconDescription Content description of the icon. - * @param afterIconPainter [Painter] used to display an [IconButton] after the list item. + * @param beforeIconTint Tint applied to [beforeIconPainter]. + * @param afterIconPainter [Painter] used to display an icon after the list item. * @param afterIconDescription Content description of the icon. - * @param onAfterIconClick Called when the user clicks on the icon. + * @param afterIconTint Tint applied to [afterIconPainter]. + * @param onAfterIconClick Called when the user clicks on the icon. An [IconButton] will be + * displayed if this is provided. Otherwise, an [Icon] will be displayed. */ @Composable fun IconListItem( label: String, + labelTextColor: Color = FirefoxTheme.colors.textPrimary, description: String? = null, + enabled: Boolean = true, onClick: (() -> Unit)? = null, beforeIconPainter: Painter, beforeIconDescription: String? = null, + beforeIconTint: Color = FirefoxTheme.colors.iconPrimary, afterIconPainter: Painter? = null, afterIconDescription: String? = null, + afterIconTint: Color = FirefoxTheme.colors.iconPrimary, onAfterIconClick: (() -> Unit)? = null, ) { ListItem( label = label, + labelTextColor = labelTextColor, description = description, + enabled = enabled, onClick = onClick, beforeListAction = { Icon( painter = beforeIconPainter, contentDescription = beforeIconDescription, modifier = Modifier.padding(horizontal = 16.dp), - tint = FirefoxTheme.colors.iconPrimary, + tint = if (enabled) beforeIconTint else FirefoxTheme.colors.iconDisabled, ) }, afterListAction = { + val tint = if (enabled) afterIconTint else FirefoxTheme.colors.iconDisabled + if (afterIconPainter != null && onAfterIconClick != null) { IconButton( onClick = onAfterIconClick, modifier = Modifier .padding(end = 16.dp) .size(ICON_SIZE), + enabled = enabled, ) { Icon( painter = afterIconPainter, contentDescription = afterIconDescription, - tint = FirefoxTheme.colors.iconPrimary, + tint = tint, ) } + } else if (afterIconPainter != null) { + Icon( + painter = afterIconPainter, + contentDescription = afterIconDescription, + modifier = Modifier.padding(end = 16.dp), + tint = tint, + ) + } + }, + ) +} + +/** + * List item used to display a label and an icon at the beginning with an optional description + * text and an optional [TextButton] at the end. + * + * @param label The label in the list item. + * @param labelTextColor [Color] to be applied to the label. + * @param description An optional description text below the label. + * @param enabled Controls the enabled state of the list item. When `false`, the list item will not + * be clickable. + * @param onClick Called when the user clicks on the item. + * @param beforeIconPainter [Painter] used to display an [Icon] before the list item. + * @param beforeIconDescription Content description of the icon. + * @param beforeIconTint Tint applied to [beforeIconPainter]. + * @param afterButtonText The button text to be displayed after the list item. + * @param afterButtonTextColor [Color] to apply to [afterButtonText]. + * @param onAfterButtonClick Called when the user clicks on the text button. + */ +@Composable +fun IconListItem( + label: String, + labelTextColor: Color = FirefoxTheme.colors.textPrimary, + description: String? = null, + enabled: Boolean = true, + onClick: (() -> Unit)? = null, + beforeIconPainter: Painter, + beforeIconDescription: String? = null, + beforeIconTint: Color = FirefoxTheme.colors.iconPrimary, + afterButtonText: String? = null, + afterButtonTextColor: Color = FirefoxTheme.colors.actionPrimary, + onAfterButtonClick: (() -> Unit)? = null, +) { + ListItem( + label = label, + labelTextColor = labelTextColor, + description = description, + enabled = enabled, + onClick = onClick, + beforeListAction = { + Icon( + painter = beforeIconPainter, + contentDescription = beforeIconDescription, + modifier = Modifier.padding(horizontal = 16.dp), + tint = if (enabled) beforeIconTint else FirefoxTheme.colors.iconDisabled, + ) + }, + afterListAction = { + if (afterButtonText != null && onAfterButtonClick != null) { + TextButton( + text = afterButtonText, + onClick = onAfterButtonClick, + enabled = enabled, + textColor = afterButtonTextColor, + upperCaseText = false, + ) } }, ) @@ -270,9 +363,12 @@ fun RadioButtonListItem( * * @param label The label in the list item. * @param modifier [Modifier] to be applied to the layout. + * @param labelTextColor [Color] to be applied to the label. * @param maxLabelLines An optional maximum number of lines for the label text to span. * @param description An optional description text below the label. * @param maxDescriptionLines An optional maximum number of lines for the description text to span. + * @param enabled Controls the enabled state of the list item. When `false`, the list item will not + * be clickable. * @param onClick Called when the user clicks on the item. * @param beforeListAction Optional Composable for adding UI before the list item. * @param afterListAction Optional Composable for adding UI to the end of the list item. @@ -281,16 +377,18 @@ fun RadioButtonListItem( private fun ListItem( label: String, modifier: Modifier = Modifier, + labelTextColor: Color = FirefoxTheme.colors.textPrimary, maxLabelLines: Int = 1, description: String? = null, maxDescriptionLines: Int = 1, + enabled: Boolean = true, onClick: (() -> Unit)? = null, beforeListAction: @Composable RowScope.() -> Unit = {}, afterListAction: @Composable RowScope.() -> Unit = {}, ) { Row( modifier = when (onClick != null) { - true -> Modifier.clickable { onClick() } + true -> Modifier.clickable(enabled = enabled) { onClick() } false -> Modifier }.then( Modifier.defaultMinSize(minHeight = LIST_ITEM_HEIGHT), @@ -306,7 +404,7 @@ private fun ListItem( ) { Text( text = label, - color = FirefoxTheme.colors.textPrimary, + color = if (enabled) labelTextColor else FirefoxTheme.colors.textDisabled, style = FirefoxTheme.typography.subtitle1, maxLines = maxLabelLines, ) @@ -314,7 +412,7 @@ private fun ListItem( description?.let { Text( text = description, - color = FirefoxTheme.colors.textSecondary, + color = if (enabled) FirefoxTheme.colors.textSecondary else FirefoxTheme.colors.textDisabled, style = FirefoxTheme.typography.body2, maxLines = maxDescriptionLines, ) @@ -352,13 +450,21 @@ private fun TextListItemWithDescriptionPreview() { @Preview(name = "TextListItem with a right icon", uiMode = Configuration.UI_MODE_NIGHT_YES) private fun TextListItemWithIconPreview() { FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { + Column(Modifier.background(FirefoxTheme.colors.layer1)) { TextListItem( - label = "Label + right icon", - iconPainter = painterResource(R.drawable.ic_menu), + label = "Label + right icon button", + onClick = {}, + iconPainter = painterResource(R.drawable.mozac_ic_folder_24), iconDescription = "click me", onIconClick = { println("icon click") }, ) + + TextListItem( + label = "Label + right icon", + onClick = {}, + iconPainter = painterResource(R.drawable.mozac_ic_folder_24), + iconDescription = "click me", + ) } } } @@ -367,11 +473,40 @@ private fun TextListItemWithIconPreview() { @Preview(name = "IconListItem", uiMode = Configuration.UI_MODE_NIGHT_YES) private fun IconListItemPreview() { FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { + Column(Modifier.background(FirefoxTheme.colors.layer1)) { + IconListItem( + label = "Left icon list item", + onClick = {}, + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), + beforeIconDescription = "click me", + ) + IconListItem( label = "Left icon list item", - beforeIconPainter = painterResource(R.drawable.ic_folder_icon), + labelTextColor = FirefoxTheme.colors.textAccent, + onClick = {}, + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), + beforeIconDescription = "click me", + beforeIconTint = FirefoxTheme.colors.iconAccentViolet, + ) + + IconListItem( + label = "Left icon list item + right icon", + onClick = {}, + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), beforeIconDescription = "click me", + afterIconPainter = painterResource(R.drawable.mozac_ic_chevron_right_24), + afterIconDescription = null, + ) + + IconListItem( + label = "Left icon list item + right icon (disabled)", + enabled = false, + onClick = {}, + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), + beforeIconDescription = "click me", + afterIconPainter = painterResource(R.drawable.mozac_ic_chevron_right_24), + afterIconDescription = null, ) } } @@ -379,20 +514,29 @@ private fun IconListItemPreview() { @Composable @Preview( - name = "IconListItem with an interactable right icon", + name = "IconListItem with after list action", uiMode = Configuration.UI_MODE_NIGHT_YES, ) -private fun IconListItemWithRightIconPreview() { +private fun IconListItemWithAfterListActionPreview() { FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { + Column(Modifier.background(FirefoxTheme.colors.layer1)) { IconListItem( - label = "Left icon list item + right icon", - beforeIconPainter = painterResource(R.drawable.ic_folder_icon), + label = "IconListItem + right icon + clicks", + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), beforeIconDescription = null, - afterIconPainter = painterResource(R.drawable.ic_menu), + afterIconPainter = painterResource(R.drawable.mozac_ic_ellipsis_vertical_24), afterIconDescription = "click me", onAfterIconClick = { println("icon click") }, ) + + IconListItem( + label = "IconListItem + text button", + onClick = { println("list item click") }, + beforeIconPainter = painterResource(R.drawable.mozac_ic_folder_24), + beforeIconDescription = "click me", + afterButtonText = "Edit", + onAfterButtonClick = { println("text button click") }, + ) } } } @@ -410,14 +554,14 @@ private fun FaviconListItemPreview() { description = "Description text", onClick = { println("list item click") }, url = "", - iconPainter = painterResource(R.drawable.ic_menu), + iconPainter = painterResource(R.drawable.mozac_ic_ellipsis_vertical_24), onIconClick = { println("icon click") }, ) FaviconListItem( label = "Favicon + painter", description = "Description text", - faviconPainter = painterResource(id = R.drawable.ic_tab_collection), + faviconPainter = painterResource(id = R.drawable.mozac_ic_collection_24), onClick = { println("list item click") }, url = "", ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/tabstray/DismissedTabBackground.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/tabstray/DismissedTabBackground.kt index 802457d4f8..3b40e3278a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/tabstray/DismissedTabBackground.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/tabstray/DismissedTabBackground.kt @@ -60,7 +60,7 @@ fun DismissedTabBackground( .alpha( if (dismissDirection == DismissDirection.StartToEnd || dismissDirection == null) 1f else 0f, ), - tint = FirefoxTheme.colors.iconWarning, + tint = FirefoxTheme.colors.iconCritical, ) Icon( @@ -72,7 +72,7 @@ fun DismissedTabBackground( .alpha( if (dismissDirection == DismissDirection.EndToStart || dismissDirection == null) 1f else 0f, ), - tint = FirefoxTheme.colors.iconWarning, + tint = FirefoxTheme.colors.iconCritical, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt index 0f1fff66bd..6c3abb3b0c 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt @@ -34,6 +34,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.browser.BaseBrowserFragment import org.mozilla.fenix.browser.CustomTabContextMenuCandidate import org.mozilla.fenix.browser.FenixSnackbarDelegate +import org.mozilla.fenix.components.menu.MenuAccessPoint import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarMenu import org.mozilla.fenix.components.toolbar.ToolbarPosition @@ -130,8 +131,9 @@ class ExternalAppBrowserFragment : BaseBrowserFragment() { onMenuButtonClick = { nav( R.id.externalAppBrowserFragment, - ExternalAppBrowserFragmentDirections - .actionGlobalMenuDialogFragment(), + ExternalAppBrowserFragmentDirections.actionGlobalMenuDialogFragment( + accesspoint = MenuAccessPoint.External, + ), ) }, ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/tabs/TabTools.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/tabs/TabTools.kt index 5fa97b7c03..54ca60feaf 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/tabs/TabTools.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/tabs/TabTools.kt @@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldDefaults @@ -116,7 +118,8 @@ private fun TabToolsContent( Column( modifier = Modifier .fillMaxSize() - .padding(all = 16.dp), + .padding(all = 16.dp) + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp), ) { TabCounter( @@ -152,7 +155,7 @@ private fun TabCounter( Spacer(modifier = Modifier.height(16.dp)) TabCountRow( - tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_normal), + tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_active), count = activeTabCount, ) @@ -254,10 +257,10 @@ private fun TabCreationTool( textColor = FirefoxTheme.colors.textPrimary, backgroundColor = Color.Transparent, cursorColor = FirefoxTheme.colors.borderFormDefault, - errorCursorColor = FirefoxTheme.colors.borderWarning, + errorCursorColor = FirefoxTheme.colors.borderCritical, focusedIndicatorColor = FirefoxTheme.colors.borderPrimary, unfocusedIndicatorColor = FirefoxTheme.colors.borderPrimary, - errorIndicatorColor = FirefoxTheme.colors.borderWarning, + errorIndicatorColor = FirefoxTheme.colors.borderCritical, ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/DebugOverlay.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/DebugOverlay.kt index fa1959cd79..92e41355c0 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/DebugOverlay.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/DebugOverlay.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController @@ -88,6 +89,7 @@ fun DebugOverlay( onClick = { onDrawerOpen() }, + contentDescription = stringResource(R.string.debug_drawer_fab_content_description), ) // ModalDrawer utilizes a Surface, which blocks ALL clicks behind it, preventing the app diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt index 6ed2c57fb5..96d07d5276 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/StartDownloadDialog.kt @@ -17,7 +17,6 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import androidx.annotation.VisibleForTesting import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.view.ViewCompat import androidx.core.view.children import androidx.viewbinding.ViewBinding import mozilla.components.concept.base.crash.Breadcrumb @@ -124,10 +123,7 @@ abstract class StartDownloadDialog( parent?.children ?.filterNot { it.id == R.id.startDownloadDialogContainer } ?.forEach { - ViewCompat.setImportantForAccessibility( - it, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES, - ) + it.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES) } } @@ -136,10 +132,7 @@ abstract class StartDownloadDialog( parent?.children ?.filterNot { it.id == R.id.startDownloadDialogContainer } ?.forEach { - ViewCompat.setImportantForAccessibility( - it, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, - ) + it.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt index bb927596a4..d0423728d4 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt @@ -146,6 +146,27 @@ fun Activity.openSetDefaultBrowserOption( } } +/** + * Checks if the app can prompt the user to set it as the default browser. + * + * From Android 10, a new method to prompt the user to set default apps has been introduced. + * This method checks if the app can prompt the user to set it as the default browser + * based on the Android version and the availability of the ROLE_BROWSER. + */ +fun Activity.isDefaultBrowserPromptSupported(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + getSystemService(RoleManager::class.java).also { + if (it.isRoleAvailable(RoleManager.ROLE_BROWSER) && !it.isRoleHeld( + RoleManager.ROLE_BROWSER, + ) + ) { + return true + } + } + } + return false +} + @RequiresApi(Build.VERSION_CODES.N) private fun Activity.navigateToDefaultBrowserAppsSettings( from: BrowserDirection, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt index d0491be7f0..db191ed0d1 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt @@ -170,7 +170,7 @@ internal fun getFilteredSponsoredStories( fun AppState.filterState(blocklistHandler: BlocklistHandler): AppState = with(blocklistHandler) { copy( - recentBookmarks = recentBookmarks.filteredByBlocklist(), + bookmarks = bookmarks.filteredByBlocklist(), recentTabs = recentTabs.filteredByBlocklist().filterContile(), recentHistory = recentHistory.filteredByBlocklist().filterContile(), recentSyncedTabState = recentSyncedTabState.filteredByBlocklist().filterContile(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Bitmap.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Bitmap.kt index 02f63839f6..5e02e68873 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Bitmap.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Bitmap.kt @@ -8,6 +8,7 @@ import android.graphics.Bitmap import android.graphics.Matrix import android.view.View import android.widget.ImageView +import androidx.annotation.VisibleForTesting /** * This will scale the received [Bitmap] to the size of the [view]. It retains the bitmap's @@ -33,8 +34,8 @@ fun Bitmap.scaleToBottomOfView(view: ImageView) { oldRight: Int, oldBottom: Int, ) { - val viewWidth: Float = view.width.toFloat() - val viewHeight: Float = view.height.toFloat() + val viewWidth = view.width.toFloat() + val viewHeight = view.safeHeight().toFloat() val bitmapWidth = bitmap.width val bitmapHeight = bitmap.height val widthScale = viewWidth / bitmapWidth @@ -52,3 +53,16 @@ fun Bitmap.scaleToBottomOfView(view: ImageView) { }, ) } + +/** + * If the keyboard is open we must factor in the height for the correct view height. + */ +@VisibleForTesting +internal fun View.safeHeight(): Int { + val keyboardHeight = getKeyboardHeight() + return if (keyboardHeight > 0) { + keyboardHeight.plus(height) + } else { + height + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Context.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Context.kt index 11f37595d8..a4432ba37a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Context.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Context.kt @@ -145,3 +145,9 @@ fun Context.tabClosedUndoMessage(private: Boolean): String = } else { getString(R.string.snackbar_tab_closed) } + +/** + * Returns true if the device is a tablet + */ +fun Context.isTablet(): Boolean = + resources.getBoolean(R.bool.tablet) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt index 0bbc0ee729..db7f36cc40 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt @@ -6,6 +6,8 @@ package org.mozilla.fenix.ext import android.app.Activity import android.content.Intent +import android.view.View +import android.view.ViewGroup import android.view.WindowManager import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -18,9 +20,12 @@ import androidx.navigation.NavDirections import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController import mozilla.components.concept.base.crash.Breadcrumb +import mozilla.components.support.utils.ext.isLandscape import org.mozilla.fenix.NavHostActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.Components +import org.mozilla.fenix.components.toolbar.ToolbarPosition +import org.mozilla.fenix.components.toolbar.navbar.ToolbarContainerView /** * Get the requireComponents of this application. @@ -149,3 +154,53 @@ fun Fragment.registerForActivityResult( } } } + +/** + * Checks whether the current fragment is running on a tablet. + */ +fun Fragment.isTablet(): Boolean { + return resources.getBoolean(R.bool.tablet) +} + +/** + * + * Manages the state of the NavBar upon an orientation change. + * + * @param parent The top level [ViewGroup] of the fragment, which will be hosting toolbar/navbar container. + * @param toolbarView [View] responsible for showing the AddressBar. + * @param bottomToolbarContainerView The [ToolbarContainerView] hosting the NavBar. + * @param reinitializeNavBar lambda for re-initializing the NavBar inside the host [Fragment]. + */ +fun Fragment.updateNavBarForConfigurationChange( + parent: ViewGroup, + toolbarView: View, + bottomToolbarContainerView: ToolbarContainerView?, + reinitializeNavBar: () -> Unit, +) { + if (requireContext().isLandscape()) { + // In landscape mode we want to remove the navigation bar. + parent.removeView(bottomToolbarContainerView) + + // If address bar was positioned at bottom and we have removed the toolbar container, we are adding address bar + // back. + val isToolbarAtBottom = requireComponents.settings.toolbarPosition == ToolbarPosition.BOTTOM + + // Toolbar already having a parent is an edge case, but it could happen if configurationChange is called after + // onCreateView with the same orientation. Caught it on a foldable emulator while going from single screen + // portrait mode to landscape table, back and forth. + val hasParent = toolbarView.parent != null + if (isToolbarAtBottom && !hasParent) { + parent.addView(toolbarView) + } + } else { + // Already having a bottomContainer after switching back to portrait mode will happen when address bar is + // positioned at bottom and also as an edge case if configurationChange is called after onCreateView with the + // same orientation. Caught it on a foldable emulator while going from single screen portrait mode to landscape + // table, back and forth. + bottomToolbarContainerView?.let { + parent.removeView(it) + } + + reinitializeNavBar() + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/View.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/View.kt index 3c5976e428..9f2d2e0035 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/View.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/View.kt @@ -2,18 +2,16 @@ * 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("TooManyFunctions") - package org.mozilla.fenix.ext import android.graphics.Rect import android.os.Build import android.view.TouchDelegate import android.view.View -import android.view.accessibility.AccessibilityNodeInfo import androidx.annotation.Dimension import androidx.annotation.Dimension.Companion.DP import androidx.annotation.VisibleForTesting +import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import mozilla.components.support.ktx.android.util.dpToPx import mozilla.components.support.utils.ext.bottom @@ -54,98 +52,12 @@ fun View.removeTouchDelegate() { } } -/** - * Sets the new a11y parent. - */ -fun View.setNewAccessibilityParent(newParent: View) { - this.accessibilityDelegate = object : View.AccessibilityDelegate() { - override fun onInitializeAccessibilityNodeInfo( - host: View, - info: AccessibilityNodeInfo, - ) { - super.onInitializeAccessibilityNodeInfo(host, info) - info.setParent(newParent) - } - } -} - -/** - * Updates the a11y collection item info for an item in a list. - */ -fun View.updateAccessibilityCollectionItemInfo( - rowIndex: Int, - columnIndex: Int, - isSelected: Boolean, - rowSpan: Int = 1, - columnSpan: Int = 1, -) { - this.accessibilityDelegate = object : View.AccessibilityDelegate() { - override fun onInitializeAccessibilityNodeInfo( - host: View, - info: AccessibilityNodeInfo, - ) { - super.onInitializeAccessibilityNodeInfo(host, info) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - info.collectionItemInfo = - AccessibilityNodeInfo.CollectionItemInfo( - rowIndex, - rowSpan, - columnIndex, - columnSpan, - false, - isSelected, - ) - } else { - @Suppress("DEPRECATION") - AccessibilityNodeInfo.CollectionItemInfo.obtain( - rowIndex, - rowSpan, - columnIndex, - columnSpan, - false, - isSelected, - ) - } - } - } -} - -/** - * Updates the a11y collection info for a list. - */ -fun View.updateAccessibilityCollectionInfo( - rowCount: Int, - columnCount: Int, -) { - this.accessibilityDelegate = object : View.AccessibilityDelegate() { - override fun onInitializeAccessibilityNodeInfo( - host: View, - info: AccessibilityNodeInfo, - ) { - super.onInitializeAccessibilityNodeInfo(host, info) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - info.collectionInfo = AccessibilityNodeInfo.CollectionInfo( - rowCount, - columnCount, - false, - ) - } else { - @Suppress("DEPRECATION") - info.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( - rowCount, - columnCount, - false, - ) - } - } - } -} - /** * Fills a [Rect] with data about a view's location in the screen. * - * @see View.getLocationOnScreen - * @see View.getRectWithViewLocation for a version of this that is relative to a window + * @see android.view.View.getLocationOnScreen + * @see mozilla.components.support.ktx.android.view.getRectWithViewLocation for a version of this + * that is relative to a window */ fun View.getRectWithScreenLocation(): Rect { val locationOnScreen = IntArray(2).apply { getLocationOnScreen(this) } @@ -196,8 +108,10 @@ internal fun View.getWindowVisibleDisplayFrame(): Rect = with(Rect()) { this } -@VisibleForTesting -internal fun View.getKeyboardHeight(): Int { +/** + * Calculates the height of the onscreen keyboard. + */ +fun View.getKeyboardHeight(): Int { val windowRect = getWindowVisibleDisplayFrame() val statusBarHeight = windowRect.top var keyboardHeight = rootView.height - (windowRect.height() + statusBarHeight) @@ -207,10 +121,3 @@ internal fun View.getKeyboardHeight(): Int { return keyboardHeight } - -/** - * The assumed minimum height of the keyboard. - */ -@VisibleForTesting -@Dimension(unit = DP) -internal const val MINIMUM_KEYBOARD_HEIGHT = 100 diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/gecko/GeckoProvider.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/gecko/GeckoProvider.kt index b0837f80cc..b25a5dbc4a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/gecko/GeckoProvider.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/gecko/GeckoProvider.kt @@ -93,7 +93,10 @@ object GeckoProvider { isCreditCardAutofillEnabled = { context.settings().shouldAutofillCreditCardDetails }, isAddressAutofillEnabled = { context.settings().shouldAutofillAddressDetails }, ), - GeckoLoginStorageDelegate(loginStorage), + GeckoLoginStorageDelegate( + loginStorage = loginStorage, + isLoginAutofillEnabled = { context.settings().shouldAutofillLogins }, + ), ) return geckoRuntime diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 75c837a62b..ac8de36e7b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.home import android.annotation.SuppressLint +import android.content.Intent import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.drawable.ColorDrawable @@ -13,6 +14,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -80,10 +82,12 @@ import mozilla.components.lib.state.ext.consumeFlow import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.support.utils.ext.isLandscape import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.Homepage +import org.mozilla.fenix.GleanMetrics.Logins import org.mozilla.fenix.GleanMetrics.NavigationBar import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr import org.mozilla.fenix.HomeActivity @@ -93,10 +97,12 @@ import org.mozilla.fenix.addons.showSnackBar import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.tabstrip.TabStrip +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.components.menu.MenuAccessPoint import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.navbar.BottomToolbarContainerView @@ -107,16 +113,19 @@ import org.mozilla.fenix.databinding.FragmentHomeBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.containsQueryParameters import org.mozilla.fenix.ext.hideToolbar +import org.mozilla.fenix.ext.isTablet import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.ext.registerForActivityResult import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.scaleToBottomOfView import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.tabClosedUndoMessage +import org.mozilla.fenix.ext.updateNavBarForConfigurationChange +import org.mozilla.fenix.home.bookmarks.BookmarksFeature +import org.mozilla.fenix.home.bookmarks.controller.DefaultBookmarksController import org.mozilla.fenix.home.pocket.DefaultPocketStoriesController import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory import org.mozilla.fenix.home.privatebrowsing.controller.DefaultPrivateBrowsingController -import org.mozilla.fenix.home.recentbookmarks.RecentBookmarksFeature -import org.mozilla.fenix.home.recentbookmarks.controller.DefaultRecentBookmarksController import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabFeature import org.mozilla.fenix.home.recentsyncedtabs.controller.DefaultRecentSyncedTabController import org.mozilla.fenix.home.recenttabs.RecentTabsListFeature @@ -132,6 +141,7 @@ import org.mozilla.fenix.home.toolbar.SearchSelectorBinding import org.mozilla.fenix.home.toolbar.SearchSelectorMenuBinding import org.mozilla.fenix.home.topsites.DefaultTopSitesView import org.mozilla.fenix.messaging.DefaultMessageController +import org.mozilla.fenix.messaging.FenixMessageSurfaceId import org.mozilla.fenix.messaging.MessagingFeature import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks @@ -233,12 +243,14 @@ class HomeFragment : Fragment() { private val messagingFeature = ViewBoundFeatureWrapper() private val recentTabsListFeature = ViewBoundFeatureWrapper() private val recentSyncedTabFeature = ViewBoundFeatureWrapper() - private val recentBookmarksFeature = ViewBoundFeatureWrapper() + private val bookmarksFeature = ViewBoundFeatureWrapper() private val historyMetadataFeature = ViewBoundFeatureWrapper() private val searchSelectorBinding = ViewBoundFeatureWrapper() private val searchSelectorMenuBinding = ViewBoundFeatureWrapper() private val navbarIntegration = ViewBoundFeatureWrapper() + private lateinit var savedLoginsLauncher: ActivityResultLauncher + override fun onCreate(savedInstanceState: Bundle?) { // DO NOT ADD ANYTHING ABOVE THIS getProfilerTime CALL! val profilerStartTime = requireComponents.core.engine.profiler?.getProfilerTime() @@ -246,6 +258,7 @@ class HomeFragment : Fragment() { super.onCreate(savedInstanceState) bundleArgs = args.toBundle() + savedLoginsLauncher = registerForActivityResult { navigateToSavedLoginsFragment() } // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! requireComponents.core.engine.profiler?.addMarker( @@ -301,6 +314,7 @@ class HomeFragment : Fragment() { messagingFeature.set( feature = MessagingFeature( appStore = requireComponents.appStore, + surface = FenixMessageSurfaceId.HOMESCREEN, ), owner = viewLifecycleOwner, view = binding.root, @@ -347,9 +361,9 @@ class HomeFragment : Fragment() { ) } - if (requireContext().settings().showRecentBookmarksFeature) { - recentBookmarksFeature.set( - feature = RecentBookmarksFeature( + if (requireContext().settings().showBookmarksHomeFeature) { + bookmarksFeature.set( + feature = BookmarksFeature( appStore = components.appStore, bookmarksUseCase = run { requireContext().components.useCases.bookmarksUseCases @@ -409,7 +423,7 @@ class HomeFragment : Fragment() { accessPoint = TabsTrayAccessPoint.HomeRecentSyncedTab, appStore = components.appStore, ), - recentBookmarksController = DefaultRecentBookmarksController( + bookmarksController = DefaultBookmarksController( activity = activity, navController = findNavController(), appStore = components.appStore, @@ -451,7 +465,11 @@ class HomeFragment : Fragment() { searchEngine = components.core.store.state.search.selectedOrDefaultSearchEngine, ) - if (IncompleteRedesignToolbarFeature(requireContext().settings()).isEnabled) { + // We don't show the navigation bar for tablets and in landscape mode. + val shouldAddNavigationBar = IncompleteRedesignToolbarFeature(requireContext().settings()).isEnabled && + !requireContext().isLandscape() && + !isTablet() + if (shouldAddNavigationBar) { initializeNavBar(activity = activity) } @@ -478,11 +496,27 @@ class HomeFragment : Fragment() { return binding.root } + private fun reinitializeNavBar() { + initializeNavBar(activity = requireActivity() as HomeActivity) + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) homeMenuView?.dismissMenu() + // If the navbar feature could be visible, we should update it's state. + val shouldUpdateNavBarState = + IncompleteRedesignToolbarFeature(requireContext().settings()).isEnabled && !isTablet() + if (shouldUpdateNavBarState) { + updateNavBarForConfigurationChange( + parent = binding.homeLayout, + toolbarView = binding.toolbarLayout, + bottomToolbarContainerView = _bottomToolbarContainerView?.toolbarContainerView, + reinitializeNavBar = ::reinitializeNavBar, + ) + } + val currentWallpaperName = requireContext().settings().currentWallpaperName applyWallpaper( wallpaperName = currentWallpaperName, @@ -511,7 +545,10 @@ class HomeFragment : Fragment() { lifecycleOwner = viewLifecycleOwner, homeActivity = activity, navController = findNavController(), + homeFragment = this, menuButton = WeakReference(menuButton), + onShowPinVerification = { intent -> savedLoginsLauncher.launch(intent) }, + onBiometricAuthenticationSuccessful = ::navigateToSavedLoginsFragment, ).also { it.build() } _bottomToolbarContainerView = BottomToolbarContainerView( @@ -559,7 +596,9 @@ class HomeFragment : Fragment() { onMenuButtonClick = { findNavController().nav( findNavController().currentDestination?.id, - HomeFragmentDirections.actionGlobalMenuDialogFragment(), + HomeFragmentDirections.actionGlobalMenuDialogFragment( + accesspoint = MenuAccessPoint.Home, + ), ) }, ) @@ -682,7 +721,10 @@ class HomeFragment : Fragment() { lifecycleOwner = viewLifecycleOwner, homeActivity = activity as HomeActivity, navController = findNavController(), + homeFragment = this, menuButton = WeakReference(binding.menuButton), + onShowPinVerification = { intent -> savedLoginsLauncher.launch(intent) }, + onBiometricAuthenticationSuccessful = { navigateToSavedLoginsFragment() }, ).also { it.build() } tabCounterView = TabCounterView( @@ -693,7 +735,7 @@ class HomeFragment : Fragment() { ) toolbarView?.build() - if (requireContext().settings().isTabletAndTabStripEnabled) { + if (requireContext().isTabStripEnabled()) { initTabStrip() } @@ -1224,6 +1266,18 @@ class HomeFragment : Fragment() { } } + /** + * Called when authentication succeeds. + */ + private fun navigateToSavedLoginsFragment() { + val navController = findNavController() + if (navController.currentDestination?.id == R.id.homeFragment) { + Logins.openLogins.record(NoExtras()) + val directions = HomeFragmentDirections.actionLoginsListFragment() + navController.navigate(directions) + } + } + companion object { // Used to set homeViewModel.sessionToDelete when all tabs of a browsing mode are closed const val ALL_NORMAL_TABS = "all_normal" diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt index f2f7ef7522..93b9518b5a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt @@ -49,6 +49,11 @@ class HomeMenu( object Bookmarks : Item() object History : Item() object Downloads : Item() + + /** + * The Passwords menu item + */ + object Passwords : Item() object Extensions : Item() data class SyncAccount(val accountState: AccountState) : Item() @@ -142,6 +147,14 @@ class HomeMenu( onItemTapped.invoke(Item.Downloads) } + val passwordsItem = BrowserMenuImageText( + context.getString(R.string.preferences_sync_logins_2), + R.drawable.mozac_ic_login_24, + primaryTextColor, + ) { + onItemTapped.invoke(Item.Passwords) + } + val extensionsItem = BrowserMenuImageText( context.getString(R.string.browser_menu_extensions), R.drawable.ic_addons_extensions, @@ -217,6 +230,7 @@ class HomeMenu( bookmarksItem, historyItem, downloadsItem, + passwordsItem, extensionsItem, syncSignInMenuItem, accountAuthItem, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt index 7a94a7837a..611946a237 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.home import android.content.Context +import android.content.Intent import android.view.View import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.Companion.PRIVATE @@ -29,6 +30,7 @@ import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.settings.biometric.bindBiometricsCredentialsPromptOrShowWarning import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.whatsnew.WhatsNew @@ -43,18 +45,26 @@ import org.mozilla.fenix.GleanMetrics.HomeMenu as HomeMenuMetrics * @param lifecycleOwner [LifecycleOwner] for the view. * @param homeActivity [HomeActivity] used to open URLs in a new tab. * @param navController [NavController] used for navigation. + * @param homeFragment [HomeFragment] used to attach the biometric prompt. * @param menuButton The [MenuButton] that will be used to create a menu when the button is * clicked. * @param fxaEntrypoint The source entry point to FxA. + * @param onShowPinVerification Callback for registering the pin verification result. + * @param onBiometricAuthenticationSuccessful Callback for displaying the next screen after a + * successful biometric authentication. */ +@Suppress("LongParameterList") class HomeMenuView( private val view: View, private val context: Context, private val lifecycleOwner: LifecycleOwner, private val homeActivity: HomeActivity, private val navController: NavController, + private val homeFragment: HomeFragment, private val menuButton: WeakReference, private val fxaEntrypoint: FxAEntryPoint = FenixFxAEntryPoint.HomeMenu, + private val onShowPinVerification: (Intent) -> Unit, + private val onBiometricAuthenticationSuccessful: () -> Unit, ) { /** @@ -166,6 +176,13 @@ class HomeMenuView( HomeFragmentDirections.actionGlobalDownloadsFragment(), ) } + HomeMenu.Item.Passwords -> { + bindBiometricsCredentialsPromptOrShowWarning( + view = view, + onShowPinVerification = onShowPinVerification, + onAuthSuccess = onBiometricAuthenticationSuccessful, + ) + } HomeMenu.Item.Help -> { HomeMenuMetrics.helpTapped.record(NoExtras()) homeActivity.openToBrowserAndLoad( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt index 43319eb061..c8802868e8 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt @@ -19,6 +19,7 @@ import androidx.core.view.updateLayoutParams import mozilla.components.browser.state.search.SearchEngine import mozilla.components.support.ktx.android.content.res.resolveAttribute import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.databinding.FragmentHomeBinding @@ -106,7 +107,7 @@ class ToolbarView( gravity = Gravity.TOP } - val isTabletAndTabStripEnabled = context.settings().isTabletAndTabStripEnabled + val isTabletAndTabStripEnabled = context.isTabStripEnabled() ConstraintSet().apply { clone(binding.toolbarLayout) clear(binding.bottomBar.id, ConstraintSet.BOTTOM) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistHandler.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistHandler.kt index 041eb95836..80bad8fc76 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistHandler.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistHandler.kt @@ -8,7 +8,7 @@ import android.net.Uri import androidx.annotation.VisibleForTesting import mozilla.components.support.ktx.kotlin.sha1 import org.mozilla.fenix.ext.containsQueryParameters -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark +import org.mozilla.fenix.home.bookmarks.Bookmark import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem @@ -37,7 +37,7 @@ class BlocklistHandler(private val settings: Settings) { * in a scope. */ @JvmName("filterRecentBookmark") - fun List.filteredByBlocklist(): List = + fun List.filteredByBlocklist(): List = settings.homescreenBlocklist.let { blocklist -> filterNot { it.url?.let { url -> blocklistContainsUrl(blocklist, url) } ?: false diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistMiddleware.kt index 9c8928c41d..1b65af86fa 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistMiddleware.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistMiddleware.kt @@ -41,7 +41,7 @@ class BlocklistMiddleware( when (action) { is AppAction.Change -> { action.copy( - recentBookmarks = action.recentBookmarks.filteredByBlocklist(), + bookmarks = action.bookmarks.filteredByBlocklist(), recentTabs = action.recentTabs.filteredByBlocklist().filterContile(), recentHistory = action.recentHistory.filteredByBlocklist().filterContile(), recentSyncedTabState = action.recentSyncedTabState.filteredByBlocklist().filterContile(), @@ -52,9 +52,9 @@ class BlocklistMiddleware( recentTabs = action.recentTabs.filteredByBlocklist().filterContile(), ) } - is AppAction.RecentBookmarksChange -> { + is AppAction.BookmarksChange -> { action.copy( - recentBookmarks = action.recentBookmarks.filteredByBlocklist(), + bookmarks = action.bookmarks.filteredByBlocklist(), ) } is AppAction.RecentHistoryChange -> { @@ -73,8 +73,8 @@ class BlocklistMiddleware( action } } - is AppAction.RemoveRecentBookmark -> { - action.recentBookmark.url?.let { url -> + is AppAction.RemoveBookmark -> { + action.bookmark.url?.let { url -> addUrlToBlocklist(url) state.toActionFilteringAllState(this) } ?: action @@ -99,7 +99,7 @@ class BlocklistMiddleware( with(blocklistHandler) { AppAction.Change( recentTabs = recentTabs.filteredByBlocklist().filterContile(), - recentBookmarks = recentBookmarks.filteredByBlocklist(), + bookmarks = bookmarks.filteredByBlocklist(), recentHistory = recentHistory.filteredByBlocklist().filterContile(), topSites = topSites, mode = mode, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/BookmarksFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/BookmarksFeature.kt new file mode 100644 index 0000000000..79af88ea7e --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/BookmarksFeature.kt @@ -0,0 +1,59 @@ +/* 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 org.mozilla.fenix.home.bookmarks + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.concept.storage.BookmarkNode +import mozilla.components.support.base.feature.LifecycleAwareFeature +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.components.bookmarks.BookmarksUseCase +import org.mozilla.fenix.home.HomeFragment + +/** + * View-bound feature that retrieves a list of [BookmarkNode]s and dispatches + * updates to the [AppStore]. + * + * @param appStore the [AppStore] that holds the state of the [HomeFragment]. + * @param bookmarksUseCase the [BookmarksUseCase] for retrieving the list of bookmarks from storage. + * @param scope the [CoroutineScope] used to fetch the bookmarks list + * @param ioDispatcher the [CoroutineDispatcher] for performing read/write operations. + */ +class BookmarksFeature( + private val appStore: AppStore, + private val bookmarksUseCase: BookmarksUseCase, + private val scope: CoroutineScope, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, +) : LifecycleAwareFeature { + private var job: Job? = null + + override fun start() { + job = scope.launch(ioDispatcher) { + val bookmarks = bookmarksUseCase.retrieveRecentBookmarks() + appStore.dispatch(AppAction.BookmarksChange(bookmarks)) + } + } + + override fun stop() { + job?.cancel() + } +} + +/** + * The simple metadata of a bookmark. + * + * @property title The title of the bookmark. + * @property url The url of the bookmark. + * @property previewImageUrl A preview image of the page (a.k.a. the hero image), if available. + */ +data class Bookmark( + val title: String? = null, + val url: String? = null, + val previewImageUrl: String? = null, +) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/controller/BookmarksController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/controller/BookmarksController.kt new file mode 100644 index 0000000000..6487efc1b3 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/controller/BookmarksController.kt @@ -0,0 +1,86 @@ +/* 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 org.mozilla.fenix.home.bookmarks.controller + +import androidx.navigation.NavController +import mozilla.appservices.places.BookmarkRoot +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.ALLOW_JAVASCRIPT_URL +import mozilla.components.feature.tabs.TabsUseCases +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.HomeBookmarks +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.home.HomeFragmentDirections +import org.mozilla.fenix.home.bookmarks.Bookmark +import org.mozilla.fenix.home.bookmarks.interactor.BookmarksInteractor + +/** + * An interface that handles the view manipulation of the bookmarks on the + * Home screen. + */ +interface BookmarksController { + + /** + * @see [BookmarksInteractor.onBookmarkClicked] + */ + fun handleBookmarkClicked(bookmark: Bookmark) + + /** + * @see [BookmarksInteractor.onShowAllBookmarksClicked] + */ + fun handleShowAllBookmarksClicked() + + /** + * @see [BookmarksInteractor.onBookmarkRemoved] + */ + fun handleBookmarkRemoved(bookmark: Bookmark) +} + +/** + * The default implementation of [BookmarksController]. + */ +class DefaultBookmarksController( + private val activity: HomeActivity, + private val navController: NavController, + private val appStore: AppStore, + private val browserStore: BrowserStore, + private val selectTabUseCase: TabsUseCases.SelectTabUseCase, +) : BookmarksController { + + override fun handleBookmarkClicked(bookmark: Bookmark) { + val existingTabForBookmark = browserStore.state.tabs.firstOrNull { + it.content.url == bookmark.url + } + + if (existingTabForBookmark == null) { + activity.openToBrowserAndLoad( + searchTermOrURL = bookmark.url!!, + newTab = true, + from = BrowserDirection.FromHome, + flags = EngineSession.LoadUrlFlags.select(ALLOW_JAVASCRIPT_URL), + ) + } else { + selectTabUseCase.invoke(existingTabForBookmark.id) + navController.navigate(R.id.browserFragment) + } + + HomeBookmarks.bookmarkClicked.add() + } + + override fun handleShowAllBookmarksClicked() { + HomeBookmarks.showAllBookmarks.add() + navController.navigate( + HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id), + ) + } + + override fun handleBookmarkRemoved(bookmark: Bookmark) { + appStore.dispatch(AppAction.RemoveBookmark(bookmark)) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/interactor/BookmarksInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/interactor/BookmarksInteractor.kt new file mode 100644 index 0000000000..efe95d2979 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/interactor/BookmarksInteractor.kt @@ -0,0 +1,36 @@ +/* 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 org.mozilla.fenix.home.bookmarks.interactor + +import org.mozilla.fenix.home.bookmarks.Bookmark +import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor + +/** + * Interface for bookmark related actions in the [SessionControlInteractor]. + */ +interface BookmarksInteractor { + + /** + * Opens the given bookmark in a new tab. Called when an user clicks on a bookmark on the home + * screen. + * + * @param bookmark The bookmark that will be opened. + */ + fun onBookmarkClicked(bookmark: Bookmark) + + /** + * Navigates to bookmark list. Called when an user clicks on the "Show all" button for + * bookmarks on the home screen. + */ + fun onShowAllBookmarksClicked() + + /** + * Removes a bookmark from the list on the home screen. Called when a user clicks the "Remove" + * button for a bookmark on the home screen. + * + * @param bookmark The bookmark that has been removed. + */ + fun onBookmarkRemoved(bookmark: Bookmark) +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/Bookmarks.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/Bookmarks.kt new file mode 100644 index 0000000000..6f70c98db8 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/Bookmarks.kt @@ -0,0 +1,253 @@ +/* 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 org.mozilla.fenix.home.bookmarks.view + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import mozilla.components.browser.icons.compose.Loader +import mozilla.components.browser.icons.compose.Placeholder +import mozilla.components.ui.colors.PhotonColors +import org.mozilla.fenix.components.components +import org.mozilla.fenix.compose.ContextualMenu +import org.mozilla.fenix.compose.Favicon +import org.mozilla.fenix.compose.Image +import org.mozilla.fenix.compose.MenuItem +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.inComposePreview +import org.mozilla.fenix.home.bookmarks.Bookmark +import org.mozilla.fenix.theme.FirefoxTheme + +private val cardShape = RoundedCornerShape(8.dp) + +private val imageWidth = 126.dp + +private val imageModifier = Modifier + .size(width = imageWidth, height = 82.dp) + .clip(cardShape) + +/** + * A list of bookmarks. + * + * @param bookmarks List of [Bookmark]s to display. + * @param menuItems List of [BookmarksMenuItem] shown when long clicking a [BookmarkItem] + * @param backgroundColor The background [Color] of each bookmark. + * @param onBookmarkClick Invoked when the user clicks on a bookmark. + */ +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun Bookmarks( + bookmarks: List, + menuItems: List, + backgroundColor: Color, + onBookmarkClick: (Bookmark) -> Unit = {}, +) { + LazyRow( + modifier = Modifier.semantics { + testTagsAsResourceId = true + testTag = "bookmarks" + }, + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(bookmarks) { bookmark -> + BookmarkItem( + bookmark = bookmark, + menuItems = menuItems, + backgroundColor = backgroundColor, + onBookmarkClick = onBookmarkClick, + ) + } + } +} + +/** + * A bookmark item. + * + * @param bookmark The [Bookmark] to display. + * @param menuItems The list of [BookmarksMenuItem] shown when long clicking on the bookmark item. + * @param backgroundColor The background [Color] of the bookmark item. + * @param onBookmarkClick Invoked when the user clicks on the bookmark item. + */ +@OptIn( + ExperimentalFoundationApi::class, + ExperimentalComposeUiApi::class, +) +@Composable +private fun BookmarkItem( + bookmark: Bookmark, + menuItems: List, + backgroundColor: Color, + onBookmarkClick: (Bookmark) -> Unit = {}, +) { + var isMenuExpanded by remember { mutableStateOf(false) } + + Card( + modifier = Modifier + .width(158.dp) + .combinedClickable( + enabled = true, + onClick = { onBookmarkClick(bookmark) }, + onLongClick = { isMenuExpanded = true }, + ), + shape = cardShape, + backgroundColor = backgroundColor, + elevation = 6.dp, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp), + ) { + BookmarkImage(bookmark) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = bookmark.title ?: bookmark.url ?: "", + modifier = Modifier.semantics { + testTagsAsResourceId = true + testTag = "bookmark.title" + }, + color = FirefoxTheme.colors.textPrimary, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = FirefoxTheme.typography.caption, + ) + + ContextualMenu( + showMenu = isMenuExpanded, + onDismissRequest = { isMenuExpanded = false }, + menuItems = menuItems.map { item -> MenuItem(item.title) { item.onClick(bookmark) } }, + modifier = Modifier.semantics { + testTagsAsResourceId = true + testTag = "bookmark.menu" + }, + ) + } + } +} + +@Composable +private fun BookmarkImage(bookmark: Bookmark) { + when { + !bookmark.previewImageUrl.isNullOrEmpty() -> { + Image( + url = bookmark.previewImageUrl, + modifier = imageModifier, + targetSize = imageWidth, + contentScale = ContentScale.Crop, + fallback = { + if (!bookmark.url.isNullOrEmpty()) { + FallbackBookmarkFaviconImage(url = bookmark.url) + } + }, + ) + } + !bookmark.url.isNullOrEmpty() && !inComposePreview -> { + components.core.icons.Loader(bookmark.url) { + Placeholder { + PlaceholderBookmarkImage() + } + + FallbackBookmarkFaviconImage(bookmark.url) + } + } + inComposePreview -> { + PlaceholderBookmarkImage() + } + } +} + +@Composable +private fun PlaceholderBookmarkImage() { + Box( + modifier = imageModifier.background( + color = when (isSystemInDarkTheme()) { + true -> PhotonColors.DarkGrey60 + false -> PhotonColors.LightGrey30 + }, + ), + ) +} + +@Composable +private fun FallbackBookmarkFaviconImage( + url: String, +) { + Box( + modifier = imageModifier.background( + color = FirefoxTheme.colors.layer2, + ), + contentAlignment = Alignment.Center, + ) { + Favicon(url = url, size = 36.dp) + } +} + +@Composable +@LightDarkPreview +private fun BookmarksPreview() { + FirefoxTheme { + Bookmarks( + bookmarks = listOf( + Bookmark( + title = "Other Bookmark Title", + url = "https://www.example.com", + previewImageUrl = null, + ), + Bookmark( + title = "Other Bookmark Title", + url = "https://www.example.com", + previewImageUrl = null, + ), + Bookmark( + title = "Other Bookmark Title", + url = "https://www.example.com", + previewImageUrl = null, + ), + Bookmark( + title = "Other Bookmark Title", + url = "https://www.example.com", + previewImageUrl = null, + ), + ), + menuItems = listOf(), + backgroundColor = FirefoxTheme.colors.layer2, + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksHeaderViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksHeaderViewHolder.kt new file mode 100644 index 0000000000..354524b7cb --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksHeaderViewHolder.kt @@ -0,0 +1,61 @@ +/* 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 org.mozilla.fenix.home.bookmarks.view + +import android.view.View +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.LifecycleOwner +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.ComposeViewHolder +import org.mozilla.fenix.compose.home.HomeSectionHeader +import org.mozilla.fenix.home.bookmarks.interactor.BookmarksInteractor + +/** + * View holder for the bookmarks header and "Show all" button. + * + * @param composeView [ComposeView] which will be populated with Jetpack Compose UI content. + * @param viewLifecycleOwner [LifecycleOwner] life cycle owner for the view. + * @param interactor [BookmarksInteractor] which will have delegated to all user interactions. + */ +class BookmarksHeaderViewHolder( + composeView: ComposeView, + viewLifecycleOwner: LifecycleOwner, + private val interactor: BookmarksInteractor, +) : ComposeViewHolder(composeView, viewLifecycleOwner) { + + init { + val horizontalPadding = + composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin) + composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0) + } + + @Composable + override fun Content() { + Column { + Spacer(modifier = Modifier.height(40.dp)) + + HomeSectionHeader( + headerText = stringResource(R.string.home_bookmarks_title), + description = stringResource(R.string.home_bookmarks_show_all_content_description), + onShowAllClick = { + interactor.onShowAllBookmarksClicked() + }, + ) + + Spacer(Modifier.height(16.dp)) + } + } + + companion object { + val LAYOUT_ID = View.generateViewId() + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksMenuItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksMenuItem.kt new file mode 100644 index 0000000000..b447a71166 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksMenuItem.kt @@ -0,0 +1,18 @@ +/* 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 org.mozilla.fenix.home.bookmarks.view + +import org.mozilla.fenix.home.bookmarks.Bookmark + +/** + * A menu item in the bookmarks dropdown menu. + * + * @property title The menu item title. + * @property onClick Invoked when the user clicks on the menu item. + */ +data class BookmarksMenuItem( + val title: String, + val onClick: (Bookmark) -> Unit, +) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksViewHolder.kt new file mode 100644 index 0000000000..de77577701 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/bookmarks/view/BookmarksViewHolder.kt @@ -0,0 +1,56 @@ +/* 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 org.mozilla.fenix.home.bookmarks.view + +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.LifecycleOwner +import mozilla.components.lib.state.ext.observeAsComposableState +import mozilla.components.service.glean.private.NoExtras +import org.mozilla.fenix.GleanMetrics.HomeBookmarks +import org.mozilla.fenix.R +import org.mozilla.fenix.components.components +import org.mozilla.fenix.compose.ComposeViewHolder +import org.mozilla.fenix.home.bookmarks.interactor.BookmarksInteractor +import org.mozilla.fenix.wallpapers.WallpaperState + +/** + * ViewHolder for the Bookmarks section in the HomeFragment. + */ +class BookmarksViewHolder( + composeView: ComposeView, + viewLifecycleOwner: LifecycleOwner, + val interactor: BookmarksInteractor, +) : ComposeViewHolder(composeView, viewLifecycleOwner) { + + init { + HomeBookmarks.shown.record(NoExtras()) + } + + companion object { + val LAYOUT_ID = View.generateViewId() + } + + @Composable + override fun Content() { + val bookmarks = components.appStore.observeAsComposableState { state -> state.bookmarks } + val wallpaperState = components.appStore + .observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default + + Bookmarks( + bookmarks = bookmarks.value ?: emptyList(), + backgroundColor = wallpaperState.wallpaperCardColor, + onBookmarkClick = interactor::onBookmarkClicked, + menuItems = listOf( + BookmarksMenuItem( + stringResource(id = R.string.home_bookmarks_menu_item_remove), + onClick = { bookmark -> interactor.onBookmarkRemoved(bookmark) }, + ), + ), + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/collections/CollectionViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/collections/CollectionViewHolder.kt index cd70ec0612..80b783b5da 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/collections/CollectionViewHolder.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/collections/CollectionViewHolder.kt @@ -141,7 +141,7 @@ private fun getMenuItems( MenuItem( title = stringResource(R.string.collection_delete), - color = FirefoxTheme.colors.textWarning, + color = FirefoxTheme.colors.textCritical, ) { onDeleteCollectionTapped(collection) }, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/intent/OpenRecentlyClosedIntentProcessor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/intent/OpenRecentlyClosedIntentProcessor.kt new file mode 100644 index 0000000000..441e2af7f1 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/intent/OpenRecentlyClosedIntentProcessor.kt @@ -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 org.mozilla.fenix.home.intent + +import android.content.Intent +import androidx.navigation.NavController +import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.ext.nav + +/** + * Opens the "recently closed tabs" fragment when the user taps on a + * "synced tabs closed" notification. + */ +class OpenRecentlyClosedIntentProcessor : HomeIntentProcessor { + override fun process(intent: Intent, navController: NavController, out: Intent): Boolean { + return if (intent.action == ACTION_OPEN_RECENTLY_CLOSED) { + val directions = NavGraphDirections.actionGlobalRecentlyClosed() + navController.nav(null, directions) + true + } else { + false + } + } + + companion object { + const val ACTION_OPEN_RECENTLY_CLOSED = "org.mozilla.fenix.open_recently_closed" + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/RecentBookmarksFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/RecentBookmarksFeature.kt deleted file mode 100644 index 3e7dc9d6c5..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/RecentBookmarksFeature.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import mozilla.components.concept.storage.BookmarkNode -import mozilla.components.support.base.feature.LifecycleAwareFeature -import org.mozilla.fenix.components.AppStore -import org.mozilla.fenix.components.appstate.AppAction -import org.mozilla.fenix.components.bookmarks.BookmarksUseCase -import org.mozilla.fenix.home.HomeFragment - -/** - * View-bound feature that retrieves a list of recently added [BookmarkNode]s and dispatches - * updates to the [AppStore]. - * - * @param appStore the [AppStore] that holds the state of the [HomeFragment]. - * @param bookmarksUseCase the [BookmarksUseCase] for retrieving the list of recently saved - * bookmarks from storage. - * @param scope the [CoroutineScope] used to fetch the bookmarks list - * @param ioDispatcher the [CoroutineDispatcher] for performing read/write operations. - */ -class RecentBookmarksFeature( - private val appStore: AppStore, - private val bookmarksUseCase: BookmarksUseCase, - private val scope: CoroutineScope, - private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, -) : LifecycleAwareFeature { - private var job: Job? = null - - override fun start() { - job = scope.launch(ioDispatcher) { - val bookmarks = bookmarksUseCase.retrieveRecentBookmarks() - appStore.dispatch(AppAction.RecentBookmarksChange(bookmarks)) - } - } - - override fun stop() { - job?.cancel() - } -} - -/** - * A bookmark that was recently added. - * - * @property title The title of the bookmark. - * @property url The url of the bookmark. - * @property previewImageUrl A preview image of the page (a.k.a. the hero image), if available. - */ -data class RecentBookmark( - val title: String? = null, - val url: String? = null, - val previewImageUrl: String? = null, -) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/controller/RecentBookmarksController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/controller/RecentBookmarksController.kt deleted file mode 100644 index a3591d9b08..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/controller/RecentBookmarksController.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.controller - -import androidx.navigation.NavController -import mozilla.appservices.places.BookmarkRoot -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.EngineSession -import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.ALLOW_JAVASCRIPT_URL -import mozilla.components.feature.tabs.TabsUseCases -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.GleanMetrics.RecentBookmarks -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.AppStore -import org.mozilla.fenix.components.appstate.AppAction -import org.mozilla.fenix.home.HomeFragmentDirections -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark -import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor - -/** - * An interface that handles the view manipulation of the recently saved bookmarks on the - * Home screen. - */ -interface RecentBookmarksController { - - /** - * @see [RecentBookmarksInteractor.onRecentBookmarkClicked] - */ - fun handleBookmarkClicked(bookmark: RecentBookmark) - - /** - * @see [RecentBookmarksInteractor.onShowAllBookmarksClicked] - */ - fun handleShowAllBookmarksClicked() - - /** - * @see [RecentBookmarksInteractor.onRecentBookmarkRemoved] - */ - fun handleBookmarkRemoved(bookmark: RecentBookmark) -} - -/** - * The default implementation of [RecentBookmarksController]. - */ -class DefaultRecentBookmarksController( - private val activity: HomeActivity, - private val navController: NavController, - private val appStore: AppStore, - private val browserStore: BrowserStore, - private val selectTabUseCase: TabsUseCases.SelectTabUseCase, -) : RecentBookmarksController { - - override fun handleBookmarkClicked(bookmark: RecentBookmark) { - val existingTabForBookmark = browserStore.state.tabs.firstOrNull { - it.content.url == bookmark.url - } - - if (existingTabForBookmark == null) { - activity.openToBrowserAndLoad( - searchTermOrURL = bookmark.url!!, - newTab = true, - from = BrowserDirection.FromHome, - flags = EngineSession.LoadUrlFlags.select(ALLOW_JAVASCRIPT_URL), - ) - } else { - selectTabUseCase.invoke(existingTabForBookmark.id) - navController.navigate(R.id.browserFragment) - } - - RecentBookmarks.bookmarkClicked.add() - } - - override fun handleShowAllBookmarksClicked() { - RecentBookmarks.showAllBookmarks.add() - navController.navigate( - HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id), - ) - } - - override fun handleBookmarkRemoved(bookmark: RecentBookmark) { - appStore.dispatch(AppAction.RemoveRecentBookmark(bookmark)) - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/interactor/RecentBookmarksInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/interactor/RecentBookmarksInteractor.kt deleted file mode 100644 index 810da7e14a..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/interactor/RecentBookmarksInteractor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.interactor - -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark -import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor - -/** - * Interface for recently saved bookmark related actions in the [SessionControlInteractor]. - */ -interface RecentBookmarksInteractor { - - /** - * Opens the given bookmark in a new tab. Called when an user clicks on a recently saved - * bookmark on the home screen. - * - * @param bookmark The bookmark that will be opened. - */ - fun onRecentBookmarkClicked(bookmark: RecentBookmark) - - /** - * Navigates to bookmark list. Called when an user clicks on the "Show all" button for - * recently saved bookmarks on the home screen. - */ - fun onShowAllBookmarksClicked() - - /** - * Removes a bookmark from the recent bookmark list. Called when a user clicks the "Remove" - * button for recently saved bookmarks on the home screen. - * - * @param bookmark The bookmark that has been removed. - */ - fun onRecentBookmarkRemoved(bookmark: RecentBookmark) -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarks.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarks.kt deleted file mode 100644 index df32d3940b..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarks.kt +++ /dev/null @@ -1,253 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.view - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import mozilla.components.browser.icons.compose.Loader -import mozilla.components.browser.icons.compose.Placeholder -import mozilla.components.ui.colors.PhotonColors -import org.mozilla.fenix.components.components -import org.mozilla.fenix.compose.ContextualMenu -import org.mozilla.fenix.compose.Favicon -import org.mozilla.fenix.compose.Image -import org.mozilla.fenix.compose.MenuItem -import org.mozilla.fenix.compose.annotation.LightDarkPreview -import org.mozilla.fenix.compose.inComposePreview -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark -import org.mozilla.fenix.theme.FirefoxTheme - -private val cardShape = RoundedCornerShape(8.dp) - -private val imageWidth = 126.dp - -private val imageModifier = Modifier - .size(width = imageWidth, height = 82.dp) - .clip(cardShape) - -/** - * A list of recent bookmarks. - * - * @param bookmarks List of [RecentBookmark]s to display. - * @param menuItems List of [RecentBookmarksMenuItem] shown when long clicking a [RecentBookmarkItem] - * @param backgroundColor The background [Color] of each bookmark. - * @param onRecentBookmarkClick Invoked when the user clicks on a recent bookmark. - */ -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun RecentBookmarks( - bookmarks: List, - menuItems: List, - backgroundColor: Color, - onRecentBookmarkClick: (RecentBookmark) -> Unit = {}, -) { - LazyRow( - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "recent.bookmarks" - }, - contentPadding = PaddingValues(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - items(bookmarks) { bookmark -> - RecentBookmarkItem( - bookmark = bookmark, - menuItems = menuItems, - backgroundColor = backgroundColor, - onRecentBookmarkClick = onRecentBookmarkClick, - ) - } - } -} - -/** - * A recent bookmark item. - * - * @param bookmark The [RecentBookmark] to display. - * @param menuItems The list of [RecentBookmarksMenuItem] shown when long clicking on the recent bookmark item. - * @param backgroundColor The background [Color] of the recent bookmark item. - * @param onRecentBookmarkClick Invoked when the user clicks on the recent bookmark item. - */ -@OptIn( - ExperimentalFoundationApi::class, - ExperimentalComposeUiApi::class, -) -@Composable -private fun RecentBookmarkItem( - bookmark: RecentBookmark, - menuItems: List, - backgroundColor: Color, - onRecentBookmarkClick: (RecentBookmark) -> Unit = {}, -) { - var isMenuExpanded by remember { mutableStateOf(false) } - - Card( - modifier = Modifier - .width(158.dp) - .combinedClickable( - enabled = true, - onClick = { onRecentBookmarkClick(bookmark) }, - onLongClick = { isMenuExpanded = true }, - ), - shape = cardShape, - backgroundColor = backgroundColor, - elevation = 6.dp, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp), - ) { - RecentBookmarkImage(bookmark) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = bookmark.title ?: bookmark.url ?: "", - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "recent.bookmark.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - - ContextualMenu( - showMenu = isMenuExpanded, - onDismissRequest = { isMenuExpanded = false }, - menuItems = menuItems.map { item -> MenuItem(item.title) { item.onClick(bookmark) } }, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "recent.bookmark.menu" - }, - ) - } - } -} - -@Composable -private fun RecentBookmarkImage(bookmark: RecentBookmark) { - when { - !bookmark.previewImageUrl.isNullOrEmpty() -> { - Image( - url = bookmark.previewImageUrl, - modifier = imageModifier, - targetSize = imageWidth, - contentScale = ContentScale.Crop, - fallback = { - if (!bookmark.url.isNullOrEmpty()) { - FallbackBookmarkFaviconImage(url = bookmark.url) - } - }, - ) - } - !bookmark.url.isNullOrEmpty() && !inComposePreview -> { - components.core.icons.Loader(bookmark.url) { - Placeholder { - PlaceholderBookmarkImage() - } - - FallbackBookmarkFaviconImage(bookmark.url) - } - } - inComposePreview -> { - PlaceholderBookmarkImage() - } - } -} - -@Composable -private fun PlaceholderBookmarkImage() { - Box( - modifier = imageModifier.background( - color = when (isSystemInDarkTheme()) { - true -> PhotonColors.DarkGrey60 - false -> PhotonColors.LightGrey30 - }, - ), - ) -} - -@Composable -private fun FallbackBookmarkFaviconImage( - url: String, -) { - Box( - modifier = imageModifier.background( - color = FirefoxTheme.colors.layer2, - ), - contentAlignment = Alignment.Center, - ) { - Favicon(url = url, size = 36.dp) - } -} - -@Composable -@LightDarkPreview -private fun RecentBookmarksPreview() { - FirefoxTheme { - RecentBookmarks( - bookmarks = listOf( - RecentBookmark( - title = "Other Bookmark Title", - url = "https://www.example.com", - previewImageUrl = null, - ), - RecentBookmark( - title = "Other Bookmark Title", - url = "https://www.example.com", - previewImageUrl = null, - ), - RecentBookmark( - title = "Other Bookmark Title", - url = "https://www.example.com", - previewImageUrl = null, - ), - RecentBookmark( - title = "Other Bookmark Title", - url = "https://www.example.com", - previewImageUrl = null, - ), - ), - menuItems = listOf(), - backgroundColor = FirefoxTheme.colors.layer2, - ) - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksHeaderViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksHeaderViewHolder.kt deleted file mode 100644 index 210bad9623..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksHeaderViewHolder.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.view - -import android.view.View -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.LifecycleOwner -import org.mozilla.fenix.R -import org.mozilla.fenix.compose.ComposeViewHolder -import org.mozilla.fenix.compose.home.HomeSectionHeader -import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor - -/** - * View holder for the recent bookmarks header and "Show all" button. - * - * @param composeView [ComposeView] which will be populated with Jetpack Compose UI content. - * @param viewLifecycleOwner [LifecycleOwner] life cycle owner for the view. - * @param interactor [RecentBookmarksInteractor] which will have delegated to all user interactions. - */ -class RecentBookmarksHeaderViewHolder( - composeView: ComposeView, - viewLifecycleOwner: LifecycleOwner, - private val interactor: RecentBookmarksInteractor, -) : ComposeViewHolder(composeView, viewLifecycleOwner) { - - init { - val horizontalPadding = - composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin) - composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0) - } - - @Composable - override fun Content() { - Column { - Spacer(modifier = Modifier.height(40.dp)) - - HomeSectionHeader( - headerText = stringResource(R.string.recently_saved_title), - description = stringResource(R.string.recently_saved_show_all_content_description_2), - onShowAllClick = { - interactor.onShowAllBookmarksClicked() - }, - ) - - Spacer(Modifier.height(16.dp)) - } - } - - companion object { - val LAYOUT_ID = View.generateViewId() - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksMenuItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksMenuItem.kt deleted file mode 100644 index 961245ce1f..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksMenuItem.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.view - -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark - -/** - * A menu item in the recent bookmarks dropdown menu. - * - * @property title The menu item title. - * @property onClick Invoked when the user clicks on the menu item. - */ -data class RecentBookmarksMenuItem( - val title: String, - val onClick: (RecentBookmark) -> Unit, -) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksViewHolder.kt deleted file mode 100644 index 13912bd041..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/recentbookmarks/view/RecentBookmarksViewHolder.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* 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 org.mozilla.fenix.home.recentbookmarks.view - -import android.view.View -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.res.stringResource -import androidx.lifecycle.LifecycleOwner -import mozilla.components.lib.state.ext.observeAsComposableState -import mozilla.components.service.glean.private.NoExtras -import org.mozilla.fenix.R -import org.mozilla.fenix.components.components -import org.mozilla.fenix.compose.ComposeViewHolder -import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor -import org.mozilla.fenix.wallpapers.WallpaperState -import org.mozilla.fenix.GleanMetrics.RecentBookmarks as RecentBookmarksMetrics - -class RecentBookmarksViewHolder( - composeView: ComposeView, - viewLifecycleOwner: LifecycleOwner, - val interactor: RecentBookmarksInteractor, -) : ComposeViewHolder(composeView, viewLifecycleOwner) { - - init { - RecentBookmarksMetrics.shown.record(NoExtras()) - } - - companion object { - val LAYOUT_ID = View.generateViewId() - } - - @Composable - override fun Content() { - val recentBookmarks = components.appStore.observeAsComposableState { state -> state.recentBookmarks } - val wallpaperState = components.appStore - .observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default - - RecentBookmarks( - bookmarks = recentBookmarks.value ?: emptyList(), - backgroundColor = wallpaperState.wallpaperCardColor, - onRecentBookmarkClick = interactor::onRecentBookmarkClicked, - menuItems = listOf( - RecentBookmarksMenuItem( - stringResource(id = R.string.recently_saved_menu_item_remove), - onClick = { bookmark -> interactor.onRecentBookmarkRemoved(bookmark) }, - ), - ), - ) - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index ff93260109..4b5d49e697 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -18,13 +18,13 @@ import mozilla.components.service.nimbus.messaging.Message import org.mozilla.fenix.components.Components import org.mozilla.fenix.home.BottomSpacerViewHolder import org.mozilla.fenix.home.TopPlaceholderViewHolder +import org.mozilla.fenix.home.bookmarks.view.BookmarksHeaderViewHolder +import org.mozilla.fenix.home.bookmarks.view.BookmarksViewHolder import org.mozilla.fenix.home.collections.CollectionViewHolder import org.mozilla.fenix.home.collections.TabInCollectionViewHolder import org.mozilla.fenix.home.pocket.PocketCategoriesViewHolder import org.mozilla.fenix.home.pocket.PocketRecommendationsHeaderViewHolder import org.mozilla.fenix.home.pocket.PocketStoriesViewHolder -import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarksHeaderViewHolder -import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarksViewHolder import org.mozilla.fenix.home.recentsyncedtabs.view.RecentSyncedTabViewHolder import org.mozilla.fenix.home.recenttabs.view.RecentTabViewHolder import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder @@ -155,8 +155,15 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { object RecentVisitsHeader : AdapterItem(RecentVisitsHeaderViewHolder.LAYOUT_ID) object RecentVisitsItems : AdapterItem(RecentlyVisitedViewHolder.LAYOUT_ID) - object RecentBookmarksHeader : AdapterItem(RecentBookmarksHeaderViewHolder.LAYOUT_ID) - object RecentBookmarks : AdapterItem(RecentBookmarksViewHolder.LAYOUT_ID) + /** + * The header for the Bookmarks section. + */ + object BookmarksHeader : AdapterItem(BookmarksHeaderViewHolder.LAYOUT_ID) + + /** + * The Bookmarks section. + */ + object Bookmarks : AdapterItem(BookmarksViewHolder.LAYOUT_ID) object PocketStoriesItem : AdapterItem(PocketStoriesViewHolder.LAYOUT_ID) object PocketCategoriesItem : AdapterItem(PocketCategoriesViewHolder.LAYOUT_ID) @@ -230,7 +237,7 @@ class SessionControlAdapter( viewLifecycleOwner = viewLifecycleOwner, interactor = interactor, ) - RecentBookmarksViewHolder.LAYOUT_ID -> return RecentBookmarksViewHolder( + BookmarksViewHolder.LAYOUT_ID -> return BookmarksViewHolder( composeView = ComposeView(parent.context), viewLifecycleOwner = viewLifecycleOwner, interactor = interactor, @@ -255,7 +262,7 @@ class SessionControlAdapter( viewLifecycleOwner = viewLifecycleOwner, interactor = interactor, ) - RecentBookmarksHeaderViewHolder.LAYOUT_ID -> return RecentBookmarksHeaderViewHolder( + BookmarksHeaderViewHolder.LAYOUT_ID -> return BookmarksHeaderViewHolder( composeView = ComposeView(parent.context), viewLifecycleOwner = viewLifecycleOwner, interactor = interactor, @@ -314,8 +321,8 @@ class SessionControlAdapter( is CustomizeHomeButtonViewHolder, is RecentlyVisitedViewHolder, is RecentVisitsHeaderViewHolder, - is RecentBookmarksViewHolder, - is RecentBookmarksHeaderViewHolder, + is BookmarksViewHolder, + is BookmarksHeaderViewHolder, is RecentTabViewHolder, is RecentSyncedTabViewHolder, is RecentTabsHeaderViewHolder, @@ -394,7 +401,7 @@ class SessionControlAdapter( } is TopSitesViewHolder, is RecentlyVisitedViewHolder, - is RecentBookmarksViewHolder, + is BookmarksViewHolder, is RecentTabViewHolder, is RecentSyncedTabViewHolder, is PocketStoriesViewHolder, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index 6f429e6dfb..a5412eeb9a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -32,10 +32,10 @@ import mozilla.components.ui.widgets.withCenterAlignedButtons import mozilla.telemetry.glean.private.NoExtras import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.GleanMetrics.Collections +import org.mozilla.fenix.GleanMetrics.HomeBookmarks import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.GleanMetrics.Pocket -import org.mozilla.fenix.GleanMetrics.RecentBookmarks import org.mozilla.fenix.GleanMetrics.RecentTabs import org.mozilla.fenix.GleanMetrics.TopSites import org.mozilla.fenix.HomeActivity @@ -551,6 +551,6 @@ class DefaultSessionControlController( RecentTabs.sectionVisible.set(true) } - RecentBookmarks.recentBookmarksCount.set(state.recentBookmarks.size.toLong()) + HomeBookmarks.bookmarksCount.set(state.bookmarks.size.toLong()) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index 8ef6086cfe..f5d214fd92 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -11,14 +11,14 @@ import mozilla.components.service.nimbus.messaging.Message import mozilla.components.service.pocket.PocketStory import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.appstate.AppState +import org.mozilla.fenix.home.bookmarks.Bookmark +import org.mozilla.fenix.home.bookmarks.controller.BookmarksController +import org.mozilla.fenix.home.bookmarks.interactor.BookmarksInteractor import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory import org.mozilla.fenix.home.pocket.PocketStoriesController import org.mozilla.fenix.home.pocket.PocketStoriesInteractor import org.mozilla.fenix.home.privatebrowsing.controller.PrivateBrowsingController import org.mozilla.fenix.home.privatebrowsing.interactor.PrivateBrowsingInteractor -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark -import org.mozilla.fenix.home.recentbookmarks.controller.RecentBookmarksController -import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab import org.mozilla.fenix.home.recentsyncedtabs.controller.RecentSyncedTabController import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor @@ -229,7 +229,7 @@ class SessionControlInteractor( private val controller: SessionControlController, private val recentTabController: RecentTabController, private val recentSyncedTabController: RecentSyncedTabController, - private val recentBookmarksController: RecentBookmarksController, + private val bookmarksController: BookmarksController, private val recentVisitsController: RecentVisitsController, private val pocketStoriesController: PocketStoriesController, private val privateBrowsingController: PrivateBrowsingController, @@ -242,7 +242,7 @@ class SessionControlInteractor( MessageCardInteractor, RecentTabInteractor, RecentSyncedTabInteractor, - RecentBookmarksInteractor, + BookmarksInteractor, RecentVisitsInteractor, CustomizeHomeIteractor, PocketStoriesInteractor, @@ -366,16 +366,16 @@ class SessionControlInteractor( recentSyncedTabController.handleRecentSyncedTabRemoved(tab) } - override fun onRecentBookmarkClicked(bookmark: RecentBookmark) { - recentBookmarksController.handleBookmarkClicked(bookmark) + override fun onBookmarkClicked(bookmark: Bookmark) { + bookmarksController.handleBookmarkClicked(bookmark) } override fun onShowAllBookmarksClicked() { - recentBookmarksController.handleShowAllBookmarksClicked() + bookmarksController.handleShowAllBookmarksClicked() } - override fun onRecentBookmarkRemoved(bookmark: RecentBookmark) { - recentBookmarksController.handleBookmarkRemoved(bookmark) + override fun onBookmarkRemoved(bookmark: Bookmark) { + bookmarksController.handleBookmarkRemoved(bookmark) } override fun onHistoryShowAllClicked() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 3e7e9ddee7..902d7d8688 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -20,7 +20,7 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.shouldShowRecentSyncedTabs import org.mozilla.fenix.ext.shouldShowRecentTabs -import org.mozilla.fenix.home.recentbookmarks.RecentBookmark +import org.mozilla.fenix.home.bookmarks.Bookmark import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem import org.mozilla.fenix.messaging.FenixMessageSurfaceId import org.mozilla.fenix.onboarding.HomeCFRPresenter @@ -35,7 +35,7 @@ internal fun normalModeAdapterItems( topSites: List, collections: List, expandedCollections: Set, - recentBookmarks: List, + bookmarks: List, showCollectionsPlaceholder: Boolean, nimbusMessageCard: Message? = null, showRecentTab: Boolean, @@ -72,10 +72,10 @@ internal fun normalModeAdapterItems( } } - if (settings.showRecentBookmarksFeature && recentBookmarks.isNotEmpty()) { + if (settings.showBookmarksHomeFeature && bookmarks.isNotEmpty()) { shouldShowCustomizeHome = true - items.add(AdapterItem.RecentBookmarksHeader) - items.add(AdapterItem.RecentBookmarks) + items.add(AdapterItem.BookmarksHeader) + items.add(AdapterItem.Bookmarks) } if (settings.historyMetadataUIFeature && recentVisits.isNotEmpty()) { @@ -137,7 +137,7 @@ private fun AppState.toAdapterList(settings: Settings): List = when topSites, collections, expandedCollections, - recentBookmarks, + bookmarks, showCollectionPlaceholder, messaging.messageToShow[FenixMessageSurfaceId.HOMESCREEN], shouldShowRecentTabs(settings), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/PagerIndicator.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/PagerIndicator.kt index 72f315d165..8046ed3dfd 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/PagerIndicator.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/PagerIndicator.kt @@ -8,8 +8,8 @@ import android.content.Context import android.util.AttributeSet import android.util.TypedValue import android.view.View +import android.view.ViewGroup.LayoutParams import android.widget.LinearLayout -import androidx.core.view.MarginLayoutParamsCompat import org.mozilla.fenix.R /** @@ -46,7 +46,7 @@ class PagerIndicator : LinearLayout { }, LayoutParams(dpToPx(DOT_SIZE_IN_DP), dpToPx(DOT_SIZE_IN_DP)).apply { if (!isLast) { - MarginLayoutParamsCompat.setMarginEnd(this, dpToPx(DOT_MARGIN)) + this.setMarginEnd(dpToPx(DOT_MARGIN)) } }, ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index 4664675890..5afe189836 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -67,9 +67,7 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan private lateinit var bookmarkStore: BookmarkFragmentStore private lateinit var bookmarkView: BookmarkView - private var _bookmarkInteractor: BookmarkFragmentInteractor? = null - private val bookmarkInteractor: BookmarkFragmentInteractor - get() = _bookmarkInteractor!! + private lateinit var bookmarkInteractor: BookmarkFragmentInteractor private val sharedViewModel: BookmarksSharedViewModel by activityViewModels() private val desktopFolders by lazy { DesktopFolders(requireContext(), showMobileRoot = false) } @@ -92,7 +90,7 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan BookmarkFragmentStore(BookmarkFragmentState(null)) } - _bookmarkInteractor = BookmarkFragmentInteractor( + bookmarkInteractor = BookmarkFragmentInteractor( bookmarksController = DefaultBookmarkController( activity = requireActivity() as HomeActivity, navController = findNavController(), @@ -191,7 +189,7 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan menu.findItem(R.id.delete_bookmarks_multi_select).title = SpannableString(getString(R.string.bookmark_menu_delete_button)).apply { - setTextColor(requireContext(), R.attr.textWarning) + setTextColor(requireContext(), R.attr.textCritical) } } } @@ -391,7 +389,6 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan override fun onDestroyView() { super.onDestroyView() - _bookmarkInteractor = null _binding = null } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt index fe46380044..58864f750f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkItemMenu.kt @@ -104,7 +104,7 @@ class BookmarkItemMenu( }, TextMenuCandidate( text = context.getString(R.string.bookmark_menu_delete_button), - textStyle = TextStyle(color = context.getColorFromAttr(R.attr.textWarning)), + textStyle = TextStyle(color = context.getColorFromAttr(R.attr.textCritical)), ) { onItemTapped.invoke(Item.Delete) }, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt index b80b013eba..e3e8affee8 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt @@ -299,7 +299,7 @@ class EditBookmarkFragment : Fragment(R.layout.fragment_edit_bookmark), MenuProv ColorStateList.valueOf( ContextCompat.getColor( requireContext(), - R.color.fx_mobile_text_color_warning, + R.color.fx_mobile_text_color_critical, ), ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadFragment.kt index 2fcac34ab1..34d68916b9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadFragment.kt @@ -165,7 +165,7 @@ class DownloadFragment : LibraryPageFragment(), UserInteractionHan menu.findItem(R.id.delete_downloads_multi_select)?.title = SpannableString(getString(R.string.download_delete_item_1)).apply { - setTextColor(requireContext(), R.attr.textWarning) + setTextColor(requireContext(), R.attr.textCritical) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadItemMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadItemMenu.kt index e1c4dc4407..a747139d97 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadItemMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadItemMenu.kt @@ -34,7 +34,7 @@ class DownloadItemMenu( TextMenuCandidate( text = context.getString(R.string.history_delete_item), textStyle = TextStyle( - color = context.getColorFromAttr(R.attr.textWarning), + color = context.getColorFromAttr(R.attr.textCritical), ), ) { onItemTapped.invoke(Item.Delete) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index fbeda874b7..5de7644db3 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -254,7 +254,7 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandler, menu.findItem(R.id.share_history_multi_select)?.isVisible = true menu.findItem(R.id.delete_history_multi_select)?.title = SpannableString(getString(R.string.bookmark_menu_delete_button)).apply { - setTextColor(requireContext(), R.attr.textWarning) + setTextColor(requireContext(), R.attr.textCritical) } } else { inflater.inflate(R.menu.history_menu, menu) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/historymetadata/HistoryMetadataGroupFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/historymetadata/HistoryMetadataGroupFragment.kt index 7ba3a9cf13..e84fbbc440 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/historymetadata/HistoryMetadataGroupFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/historymetadata/HistoryMetadataGroupFragment.kt @@ -157,7 +157,7 @@ class HistoryMetadataGroupFragment : menu.findItem(R.id.delete_history_multi_select)?.let { deleteItem -> deleteItem.title = SpannableString(deleteItem.title).apply { - setTextColor(requireContext(), R.attr.textWarning) + setTextColor(requireContext(), R.attr.textCritical) } } } else { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt index 593a9d3678..fb9aa6bfb7 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedFragment.kt @@ -58,7 +58,7 @@ class RecentlyClosedFragment : inflater.inflate(R.menu.history_select_multi, menu) menu.findItem(R.id.delete_history_multi_select)?.let { deleteItem -> deleteItem.title = SpannableString(deleteItem.title) - .apply { setTextColor(requireContext(), R.attr.textWarning) } + .apply { setTextColor(requireContext(), R.attr.textCritical) } } } else { inflater.inflate(R.menu.library_menu, menu) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/FenixMessageSurfaceId.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/FenixMessageSurfaceId.kt index f7092f6b01..e362b435cb 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/FenixMessageSurfaceId.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/FenixMessageSurfaceId.kt @@ -22,4 +22,9 @@ object FenixMessageSurfaceId { * A survey dialog that is intended to be disruptive. */ const val SURVEY = "survey" + + /** + * A microsurvey UI for a specific feature. + */ + const val MICROSURVEY = "microsurvey" } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/MessagingFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/MessagingFeature.kt index 9e9a5ef812..c8eb6508c7 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/MessagingFeature.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/messaging/MessagingFeature.kt @@ -4,17 +4,18 @@ package org.mozilla.fenix.messaging +import mozilla.components.service.nimbus.messaging.MessageSurfaceId import mozilla.components.support.base.feature.LifecycleAwareFeature import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.appstate.AppAction.MessagingAction /** - * A message observer that updates the provided. + * A [LifecycleAwareFeature] which tries to evaluate if message is available for the provided [surface]. */ -class MessagingFeature(val appStore: AppStore) : LifecycleAwareFeature { +class MessagingFeature(val appStore: AppStore, val surface: MessageSurfaceId) : LifecycleAwareFeature { override fun start() { - appStore.dispatch(MessagingAction.Evaluate(FenixMessageSurfaceId.HOMESCREEN)) + appStore.dispatch(MessagingAction.Evaluate(surface)) } override fun stop() = Unit diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyContent.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyContent.kt new file mode 100644 index 0000000000..e89c5b64ed --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyContent.kt @@ -0,0 +1,123 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewScreenSizes +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.list.RadioButtonListItem +import org.mozilla.fenix.theme.FirefoxTheme + +private val shape = RoundedCornerShape(16.dp) +private val elevation: Dp = 5.dp + +/** + * The micro survey content UI to hold question and answer data. + * + * @param question The survey question text. + * @param answers The survey answer text options available for the question. + * @param icon The survey icon, this will represent the feature the survey is for. + * @param backgroundColor The view background color. + * @param selectedAnswer The current selected answer. Will be null until user selects an option. + * @param onSelectionChange An event that updates the [selectedAnswer]. + */ +@Composable +fun MicroSurveyContent( + question: String, + answers: List, + @DrawableRes icon: Int = R.drawable.ic_print, // todo currently unknown what the default will be if any. + backgroundColor: Color = FirefoxTheme.colors.layer2, + selectedAnswer: String? = null, + onSelectionChange: (String) -> Unit, +) { + Card( + shape = shape, + backgroundColor = backgroundColor, + elevation = elevation, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + ) { + Column(modifier = Modifier.wrapContentHeight()) { + Header(icon, question) + + answers.forEach { + RadioButtonListItem( + label = it, + selected = selectedAnswer == it, + onClick = { + onSelectionChange.invoke(it) + }, + ) + } + } + } +} + +@Composable +private fun Header(icon: Int, question: String) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(icon), + contentDescription = "Survey icon", // todo update to string res once a11y strings are available. + modifier = Modifier.size(24.dp), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Text( + text = question, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline7, + ) + } +} + +/** + * Preview for [MicroSurveyContent]. + */ +@PreviewScreenSizes +@LightDarkPreview +@Composable +fun MicroSurveyContentPreview() { + FirefoxTheme { + MicroSurveyContent( + question = "How satisfied are you with printing in Firefox?", + icon = R.drawable.ic_print, + answers = listOf( + stringResource(id = R.string.likert_scale_option_1), + stringResource(id = R.string.likert_scale_option_2), + stringResource(id = R.string.likert_scale_option_3), + stringResource(id = R.string.likert_scale_option_4), + stringResource(id = R.string.likert_scale_option_5), + ), + onSelectionChange = {}, + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyFooter.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyFooter.kt new file mode 100644 index 0000000000..e9dd23c97f --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyFooter.kt @@ -0,0 +1,105 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.PreviewScreenSizes +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.LinkText +import org.mozilla.fenix.compose.LinkTextState +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * The footer UI used for micro-survey. + * + * @param isSubmitted Whether the user has "Submitted" the survey or not. + * @param isContentAnswerSelected Whether the user clicked on one of the answers or not. + * @param onLinkClick Invoked when the link is clicked. + * @param onButtonClick Invoked when the "Submit"/"Close" button is clicked. + */ +@Composable +fun MicroSurveyFooter( + isSubmitted: Boolean, + isContentAnswerSelected: Boolean, + onLinkClick: () -> Unit, + onButtonClick: () -> Unit, +) { + val buttonText = if (isSubmitted) { + stringResource(id = R.string.micro_survey_close_button_label) + } else { + stringResource(id = R.string.micro_survey_submit_button_label) + } + val buttonColor = if (isContentAnswerSelected) { + FirefoxTheme.colors.actionPrimary + } else { + FirefoxTheme.colors.actionPrimaryDisabled + } + + Row( + verticalAlignment = Alignment.Bottom, + modifier = Modifier.fillMaxWidth(), + ) { + LinkText( + text = stringResource(id = R.string.about_privacy_notice), + linkTextStates = listOf( + LinkTextState( + text = stringResource(id = R.string.micro_survey_privacy_notice), + url = "", + onClick = { + onLinkClick() + }, + ), + ), + style = FirefoxTheme.typography.caption, + linkTextDecoration = TextDecoration.Underline, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Button( + onClick = { onButtonClick() }, + enabled = isContentAnswerSelected, + shape = RoundedCornerShape(size = 4.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = buttonColor, + ), + contentPadding = PaddingValues(16.dp, 12.dp), + ) { + Text( + text = buttonText, + color = FirefoxTheme.colors.textActionPrimary, + style = FirefoxTheme.typography.button, + ) + } + } +} + +@PreviewScreenSizes +@LightDarkPreview +@Composable +private fun ReviewQualityCheckFooterPreview() { + FirefoxTheme { + MicroSurveyFooter( + isSubmitted = false, + isContentAnswerSelected = false, + onLinkClick = {}, + onButtonClick = {}, + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyHeader.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyHeader.kt new file mode 100644 index 0000000000..77fda94dc2 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyHeader.kt @@ -0,0 +1,86 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewScreenSizes +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * The header UI used for micro-survey. + * + * @param title The text that will be visible on the header. + * @param onCloseButtonClick Invoked when the close button is clicked. + */ +@Composable +fun MicroSurveyHeader( + title: String, + onCloseButtonClick: () -> Unit, +) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Image( + painter = painterResource(R.drawable.ic_firefox), + contentDescription = null, // todo update to string res once a11y strings are available. + modifier = Modifier.size(24.dp), + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = title, + style = FirefoxTheme.typography.headline7, + color = FirefoxTheme.colors.textPrimary, + modifier = Modifier.weight(1f), + ) + + IconButton(onClick = onCloseButtonClick) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = null, // todo update to string res once a11y strings are available. + tint = FirefoxTheme.colors.iconPrimary, + modifier = Modifier.size(20.dp), + ) + } + } +} + +@PreviewScreenSizes +@LightDarkPreview +@Composable +private fun MicroSurveyHeaderPreview() { + FirefoxTheme { + Box( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer1) + .padding(16.dp), + ) { + MicroSurveyHeader(stringResource(R.string.micro_survey_survey_header)) {} + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyScaffold.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyScaffold.kt new file mode 100644 index 0000000000..20294a0158 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicroSurveyScaffold.kt @@ -0,0 +1,67 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * A scaffold for micro-survey UI that implements the basic layout structure with + * [content]. + * + * @param content The content of micro-survey. + */ +@Composable +fun MicroSurveyScaffold( + content: @Composable () -> Unit, +) { + var isOpen by remember { mutableStateOf(false) } + val cardShape = if (isOpen) { + RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + } else { + RectangleShape + } + val height = if (isOpen) { + 600.dp + } else { + 200.dp + } + + Card( + shape = cardShape, + backgroundColor = FirefoxTheme.colors.actionQuarternary, + modifier = Modifier + .fillMaxWidth() + .clickable( + onClick = { isOpen = !isOpen }, + ), + ) { + Column(modifier = Modifier.height(height)) { + content() + } + } +} + +@LightDarkPreview +@Composable +private fun MicroSurveyScaffoldPreview() { + FirefoxTheme { + MicroSurveyScaffold {} + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheet.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheet.kt new file mode 100644 index 0000000000..eaf2f63081 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheet.kt @@ -0,0 +1,117 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.traversalIndex +import androidx.compose.ui.tooling.preview.PreviewScreenSizes +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.BottomSheetHandle +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme + +private const val BOTTOM_SHEET_HANDLE_WIDTH_PERCENT = 0.1f +private val bottomSheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + +/** + * The microsurvey bottom sheet. + * + * @param question The question text. + * @param answers The answer text options available for the given [question]. + * @param icon The icon that represents the feature for the given [question]. + */ +@Composable +fun MicrosurveyBottomSheet( + question: String, + answers: List, + @DrawableRes icon: Int = R.drawable.ic_print, // todo currently unknown if default is used FXDROID-1921. +) { + var selectedAnswer by remember { mutableStateOf(null) } + var isSubmitted by remember { mutableStateOf(false) } + + Surface( + color = FirefoxTheme.colors.layer1, + shape = bottomSheetShape, + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding( + vertical = 8.dp, + horizontal = 16.dp, + ), + ) { + BottomSheetHandle( + onRequestDismiss = {}, + contentDescription = stringResource(R.string.review_quality_check_close_handle_content_description), + modifier = Modifier + .fillMaxWidth(BOTTOM_SHEET_HANDLE_WIDTH_PERCENT) + .align(Alignment.CenterHorizontally) + .semantics { traversalIndex = -1f }, + ) + + Spacer(modifier = Modifier.height(4.dp)) + + MicroSurveyHeader(title = stringResource(id = R.string.micro_survey_survey_header)) {} + + Spacer(modifier = Modifier.height(8.dp)) + + MicroSurveyContent( + question = question, + icon = icon, + answers = answers, + selectedAnswer = selectedAnswer, + onSelectionChange = { selectedAnswer = it }, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + MicroSurveyFooter( + isSubmitted = isSubmitted, + isContentAnswerSelected = selectedAnswer != null, + onLinkClick = {}, // todo add privacy policy link and open new tab FXDROID-1876. + onButtonClick = { isSubmitted = true }, + ) + } + } +} + +@PreviewScreenSizes +@LightDarkPreview +@Composable +private fun MicroSurveyBottomSheetPreview() { + FirefoxTheme { + MicrosurveyBottomSheet( + question = "How satisfied are you with printing in Firefox?", + icon = R.drawable.ic_print, + answers = listOf( + stringResource(id = R.string.likert_scale_option_1), + stringResource(id = R.string.likert_scale_option_2), + stringResource(id = R.string.likert_scale_option_3), + stringResource(id = R.string.likert_scale_option_4), + stringResource(id = R.string.likert_scale_option_5), + ), + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheetFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheetFragment.kt new file mode 100644 index 0000000000..338b1f3779 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyBottomSheetFragment.kt @@ -0,0 +1,66 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.mozilla.fenix.R +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * todo update behaviour FXDROID-1944. + * todo pass question and icon values from messaging FXDROID-1945. + * todo add dismiss request FXDROID-1946. + */ + +/** + * A bottom sheet fragment for displaying a microsurvey. + */ +class MicrosurveyBottomSheetFragment : BottomSheetDialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + super.onCreateDialog(savedInstanceState).apply { + setOnShowListener { + val bottomSheet = findViewById(R.id.design_bottom_sheet) + bottomSheet?.setBackgroundResource(android.R.color.transparent) + val behavior = BottomSheetBehavior.from(bottomSheet) + behavior.peekHeight = resources.displayMetrics.heightPixels + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = ComposeView(requireContext()).apply { + val answers = listOf( + getString(R.string.likert_scale_option_1), + getString(R.string.likert_scale_option_2), + getString(R.string.likert_scale_option_3), + getString(R.string.likert_scale_option_4), + getString(R.string.likert_scale_option_5), + ) + + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + + setContent { + FirefoxTheme { + MicrosurveyBottomSheet( + question = "How satisfied are you with printing in Firefox?", // todo get value from messaging + icon = R.drawable.ic_print, // todo get value from messaging + answers = answers, // todo get value from messaging + ) + } + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyRequestPrompt.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyRequestPrompt.kt new file mode 100644 index 0000000000..44d3ce71b2 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/microsurvey/ui/MicrosurveyRequestPrompt.kt @@ -0,0 +1,96 @@ +/* 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 org.mozilla.fenix.microsurvey.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewScreenSizes +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.button.PrimaryButton +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * Initial microsurvey prompt displayed to the user to request completion of feedback. + * + * @param title The prompt header title. + */ +@Composable +fun MicrosurveyRequestPrompt( + // todo this is the message title FXDROID-1966). + title: String = "Help make printing in Firefox better. It only takes a sec.", +) { + Column( + modifier = Modifier + .background(color = FirefoxTheme.colors.layer1) + .padding(all = 16.dp), + ) { + Header(title) + + Spacer(modifier = Modifier.height(8.dp)) + + PrimaryButton(text = stringResource(id = R.string.micro_survey_continue_button_label)) {} + } +} + +@Composable +private fun Header( + title: String, +) { + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Image( + painter = painterResource(R.drawable.ic_firefox), + contentDescription = null, // todo update to string res once a11y strings are available FXDROID-1919. + modifier = Modifier.size(24.dp), + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = title, + style = FirefoxTheme.typography.headline7, + color = FirefoxTheme.colors.textPrimary, + modifier = Modifier.weight(1f), + ) + + IconButton( + onClick = {}, // todo FXDROID-1947. + modifier = Modifier.size(20.dp), + ) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = null, // todo update to string res once a11y strings are available FXDROID-1919. + tint = FirefoxTheme.colors.iconPrimary, + ) + } + } +} + +@PreviewScreenSizes +@LightDarkPreview +@Composable +private fun MicrosurveyRequestPromptPreview() { + FirefoxTheme { + MicrosurveyRequestPrompt() + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt index 487e46b7d6..7bf2e6a547 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt @@ -10,6 +10,7 @@ import android.content.IntentFilter import android.content.pm.ActivityInfo import android.os.Build import android.os.Bundle +import android.os.StrictMode import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,6 +30,7 @@ import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint import org.mozilla.fenix.compose.LinkTextState import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.hideToolbar +import org.mozilla.fenix.ext.isDefaultBrowserPromptSupported import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.openSetDefaultBrowserOption import org.mozilla.fenix.ext.requireComponents @@ -51,7 +53,8 @@ class OnboardingFragment : Fragment() { private val pagesToDisplay by lazy { pagesToDisplay( - isNotDefaultBrowser(requireContext()), + isNotDefaultBrowser(requireContext()) && + activity?.isDefaultBrowserPromptSupported() == false, canShowNotificationPage(requireContext()), canShowAddSearchWidgetPrompt(), ) @@ -76,9 +79,11 @@ class OnboardingFragment : Fragment() { .registerReceiver(pinAppWidgetReceiver, filter) if (isNotDefaultBrowser(context) && - pagesToDisplay.none { it.type == OnboardingPageUiData.Type.DEFAULT_BROWSER } + activity?.isDefaultBrowserPromptSupported() == true ) { - promptToSetAsDefaultBrowser() + requireComponents.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + promptToSetAsDefaultBrowser() + } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt index f08b1a6552..351860f442 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt @@ -56,9 +56,8 @@ class ProfilerStopDialogFragment : DialogFragment() { } } - override fun dismiss() { + private fun setProfilerState() { profilerViewModel.setProfilerState(requireContext().components.core.engine.profiler!!.isProfilerActive()) - super.dismiss() } @Composable @@ -111,7 +110,14 @@ class ProfilerStopDialogFragment : DialogFragment() { ) { TextButton( onClick = { - requireContext().components.core.engine.profiler?.stopProfiler({}, {}) + requireContext().components.core.engine.profiler?.stopProfiler( + onSuccess = { + setProfilerState() + }, + onError = { + setProfilerState() + }, + ) dismiss() }, ) { @@ -162,6 +168,7 @@ class ProfilerStopDialogFragment : DialogFragment() { resources.getString(message) + extra, Toast.LENGTH_LONG, ).show() + setProfilerState() dismiss() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt index 28a8211e59..5fddb6500e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt @@ -34,6 +34,7 @@ private val firefox_features = arrayOf( "java", "processcpu", "ipcmessages", + "memory", ) private val firefox_threads = arrayOf( "GeckoMain", @@ -43,7 +44,8 @@ private val firefox_threads = arrayOf( "DOM Worker", ) -private val graphics_features = arrayOf("stackwalk", "js", "cpu", "java", "processcpu", "ipcmessages") +private val graphics_features = + arrayOf("stackwalk", "js", "cpu", "java", "processcpu", "ipcmessages", "memory") private val graphics_threads = arrayOf( "GeckoMain", "Compositor", @@ -64,6 +66,7 @@ private val media_features = arrayOf( "ipcmessages", "processcpu", "java", + "memory", ) private val media_threads = arrayOf( "cubeb", "audio", "BackgroundThreadPool", "camera", "capture", "Compositor", "decoder", "GeckoMain", "gmp", @@ -81,6 +84,7 @@ private val networking_features = arrayOf( "processcpu", "bandwidth", "ipcmessages", + "memory", ) private val networking_threads = arrayOf( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index cd0bc2f758..def5cd4999 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -17,6 +17,7 @@ import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.ext.settings @@ -139,7 +140,7 @@ class ToolbarView( }, ) - if (settings.isTabletAndTabStripEnabled) { + if (context.isTabStripEnabled()) { (layoutParams as ViewGroup.MarginLayoutParams).updateMargins( top = context.resources.getDimensionPixelSize(R.dimen.tab_strip_height), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt index 9f5d6b6d05..642e4d4160 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt @@ -19,6 +19,7 @@ import org.mozilla.fenix.GleanMetrics.AppTheme import org.mozilla.fenix.GleanMetrics.PullToRefreshInBrowser import org.mozilla.fenix.GleanMetrics.ToolbarSettings import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings @@ -53,7 +54,7 @@ class CustomizationFragment : PreferenceFragmentCompat() { bindLightTheme() bindAutoBatteryTheme() setupRadioGroups() - val tabletAndTabStripEnabled = requireContext().settings().isTabletAndTabStripEnabled + val tabletAndTabStripEnabled = requireContext().isTabStripEnabled() if (tabletAndTabStripEnabled) { val preferenceScreen: PreferenceScreen = requirePreference(R.string.pref_key_customization_preference_screen) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt index 9bba8fca3d..17427cda52 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt @@ -84,14 +84,14 @@ class HomeSettingsFragment : PreferenceFragmentCompat() { } } - requirePreference(R.string.pref_key_recent_bookmarks).apply { - isChecked = context.settings().showRecentBookmarksFeature + requirePreference(R.string.pref_key_customization_bookmarks).apply { + isChecked = context.settings().showBookmarksHomeFeature onPreferenceChangeListener = object : SharedPreferenceUpdater() { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { CustomizeHome.preferenceToggled.record( CustomizeHome.PreferenceToggledExtra( newValue as Boolean, - "recently_saved", + "bookmarks", ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt index 5a1f3c3a10..ea82f9f13b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt @@ -16,6 +16,9 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import mozilla.components.feature.qr.QrFeature +import mozilla.components.service.fxa.manager.SCOPE_PROFILE +import mozilla.components.service.fxa.manager.SCOPE_SESSION +import mozilla.components.service.fxa.manager.SCOPE_SYNC import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.R @@ -54,6 +57,7 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { requireContext(), pairingUrl, args.entrypoint, + setOf(SCOPE_SYNC, SCOPE_PROFILE, SCOPE_SESSION), ) val vibrator = requireContext().getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt index 5101f10f28..c50cbe6a1c 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt @@ -18,6 +18,7 @@ import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.isTabStripEligible import org.mozilla.fenix.debugsettings.data.DefaultDebugSettingsRepository import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav @@ -142,7 +143,7 @@ class SecretSettingsFragment : PreferenceFragmentCompat() { private fun setupTabStripPreference() { requirePreference(R.string.pref_key_enable_tab_strip).apply { - isVisible = Config.channel.isNightlyOrDebug && context.resources.getBoolean(R.bool.tablet) + isVisible = context.isTabStripEligible() isChecked = context.settings().isTabStripEnabled onPreferenceChangeListener = SharedPreferenceUpdater() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index d392ba8697..b6d9e1ea78 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -47,6 +47,7 @@ import org.mozilla.fenix.GleanMetrics.Addons import org.mozilla.fenix.GleanMetrics.CookieBanners import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.TrackingProtection +import org.mozilla.fenix.GleanMetrics.Translations import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint @@ -158,6 +159,10 @@ class SettingsFragment : PreferenceFragmentCompat() { updateProfilerUI(it) }, ) + + findPreference( + getPreferenceKey(R.string.pref_key_translation), + )?.isVisible = FxNimbus.features.translations.value().globalSettingsEnabled } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -315,6 +320,11 @@ class SettingsFragment : PreferenceFragmentCompat() { SettingsFragmentDirections.actionSettingsFragmentToLocaleSettingsFragment() } + resources.getString(R.string.pref_key_translation) -> { + Translations.action.record(Translations.ActionExtra("global_settings_from_preferences")) + SettingsFragmentDirections.actionSettingsFragmentToTranslationsSettingsFragment() + } + /* Privacy and security preferences */ resources.getString(R.string.pref_key_private_browsing) -> { SettingsFragmentDirections.actionSettingsFragmentToPrivateBrowsingFragment() diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt index da73fcc10e..9b0c7649ce 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt @@ -39,6 +39,11 @@ object SupportUtils { 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" + // This is locale-less on purpose so that the content negotiation happens on the AMO side because the current + // user language might not be supported by AMO and/or the language might not be exactly what AMO is expecting + // (e.g. `en` instead of `en-US`). + const val AMO_HOMEPAGE_FOR_ANDROID = "${BuildConfig.AMO_BASE_URL}/android/" + enum class SumoTopic(internal val topicStr: String) { HELP("faq-android"), PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SyncDebugFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SyncDebugFragment.kt index b5a0886498..62f33c6ce3 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SyncDebugFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SyncDebugFragment.kt @@ -11,6 +11,7 @@ import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceClickListener import androidx.preference.PreferenceFragmentCompat import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import kotlin.system.exitProcess @@ -59,6 +60,24 @@ class SyncDebugFragment : PreferenceFragmentCompat() { requirePreference(R.string.pref_key_use_react_fxa).apply { onPreferenceChangeListener = SharedPreferenceUpdater() } + requirePreference(R.string.pref_key_sync_debug_network_error).let { pref -> + pref.onPreferenceClickListener = OnPreferenceClickListener { + requireComponents.backgroundServices.accountManager.simulateNetworkError() + true + } + } + requirePreference(R.string.pref_key_sync_debug_temporary_auth_error).let { pref -> + pref.onPreferenceClickListener = OnPreferenceClickListener { + requireComponents.backgroundServices.accountManager.simulateTemporaryAuthTokenIssue() + true + } + } + requirePreference(R.string.pref_key_sync_debug_permanent_auth_error).let { pref -> + pref.onPreferenceClickListener = OnPreferenceClickListener { + requireComponents.backgroundServices.accountManager.simulatePermanentAuthTokenIssue() + true + } + } updateMenu() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/AccountProblemFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/AccountProblemFragment.kt index 9b82a91a88..6e39f105d4 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/AccountProblemFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/AccountProblemFragment.kt @@ -15,6 +15,8 @@ import kotlinx.coroutines.launch import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.service.fxa.manager.SCOPE_PROFILE +import mozilla.components.service.fxa.manager.SCOPE_SYNC import mozilla.telemetry.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.SyncAuth import org.mozilla.fenix.R @@ -27,7 +29,11 @@ class AccountProblemFragment : PreferenceFragmentCompat(), AccountObserver { private val args by navArgs() private val signInClickListener = Preference.OnPreferenceClickListener { - requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext(), args.entrypoint) + requireComponents.services.accountsAuthFeature.beginAuthentication( + requireContext(), + args.entrypoint, + setOf(SCOPE_PROFILE, SCOPE_SYNC), + ) SyncAuth.useEmailProblem.record(NoExtras()) // TODO The sign-in web content populates session history, // so pressing "back" after signing in won't take us back into the settings screen, but rather up the diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index ab03b079b7..a2e5f5b8e5 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -17,6 +17,8 @@ import androidx.navigation.fragment.navArgs import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.service.fxa.manager.SCOPE_PROFILE +import mozilla.components.service.fxa.manager.SCOPE_SYNC import mozilla.components.support.ktx.android.content.hasCamera import mozilla.components.support.ktx.android.content.isPermissionGranted import mozilla.components.support.ktx.android.view.hideKeyboard @@ -184,6 +186,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { requireComponents.services.accountsAuthFeature.beginAuthentication( requireContext(), entrypoint = args.entrypoint, + setOf(SCOPE_PROFILE, SCOPE_SYNC), ) SyncAuth.useEmail.record(NoExtras()) // TODO The sign-in web content populates session history, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt new file mode 100644 index 0000000000..db83dee4a1 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt @@ -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 org.mozilla.fenix.settings.biometric + +import android.app.KeyguardManager +import android.content.DialogInterface +import android.content.Intent +import android.provider.Settings +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.content.getSystemService +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.findFragment +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.ui.widgets.withCenterAlignedButtons +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.runIfFragmentIsAttached +import org.mozilla.fenix.ext.secure +import org.mozilla.fenix.ext.settings + +/** + * Prompts the biometric authentication before navigating to new fragment + * or displays warning dialog in case the feature is not available + */ +@Suppress("Deprecation") +fun bindBiometricsCredentialsPromptOrShowWarning( + view: View, + onShowPinVerification: (Intent) -> Unit, + onAuthSuccess: () -> Unit, + onAuthFailure: () -> Unit = {}, + doWhileAuthenticating: () -> Unit = {}, +) { + val (fragment, context) = Result.runCatching { + view.findFragment() as Fragment to view.context + }.getOrElse { return } + + val biometricPromptFeature = ViewBoundFeatureWrapper( + owner = fragment.viewLifecycleOwner, + view = view, + feature = BiometricPromptFeature( + context = context, + fragment = fragment, + onAuthSuccess = { + fragment.runIfFragmentIsAttached { + fragment.lifecycleScope.launch(Dispatchers.Main) { + onAuthSuccess() + } + } + }, + onAuthFailure = onAuthFailure, + ), + ) + // Use the BiometricPrompt first + if (BiometricPromptFeature.canUseFeature(context)) { + doWhileAuthenticating() + biometricPromptFeature.get() + ?.requestAuthentication(context.resources.getString(R.string.logins_biometric_prompt_message_2)) + return + } + + // Fallback to prompting for password with the KeyguardManager + val manager = context.getSystemService() + if (manager?.isKeyguardSecure == true) { + val confirmDeviceCredentialIntent = manager.createConfirmDeviceCredentialIntent( + context.resources.getString(R.string.logins_biometric_prompt_message_pin), + context.resources.getString(R.string.logins_biometric_prompt_message), + ) + onShowPinVerification(confirmDeviceCredentialIntent) + } else { + // Warn that the device has not been secured + if (context.settings().shouldShowSecurityPinWarning) { + fragment.activity?.let { + showPinDialogWarning(it, onAuthSuccess) + } ?: return + } else { + onAuthSuccess() + } + } +} + +@Suppress("MaxLineLength") +private fun showPinDialogWarning( + activity: FragmentActivity, + onIgnorePinWarning: () -> Unit, +) { + AlertDialog.Builder(activity).apply { + setTitle(context.resources.getString(R.string.logins_warning_dialog_title_2)) + setMessage( + context.resources.getString(R.string.logins_warning_dialog_message_2), + ) + + setNegativeButton(context.resources.getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ -> + onIgnorePinWarning() + } + + setPositiveButton(context.resources.getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ -> + it.dismiss() + val intent = Intent(Settings.ACTION_SECURITY_SETTINGS) + context.startActivity(intent) + } + create().withCenterAlignedButtons() + }.show().secure(activity) + activity.settings().incrementSecureWarningCount() +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt index f59b6a1f87..b2501cab3f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt @@ -62,12 +62,12 @@ class CreditCardEditorView( binding.cardNumberLayout.setErrorTextColor( ColorStateList.valueOf( - binding.root.context.getColorFromAttr(R.attr.textWarning), + binding.root.context.getColorFromAttr(R.attr.textCritical), ), ) binding.nameOnCardLayout.setErrorTextColor( ColorStateList.valueOf( - binding.root.context.getColorFromAttr(R.attr.textWarning), + binding.root.context.getColorFromAttr(R.attr.textCritical), ), ) @@ -128,7 +128,7 @@ class CreditCardEditorView( binding.cardNumberLayout.error = binding.root.context.getString(R.string.credit_cards_number_validation_error_message_2) - binding.cardNumberTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.textWarning)) + binding.cardNumberTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.textCritical)) } if (binding.nameOnCardInput.text.toString().isNotBlank()) { @@ -139,7 +139,7 @@ class CreditCardEditorView( binding.nameOnCardLayout.error = binding.root.context.getString(R.string.credit_cards_name_on_card_validation_error_message_2) - binding.nameOnCardTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.textWarning)) + binding.nameOnCardTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.textCritical)) } return isValid diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/AddLoginFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/AddLoginFragment.kt index e1cadea39c..5a85e34302 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/AddLoginFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/AddLoginFragment.kt @@ -281,7 +281,7 @@ class AddLoginFragment : Fragment(R.layout.fragment_add_login), MenuProvider { ColorStateList.valueOf( ContextCompat.getColor( requireContext(), - R.color.fx_mobile_text_color_warning, + R.color.fx_mobile_text_color_critical, ), ), ) @@ -295,7 +295,7 @@ class AddLoginFragment : Fragment(R.layout.fragment_add_login), MenuProvider { ColorStateList.valueOf( ContextCompat.getColor( requireContext(), - R.color.fx_mobile_text_color_warning, + R.color.fx_mobile_text_color_critical, ), ), ) @@ -319,7 +319,7 @@ class AddLoginFragment : Fragment(R.layout.fragment_add_login), MenuProvider { layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) layout.setErrorIconTintList( ColorStateList.valueOf( - ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_warning), + ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_critical), ), ) } @@ -332,7 +332,7 @@ class AddLoginFragment : Fragment(R.layout.fragment_add_login), MenuProvider { layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) layout.setErrorIconTintList( ColorStateList.valueOf( - ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_warning), + ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_critical), ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt index 59b210ae36..5310536b77 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt @@ -273,7 +273,7 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login), MenuProvider { ColorStateList.valueOf( ContextCompat.getColor( requireContext(), - R.color.fx_mobile_text_color_warning, + R.color.fx_mobile_text_color_critical, ), ), ) @@ -290,7 +290,7 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login), MenuProvider { layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) layout.setErrorIconTintList( ColorStateList.valueOf( - ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_warning), + ContextCompat.getColor(requireContext(), R.color.fx_mobile_text_color_critical), ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt index 5b7c0c2d71..f492e70bad 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt @@ -4,29 +4,16 @@ package org.mozilla.fenix.settings.logins.fragment -import android.app.KeyguardManager -import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.os.Bundle -import android.provider.Settings.ACTION_SECURITY_SETTINGS -import android.view.View import androidx.activity.result.ActivityResultLauncher -import androidx.appcompat.app.AlertDialog -import androidx.core.content.getSystemService -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import mozilla.components.feature.autofill.preference.AutofillPreference import mozilla.components.service.fxa.SyncEngine import mozilla.components.service.glean.private.NoExtras -import mozilla.components.support.base.feature.ViewBoundFeatureWrapper -import mozilla.components.ui.widgets.withCenterAlignedButtons import org.mozilla.fenix.GleanMetrics.Logins import org.mozilla.fenix.R import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint @@ -34,24 +21,20 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.navigateWithBreadcrumb import org.mozilla.fenix.ext.registerForActivityResult import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.runIfFragmentIsAttached -import org.mozilla.fenix.ext.secure import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.SharedPreferenceUpdater import org.mozilla.fenix.settings.SyncPreferenceView -import org.mozilla.fenix.settings.biometric.BiometricPromptFeature +import org.mozilla.fenix.settings.biometric.bindBiometricsCredentialsPromptOrShowWarning import org.mozilla.fenix.settings.requirePreference @Suppress("TooManyFunctions") class SavedLoginsAuthFragment : PreferenceFragmentCompat() { - private val biometricPromptFeature = ViewBoundFeatureWrapper() - private lateinit var startForResult: ActivityResultLauncher + private lateinit var savedLoginsFragmentLauncher: ActivityResultLauncher override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - startForResult = registerForActivityResult { + savedLoginsFragmentLauncher = registerForActivityResult { navigateToSavedLoginsFragment() } } @@ -71,32 +54,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { requirePreference(R.string.pref_key_saved_logins).isEnabled = enabled } - private fun navigateToSavedLogins() { - runIfFragmentIsAttached { - viewLifecycleOwner.lifecycleScope.launch(Main) { - // Workaround for likely biometric library bug - // https://github.com/mozilla-mobile/fenix/issues/8438 - delay(SHORT_DELAY_MS) - navigateToSavedLoginsFragment() - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - biometricPromptFeature.set( - feature = BiometricPromptFeature( - context = requireContext(), - fragment = this, - onAuthFailure = { togglePrefsEnabledWhileAuthenticating(true) }, - onAuthSuccess = ::navigateToSavedLogins, - ), - owner = this, - view = view, - ) - } - + @Suppress("LongMethod") override fun onResume() { super.onResume() showToolbar(getString(R.string.preferences_passwords_logins_and_passwords)) @@ -146,7 +104,17 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { } requirePreference(R.string.pref_key_saved_logins).setOnPreferenceClickListener { - verifyCredentialsOrShowSetupWarning(it.context) + view?.let { view -> + bindBiometricsCredentialsPromptOrShowWarning( + view = view, + onShowPinVerification = { intent -> + savedLoginsFragmentLauncher.launch(intent) + }, + onAuthSuccess = ::navigateToSavedLoginsFragment, + onAuthFailure = { togglePrefsEnabledWhileAuthenticating(true) }, + doWhileAuthenticating = { togglePrefsEnabledWhileAuthenticating(false) }, + ) + } true } @@ -178,60 +146,6 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { togglePrefsEnabledWhileAuthenticating(true) } - private fun verifyCredentialsOrShowSetupWarning(context: Context) { - // Use the BiometricPrompt first - if (BiometricPromptFeature.canUseFeature(context)) { - togglePrefsEnabledWhileAuthenticating(false) - biometricPromptFeature.get() - ?.requestAuthentication(getString(R.string.logins_biometric_prompt_message_2)) - return - } - - // Fallback to prompting for password with the KeyguardManager - val manager = context.getSystemService() - if (manager?.isKeyguardSecure == true) { - showPinVerification(manager) - } else { - // Warn that the device has not been secured - if (context.settings().shouldShowSecurityPinWarning) { - showPinDialogWarning(context) - } else { - navigateToSavedLoginsFragment() - } - } - } - - private fun showPinDialogWarning(context: Context) { - AlertDialog.Builder(context).apply { - setTitle(getString(R.string.logins_warning_dialog_title_2)) - setMessage( - getString(R.string.logins_warning_dialog_message_2), - ) - - setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ -> - navigateToSavedLoginsFragment() - } - - setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ -> - it.dismiss() - val intent = Intent(ACTION_SECURITY_SETTINGS) - startActivity(intent) - } - create().withCenterAlignedButtons() - }.show().secure(activity) - context.settings().incrementSecureWarningCount() - } - - @Suppress("Deprecation") - private fun showPinVerification(manager: KeyguardManager) { - val intent = manager.createConfirmDeviceCredentialIntent( - getString(R.string.logins_biometric_prompt_message_pin), - getString(R.string.logins_biometric_prompt_message), - ) - - startForResult.launch(intent) - } - /** * Called when authentication succeeds. */ @@ -260,9 +174,4 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginExceptionsFragment() findNavController().navigate(directions) } - - companion object { - const val SHORT_DELAY_MS = 100L - const val PIN_REQUEST = 303 - } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineMenu.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineMenu.kt index d28867513b..cb1b17788f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineMenu.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineMenu.kt @@ -40,7 +40,7 @@ class SearchEngineMenu( items.add( SimpleBrowserMenuItem( context.getString(R.string.search_engine_delete), - textColorResource = ThemeManager.resolveAttribute(R.attr.textWarning, context), + textColorResource = ThemeManager.resolveAttribute(R.attr.textCritical, context), ) { onItemTapped.invoke(Item.Delete) }, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineShortcuts.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineShortcuts.kt index 46ca337e42..04d4d90dde 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineShortcuts.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineShortcuts.kt @@ -198,13 +198,13 @@ private fun SearchItem( menuItems = listOf( MenuItem( stringResource(R.string.search_engine_edit), - color = FirefoxTheme.colors.textWarning, + color = FirefoxTheme.colors.textCritical, ) { onEditEngineClicked(engine) }, MenuItem( stringResource(R.string.search_engine_delete), - color = FirefoxTheme.colors.textWarning, + color = FirefoxTheme.colors.textCritical, ) { onDeleteEngineClicked(engine) }, @@ -268,7 +268,7 @@ private fun SearchEngineShortcutsPreview() { initialState = BrowserState( search = SearchState( regionSearchEngines = generateFakeEnginesList(), - disabledSearchEngineIds = listOf("8", "9"), + disabledSearchEngineIds = listOf("7", "8"), ), ), ), @@ -288,13 +288,12 @@ private fun generateFakeEnginesList(): List { generateFakeEngines("1", "Google"), generateFakeEngines("2", "Bing"), generateFakeEngines("3", "Bing"), - generateFakeEngines("4", "Amazon.com"), - generateFakeEngines("5", "DuckDuckGo"), - generateFakeEngines("6", "Qwant"), - generateFakeEngines("7", "eBay"), - generateFakeEngines("8", "Reddit"), - generateFakeEngines("9", "YouTube"), - generateFakeEngines("10", "Yandex", SearchEngine.Type.CUSTOM), + generateFakeEngines("4", "DuckDuckGo"), + generateFakeEngines("5", "Qwant"), + generateFakeEngines("6", "eBay"), + generateFakeEngines("7", "Reddit"), + generateFakeEngines("8", "YouTube"), + generateFakeEngines("9", "Yandex", SearchEngine.Type.CUSTOM), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/SaveToPDFMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/SaveToPDFMiddleware.kt index 25f28887f4..3e9593851b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/SaveToPDFMiddleware.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/SaveToPDFMiddleware.kt @@ -16,6 +16,7 @@ import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext +import org.mozilla.experiments.nimbus.NimbusEventStore import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.R import org.mozilla.fenix.browser.StandardSnackbarError @@ -35,10 +36,12 @@ import java.io.IOException * * @param context An Application context. * @param mainScope Coroutine scope to launch coroutines. + * @param nimbusEventStore Nimbus event store for recording events. */ class SaveToPDFMiddleware( private val context: Context, private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main), + private val nimbusEventStore: NimbusEventStore = context.components.nimbus.events, ) : Middleware { override fun invoke( @@ -151,6 +154,7 @@ class SaveToPDFMiddleware( source = telemetrySource(isPdf), ), ) + nimbusEventStore.recordEvent("print_tapped") } else { Events.saveToPdfTapped.record( Events.SaveToPdfTappedExtra( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckInfoCard.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckInfoCard.kt index 3fbb9b8d5a..5c033066c7 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckInfoCard.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckInfoCard.kt @@ -40,19 +40,20 @@ import org.mozilla.fenix.theme.FirefoxTheme /** * Review Quality Check Info Card UI. * + * @param modifier Modifier to be applied to the card. * @param title The primary text of the info message. * @param type The [ReviewQualityCheckInfoType] of message to display. - * @param modifier Modifier to be applied to the card. * @param verticalRowAlignment An optional adjustment of how the row of text aligns. * @param description The optional secondary piece of text. * @param footer An optional piece of text with a clickable link. * @param buttonText The text to show in the optional button. */ +@Suppress("LongMethod") @Composable fun ReviewQualityCheckInfoCard( - title: String, - type: ReviewQualityCheckInfoType, modifier: Modifier = Modifier, + title: String? = null, + type: ReviewQualityCheckInfoType, verticalRowAlignment: Alignment.Vertical = Alignment.Top, description: String? = null, footer: Pair? = null, @@ -67,7 +68,7 @@ fun ReviewQualityCheckInfoCard( ), elevation = 0.dp, ) { - val titleContentDescription = headingResource(title) + val titleContentDescription = title?.let { headingResource(it) } Row( verticalAlignment = verticalRowAlignment, @@ -81,7 +82,10 @@ fun ReviewQualityCheckInfoCard( InfoCardIcon(iconId = R.drawable.mozac_ic_checkmark_24) } - ReviewQualityCheckInfoType.Error, + ReviewQualityCheckInfoType.Error -> { + InfoCardIcon(iconId = R.drawable.mozac_ic_critical_fill_24) + } + ReviewQualityCheckInfoType.Info, ReviewQualityCheckInfoType.AnalysisUpdate, -> { @@ -92,18 +96,22 @@ fun ReviewQualityCheckInfoCard( Spacer(modifier = Modifier.width(12.dp)) Column { - Text( - text = title, - color = FirefoxTheme.colors.textPrimary, - style = FirefoxTheme.typography.headline8, - modifier = Modifier.semantics { - heading() - contentDescription = titleContentDescription - }, - ) + title?.let { + Text( + text = it, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline8, + modifier = Modifier.semantics { + heading() + if (titleContentDescription != null) { + contentDescription = titleContentDescription + } + }, + ) + } description?.let { - Spacer(modifier = Modifier.height(4.dp)) + title?.let { Spacer(modifier = Modifier.height(4.dp)) } Text( text = description, @@ -170,9 +178,9 @@ enum class ReviewQualityCheckInfoType { @Composable get() = when (this) { Warning -> FirefoxTheme.colors.layerWarning - Confirmation -> FirefoxTheme.colors.layerConfirmation - Error -> FirefoxTheme.colors.layerError - Info -> FirefoxTheme.colors.layerInfo + Confirmation -> FirefoxTheme.colors.layerSuccess + Error -> FirefoxTheme.colors.layerCritical + Info -> FirefoxTheme.colors.layerInformation AnalysisUpdate -> Color.Transparent } @@ -180,9 +188,9 @@ enum class ReviewQualityCheckInfoType { @Composable get() = when (this) { Warning -> FirefoxTheme.colors.actionWarning - Confirmation -> FirefoxTheme.colors.actionConfirmation - Error -> FirefoxTheme.colors.actionError - Info -> FirefoxTheme.colors.actionInfo + Confirmation -> FirefoxTheme.colors.actionSuccess + Error -> FirefoxTheme.colors.actionCritical + Info -> FirefoxTheme.colors.actionInformation AnalysisUpdate -> FirefoxTheme.colors.actionSecondary } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsController.kt index 27336f6343..58b0042c64 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsController.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsController.kt @@ -16,4 +16,9 @@ interface SyncedTabsController { * @param tab The synced [Tab] that was clicked. */ fun handleSyncedTabClicked(tab: Tab) + + /** + * Handles a click on the "close" button for a synced tab. + */ + fun handleSyncedTabClosed(deviceId: String, tab: Tab) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsInteractor.kt index 153a0f48d7..f37655bcac 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsInteractor.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/SyncedTabsInteractor.kt @@ -16,4 +16,9 @@ interface SyncedTabsInteractor { * @param tab The synced [Tab] that was clicked. */ fun onSyncedTabClicked(tab: Tab) + + /** + * Invoked when the user closes a synced [Tab]. + */ + fun onSyncedTabClosed(deviceId: String, tab: Tab) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt index 65909b5261..533a6454df 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt @@ -48,6 +48,8 @@ import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsList import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsListItem import org.mozilla.fenix.theme.FirefoxTheme import mozilla.components.browser.storage.sync.Tab as SyncTab +import org.mozilla.fenix.tabstray.syncedtabs.OnTabClick as OnSyncedTabClick +import org.mozilla.fenix.tabstray.syncedtabs.OnTabCloseClick as OnSyncedTabClose /** * Top-level UI for displaying the Tabs Tray feature. @@ -75,6 +77,7 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab * @param onInactiveTabClick Invoked when the user clicks on an inactive tab. * @param onInactiveTabClose Invoked when the user clicks on an inactive tab's close button. * @param onSyncedTabClick Invoked when the user clicks on a synced tab. + * @param onSyncedTabClose Invoked when the user clicks on a synced tab's close button. * @param onSaveToCollectionClick Invoked when the user clicks on the save to collection button from * the multi select banner. * @param onShareSelectedTabsClick Invoked when the user clicks on the share button from the @@ -92,6 +95,10 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab * @param onTabAutoCloseBannerDismiss Invoked when the user clicks to dismiss the auto close banner. * @param onTabAutoCloseBannerShown Invoked when the auto close banner has been shown to the user. * @param onMove Invoked after the drag and drop gesture completed. Swaps positions of two tabs. + * @param shouldShowInactiveTabsCFR Returns whether the inactive tabs CFR should be displayed. + * @param onInactiveTabsCFRShown Invoked when the inactive tabs CFR is displayed. + * @param onInactiveTabsCFRClick Invoked when the inactive tabs CFR is clicked. + * @param onInactiveTabsCFRDismiss Invoked when the inactive tabs CFR is dismissed. */ @OptIn(ExperimentalFoundationApi::class) @Suppress("LongMethod", "LongParameterList", "ComplexMethod") @@ -116,7 +123,8 @@ fun TabsTray( onEnableInactiveTabAutoCloseClick: () -> Unit, onInactiveTabClick: (TabSessionState) -> Unit, onInactiveTabClose: (TabSessionState) -> Unit, - onSyncedTabClick: (SyncTab) -> Unit, + onSyncedTabClick: OnSyncedTabClick, + onSyncedTabClose: OnSyncedTabClose, onSaveToCollectionClick: () -> Unit, onShareSelectedTabsClick: () -> Unit, onShareAllTabsClick: () -> Unit, @@ -132,6 +140,10 @@ fun TabsTray( onTabAutoCloseBannerDismiss: () -> Unit, onTabAutoCloseBannerShown: () -> Unit, onMove: (String, String?, Boolean) -> Unit, + shouldShowInactiveTabsCFR: () -> Boolean, + onInactiveTabsCFRShown: () -> Unit, + onInactiveTabsCFRClick: () -> Unit, + onInactiveTabsCFRDismiss: () -> Unit, ) { val multiselectMode = tabsTrayStore .observeAsComposableState { state -> state.mode }.value ?: TabsTrayState.Mode.Normal @@ -210,6 +222,10 @@ fun TabsTray( onInactiveTabClick = onInactiveTabClick, onInactiveTabClose = onInactiveTabClose, onMove = onMove, + shouldShowInactiveTabsCFR = shouldShowInactiveTabsCFR, + onInactiveTabsCFRShown = onInactiveTabsCFRShown, + onInactiveTabsCFRClick = onInactiveTabsCFRClick, + onInactiveTabsCFRDismiss = onInactiveTabsCFRDismiss, ) } @@ -230,6 +246,7 @@ fun TabsTray( SyncedTabsPage( tabsTrayStore = tabsTrayStore, onTabClick = onSyncedTabClick, + onTabClose = onSyncedTabClose, ) } } @@ -258,6 +275,10 @@ private fun NormalTabsPage( onInactiveTabClick: (TabSessionState) -> Unit, onInactiveTabClose: (TabSessionState) -> Unit, onMove: (String, String?, Boolean) -> Unit, + shouldShowInactiveTabsCFR: () -> Boolean, + onInactiveTabsCFRShown: () -> Unit, + onInactiveTabsCFRClick: () -> Unit, + onInactiveTabsCFRDismiss: () -> Unit, ) { val inactiveTabsExpanded = appStore .observeAsComposableState { state -> state.inactiveTabsExpanded }.value ?: false @@ -283,6 +304,7 @@ private fun NormalTabsPage( inactiveTabs = inactiveTabs, expanded = inactiveTabsExpanded, showAutoCloseDialog = showAutoCloseDialog, + showCFR = shouldShowInactiveTabsCFR(), onHeaderClick = onInactiveTabsHeaderClick, onDeleteAllButtonClick = onDeleteAllInactiveTabsClick, onAutoCloseDismissClick = { @@ -295,6 +317,9 @@ private fun NormalTabsPage( }, onTabClick = onInactiveTabClick, onTabCloseClick = onInactiveTabClose, + onCFRShown = onInactiveTabsCFRShown, + onCFRClick = onInactiveTabsCFRClick, + onCFRDismiss = onInactiveTabsCFRDismiss, ) } } @@ -366,7 +391,8 @@ private fun PrivateTabsPage( @Composable private fun SyncedTabsPage( tabsTrayStore: TabsTrayStore, - onTabClick: (SyncTab) -> Unit, + onTabClick: OnSyncedTabClick, + onTabClose: OnSyncedTabClose, ) { val syncedTabs = tabsTrayStore .observeAsComposableState { state -> state.syncedTabs }.value ?: emptyList() @@ -374,6 +400,7 @@ private fun SyncedTabsPage( SyncedTabsList( syncedTabs = syncedTabs, onTabClick = onTabClick, + onTabCloseClick = onTabClose, ) } @@ -565,6 +592,7 @@ private fun TabsTrayPreviewRoot( onInactiveTabClick = {}, onInactiveTabClose = inactiveTabsState::remove, onSyncedTabClick = {}, + onSyncedTabClose = { _, _ -> }, onSaveToCollectionClick = {}, onShareSelectedTabsClick = {}, onShareAllTabsClick = {}, @@ -580,6 +608,10 @@ private fun TabsTrayPreviewRoot( onTabAutoCloseBannerDismiss = {}, onTabAutoCloseBannerShown = {}, onMove = { _, _, _ -> }, + shouldShowInactiveTabsCFR = { false }, + onInactiveTabsCFRShown = {}, + onInactiveTabsCFRClick = {}, + onInactiveTabsCFRDismiss = {}, ) } } @@ -607,10 +639,15 @@ private fun generateFakeSyncedTabsList(deviceCount: Int = 1): List Unit, @@ -520,6 +523,12 @@ class DefaultTabsTrayController( ) } + override fun handleSyncedTabClosed(deviceId: String, tab: Tab) { + CoroutineScope(ioDispatcher).launch { + closeSyncedTabsUseCases.close(deviceId, tab.active().url) + } + } + override fun handleTabLongClick(tab: TabSessionState): Boolean { return if (tab.isNormalTab() && tabsTrayStore.state.mode.selectedTabs.isEmpty()) { Collections.longPress.record(NoExtras()) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index fded1b9494..9086708892 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -30,6 +30,7 @@ import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.base.crash.Breadcrumb +import mozilla.components.feature.accounts.push.CloseTabsUseCases import mozilla.components.feature.downloads.ui.DownloadCancelDialogFragment import mozilla.components.feature.tabs.tabstray.TabsFeature import mozilla.components.support.base.feature.ViewBoundFeatureWrapper @@ -181,6 +182,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { navigationInteractor = navigationInteractor, profiler = requireComponents.core.engine.profiler, tabsUseCases = requireComponents.useCases.tabsUseCases, + closeSyncedTabsUseCases = CloseTabsUseCases(requireComponents.backgroundServices.accountManager), bookmarksUseCase = requireComponents.useCases.bookmarksUseCases, ioDispatcher = Dispatchers.IO, collectionStorage = requireComponents.core.tabCollectionStorage, @@ -275,6 +277,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { onInactiveTabClick = tabsTrayInteractor::onInactiveTabClicked, onInactiveTabClose = tabsTrayInteractor::onInactiveTabClosed, onSyncedTabClick = tabsTrayInteractor::onSyncedTabClicked, + onSyncedTabClose = tabsTrayInteractor::onSyncedTabClosed, onSaveToCollectionClick = tabsTrayInteractor::onAddSelectedTabsToCollectionClicked, onShareSelectedTabsClick = tabsTrayInteractor::onShareSelectedTabs, onShareAllTabsClick = { @@ -307,6 +310,23 @@ class TabsTrayFragment : AppCompatDialogFragment() { requireContext().settings().lastCfrShownTimeInMillis = System.currentTimeMillis() }, onMove = tabsTrayInteractor::onTabsMove, + shouldShowInactiveTabsCFR = { + requireContext().settings().shouldShowInactiveTabsOnboardingPopup && + requireContext().settings().canShowCfr + }, + onInactiveTabsCFRShown = { + TabsTray.inactiveTabsCfrVisible.record(NoExtras()) + }, + onInactiveTabsCFRClick = { + requireContext().settings().shouldShowInactiveTabsOnboardingPopup = false + navigationInteractor.onTabSettingsClicked() + TabsTray.inactiveTabsCfrSettings.record(NoExtras()) + onTabsTrayDismissed() + }, + onInactiveTabsCFRDismiss = { + requireContext().settings().shouldShowInactiveTabsOnboardingPopup = false + TabsTray.inactiveTabsCfrDismissed.record(NoExtras()) + }, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt index b9f8bb5473..e29aafc793 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt @@ -157,6 +157,10 @@ class DefaultTabsTrayInteractor( controller.handleSyncedTabClicked(tab) } + override fun onSyncedTabClosed(deviceId: String, tab: Tab) { + controller.handleSyncedTabClosed(deviceId, tab) + } + override fun onBackPressed(): Boolean = controller.handleBackPressed() override fun onTabClosed(tab: TabSessionState, source: String?) { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt index 6980806179..945cacd5be 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/InactiveTabViewHolder.kt @@ -60,6 +60,7 @@ class InactiveTabViewHolder( inactiveTabs = inactiveTabs, expanded = expanded, showAutoCloseDialog = showAutoClosePrompt, + showCFR = false, // The CFR in XML is handled by [TabsTrayInactiveTabsOnboardingBinding] onHeaderClick = { interactor.onInactiveTabsHeaderClicked(!expanded) }, onDeleteAllButtonClick = interactor::onDeleteAllInactiveTabsClicked, onAutoCloseDismissClick = { @@ -73,6 +74,9 @@ class InactiveTabViewHolder( }, onTabClick = interactor::onInactiveTabClicked, onTabCloseClick = interactor::onInactiveTabClosed, + onCFRShown = {}, + onCFRClick = {}, + onCFRDismiss = {}, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt index b9d762f2f9..37ac921e0f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt @@ -92,7 +92,7 @@ class TouchCallback( val icon = recyclerView.context.getDrawableWithTint( R.drawable.ic_delete, - recyclerView.context.getColorFromAttr(R.attr.textWarning), + recyclerView.context.getColorFromAttr(R.attr.textCritical), )!! val background = AppCompatResources.getDrawable( recyclerView.context, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ext/SyncedDeviceTabs.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ext/SyncedDeviceTabs.kt index 39462fa9b0..c8654eac3f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ext/SyncedDeviceTabs.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ext/SyncedDeviceTabs.kt @@ -5,22 +5,41 @@ package org.mozilla.fenix.tabstray.ext import mozilla.components.browser.storage.sync.SyncedDeviceTabs +import mozilla.components.concept.sync.DeviceCapability import mozilla.components.support.ktx.kotlin.trimmed import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsListItem +import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsListSupportedFeature /** * Converts a list of [SyncedDeviceTabs] into a list of [SyncedTabsListItem]. + * + * @param features Supported [SyncedTabsListSupportedFeature]s. */ -fun List.toComposeList(): List = asSequence().flatMap { (device, tabs) -> - val deviceTabs = if (tabs.isEmpty()) { - emptyList() - } else { - tabs.map { - val url = it.active().url - val titleText = it.active().title.ifEmpty { url.trimmed() } - SyncedTabsListItem.Tab(titleText, url, it) +fun List.toComposeList( + features: Set = emptySet(), +): List = + asSequence().flatMap { (device, tabs) -> + val deviceTabs = if (tabs.isEmpty()) { + emptyList() + } else { + tabs.map { + val url = it.active().url + val titleText = it.active().title.ifEmpty { url.trimmed() } + SyncedTabsListItem.Tab( + displayTitle = titleText, + displayURL = url, + action = if ( + features.contains(SyncedTabsListSupportedFeature.CLOSE_TABS) && + device.capabilities.contains(DeviceCapability.CLOSE_TABS) + ) { + SyncedTabsListItem.Tab.Action.Close(deviceId = device.id) + } else { + SyncedTabsListItem.Tab.Action.None + }, + tab = it, + ) + } } - } - sequenceOf(SyncedTabsListItem.DeviceSection(device.displayName, deviceTabs)) -}.toList() + sequenceOf(SyncedTabsListItem.DeviceSection(device.displayName, deviceTabs)) + }.toList() diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/inactivetabs/InactiveTabs.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/inactivetabs/InactiveTabs.kt index 27ad2c1b6c..0fbe3941f8 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/inactivetabs/InactiveTabs.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/inactivetabs/InactiveTabs.kt @@ -6,9 +6,9 @@ package org.mozilla.fenix.tabstray.inactivetabs -import android.content.res.Configuration import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -31,14 +31,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.compose.cfr.CFRPopup +import mozilla.components.compose.cfr.CFRPopupLayout +import mozilla.components.compose.cfr.CFRPopupProperties import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.button.TextButton import org.mozilla.fenix.compose.list.ExpandableListHeader import org.mozilla.fenix.compose.list.FaviconListItem @@ -54,12 +59,16 @@ private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(8.dp) * @param inactiveTabs List of [TabSessionState] to display. * @param expanded Whether to show the inactive tabs section expanded or collapsed. * @param showAutoCloseDialog Whether to show the auto close inactive tabs dialog. + * @param showCFR Whether to show the CFR. * @param onHeaderClick Called when the user clicks on the inactive tabs section header. * @param onDeleteAllButtonClick Called when the user clicks on the delete all inactive tabs button. * @param onAutoCloseDismissClick Called when the user clicks on the auto close dialog's dismiss button. * @param onEnableAutoCloseClick Called when the user clicks on the auto close dialog's enable button. * @param onTabClick Called when the user clicks on a specific inactive tab. * @param onTabCloseClick Called when the user clicks on a specific inactive tab's close button. + * @param onCFRShown Invoked when the CFR is displayed. + * @param onCFRClick Invoked when the CFR is clicked. + * @param onCFRDismiss Invoked when the CFR is dismissed. */ @Composable @Suppress("LongParameterList") @@ -67,12 +76,16 @@ fun InactiveTabsList( inactiveTabs: List, expanded: Boolean, showAutoCloseDialog: Boolean, + showCFR: Boolean, onHeaderClick: (Boolean) -> Unit, onDeleteAllButtonClick: () -> Unit, onAutoCloseDismissClick: () -> Unit, onEnableAutoCloseClick: () -> Unit, onTabClick: (TabSessionState) -> Unit, onTabCloseClick: (TabSessionState) -> Unit, + onCFRShown: () -> Unit, + onCFRClick: () -> Unit, + onCFRDismiss: () -> Unit, ) { Card( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), @@ -88,6 +101,10 @@ fun InactiveTabsList( ) { InactiveTabsHeader( expanded = expanded, + showCFR = showCFR, + onCFRShown = onCFRShown, + onCFRClick = onCFRClick, + onCFRDismiss = onCFRDismiss, onClick = { onHeaderClick(!expanded) }, onDeleteAllClick = onDeleteAllButtonClick, ) @@ -128,35 +145,84 @@ fun InactiveTabsList( } /** - * Collapsible header for the Inactive Tabs section. + * Collapsible header for the Inactive Tabs section with a CFR. * * @param expanded Whether the section is expanded. + * @param showCFR Whether to show the CFR. * @param onClick Called when the user clicks on the header. * @param onDeleteAllClick Called when the user clicks on the delete all button. + * @param onCFRShown Invoked when the CFR is displayed. + * @param onCFRClick Invoked when the CFR is clicked. + * @param onCFRDismiss Invoked when the CFR is dismissed. */ @Composable private fun InactiveTabsHeader( expanded: Boolean, + showCFR: Boolean, onClick: () -> Unit, onDeleteAllClick: () -> Unit, + onCFRShown: () -> Unit, + onCFRClick: () -> Unit, + onCFRDismiss: () -> Unit, ) { - ExpandableListHeader( - headerText = stringResource(R.string.inactive_tabs_title), - headerTextStyle = FirefoxTheme.typography.headline7, - expanded = expanded, - expandActionContentDescription = stringResource(R.string.inactive_tabs_expand_content_description), - collapseActionContentDescription = stringResource(R.string.inactive_tabs_collapse_content_description), - onClick = onClick, + CFRPopupLayout( + showCFR = showCFR, + properties = CFRPopupProperties( + popupBodyColors = listOf( + FirefoxTheme.colors.layerGradientEnd.toArgb(), + FirefoxTheme.colors.layerGradientStart.toArgb(), + ), + dismissButtonColor = FirefoxTheme.colors.iconOnColor.toArgb(), + indicatorDirection = CFRPopup.IndicatorDirection.UP, + popupVerticalOffset = (-12).dp, + dismissOnBackPress = true, + dismissOnClickOutside = false, + ), + onCFRShown = onCFRShown, + onDismiss = { onCFRDismiss() }, + text = { + FirefoxTheme { + Text( + text = stringResource(R.string.tab_tray_inactive_onboarding_message), + color = FirefoxTheme.colors.textOnColorPrimary, + style = FirefoxTheme.typography.body2, + ) + } + }, + action = { dismissCFR -> + FirefoxTheme { + Text( + text = stringResource(R.string.tab_tray_inactive_onboarding_button_text), + color = FirefoxTheme.colors.textOnColorPrimary, + modifier = Modifier.clickable { + dismissCFR() + onCFRClick() + }, + style = FirefoxTheme.typography.body2.copy( + textDecoration = TextDecoration.Underline, + ), + ) + } + }, ) { - IconButton( - onClick = onDeleteAllClick, - modifier = Modifier.padding(horizontal = 4.dp), + ExpandableListHeader( + headerText = stringResource(R.string.inactive_tabs_title), + headerTextStyle = FirefoxTheme.typography.headline7, + expanded = expanded, + expandActionContentDescription = stringResource(R.string.inactive_tabs_expand_content_description), + collapseActionContentDescription = stringResource(R.string.inactive_tabs_collapse_content_description), + onClick = onClick, ) { - Icon( - painter = painterResource(R.drawable.ic_delete), - contentDescription = stringResource(R.string.inactive_tabs_delete_all), - tint = FirefoxTheme.colors.iconPrimary, - ) + IconButton( + onClick = onDeleteAllClick, + modifier = Modifier.padding(horizontal = 4.dp), + ) { + Icon( + painter = painterResource(R.drawable.ic_delete), + contentDescription = stringResource(R.string.inactive_tabs_delete_all), + tint = FirefoxTheme.colors.iconPrimary, + ) + } } } } @@ -229,8 +295,7 @@ private fun InactiveTabsAutoClosePrompt( } @Composable -@Preview(name = "Auto close dialog dark", uiMode = Configuration.UI_MODE_NIGHT_YES) -@Preview(name = "Auto close dialog light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@LightDarkPreview private fun InactiveTabsAutoClosePromptPreview() { FirefoxTheme { Box(Modifier.background(FirefoxTheme.colors.layer1)) { @@ -243,8 +308,7 @@ private fun InactiveTabsAutoClosePromptPreview() { } @Composable -@Preview(name = "Full preview dark", uiMode = Configuration.UI_MODE_NIGHT_YES) -@Preview(name = "Full preview light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@LightDarkPreview private fun InactiveTabsListPreview() { var expanded by remember { mutableStateOf(true) } var showAutoClosePrompt by remember { mutableStateOf(true) } @@ -255,12 +319,16 @@ private fun InactiveTabsListPreview() { inactiveTabs = generateFakeInactiveTabsList(), expanded = expanded, showAutoCloseDialog = showAutoClosePrompt, + showCFR = false, onHeaderClick = { expanded = !expanded }, onDeleteAllButtonClick = {}, onAutoCloseDismissClick = { showAutoClosePrompt = !showAutoClosePrompt }, onEnableAutoCloseClick = { showAutoClosePrompt = !showAutoClosePrompt }, onTabClick = {}, onTabCloseClick = {}, + onCFRShown = {}, + onCFRClick = {}, + onCFRDismiss = {}, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabs.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabs.kt deleted file mode 100644 index fe39760447..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabs.kt +++ /dev/null @@ -1,314 +0,0 @@ -/* 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("TooManyFunctions") - -package org.mozilla.fenix.tabstray.syncedtabs - -import android.content.res.Configuration -import androidx.annotation.VisibleForTesting -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.toMutableStateList -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import mozilla.components.browser.storage.sync.TabEntry -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import org.mozilla.fenix.R -import org.mozilla.fenix.compose.Divider -import org.mozilla.fenix.compose.button.PrimaryButton -import org.mozilla.fenix.compose.ext.dashedBorder -import org.mozilla.fenix.compose.list.ExpandableListHeader -import org.mozilla.fenix.compose.list.FaviconListItem -import org.mozilla.fenix.tabstray.TabsTrayTestTag -import org.mozilla.fenix.theme.FirefoxTheme -import mozilla.components.browser.storage.sync.Tab as SyncTab - -private const val EXPANDED_BY_DEFAULT = true - -/** - * Top-level list UI for displaying Synced Tabs in the Tabs Tray. - * - * @param syncedTabs The tab UI items to be displayed. - * @param onTabClick The lambda for handling clicks on synced tabs. - */ -@SuppressWarnings("LongMethod") -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun SyncedTabsList( - syncedTabs: List, - onTabClick: (SyncTab) -> Unit, -) { - val listState = rememberLazyListState() - val expandedState = - remember(syncedTabs) { syncedTabs.map { EXPANDED_BY_DEFAULT }.toMutableStateList() } - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .testTag(TabsTrayTestTag.syncedTabsList), - state = listState, - ) { - syncedTabs.forEachIndexed { index, syncedTabItem -> - when (syncedTabItem) { - is SyncedTabsListItem.DeviceSection -> { - val sectionExpanded = expandedState[index] - - stickyHeader { - SyncedTabsSectionHeader( - headerText = syncedTabItem.displayName, - expanded = sectionExpanded, - ) { - expandedState[index] = !sectionExpanded - } - } - - if (sectionExpanded) { - if (syncedTabItem.tabs.isNotEmpty()) { - items(syncedTabItem.tabs) { syncedTab -> - FaviconListItem( - label = syncedTab.displayTitle, - description = syncedTab.displayURL, - url = syncedTab.displayURL, - onClick = { onTabClick(syncedTab.tab) }, - ) - } - } else { - item { SyncedTabsNoTabsItem() } - } - } - } - - is SyncedTabsListItem.Error -> { - item { - SyncedTabsErrorItem( - errorText = syncedTabItem.errorText, - errorButton = syncedTabItem.errorButton, - ) - } - } - else -> { - // no-op - } - } - } - - item { - // The Spacer here is to act as a footer to add padding to the bottom of the list so - // the FAB or any potential SnackBar doesn't overlap with the items at the end. - Spacer(modifier = Modifier.height(240.dp)) - } - } -} - -/** - * Collapsible header for sections of synced tabs - * - * @param headerText The section title for a group of synced tabs. - * @param expanded Indicates whether the section of content is expanded. If null, the Icon will be hidden. - * @param onClick Optional lambda for handling section header clicks. - */ -@Composable -fun SyncedTabsSectionHeader( - headerText: String, - expanded: Boolean? = null, - onClick: () -> Unit = {}, -) { - Column( - modifier = Modifier - .fillMaxWidth() - .background(FirefoxTheme.colors.layer1), - ) { - ExpandableListHeader( - headerText = headerText, - expanded = expanded, - expandActionContentDescription = stringResource(R.string.synced_tabs_expand_group), - collapseActionContentDescription = stringResource(R.string.synced_tabs_collapse_group), - onClick = onClick, - ) - - Divider() - } -} - -/** - * Error UI to show if there is one of the errors outlined in [SyncedTabsView.ErrorType]. - * - * @param errorText The text to be displayed to the user. - * @param errorButton Optional class to set up and handle any clicks in the Error UI. - */ -@Composable -fun SyncedTabsErrorItem( - errorText: String, - errorButton: SyncedTabsListItem.ErrorButton? = null, -) { - Box( - Modifier - .padding(all = 8.dp) - .height(IntrinsicSize.Min) - .dashedBorder( - color = FirefoxTheme.colors.borderPrimary, - cornerRadius = 8.dp, - dashHeight = 2.dp, - dashWidth = 4.dp, - ), - ) { - Column( - Modifier - .padding(all = 16.dp) - .fillMaxWidth(), - ) { - Text( - text = errorText, - color = FirefoxTheme.colors.textPrimary, - modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, - ) - - errorButton?.let { - Spacer(modifier = Modifier.height(12.dp)) - - PrimaryButton( - text = it.buttonText, - icon = painterResource(R.drawable.ic_sign_in), - onClick = it.onClick, - ) - } - } - } -} - -/** - * UI to be displayed when a user's device has no synced tabs. - */ -@Composable -fun SyncedTabsNoTabsItem() { - Text( - text = stringResource(R.string.synced_tabs_no_open_tabs), - color = FirefoxTheme.colors.textSecondary, - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth(), - fontSize = 16.sp, - maxLines = 1, - ) -} - -@Composable -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun SyncedTabsListItemsPreview() { - FirefoxTheme { - Column(Modifier.background(FirefoxTheme.colors.layer1)) { - SyncedTabsSectionHeader(headerText = "Google Pixel Pro Max +Ultra 5000") - - Spacer(modifier = Modifier.height(16.dp)) - - SyncedTabsSectionHeader( - headerText = "Collapsible Google Pixel Pro Max +Ultra 5000", - expanded = true, - ) { println("Clicked section header") } - - Spacer(modifier = Modifier.height(16.dp)) - - FaviconListItem( - label = "Mozilla", - description = "www.mozilla.org", - url = "www.mozilla.org", - onClick = {}, - ) - - Spacer(modifier = Modifier.height(16.dp)) - - SyncedTabsErrorItem(errorText = stringResource(R.string.synced_tabs_reauth)) - - Spacer(modifier = Modifier.height(16.dp)) - - SyncedTabsNoTabsItem() - - Spacer(modifier = Modifier.height(16.dp)) - } - } -} - -@Composable -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun SyncedTabsErrorPreview() { - FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { - SyncedTabsErrorItem( - errorText = stringResource(R.string.synced_tabs_no_tabs), - errorButton = SyncedTabsListItem.ErrorButton( - buttonText = stringResource(R.string.synced_tabs_sign_in_button), - ) { - println("SyncedTabsErrorButton click") - }, - ) - } - } -} - -@Composable -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun SyncedTabsListPreview() { - FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer1)) { - SyncedTabsList( - syncedTabs = getFakeSyncedTabList(), - ) { - println("Tab clicked") - } - } - } -} - -/** - * Helper function to create a List of [SyncedTabsListItem] for previewing. - */ -@VisibleForTesting -internal fun getFakeSyncedTabList(): List = listOf( - SyncedTabsListItem.DeviceSection( - displayName = "Device 1", - tabs = listOf( - generateFakeTab("Mozilla", "www.mozilla.org"), - generateFakeTab("Google", "www.google.com"), - generateFakeTab("", "www.google.com"), - ), - ), - SyncedTabsListItem.DeviceSection("Device 2", emptyList()), - SyncedTabsListItem.Error("Please re-authenticate"), -) - -/** - * Helper function to create a [SyncedTabsListItem.Tab] for previewing. - */ -private fun generateFakeTab(tabName: String, tabUrl: String): SyncedTabsListItem.Tab = - SyncedTabsListItem.Tab( - tabName.ifEmpty { tabUrl }, - tabUrl, - SyncTab( - history = listOf(TabEntry(tabName, tabUrl, null)), - active = 0, - lastUsed = 0L, - inactive = false, - ), - ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsIntegration.kt index 2bc7814e53..9dc0a2246f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsIntegration.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsIntegration.kt @@ -15,6 +15,7 @@ import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.support.base.feature.LifecycleAwareFeature import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.ObserverRegistry +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.tabstray.FloatingActionButtonBinding import org.mozilla.fenix.tabstray.TabsTrayAction import org.mozilla.fenix.tabstray.TabsTrayStore @@ -91,7 +92,13 @@ class SyncedTabsIntegration( override fun displaySyncedTabs(syncedTabs: List) { store.dispatch( TabsTrayAction.UpdateSyncedTabs( - syncedTabs.toComposeList(), + syncedTabs.toComposeList( + buildSet { + if (context.settings().enableCloseSyncedTabs) { + add(SyncedTabsListSupportedFeature.CLOSE_TABS) + } + }, + ), ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsList.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsList.kt new file mode 100644 index 0000000000..42e8799697 --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsList.kt @@ -0,0 +1,348 @@ +/* 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("TooManyFunctions") + +package org.mozilla.fenix.tabstray.syncedtabs + +import android.content.res.Configuration +import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.browser.storage.sync.TabEntry +import mozilla.components.feature.syncedtabs.view.SyncedTabsView +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.button.PrimaryButton +import org.mozilla.fenix.compose.ext.dashedBorder +import org.mozilla.fenix.compose.list.ExpandableListHeader +import org.mozilla.fenix.compose.list.FaviconListItem +import org.mozilla.fenix.tabstray.TabsTrayTestTag +import org.mozilla.fenix.theme.FirefoxTheme +import mozilla.components.browser.storage.sync.Tab as SyncTab + +private const val EXPANDED_BY_DEFAULT = true + +/** + * A lambda invoked when the user clicks on a synced tab in the [SyncedTabsList]. + */ +typealias OnTabClick = (tab: SyncTab) -> Unit + +/** + * A lambda invoked when the user clicks a synced tab's close button in the [SyncedTabsList]. + */ +typealias OnTabCloseClick = (deviceId: String, tab: SyncTab) -> Unit + +/** + * Top-level list UI for displaying Synced Tabs in the Tabs Tray. + * + * @param syncedTabs The tab UI items to be displayed. + * @param onTabClick The lambda for handling clicks on synced tabs. + * @param onTabCloseClick The lambda for handling clicks on a synced tab's close button. + */ +@SuppressWarnings("LongMethod") +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun SyncedTabsList( + syncedTabs: List, + onTabClick: OnTabClick, + onTabCloseClick: OnTabCloseClick, +) { + val listState = rememberLazyListState() + val expandedState = + remember(syncedTabs) { syncedTabs.map { EXPANDED_BY_DEFAULT }.toMutableStateList() } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .testTag(TabsTrayTestTag.syncedTabsList), + state = listState, + ) { + syncedTabs.forEachIndexed { index, syncedTabItem -> + when (syncedTabItem) { + is SyncedTabsListItem.DeviceSection -> { + val sectionExpanded = expandedState[index] + + stickyHeader { + SyncedTabsSectionHeader( + headerText = syncedTabItem.displayName, + expanded = sectionExpanded, + ) { + expandedState[index] = !sectionExpanded + } + } + + if (sectionExpanded) { + if (syncedTabItem.tabs.isNotEmpty()) { + items(syncedTabItem.tabs) { syncedTab -> + when (syncedTab.action) { + is SyncedTabsListItem.Tab.Action.Close -> FaviconListItem( + label = syncedTab.displayTitle, + description = syncedTab.displayURL, + url = syncedTab.displayURL, + onClick = { onTabClick(syncedTab.tab) }, + iconPainter = painterResource(R.drawable.ic_close), + onIconClick = { onTabCloseClick(syncedTab.action.deviceId, syncedTab.tab) }, + ) + is SyncedTabsListItem.Tab.Action.None -> FaviconListItem( + label = syncedTab.displayTitle, + description = syncedTab.displayURL, + url = syncedTab.displayURL, + onClick = { onTabClick(syncedTab.tab) }, + ) + } + } + } else { + item { SyncedTabsNoTabsItem() } + } + } + } + + is SyncedTabsListItem.Error -> { + item { + SyncedTabsErrorItem( + errorText = syncedTabItem.errorText, + errorButton = syncedTabItem.errorButton, + ) + } + } + else -> { + // no-op + } + } + } + + item { + // The Spacer here is to act as a footer to add padding to the bottom of the list so + // the FAB or any potential SnackBar doesn't overlap with the items at the end. + Spacer(modifier = Modifier.height(240.dp)) + } + } +} + +/** + * Collapsible header for sections of synced tabs + * + * @param headerText The section title for a group of synced tabs. + * @param expanded Indicates whether the section of content is expanded. If null, the Icon will be hidden. + * @param onClick Optional lambda for handling section header clicks. + */ +@Composable +fun SyncedTabsSectionHeader( + headerText: String, + expanded: Boolean? = null, + onClick: () -> Unit = {}, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(FirefoxTheme.colors.layer1), + ) { + ExpandableListHeader( + headerText = headerText, + expanded = expanded, + expandActionContentDescription = stringResource(R.string.synced_tabs_expand_group), + collapseActionContentDescription = stringResource(R.string.synced_tabs_collapse_group), + onClick = onClick, + ) + + Divider() + } +} + +/** + * Error UI to show if there is one of the errors outlined in [SyncedTabsView.ErrorType]. + * + * @param errorText The text to be displayed to the user. + * @param errorButton Optional class to set up and handle any clicks in the Error UI. + */ +@Composable +fun SyncedTabsErrorItem( + errorText: String, + errorButton: SyncedTabsListItem.ErrorButton? = null, +) { + Box( + Modifier + .padding(all = 8.dp) + .height(IntrinsicSize.Min) + .dashedBorder( + color = FirefoxTheme.colors.borderPrimary, + cornerRadius = 8.dp, + dashHeight = 2.dp, + dashWidth = 4.dp, + ), + ) { + Column( + Modifier + .padding(all = 16.dp) + .fillMaxWidth(), + ) { + Text( + text = errorText, + color = FirefoxTheme.colors.textPrimary, + modifier = Modifier.fillMaxWidth(), + fontSize = 14.sp, + ) + + errorButton?.let { + Spacer(modifier = Modifier.height(12.dp)) + + PrimaryButton( + text = it.buttonText, + icon = painterResource(R.drawable.ic_sign_in), + onClick = it.onClick, + ) + } + } + } +} + +/** + * UI to be displayed when a user's device has no synced tabs. + */ +@Composable +fun SyncedTabsNoTabsItem() { + Text( + text = stringResource(R.string.synced_tabs_no_open_tabs), + color = FirefoxTheme.colors.textSecondary, + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth(), + fontSize = 16.sp, + maxLines = 1, + ) +} + +@Composable +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SyncedTabsListItemsPreview() { + FirefoxTheme { + Column(Modifier.background(FirefoxTheme.colors.layer1)) { + SyncedTabsSectionHeader(headerText = "Google Pixel Pro Max +Ultra 5000") + + Spacer(modifier = Modifier.height(16.dp)) + + SyncedTabsSectionHeader( + headerText = "Collapsible Google Pixel Pro Max +Ultra 5000", + expanded = true, + ) { println("Clicked section header") } + + Spacer(modifier = Modifier.height(16.dp)) + + FaviconListItem( + label = "Mozilla", + description = "www.mozilla.org", + url = "www.mozilla.org", + onClick = {}, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + SyncedTabsErrorItem(errorText = stringResource(R.string.synced_tabs_reauth)) + + Spacer(modifier = Modifier.height(16.dp)) + + SyncedTabsNoTabsItem() + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Composable +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SyncedTabsErrorPreview() { + FirefoxTheme { + Box(Modifier.background(FirefoxTheme.colors.layer1)) { + SyncedTabsErrorItem( + errorText = stringResource(R.string.synced_tabs_no_tabs), + errorButton = SyncedTabsListItem.ErrorButton( + buttonText = stringResource(R.string.synced_tabs_sign_in_button), + ) { + println("SyncedTabsErrorButton click") + }, + ) + } + } +} + +@Composable +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SyncedTabsListPreview() { + FirefoxTheme { + Box(Modifier.background(FirefoxTheme.colors.layer1)) { + SyncedTabsList( + syncedTabs = getFakeSyncedTabList(), + onTabClick = { println("Tab clicked") }, + onTabCloseClick = { _, _ -> println("Tab closed") }, + ) + } + } +} + +/** + * Helper function to create a List of [SyncedTabsListItem] for previewing. + */ +@VisibleForTesting +internal fun getFakeSyncedTabList(): List = listOf( + SyncedTabsListItem.DeviceSection( + displayName = "Device 1", + tabs = listOf( + generateFakeTab("Mozilla", "www.mozilla.org"), + generateFakeTab("Google", "www.google.com"), + generateFakeTab("", "www.google.com"), + ), + ), + SyncedTabsListItem.DeviceSection( + displayName = "Device 2", + tabs = listOf( + generateFakeTab("Firefox", "www.getfirefox.org", SyncedTabsListItem.Tab.Action.Close("device2222")), + generateFakeTab("Thunderbird", "www.getthunderbird.org", SyncedTabsListItem.Tab.Action.Close("device2222")), + ), + ), + SyncedTabsListItem.DeviceSection("Device 3", emptyList()), + SyncedTabsListItem.Error("Please re-authenticate"), +) + +/** + * Helper function to create a [SyncedTabsListItem.Tab] for previewing. + */ +private fun generateFakeTab( + tabName: String, + tabUrl: String, + action: SyncedTabsListItem.Tab.Action = SyncedTabsListItem.Tab.Action.None, +): SyncedTabsListItem.Tab = + SyncedTabsListItem.Tab( + tabName.ifEmpty { tabUrl }, + tabUrl, + action, + SyncTab( + history = listOf(TabEntry(tabName, tabUrl, null)), + active = 0, + lastUsed = 0L, + inactive = false, + ), + ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListItem.kt index 186d3192a4..0943187e0b 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListItem.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListItem.kt @@ -31,13 +31,29 @@ sealed class SyncedTabsListItem { * * @property displayTitle The title of the tab's web page. * @property displayURL The tab's URL up to BrowserToolbar.MAX_URI_LENGTH characters long. + * @property action The action button to show for this tab. * @property tab The underlying SyncTab object passed when the tab is clicked. */ data class Tab( val displayTitle: String, val displayURL: String, + val action: Action, val tab: SyncTab, - ) : SyncedTabsListItem() + ) : SyncedTabsListItem() { + /** An action button to show for a [Tab]. */ + sealed class Action { + /** + * An action button to close the [Tab] on the synced device. + * + * @property deviceId The ID of the device on which the [Tab] is + * currently open. + */ + data class Close(val deviceId: String) : Action() + + /** A placeholder for a [Tab] without an action button. */ + data object None : Action() + } + } /** * A placeholder for a device that has no tabs synced. diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListSupportedFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListSupportedFeature.kt new file mode 100644 index 0000000000..55fac2a78f --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsListSupportedFeature.kt @@ -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/. */ + +package org.mozilla.fenix.tabstray.syncedtabs + +/** + * Configurable or experimental features that a [SyncedTabsList] supports. + */ +enum class SyncedTabsListSupportedFeature { + CLOSE_TABS, +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt index 58ef98f1f1..d6f96723a4 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabsPageViewHolder.kt @@ -35,6 +35,7 @@ class SyncedTabsPageViewHolder( SyncedTabsList( syncedTabs = tabs ?: emptyList(), onTabClick = interactor::onSyncedTabClicked, + onTabCloseClick = interactor::onSyncedTabClosed, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/theme/FirefoxTheme.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/theme/FirefoxTheme.kt index 921e989a18..02cff3042f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/theme/FirefoxTheme.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/theme/FirefoxTheme.kt @@ -102,19 +102,19 @@ private val darkColorPalette = FirefoxColors( layerGradientStart = PhotonColors.Violet70, layerGradientEnd = PhotonColors.Violet40, layerWarning = PhotonColors.Yellow70A77, - layerConfirmation = PhotonColors.Green80, - layerError = PhotonColors.Pink80, - layerInfo = PhotonColors.Blue50, + layerSuccess = PhotonColors.Green80, + layerCritical = PhotonColors.Pink80, + layerInformation = PhotonColors.Blue50, layerSearch = PhotonColors.DarkGrey80, actionPrimary = PhotonColors.Violet60, actionPrimaryDisabled = PhotonColors.Violet60A50, - actionSecondary = PhotonColors.LightGrey30, + actionSecondary = PhotonColors.DarkGrey05, actionTertiary = PhotonColors.DarkGrey10, actionQuarternary = PhotonColors.DarkGrey80, actionWarning = PhotonColors.Yellow40A41, - actionConfirmation = PhotonColors.Green70, - actionError = PhotonColors.Pink70A69, - actionInfo = PhotonColors.Blue60, + actionSuccess = PhotonColors.Green70, + actionCritical = PhotonColors.Pink70A69, + actionInformation = PhotonColors.Blue60, formDefault = PhotonColors.LightGrey05, formSelected = PhotonColors.Violet40, formSurface = PhotonColors.DarkGrey05, @@ -126,15 +126,15 @@ private val darkColorPalette = FirefoxColors( textPrimary = PhotonColors.LightGrey05, textSecondary = PhotonColors.LightGrey40, textDisabled = PhotonColors.LightGrey05A40, - textWarning = PhotonColors.Red20, - textWarningButton = PhotonColors.Red70, + textCritical = PhotonColors.Red20, + textCriticalButton = PhotonColors.Red20, textAccent = PhotonColors.Violet20, textAccentDisabled = PhotonColors.Violet20A60, textOnColorPrimary = PhotonColors.LightGrey05, textOnColorSecondary = PhotonColors.LightGrey40, textActionPrimary = PhotonColors.LightGrey05, textActionPrimaryDisabled = PhotonColors.LightGrey05A40, - textActionSecondary = PhotonColors.DarkGrey90, + textActionSecondary = PhotonColors.LightGrey05, textActionTertiary = PhotonColors.LightGrey05, textActionTertiaryActive = PhotonColors.LightGrey05, iconPrimary = PhotonColors.LightGrey05, @@ -146,15 +146,15 @@ private val darkColorPalette = FirefoxColors( iconOnColorDisabled = PhotonColors.LightGrey05A40, iconNotice = PhotonColors.Blue30, iconButton = PhotonColors.LightGrey05, - iconWarning = PhotonColors.Red20, - iconWarningButton = PhotonColors.Red70, + iconCritical = PhotonColors.Red20, + iconCriticalButton = PhotonColors.Red20, iconAccentViolet = PhotonColors.Violet20, iconAccentBlue = PhotonColors.Blue20, iconAccentPink = PhotonColors.Pink20, iconAccentGreen = PhotonColors.Green20, iconAccentYellow = PhotonColors.Yellow20, iconActionPrimary = PhotonColors.LightGrey05, - iconActionSecondary = PhotonColors.DarkGrey90, + iconActionSecondary = PhotonColors.LightGrey05, iconActionTertiary = PhotonColors.LightGrey05, iconGradientStart = PhotonColors.Violet20, iconGradientEnd = PhotonColors.Blue20, @@ -164,7 +164,7 @@ private val darkColorPalette = FirefoxColors( borderFormDefault = PhotonColors.LightGrey05, borderAccent = PhotonColors.Violet40, borderDisabled = PhotonColors.LightGrey05A40, - borderWarning = PhotonColors.Red40, + borderCritical = PhotonColors.Red20, borderToolbarDivider = PhotonColors.DarkGrey60, ) @@ -182,9 +182,9 @@ private val lightColorPalette = FirefoxColors( layerGradientStart = PhotonColors.Violet70, layerGradientEnd = PhotonColors.Violet40, layerWarning = PhotonColors.Yellow20, - layerConfirmation = PhotonColors.Green20, - layerError = PhotonColors.Red10, - layerInfo = PhotonColors.Blue50A44, + layerSuccess = PhotonColors.Green20, + layerCritical = PhotonColors.Red10, + layerInformation = PhotonColors.Blue50A44, layerSearch = PhotonColors.LightGrey30, actionPrimary = PhotonColors.Ink20, actionPrimaryDisabled = PhotonColors.Ink20A50, @@ -192,9 +192,9 @@ private val lightColorPalette = FirefoxColors( actionTertiary = PhotonColors.LightGrey40, actionQuarternary = PhotonColors.LightGrey10, actionWarning = PhotonColors.Yellow60A40, - actionConfirmation = PhotonColors.Green60, - actionError = PhotonColors.Red30, - actionInfo = PhotonColors.Blue50, + actionSuccess = PhotonColors.Green60, + actionCritical = PhotonColors.Red30, + actionInformation = PhotonColors.Blue50, formDefault = PhotonColors.DarkGrey90, formSelected = PhotonColors.Ink20, formSurface = PhotonColors.LightGrey50, @@ -206,8 +206,8 @@ private val lightColorPalette = FirefoxColors( textPrimary = PhotonColors.DarkGrey90, textSecondary = PhotonColors.DarkGrey05, textDisabled = PhotonColors.DarkGrey90A40, - textWarning = PhotonColors.Red70, - textWarningButton = PhotonColors.Red70, + textCritical = PhotonColors.Red70, + textCriticalButton = PhotonColors.Red70, textAccent = PhotonColors.Violet70, textAccentDisabled = PhotonColors.Violet70A80, textOnColorPrimary = PhotonColors.LightGrey05, @@ -226,9 +226,9 @@ private val lightColorPalette = FirefoxColors( iconOnColorDisabled = PhotonColors.LightGrey05A40, iconNotice = PhotonColors.Blue30, iconButton = PhotonColors.Ink20, - iconWarning = PhotonColors.Red70, - iconWarningButton = PhotonColors.Red70, - iconAccentViolet = PhotonColors.Violet60, + iconCritical = PhotonColors.Red70, + iconCriticalButton = PhotonColors.Red70, + iconAccentViolet = PhotonColors.Violet70, iconAccentBlue = PhotonColors.Blue60, iconAccentPink = PhotonColors.Pink60, iconAccentGreen = PhotonColors.Green60, @@ -244,15 +244,16 @@ private val lightColorPalette = FirefoxColors( borderFormDefault = PhotonColors.DarkGrey90, borderAccent = PhotonColors.Ink20, borderDisabled = PhotonColors.DarkGrey90A40, - borderWarning = PhotonColors.Red70, + borderCritical = PhotonColors.Red70, borderToolbarDivider = PhotonColors.LightGrey10, ) private val privateColorPalette = darkColorPalette.copy( - layer1 = PhotonColors.Ink50, - layer2 = PhotonColors.Ink50, + layer1 = PhotonColors.Violet90, + layer2 = PhotonColors.Violet90, layer3 = PhotonColors.Ink90, layerSearch = PhotonColors.Ink90, + borderPrimary = PhotonColors.Ink05, borderSecondary = PhotonColors.Ink10, borderToolbarDivider = PhotonColors.Violet80, ) @@ -276,9 +277,9 @@ class FirefoxColors( layerGradientStart: Color, layerGradientEnd: Color, layerWarning: Color, - layerConfirmation: Color, - layerError: Color, - layerInfo: Color, + layerSuccess: Color, + layerCritical: Color, + layerInformation: Color, layerSearch: Color, actionPrimary: Color, actionPrimaryDisabled: Color, @@ -286,9 +287,9 @@ class FirefoxColors( actionTertiary: Color, actionQuarternary: Color, actionWarning: Color, - actionConfirmation: Color, - actionError: Color, - actionInfo: Color, + actionSuccess: Color, + actionCritical: Color, + actionInformation: Color, formDefault: Color, formSelected: Color, formSurface: Color, @@ -300,8 +301,8 @@ class FirefoxColors( textPrimary: Color, textSecondary: Color, textDisabled: Color, - textWarning: Color, - textWarningButton: Color, + textCritical: Color, + textCriticalButton: Color, textAccent: Color, textAccentDisabled: Color, textOnColorPrimary: Color, @@ -320,8 +321,8 @@ class FirefoxColors( iconOnColorDisabled: Color, iconNotice: Color, iconButton: Color, - iconWarning: Color, - iconWarningButton: Color, + iconCritical: Color, + iconCriticalButton: Color, iconAccentViolet: Color, iconAccentBlue: Color, iconAccentPink: Color, @@ -338,7 +339,7 @@ class FirefoxColors( borderFormDefault: Color, borderAccent: Color, borderDisabled: Color, - borderWarning: Color, + borderCritical: Color, borderToolbarDivider: Color, ) { // Layers @@ -395,15 +396,15 @@ class FirefoxColors( private set // Confirmation background - var layerConfirmation by mutableStateOf(layerConfirmation) + var layerSuccess by mutableStateOf(layerSuccess) private set // Error Background - var layerError by mutableStateOf(layerError) + var layerCritical by mutableStateOf(layerCritical) private set // Info background - var layerInfo by mutableStateOf(layerInfo) + var layerInformation by mutableStateOf(layerInformation) private set // Search @@ -437,15 +438,15 @@ class FirefoxColors( private set // Confirmation button - var actionConfirmation by mutableStateOf(actionConfirmation) + var actionSuccess by mutableStateOf(actionSuccess) private set // Error button - var actionError by mutableStateOf(actionError) + var actionCritical by mutableStateOf(actionCritical) private set // Info button - var actionInfo by mutableStateOf(actionInfo) + var actionInformation by mutableStateOf(actionInformation) private set // Checkbox default, Radio button default @@ -495,11 +496,11 @@ class FirefoxColors( private set // Warning text - var textWarning by mutableStateOf(textWarning) + var textCritical by mutableStateOf(textCritical) private set // Warning text on Secondary button - var textWarningButton by mutableStateOf(textWarningButton) + var textCriticalButton by mutableStateOf(textCriticalButton) private set // Small heading, Text link @@ -575,11 +576,11 @@ class FirefoxColors( // Icon button var iconButton by mutableStateOf(iconButton) private set - var iconWarning by mutableStateOf(iconWarning) + var iconCritical by mutableStateOf(iconCritical) private set // Warning icon on Secondary button - var iconWarningButton by mutableStateOf(iconWarningButton) + var iconCriticalButton by mutableStateOf(iconCriticalButton) private set var iconAccentViolet by mutableStateOf(iconAccentViolet) private set @@ -638,7 +639,7 @@ class FirefoxColors( private set // Form parts - var borderWarning by mutableStateOf(borderWarning) + var borderCritical by mutableStateOf(borderCritical) private set // Toolbar divider @@ -663,9 +664,9 @@ class FirefoxColors( layerGradientStart = other.layerGradientStart layerGradientEnd = other.layerGradientEnd layerWarning = other.layerWarning - layerConfirmation = other.layerConfirmation - layerError = other.layerError - layerInfo = other.layerInfo + layerSuccess = other.layerSuccess + layerCritical = other.layerCritical + layerInformation = other.layerInformation layerSearch = other.layerSearch actionPrimary = other.actionPrimary actionPrimaryDisabled = other.actionPrimaryDisabled @@ -673,9 +674,9 @@ class FirefoxColors( actionTertiary = other.actionTertiary actionQuarternary = other.actionQuarternary actionWarning = other.actionWarning - actionConfirmation = other.actionConfirmation - actionError = other.actionError - actionInfo = other.actionInfo + actionSuccess = other.actionSuccess + actionCritical = other.actionCritical + actionInformation = other.actionInformation formDefault = other.formDefault formSelected = other.formSelected formSurface = other.formSurface @@ -687,8 +688,8 @@ class FirefoxColors( textPrimary = other.textPrimary textSecondary = other.textSecondary textDisabled = other.textDisabled - textWarning = other.textWarning - textWarningButton = other.textWarningButton + textCritical = other.textCritical + textCriticalButton = other.textCriticalButton textAccent = other.textAccent textAccentDisabled = other.textAccentDisabled textOnColorPrimary = other.textOnColorPrimary @@ -707,8 +708,8 @@ class FirefoxColors( iconOnColorDisabled = other.iconOnColorDisabled iconNotice = other.iconNotice iconButton = other.iconButton - iconWarning = other.iconWarning - iconWarningButton = other.iconWarningButton + iconCritical = other.iconCritical + iconCriticalButton = other.iconCriticalButton iconAccentViolet = other.iconAccentViolet iconAccentBlue = other.iconAccentBlue iconAccentPink = other.iconAccentPink @@ -725,7 +726,7 @@ class FirefoxColors( borderFormDefault = other.borderFormDefault borderAccent = other.borderAccent borderDisabled = other.borderDisabled - borderWarning = other.borderWarning + borderCritical = other.borderCritical borderToolbarDivider = other.borderToolbarDivider } @@ -747,9 +748,9 @@ class FirefoxColors( layerGradientStart: Color = this.layerGradientStart, layerGradientEnd: Color = this.layerGradientEnd, layerWarning: Color = this.layerWarning, - layerConfirmation: Color = this.layerConfirmation, - layerError: Color = this.layerError, - layerInfo: Color = this.layerInfo, + layerSuccess: Color = this.layerSuccess, + layerCritical: Color = this.layerCritical, + layerInformation: Color = this.layerInformation, layerSearch: Color = this.layerSearch, actionPrimary: Color = this.actionPrimary, actionPrimaryDisabled: Color = this.actionPrimaryDisabled, @@ -757,9 +758,9 @@ class FirefoxColors( actionTertiary: Color = this.actionTertiary, actionQuarternary: Color = this.actionQuarternary, actionWarning: Color = this.actionWarning, - actionConfirmation: Color = this.actionConfirmation, - actionError: Color = this.actionError, - actionInfo: Color = this.actionInfo, + actionSuccess: Color = this.actionSuccess, + actionCritical: Color = this.actionCritical, + actionInformation: Color = this.actionInformation, formDefault: Color = this.formDefault, formSelected: Color = this.formSelected, formSurface: Color = this.formSurface, @@ -771,8 +772,8 @@ class FirefoxColors( textPrimary: Color = this.textPrimary, textSecondary: Color = this.textSecondary, textDisabled: Color = this.textDisabled, - textWarning: Color = this.textWarning, - textWarningButton: Color = this.textWarningButton, + textCritical: Color = this.textCritical, + textCriticalButton: Color = this.textCriticalButton, textAccent: Color = this.textAccent, textAccentDisabled: Color = this.textAccentDisabled, textOnColorPrimary: Color = this.textOnColorPrimary, @@ -791,8 +792,8 @@ class FirefoxColors( iconOnColorDisabled: Color = this.iconOnColorDisabled, iconNotice: Color = this.iconNotice, iconButton: Color = this.iconButton, - iconWarning: Color = this.iconWarning, - iconWarningButton: Color = this.iconWarningButton, + iconCritical: Color = this.iconCritical, + iconCriticalButton: Color = this.iconCriticalButton, iconAccentViolet: Color = this.iconAccentViolet, iconAccentBlue: Color = this.iconAccentBlue, iconAccentPink: Color = this.iconAccentPink, @@ -809,7 +810,7 @@ class FirefoxColors( borderFormDefault: Color = this.borderFormDefault, borderAccent: Color = this.borderAccent, borderDisabled: Color = this.borderDisabled, - borderWarning: Color = this.borderWarning, + borderWarning: Color = this.borderCritical, borderToolbarDivider: Color = this.borderToolbarDivider, ): FirefoxColors = FirefoxColors( layer1 = layer1, @@ -825,9 +826,9 @@ class FirefoxColors( layerGradientStart = layerGradientStart, layerGradientEnd = layerGradientEnd, layerWarning = layerWarning, - layerConfirmation = layerConfirmation, - layerError = layerError, - layerInfo = layerInfo, + layerSuccess = layerSuccess, + layerCritical = layerCritical, + layerInformation = layerInformation, layerSearch = layerSearch, actionPrimary = actionPrimary, actionPrimaryDisabled = actionPrimaryDisabled, @@ -835,9 +836,9 @@ class FirefoxColors( actionTertiary = actionTertiary, actionQuarternary = actionQuarternary, actionWarning = actionWarning, - actionConfirmation = actionConfirmation, - actionError = actionError, - actionInfo = actionInfo, + actionSuccess = actionSuccess, + actionCritical = actionCritical, + actionInformation = actionInformation, formDefault = formDefault, formSelected = formSelected, formSurface = formSurface, @@ -849,8 +850,8 @@ class FirefoxColors( textPrimary = textPrimary, textSecondary = textSecondary, textDisabled = textDisabled, - textWarning = textWarning, - textWarningButton = textWarningButton, + textCritical = textCritical, + textCriticalButton = textCriticalButton, textAccent = textAccent, textAccentDisabled = textAccentDisabled, textOnColorPrimary = textOnColorPrimary, @@ -869,8 +870,8 @@ class FirefoxColors( iconOnColorDisabled = iconOnColorDisabled, iconNotice = iconNotice, iconButton = iconButton, - iconWarning = iconWarning, - iconWarningButton = iconWarningButton, + iconCritical = iconCritical, + iconCriticalButton = iconCriticalButton, iconAccentViolet = iconAccentViolet, iconAccentBlue = iconAccentBlue, iconAccentPink = iconAccentPink, @@ -887,7 +888,7 @@ class FirefoxColors( borderFormDefault = borderFormDefault, borderAccent = borderAccent, borderDisabled = borderDisabled, - borderWarning = borderWarning, + borderCritical = borderWarning, borderToolbarDivider = borderToolbarDivider, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/DownloadIndicator.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/DownloadIndicator.kt index 952727fe85..89c9cb47dc 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/DownloadIndicator.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/DownloadIndicator.kt @@ -14,6 +14,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -26,7 +29,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.role import androidx.compose.ui.unit.dp import org.mozilla.fenix.R @@ -87,15 +89,15 @@ fun DownloadIndicator( modifier = modifier.then( Modifier .clearAndSetSemantics { - disabled() role = Role.Button contentDescription?.let { this.contentDescription = contentDescription } - }, + } + .wrapContentSize(), ), - enabled = false, icon = icon, iconModifier = Modifier - .rotate(rotationAnimation()), + .rotate(rotationAnimation()) + .size(ButtonDefaults.IconSize), onClick = {}, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt index d29da59cfd..9edbaef823 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt @@ -46,6 +46,7 @@ fun TranslationSettings( onNeverTranslationClicked: () -> Unit, onDownloadLanguageClicked: () -> Unit, ) { + val showHeader = showAutomaticTranslations || showNeverTranslate || showDownloads Column( modifier = Modifier .background( @@ -67,12 +68,12 @@ fun TranslationSettings( .padding(start = 72.dp, end = 16.dp), ) - if (item.type.hasDivider) { + if (item.type.hasDivider && showHeader) { Divider(Modifier.padding(top = 8.dp, bottom = 8.dp)) } } - if (showAutomaticTranslations || showNeverTranslate || showDownloads) { + if (showHeader) { item { Text( text = stringResource( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettingsFragment.kt index 5fbd7a2dc1..0859c51bd3 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettingsFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationSettingsFragment.kt @@ -17,11 +17,9 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs import mozilla.components.browser.state.action.TranslationsAction -import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.TranslationsBrowserState import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.translate.TranslationPageSettingOperation import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.support.base.feature.UserInteractionHandler import org.mozilla.fenix.GleanMetrics.Translations @@ -36,7 +34,6 @@ import org.mozilla.fenix.theme.FirefoxTheme * A fragment displaying the Firefox Translation settings screen. */ class TranslationSettingsFragment : Fragment(), UserInteractionHandler { - private val args by navArgs() private val browserStore: BrowserStore by lazy { requireComponents.core.store } override fun onResume() { @@ -67,7 +64,7 @@ class TranslationSettingsFragment : Fragment(), UserInteractionHandler { Translations.action.record(Translations.ActionExtra("global_site_settings")) findNavController().navigate( TranslationSettingsFragmentDirections - .actionTranslationSettingsFragmentToNeverTranslateSitePreferenceFragment(), + .actionTranslationSettingsToNeverTranslateSitePreference(), ) }, onDownloadLanguageClicked = { @@ -84,19 +81,19 @@ class TranslationSettingsFragment : Fragment(), UserInteractionHandler { /** * Set the switch item values. - * The first one is based on [TranslationPageSettings.alwaysOfferPopup]. + * The first one is based on [TranslationsBrowserState.offerTranslation]. * The second one is [DownloadLanguageFileDialog] visibility. * This pop-up will appear if the switch item is unchecked, the phone is in saving mode, and * doesn't have a WiFi connection. */ @Composable private fun getTranslationSwitchItemList(): MutableList { - val pageSettingsState = browserStore.observeAsComposableState { state -> - state.findTab(args.sessionId)?.translationsState?.pageSettings + val offerToTranslate = browserStore.observeAsComposableState { state -> + state.translationEngine.offerTranslation }.value val translationSwitchItems = mutableListOf() - pageSettingsState?.alwaysOfferPopup?.let { + offerToTranslate?.let { translationSwitchItems.add( TranslationSwitchItem( type = TranslationSettingsScreenOption.OfferToTranslate( @@ -107,10 +104,8 @@ class TranslationSettingsFragment : Fragment(), UserInteractionHandler { isEnabled = true, onStateChange = { _, checked -> browserStore.dispatch( - TranslationsAction.UpdatePageSettingAction( - tabId = args.sessionId, - operation = TranslationPageSettingOperation.UPDATE_ALWAYS_OFFER_POPUP, - setting = checked, + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = checked, ), ) // Ensures persistence of value @@ -141,12 +136,15 @@ class TranslationSettingsFragment : Fragment(), UserInteractionHandler { } override fun onBackPressed(): Boolean { - findNavController().navigate( - TranslationSettingsFragmentDirections.actionTranslationSettingsFragmentToTranslationsDialogFragment( - sessionId = args.sessionId, - translationsDialogAccessPoint = TranslationsDialogAccessPoint.TranslationsOptions, - ), - ) - return true + return if (findNavController().previousBackStackEntry?.destination?.id == R.id.browserFragment) { + findNavController().navigate( + TranslationSettingsFragmentDirections.actionTranslationSettingsFragmentToTranslationsDialogFragment( + translationsDialogAccessPoint = TranslationsDialogAccessPoint.TranslationsOptions, + ), + ) + true + } else { + false + } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt index 8d4a74e02c..f6e27391be 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt @@ -171,6 +171,7 @@ internal fun TranslationsOptionsDialog( context: Context, showGlobalSettings: Boolean, translationPageSettings: TranslationPageSettings? = null, + offerTranslation: Boolean? = null, initialFrom: Language? = null, onStateChange: (TranslationSettingsOption, Boolean) -> Unit, onBackClicked: () -> Unit, @@ -181,6 +182,7 @@ internal fun TranslationsOptionsDialog( showGlobalSettings = showGlobalSettings, translationOptionsList = getTranslationSwitchItemList( translationPageSettings = translationPageSettings, + offerTranslation = offerTranslation, initialFrom = initialFrom, context = context, onStateChange = onStateChange, @@ -194,6 +196,7 @@ internal fun TranslationsOptionsDialog( @Composable private fun getTranslationSwitchItemList( translationPageSettings: TranslationPageSettings? = null, + offerTranslation: Boolean? = null, initialFrom: Language? = null, context: Context, onStateChange: (TranslationSettingsOption, Boolean) -> Unit, @@ -201,12 +204,11 @@ private fun getTranslationSwitchItemList( val translationSwitchItemList = mutableListOf() translationPageSettings?.let { - val alwaysOfferPopup = translationPageSettings.alwaysOfferPopup val alwaysTranslateLanguage = translationPageSettings.alwaysTranslateLanguage val neverTranslateLanguage = translationPageSettings.neverTranslateLanguage val neverTranslateSite = translationPageSettings.neverTranslateSite - alwaysOfferPopup?.let { + offerTranslation?.let { translationSwitchItemList.add( TranslationSwitchItem( type = TranslationPageSettingsOption.AlwaysOfferPopup(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt index e2d25f82dc..b053e70498 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.mapNotNull -import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TranslationsBrowserState @@ -29,7 +29,6 @@ import java.util.Locale class TranslationsDialogBinding( browserStore: BrowserStore, private val translationsDialogStore: TranslationsDialogStore, - private val sessionId: String, private val getTranslatedPageTitle: (localizedFrom: String?, localizedTo: String?) -> String, ) : AbstractBinding(browserStore) { @@ -42,7 +41,7 @@ class TranslationsDialogBinding( } // Session level flows - val sessionFlow = flow.mapNotNull { state -> state.findTab(sessionId) } + val sessionFlow = flow.mapNotNull { state -> state.selectedTab } .distinctUntilChangedBy { it.translationsState } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt index 2c834aea08..727034c0d8 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt @@ -484,16 +484,18 @@ private fun TranslationErrorWarning( when (translationError) { is TranslationError.CouldNotTranslateError -> { ReviewQualityCheckInfoCard( - title = stringResource(id = R.string.translation_error_could_not_translate_warning_text), + description = stringResource(id = R.string.translation_error_could_not_translate_warning_text), type = ReviewQualityCheckInfoType.Error, + verticalRowAlignment = Alignment.CenterVertically, modifier = modifier, ) } is TranslationError.CouldNotLoadLanguagesError -> { ReviewQualityCheckInfoCard( - title = stringResource(id = R.string.translation_error_could_not_load_languages_warning_text), + description = stringResource(id = R.string.translation_error_could_not_load_languages_warning_text), type = ReviewQualityCheckInfoType.Error, + verticalRowAlignment = Alignment.CenterVertically, modifier = modifier, ) } @@ -501,7 +503,7 @@ private fun TranslationErrorWarning( is TranslationError.LanguageNotSupportedError -> { documentLangDisplayName?.let { ReviewQualityCheckInfoCard( - title = stringResource( + description = stringResource( id = R.string.translation_error_language_not_supported_warning_text, it, ), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt index 7e31dd594d..0d5909548e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf @@ -28,7 +29,7 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.translate.Language import mozilla.components.concept.engine.translate.TranslationError @@ -48,6 +49,9 @@ import org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLang import org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguageFileDialogType import org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguagesFeature +// Friction should be increased, since peek height on this dialog is to fill the screen. +private const val DIALOG_FRICTION = .65f + /** * The enum is to know what bottom sheet to open. */ @@ -78,6 +82,7 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { behavior = BottomSheetBehavior.from(bottomSheet) behavior?.peekHeight = resources.displayMetrics.heightPixels behavior?.state = BottomSheetBehavior.STATE_EXPANDED + behavior?.hideFriction = DIALOG_FRICTION } } @@ -92,7 +97,6 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { listOf( TranslationsDialogMiddleware( browserStore = browserStore, - sessionId = args.sessionId, settings = requireContext().settings(), ), ), @@ -245,7 +249,6 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { feature = TranslationsDialogBinding( browserStore = browserStore, translationsDialogStore = translationsDialogStore, - sessionId = args.sessionId, getTranslatedPageTitle = { localizedFrom, localizedTo -> requireContext().getString( R.string.translations_bottom_sheet_title_translation_completed, @@ -280,6 +283,8 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { onSettingClicked: () -> Unit, onShowDownloadLanguageFileDialog: () -> Unit, ) { + val localView = LocalView.current + TranslationsDialog( translationsDialogState = translationsDialogState, learnMoreUrl = learnMoreUrl, @@ -295,6 +300,11 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { }, onNegativeButtonClicked = { if (translationsDialogState.isTranslated) { + localView.announceForAccessibility( + requireContext().getString( + R.string.translations_bottom_sheet_restore_accessibility_announcement, + ), + ) translationsDialogStore.dispatch(TranslationsDialogAction.RestoreTranslation) } dismiss() @@ -384,12 +394,19 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { ) { val pageSettingsState = browserStore.observeAsComposableState { state -> - state.findTab(args.sessionId)?.translationsState?.pageSettings + state.selectedTab?.translationsState?.pageSettings }.value + val offerTranslation = browserStore.observeAsComposableState { state -> + state.translationEngine.offerTranslation + }.value + + val localView = LocalView.current + TranslationsOptionsDialog( context = requireContext(), translationPageSettings = pageSettingsState, + offerTranslation = offerTranslation, showGlobalSettings = showGlobalSettings, initialFrom = initialFrom, onStateChange = { type, checked -> @@ -402,15 +419,17 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { checked, ), ) + + if (checked) { + localView.announceForAccessibility(type.descriptionId?.let { getString(it) }) + } }, onBackClicked = onBackClicked, onTranslationSettingsClicked = { Translations.action.record(Translations.ActionExtra("global_settings")) findNavController().navigate( TranslationsDialogFragmentDirections - .actionTranslationsDialogFragmentToTranslationSettingsFragment( - sessionId = args.sessionId, - ), + .actionTranslationsDialogFragmentToTranslationSettingsFragment(), ) }, aboutTranslationClicked = { @@ -425,7 +444,7 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { setFragmentResult( TRANSLATION_IN_PROGRESS, bundleOf( - SESSION_ID to args.sessionId, + SESSION_ID to browserStore.state.selectedTab?.id, ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt index 20bfee0d84..cb6ac2c62d 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt @@ -5,12 +5,12 @@ package org.mozilla.fenix.translations import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.translate.TranslationOperation import mozilla.components.concept.engine.translate.TranslationPageSettingOperation import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext -import org.mozilla.fenix.ext.settings import org.mozilla.fenix.utils.Settings /** @@ -18,16 +18,17 @@ import org.mozilla.fenix.utils.Settings */ class TranslationsDialogMiddleware( private val browserStore: BrowserStore, - private val sessionId: String, private val settings: Settings, ) : Middleware { - @Suppress("LongMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") override fun invoke( context: MiddlewareContext, next: (TranslationsDialogAction) -> Unit, action: TranslationsDialogAction, ) { + val sessionId = browserStore.state.selectedTab?.id ?: return + when (action) { is TranslationsDialogAction.InitTranslationsDialog -> { // If the languages are missing, we should attempt to fetch the supported languages. @@ -98,10 +99,8 @@ class TranslationsDialogMiddleware( is TranslationPageSettingsOption.AlwaysOfferPopup -> { // Ensures the translations engine has the correct value browserStore.dispatch( - TranslationsAction.UpdatePageSettingAction( - tabId = sessionId, - operation = TranslationPageSettingOperation.UPDATE_ALWAYS_OFFER_POPUP, - setting = action.checkValue, + TranslationsAction.SetGlobalOfferTranslateSettingAction( + offerTranslation = action.checkValue, ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationItemPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationItemPreference.kt index 30bfed028e..8c32bc570c 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationItemPreference.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationItemPreference.kt @@ -6,17 +6,20 @@ package org.mozilla.fenix.translations.preferences.automatic import android.os.Parcelable import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.LanguageSetting import org.mozilla.fenix.R /** * AutomaticTranslationItem that will appear on Automatic Translation screen. * - * @property displayName The text that will appear in the list. + * @property language The text that will appear in the list. * @property automaticTranslationOptionPreference The option that the user selected. */ @Parcelize data class AutomaticTranslationItemPreference( - val displayName: String, + val language: @RawValue Language, val automaticTranslationOptionPreference: AutomaticTranslationOptionPreference, ) : Parcelable @@ -65,3 +68,23 @@ sealed class AutomaticTranslationOptionPreference( ), ) : AutomaticTranslationOptionPreference(titleId = titleId, summaryId = summaryId) } + +internal fun getAutomaticTranslationOptionPreference( + languageSetting: LanguageSetting, +): AutomaticTranslationOptionPreference { + return when (languageSetting) { + LanguageSetting.ALWAYS -> AutomaticTranslationOptionPreference.AlwaysTranslate() + LanguageSetting.OFFER -> AutomaticTranslationOptionPreference.OfferToTranslate() + LanguageSetting.NEVER -> AutomaticTranslationOptionPreference.NeverTranslate() + } +} + +internal fun getLanguageSetting( + automaticTranslationItemPreference: AutomaticTranslationOptionPreference, +): LanguageSetting { + return when (automaticTranslationItemPreference) { + is AutomaticTranslationOptionPreference.AlwaysTranslate -> LanguageSetting.ALWAYS + is AutomaticTranslationOptionPreference.NeverTranslate -> LanguageSetting.NEVER + is AutomaticTranslationOptionPreference.OfferToTranslate -> LanguageSetting.OFFER + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreference.kt index bbfd3d42ba..a62e6d3a36 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreference.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreference.kt @@ -21,10 +21,12 @@ import org.mozilla.fenix.theme.FirefoxTheme * Firefox Automatic Translation Options preference screen. * * @param selectedOption Selected option that will come from the translations engine. + * @param onItemClick Invoked when the user clicks on a [AutomaticTranslationOptionPreference] from the list. */ @Composable fun AutomaticTranslationOptionsPreference( selectedOption: AutomaticTranslationOptionPreference, + onItemClick: (AutomaticTranslationOptionPreference) -> Unit, ) { val optionsList = arrayListOf( AutomaticTranslationOptionPreference.OfferToTranslate(), @@ -50,6 +52,7 @@ fun AutomaticTranslationOptionsPreference( maxDescriptionLines = Int.MAX_VALUE, onClick = { selected.value = item + onItemClick(item) }, ) } @@ -63,6 +66,7 @@ private fun AutomaticTranslationOptionsPreview() { FirefoxTheme { AutomaticTranslationOptionsPreference( selectedOption = AutomaticTranslationOptionPreference.AlwaysTranslate(), + onItemClick = {}, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreferenceFragment.kt index b144227312..ad2dea0072 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreferenceFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationOptionsPreferenceFragment.kt @@ -11,6 +11,9 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.theme.FirefoxTheme @@ -19,10 +22,11 @@ import org.mozilla.fenix.theme.FirefoxTheme */ class AutomaticTranslationOptionsPreferenceFragment : Fragment() { private val args by navArgs() + private val browserStore: BrowserStore by lazy { requireComponents.core.store } override fun onResume() { super.onResume() - showToolbar(args.selectedTranslationOptionPreference.displayName) + args.selectedTranslationOptionPreference.language.localizedDisplayName?.let { showToolbar(it) } } override fun onCreateView( @@ -34,6 +38,14 @@ class AutomaticTranslationOptionsPreferenceFragment : Fragment() { FirefoxTheme { AutomaticTranslationOptionsPreference( selectedOption = args.selectedTranslationOptionPreference.automaticTranslationOptionPreference, + onItemClick = { + browserStore.dispatch( + TranslationsAction.UpdateLanguageSettingsAction( + languageCode = args.selectedTranslationOptionPreference.language.code, + setting = getLanguageSetting(it), + ), + ) + }, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreference.kt index 4ce45c4e2b..76183742c3 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreference.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreference.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import mozilla.components.concept.engine.translate.Language import org.mozilla.fenix.R import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.list.TextListItem @@ -58,16 +59,18 @@ fun AutomaticTranslationPreference( ) { description = stringResource(item.automaticTranslationOptionPreference.titleId) } - TextListItem( - label = item.displayName, - description = description, - modifier = Modifier - .fillMaxWidth() - .padding(start = 56.dp), - onClick = { - onItemClick(item) - }, - ) + item.language.localizedDisplayName?.let { + TextListItem( + label = it, + description = description, + modifier = Modifier + .fillMaxWidth() + .padding(start = 56.dp), + onClick = { + onItemClick(item) + }, + ) + } } } } @@ -78,25 +81,25 @@ internal fun getAutomaticTranslationListPreferences(): List().apply { add( AutomaticTranslationItemPreference( - displayName = Locale.ENGLISH.displayLanguage, + language = Language(Locale.ENGLISH.toLanguageTag(), Locale.ENGLISH.displayLanguage), automaticTranslationOptionPreference = AutomaticTranslationOptionPreference.AlwaysTranslate(), ), ) add( AutomaticTranslationItemPreference( - displayName = Locale.FRENCH.displayLanguage, + language = Language(Locale.FRANCE.toLanguageTag(), Locale.FRANCE.displayLanguage), automaticTranslationOptionPreference = AutomaticTranslationOptionPreference.OfferToTranslate(), ), ) add( AutomaticTranslationItemPreference( - displayName = Locale.GERMAN.displayLanguage, + language = Language(Locale.GERMAN.toLanguageTag(), Locale.GERMAN.displayLanguage), automaticTranslationOptionPreference = AutomaticTranslationOptionPreference.NeverTranslate(), ), ) add( AutomaticTranslationItemPreference( - displayName = Locale.ITALIAN.displayLanguage, + language = Language(Locale.ITALIAN.toLanguageTag(), Locale.ITALIAN.displayLanguage), automaticTranslationOptionPreference = AutomaticTranslationOptionPreference.AlwaysTranslate(), ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreferenceFragment.kt index 9830a17156..c2b07f98bb 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreferenceFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/automatic/AutomaticTranslationPreferenceFragment.kt @@ -11,7 +11,13 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.LanguageSetting +import mozilla.components.concept.engine.translate.TranslationSupport +import mozilla.components.concept.engine.translate.findLanguage +import mozilla.components.lib.state.ext.observeAsComposableState import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.theme.FirefoxTheme @@ -19,6 +25,8 @@ import org.mozilla.fenix.theme.FirefoxTheme * A fragment displaying the Firefox Automatic Translation list screen. */ class AutomaticTranslationPreferenceFragment : Fragment() { + private val browserStore: BrowserStore by lazy { requireComponents.core.store } + override fun onResume() { super.onResume() showToolbar(getString(R.string.automatic_translation_toolbar_title_preference)) @@ -31,8 +39,18 @@ class AutomaticTranslationPreferenceFragment : Fragment() { ): View = ComposeView(requireContext()).apply { setContent { FirefoxTheme { + val languageSettings = browserStore.observeAsComposableState { state -> + state.translationEngine.languageSettings + }.value + val translationSupport = browserStore.observeAsComposableState { state -> + state.translationEngine.supportedLanguages + }.value + AutomaticTranslationPreference( - automaticTranslationListPreferences = getAutomaticTranslationListPreferences(), + automaticTranslationListPreferences = getAutomaticTranslationListPreferences( + languageSettings = languageSettings, + translationSupport = translationSupport, + ), onItemClick = { findNavController().navigate( AutomaticTranslationPreferenceFragmentDirections @@ -45,4 +63,28 @@ class AutomaticTranslationPreferenceFragment : Fragment() { } } } + + private fun getAutomaticTranslationListPreferences( + languageSettings: Map? = null, + translationSupport: TranslationSupport? = null, + ): List { + val automaticTranslationListPreferences = + mutableListOf() + + if (translationSupport != null && languageSettings != null) { + languageSettings.forEach { entry -> + translationSupport.findLanguage(entry.key)?.let { + automaticTranslationListPreferences.add( + AutomaticTranslationItemPreference( + language = it, + automaticTranslationOptionPreference = getAutomaticTranslationOptionPreference( + entry.value, + ), + ), + ) + } + } + } + return automaticTranslationListPreferences + } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteDialogPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteDialogPreferenceFragment.kt index 20204b2afb..42caba39a5 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteDialogPreferenceFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteDialogPreferenceFragment.kt @@ -13,6 +13,9 @@ import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.DialogFragment import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.theme.FirefoxTheme /** @@ -21,6 +24,7 @@ import org.mozilla.fenix.theme.FirefoxTheme class NeverTranslateSiteDialogPreferenceFragment : DialogFragment() { private val args by navArgs() + private val browserStore: BrowserStore by lazy { requireComponents.core.store } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = super.onCreateDialog(savedInstanceState).apply { @@ -37,9 +41,18 @@ class NeverTranslateSiteDialogPreferenceFragment : DialogFragment() { setContent { FirefoxTheme { NeverTranslateSiteDialogPreference( - websiteUrl = args.websiteUrl, - onConfirmDelete = { findNavController().popBackStack() }, - onCancel = { findNavController().popBackStack() }, + websiteUrl = args.neverTranslateSiteUrl, + onConfirmDelete = { + browserStore.dispatch( + TranslationsAction.RemoveNeverTranslateSiteAction( + origin = args.neverTranslateSiteUrl, + ), + ) + findNavController().popBackStack() + }, + onCancel = { + findNavController().popBackStack() + }, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteListItemPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteListItemPreference.kt deleted file mode 100644 index 6baf2868ef..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSiteListItemPreference.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* 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 org.mozilla.fenix.translations.preferences.nevertranslatesite - -/** - * NeverTranslateSiteListItemPreference that will appear on [NeverTranslateSitePreferenceFragment] screens. - * - * @property websiteUrl The text that will appear on the item list. - */ -data class NeverTranslateSiteListItemPreference(val websiteUrl: String) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreference.kt deleted file mode 100644 index e8cf6c1a44..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreference.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* 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 org.mozilla.fenix.translations.preferences.nevertranslatesite - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.heading -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import org.mozilla.fenix.R -import org.mozilla.fenix.compose.annotation.LightDarkPreview -import org.mozilla.fenix.compose.list.TextListItem -import org.mozilla.fenix.theme.FirefoxTheme - -/** - * Never Translate Site preference screen. - * - * @param neverTranslateSiteListPreferences List of [NeverTranslateSiteListItemPreference]s to display. - * @param onItemClick Invoked when the user clicks on the a item from the list. - */ -@Composable -fun NeverTranslateSitePreference( - neverTranslateSiteListPreferences: List, - onItemClick: (NeverTranslateSiteListItemPreference) -> Unit, -) { - Column( - modifier = Modifier - .background( - color = FirefoxTheme.colors.layer1, - ), - ) { - TextListItem( - label = stringResource(R.string.never_translate_site_header_preference), - modifier = Modifier - .padding( - start = 56.dp, - ) - .semantics { heading() }, - maxLabelLines = Int.MAX_VALUE, - ) - - LazyColumn { - items(neverTranslateSiteListPreferences) { item: NeverTranslateSiteListItemPreference -> - val itemContentDescription = stringResource( - id = R.string.never_translate_site_item_list_content_description_preference, - item.websiteUrl, - ) - TextListItem( - label = item.websiteUrl, - modifier = Modifier - .padding( - start = 56.dp, - ) - .clearAndSetSemantics { - role = Role.Button - contentDescription = itemContentDescription - }, - onClick = { onItemClick(item) }, - iconPainter = painterResource(R.drawable.mozac_ic_delete_24), - onIconClick = { onItemClick(item) }, - ) - } - } - } -} - -@Composable -internal fun getNeverTranslateListItemsPreference(): List { - return mutableListOf().apply { - add( - NeverTranslateSiteListItemPreference( - websiteUrl = "mozilla.org", - ), - ) - } -} - -@Composable -@LightDarkPreview -private fun NeverTranslateSitePreferencePreview() { - FirefoxTheme { - NeverTranslateSitePreference( - neverTranslateSiteListPreferences = getNeverTranslateListItemsPreference(), - ) {} - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreferenceFragment.kt deleted file mode 100644 index 473d397b86..0000000000 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitePreferenceFragment.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* 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 org.mozilla.fenix.translations.preferences.nevertranslatesite - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import org.mozilla.fenix.R -import org.mozilla.fenix.ext.showToolbar -import org.mozilla.fenix.theme.FirefoxTheme - -/** - * A fragment displaying never translate site items list. - */ -class NeverTranslateSitePreferenceFragment : Fragment() { - override fun onResume() { - super.onResume() - showToolbar(getString(R.string.never_translate_site_toolbar_title_preference)) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View = ComposeView(requireContext()).apply { - setContent { - FirefoxTheme { - NeverTranslateSitePreference( - neverTranslateSiteListPreferences = getNeverTranslateListItemsPreference(), - onItemClick = { - findNavController().navigate( - NeverTranslateSitePreferenceFragmentDirections - .actionNeverTranslateSitePreferenceFragmentToNeverTranslateSiteDialogPreferenceFragment( - it.websiteUrl, - ), - ) - }, - ) - } - } - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreference.kt new file mode 100644 index 0000000000..02215ea1fa --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreference.kt @@ -0,0 +1,97 @@ +/* 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 org.mozilla.fenix.translations.preferences.nevertranslatesite + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.compose.list.TextListItem +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * Never Translate Site preference screen. + * + * @param neverTranslateSitesListPreferences List of site urls to display. + * @param onItemClick Invoked when the user clicks on the a item from the list. + */ +@Composable +fun NeverTranslateSitesPreference( + neverTranslateSitesListPreferences: List, + onItemClick: (String) -> Unit, +) { + Column( + modifier = Modifier + .background( + color = FirefoxTheme.colors.layer1, + ), + ) { + TextListItem( + label = stringResource(R.string.never_translate_site_header_preference), + modifier = Modifier + .padding( + start = 56.dp, + ) + .semantics { heading() }, + maxLabelLines = Int.MAX_VALUE, + ) + + LazyColumn { + items(neverTranslateSitesListPreferences) { item: String -> + val itemContentDescription = stringResource( + id = R.string.never_translate_site_item_list_content_description_preference, + item, + ) + TextListItem( + label = item, + modifier = Modifier + .padding( + start = 56.dp, + ) + .clearAndSetSemantics { + role = Role.Button + contentDescription = itemContentDescription + }, + onClick = { onItemClick(item) }, + iconPainter = painterResource(R.drawable.mozac_ic_delete_24), + onIconClick = { onItemClick(item) }, + ) + } + } + } +} + +@Composable +internal fun getNeverTranslateSitesList(): List { + return mutableListOf().apply { + add( + "mozilla.org", + ) + } +} + +@Composable +@LightDarkPreview +private fun NeverTranslateSitePreferencePreview() { + FirefoxTheme { + NeverTranslateSitesPreference( + neverTranslateSitesListPreferences = getNeverTranslateSitesList(), + ) {} + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreferenceFragment.kt new file mode 100644 index 0000000000..429c89f18f --- /dev/null +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/nevertranslatesite/NeverTranslateSitesPreferenceFragment.kt @@ -0,0 +1,60 @@ +/* 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 org.mozilla.fenix.translations.preferences.nevertranslatesite + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.lib.state.ext.observeAsComposableState +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * A fragment displaying never translate site items list. + */ +class NeverTranslateSitesPreferenceFragment : Fragment() { + + private val browserStore: BrowserStore by lazy { requireComponents.core.store } + + override fun onResume() { + super.onResume() + showToolbar(getString(R.string.never_translate_site_toolbar_title_preference)) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = ComposeView(requireContext()).apply { + setContent { + FirefoxTheme { + val neverTranslateSites = browserStore.observeAsComposableState { state -> + state.translationEngine.neverTranslateSites + }.value + + neverTranslateSites?.let { neverTranslateSitesList -> + NeverTranslateSitesPreference( + neverTranslateSitesListPreferences = neverTranslateSitesList, + onItemClick = { + findNavController().navigate( + NeverTranslateSitesPreferenceFragmentDirections + .actionNeverTranslateSitePreferenceToNeverTranslateSiteDialogPreference( + neverTranslateSiteUrl = it, + ), + ) + }, + ) + } + } + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index ebcf83e21f..5262cad451 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -35,6 +35,7 @@ import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled import org.mozilla.fenix.components.metrics.MozillaProductDetector import org.mozilla.fenix.components.settings.counterPreference import org.mozilla.fenix.components.settings.featureFlagPreference @@ -862,9 +863,6 @@ class Settings(private val appContext: Context) : PreferencesHolder { return touchExplorationIsEnabled || switchServiceIsEnabled } - private val isTablet: Boolean - get() = appContext.resources.getBoolean(R.bool.tablet) - /** * Indicates if the user has enabled the tab strip feature. */ @@ -873,9 +871,6 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false, ) - val isTabletAndTabStripEnabled: Boolean - get() = isTablet && isTabStripEnabled - var lastKnownMode: BrowsingMode = BrowsingMode.Normal get() { val lastKnownModeWasPrivate = preferences.getBoolean( @@ -944,7 +939,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { ) val toolbarPosition: ToolbarPosition - get() = if (isTabletAndTabStripEnabled) { + get() = if (appContext.isTabStripEnabled()) { ToolbarPosition.TOP } else if (shouldUseBottomToolbar) { ToolbarPosition.BOTTOM @@ -1594,9 +1589,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { /** * Indicates if the recent saved bookmarks functionality should be visible. */ - var showRecentBookmarksFeature by lazyFeatureFlagPreference( - appContext.getPreferenceKey(R.string.pref_key_recent_bookmarks), - default = { homescreenSections[HomeScreenSection.RECENTLY_SAVED] == true }, + var showBookmarksHomeFeature by lazyFeatureFlagPreference( + appContext.getPreferenceKey(R.string.pref_key_customization_bookmarks), + default = { homescreenSections[HomeScreenSection.BOOKMARKS] == true }, featureFlag = true, ) @@ -2004,6 +1999,12 @@ class Settings(private val appContext: Context) : PreferencesHolder { featureFlag = FeatureFlags.completeToolbarRedesignEnabled, ) + /** + * Indicates if the feature to close synced tabs is enabled. + */ + val enableCloseSyncedTabs: Boolean + get() = FxNimbus.features.remoteTabManagement.value().closeTabsEnabled + /** * Returns the height of the bottom toolbar. * @@ -2033,7 +2034,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { val isToolbarAtTop = toolbarPosition == ToolbarPosition.TOP val toolbarHeight = appContext.resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) - return if (isToolbarAtTop && includeTabStrip && isTabletAndTabStripEnabled) { + return if (isToolbarAtTop && includeTabStrip) { toolbarHeight + appContext.resources.getDimensionPixelSize(R.dimen.tab_strip_height) } else if (isToolbarAtTop) { toolbarHeight diff --git a/mobile/android/fenix/app/src/main/res/drawable/microsurvey_success.xml b/mobile/android/fenix/app/src/main/res/drawable/microsurvey_success.xml new file mode 100644 index 0000000000..2dc8041181 --- /dev/null +++ b/mobile/android/fenix/app/src/main/res/drawable/microsurvey_success.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/fenix/app/src/main/res/layout/fragment_address_editor.xml b/mobile/android/fenix/app/src/main/res/layout/fragment_address_editor.xml index 4f6e4e14d7..2b02b16a4f 100644 --- a/mobile/android/fenix/app/src/main/res/layout/fragment_address_editor.xml +++ b/mobile/android/fenix/app/src/main/res/layout/fragment_address_editor.xml @@ -354,7 +354,7 @@ android:text="@string/addressess_delete_address_button" android:textAlignment="center" android:textAllCaps="false" - android:textColor="@color/fx_mobile_text_color_warning" + android:textColor="@color/fx_mobile_text_color_critical" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/email_layout" app:layout_constraintStart_toStartOf="parent" /> diff --git a/mobile/android/fenix/app/src/main/res/layout/fragment_browser.xml b/mobile/android/fenix/app/src/main/res/layout/fragment_browser.xml index b61d82c1b9..43794b80d5 100644 --- a/mobile/android/fenix/app/src/main/res/layout/fragment_browser.xml +++ b/mobile/android/fenix/app/src/main/res/layout/fragment_browser.xml @@ -45,7 +45,7 @@ android:clickable="true" android:focusable="true" android:visibility="gone" - app:findInPageNoMatchesTextColor="?attr/textWarning" + app:findInPageNoMatchesTextColor="?attr/textCritical" app:findInPageButtonsTint="?attr/textPrimary" app:findInPageResultCountTextColor="?attr/textPrimary" /> @@ -78,6 +78,8 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:visibility="gone" + android:clickable="true" + android:focusable="true" android:elevation="@dimen/browser_fragment_toolbar_elevation"/> diff --git a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml index f33313ff0d..1e59de2f0b 100644 --- a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml +++ b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml @@ -118,6 +118,14 @@ + + @@ -208,6 +216,13 @@ android:name="scrollToCollection" android:defaultValue="false" app:argType="boolean" /> + + app:destination="@id/translationsDialogFragment" /> + + - + - - - - - + android:name="translationsDialogAccessPoint" + android:defaultValue="Translations" + app:argType="org.mozilla.fenix.translations.TranslationsDialogAccessPoint" /> + + + + - + android:name="org.mozilla.fenix.translations.preferences.nevertranslatesite.NeverTranslateSitesPreferenceFragment"> + + android:name="org.mozilla.fenix.components.menu.MenuDialogFragment"> + + + diff --git a/mobile/android/fenix/app/src/main/res/raw/initial_experiments.json b/mobile/android/fenix/app/src/main/res/raw/initial_experiments.json index f0efae5b6d..d67e48ce88 100644 --- a/mobile/android/fenix/app/src/main/res/raw/initial_experiments.json +++ b/mobile/android/fenix/app/src/main/res/raw/initial_experiments.json @@ -68,7 +68,7 @@ "channel": "release", "userFacingName": "Android Onboarding - Remove Sync Card", "userFacingDescription": "Mobile Onboarding experiment", - "isEnrollmentPaused": false, + "isEnrollmentPaused": true, "isRollout": false, "bucketConfig": { "randomizationUnit": "nimbus_id", @@ -160,7 +160,7 @@ ], "targeting": "((is_already_enrolled) || ((isFirstRun == 'true') && (app_version|versionCompare('126.!') >= 0) && (language in ['en'])))", "startDate": "2024-04-30", - "enrollmentEndDate": null, + "enrollmentEndDate": "2024-05-30", "endDate": null, "proposedDuration": 42, "proposedEnrollment": 14, @@ -169,111 +169,6 @@ "localizations": null, "locales": null, "publishedDate": "2024-04-30T20:18:06.070479Z" - }, - { - "schemaVersion": "1.12.0", - "slug": "splash-screen-max-duration-test-lower-times", - "id": "splash-screen-max-duration-test-lower-times", - "arguments": {}, - "application": "org.mozilla.firefox", - "appName": "fenix", - "appId": "org.mozilla.firefox", - "channel": "release", - "userFacingName": "Splash screen max duration test - lower times", - "userFacingDescription": "Testing a splashscreen on app launch.", - "isEnrollmentPaused": true, - "isRollout": false, - "bucketConfig": { - "randomizationUnit": "nimbus_id", - "namespace": "fenix-splash-screen-release-3", - "start": 0, - "count": 10000, - "total": 10000 - }, - "featureIds": [ - "splash-screen" - ], - "probeSets": [], - "outcomes": [ - { - "slug": "onboarding", - "priority": "primary" - }, - { - "slug": "default-browser", - "priority": "primary" - } - ], - "branches": [ - { - "slug": "control", - "ratio": 1, - "feature": { - "featureId": "this-is-included-for-mobile-pre-96-support", - "enabled": false, - "value": {} - }, - "features": [ - { - "featureId": "splash-screen", - "enabled": true, - "value": { - "enabled": true, - "maximum_duration_ms": 0 - } - } - ] - }, - { - "slug": "treatment-a", - "ratio": 1, - "feature": { - "featureId": "this-is-included-for-mobile-pre-96-support", - "enabled": false, - "value": {} - }, - "features": [ - { - "featureId": "splash-screen", - "enabled": true, - "value": { - "enabled": true, - "maximum_duration_ms": 1750 - } - } - ] - }, - { - "slug": "treatment-b", - "ratio": 1, - "feature": { - "featureId": "this-is-included-for-mobile-pre-96-support", - "enabled": false, - "value": {} - }, - "features": [ - { - "featureId": "splash-screen", - "enabled": true, - "value": { - "enabled": true, - "maximum_duration_ms": 2500 - } - } - ] - } - ], - "targeting": "((is_already_enrolled) || ((isFirstRun == 'true') && (app_version|versionCompare('124.!') >= 0) && (region in ['AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'])))", - "startDate": "2024-03-13", - "enrollmentEndDate": "2024-04-03", - "endDate": null, - "proposedDuration": 56, - "proposedEnrollment": 28, - "referenceBranch": "control", - "featureValidationOptOut": false, - "localizations": null, - "locales": null, - "publishedDate": "2024-03-13T15:04:41.938148Z" } ] } diff --git a/mobile/android/fenix/app/src/main/res/values-azb/strings.xml b/mobile/android/fenix/app/src/main/res/values-azb/strings.xml index 38bae805ff..1be1d508e8 100644 --- a/mobile/android/fenix/app/src/main/res/values-azb/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-azb/strings.xml @@ -58,13 +58,22 @@ - سون ساخلانان‌لار + سون ساخلانان‌لار - بوتون ساخلانمیش بوکمارک‌لاری گؤستر + بوتون ساخلانمیش بوکمارک‌لاری گؤستر - قالدیر + قالدیر + + + + بوکمارک‌لار + + + بوتون بوکمارک‌لاری گؤستر + + قالدیر %1$s موزیلا طرفیندن دوزه‌دیلمیش. @@ -210,6 +219,10 @@ تاخیلان‌لار اوزانتی‌لار + + اوزانتی‌ْ‌لار مودیریتی + + داها چوْخ اوزانتیْ گؤر حساب بیلگی‌لری @@ -230,6 +243,8 @@ معمولی تاغدا آچ آناصفحه‌یه آرتیر + + آنا اکرانا آرتیر… قوْش @@ -238,12 +253,18 @@ صفحه‌ده تاپ + + یارپاقدا آختار… صفحه‌نی ترجومه ائله + مجموعه‌‌ده ساخلا… + مجموعه‌ده ساخلا پایلاش + + پایلاش… %1$s ایله آچ @@ -287,10 +308,33 @@ The first parameter is the name of the app defined in app_name (for example: Fenix)--> %1$s-دا یئنی + + دسکتاپ سایتا گئچ + + آلت‌لر ساخلا + + بو صفحه‌نی بوکمارک ائله + + بوکمارک دوز‌ه‌لیشی + + PDF اوْلاراق ساخلا… + + اوْخوجو گؤرونوشونو آچ + + اوْخوجو گؤرونوشونو باغلا + + صفحه‌نی ترجومه ائله… + + + %1$s دیلینه چئویر + + پرینت… + بوردا هئچ اوزانتی یوخ @@ -391,8 +435,6 @@ فایرفاکس گیزللیک بیلدیریمی - - گیزلیلیک بیلدیریمیزده آرتیق بیلین سیزی قوروماغی سئویریک دیل + + ترجومه + + ترجومه‌لر دیتا سئچیمی @@ -672,8 +718,11 @@ ایجازه وئریلمه‌دی - - سایتی سیل + + + لازیم + + گؤیوللو بوتون سایتلار اوچون ایجازه وئر @@ -696,7 +745,7 @@ قاباقکی تاغ‌لارا قاییت - سوْن بوکمارک‌لار + سوْن بوکمارک‌لار سون باخیلان‌لار @@ -780,8 +829,6 @@ بوُکمارک‌لار - - گیریش‌لر رمز‌لر @@ -810,8 +857,6 @@ The first parameter is the application name, the second is the device manufacturer name and the third is the device model. --> %1$s - %2$s %3$s - - اعتباری کارتلار اؤدمه آپاری‌ْلاری diff --git a/mobile/android/fenix/app/src/main/res/values-be/strings.xml b/mobile/android/fenix/app/src/main/res/values-be/strings.xml index af5728eb07..0c182fa6af 100644 --- a/mobile/android/fenix/app/src/main/res/values-be/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-be/strings.xml @@ -48,12 +48,20 @@ - Нядаўна захаваныя + Нядаўна захаваныя - Паказаць усе захаваныя закладкі + Паказаць усе захаваныя закладкі - Выдаліць + Выдаліць + + + + Закладкі + + Паказаць усе закладкі + + Выдаліць %1$s распрацаваны Mozilla. @@ -141,8 +149,10 @@ Прыватная картка - - Хуткі доступ да пароляў + + Паролі + + Хуткі доступ да пароляў @@ -186,6 +196,10 @@ Дадаткі Пашырэнні + + Кіраваць пашырэннямі + + Адкрыйце для сябе іншыя пашырэнні Ваш уліковы запіс @@ -204,20 +218,28 @@ Адкрыць у звычайнай картцы Дадаць на хатні экран + + Дадаць на хатні экран… Усталяваць Сінхранізаваць ізноў Знайсці на старонцы + + Знайсці на старонцы… Перакласці старонку + Захаваць у калекцыі… + Захаваць у калекцыі Падзяліцца + + Падзяліцца… Адкрыць у %1$s @@ -246,7 +268,50 @@ Уладкаваць хатнюю старонку - Увайсці + Увайсці + + + Сінхранізуйце паролі, карткі і іншае + + Увайдзіце зноў, каб сінхранізаваць + + Сінхранізацыя прыпынена + + Прыватная картка + + Паролі + + Новае ў %1$s + + Пераключыцца на версію для ПК + + Прылады + + Захаваць + + + Дадаць закладку на старонку + + Рэдагаваць закладку + + Захаваць як PDF… + + Уключыць рэжым чытання + + Выключыць рэжым чытання + + Перакласці старонку… + + Перакладзена на %1$s + + Друкаваць… + + + + Тут няма пашырэнняў @@ -340,13 +405,13 @@ Паведамленне аб прыватнасці Firefox - - Даведайцеся больш у нашым паведамленні аб прыватнасці Нам падабаецца забяспечваць вашу бяспеку Даведайцеся, чаму мільёны людзей любяць Firefox + + Бяспечнае агляданне з вялікім выбарам Наш браўзер, падтрыманы некамерцыйнай арганізацыяй, дапамагае не даваць кампаніям таемна сачыць за вамі ў сеціве. @@ -549,6 +614,10 @@ Падключыцеся, каб аднавіць сінхранізацыю Мова + + Пераклад + + Пераклады Выбар дадзеных @@ -605,6 +674,8 @@ Пашырэнні Усталяваць дадатак з файла + + Усталяваць пашырэнне з файла Абвесткі @@ -613,6 +684,14 @@ Не дазволена + + + Абавязкова + + Неабавязкова + + Дазволіць для ўсіх сайтаў + Уласная калекцыя дадаткаў @@ -632,7 +711,9 @@ Пераход назад да - Нядаўнія закладкі + Нядаўнія закладкі + + Закладкі Нядаўна наведаныя @@ -684,6 +765,8 @@ Даступныя новыя дадаткі + + Даступныя новыя пашырэнні Азнаёмцеся з больш за 100 новых пашырэнняў, якія дазваляюць зрабіць Firefox вашым уласным. @@ -712,8 +795,6 @@ Гісторыю Закладкі - - Лагіны Паролі @@ -740,8 +821,6 @@ and the third is the device model. --> %1$s на %2$s %3$s - - Крэдытныя карты Спосабы аплаты @@ -1697,13 +1776,9 @@ Вы можаце лёгка дадаць гэты вэб-сайт на хатні экран вашай прылады, каб мець да яго імгненны доступ і аглядаць хутчэй, нібыта гэта асобная праграма. - - Лагіны і паролі Паролі - Захаванне лагінаў і пароляў - Захоўваць паролі Пытаць пра захаванне @@ -1718,41 +1793,23 @@ Аўтазапаўненне ў іншых праграмах Запаўняць імёны карыстальнікаў і паролі ў іншых праграмах на вашай прыладзе. - - Дадаць лагін Дадаць пароль - - Сінхранізацыя лагінаў Сінхранізаваць паролі - - Сінхранізаваць лагіны паміж прыладамі Сінхранізаваць паролі паміж прыладамі - - Захаваныя лагіны Захаваныя паролі - - Лагіны, якія вы захаваеце альбо сінхранізуеце праз %s, з’явяцца тут. - - Даведацца больш пра сінхранізацыю. Даведайцеся больш пра сінхранізацыю Выключэнні - - Не захаваныя лагіны і паролі з’явяцца тут. - - Лагіны і паролі не будуць захаваны для гэтых сайтаў. Выдаліць усе выключэнні - - Шукаць лагіны Пошук пароляў @@ -1782,18 +1839,12 @@ Паказаць пароль Схаваць пароль - - Разблакуйце, каб пабачыць захаваныя лагіны Разблакуйце, каб пабачыць захаваныя паролі - - Абараніце свае лагіны і паролі Абараніце захаваныя паролі - Наладзьце графічны ключ, пін ці пароль для блакавання прылады, каб абараніць захаваныя лагіны і паролі ад крадзяжу, калі Вашай прыладай завалодае хтосьці іншы. - Наладзьце графічны ключ, пін або пароль для блакавання прылады, каб абараніць захаваныя паролі, калі хтосьці іншы атрымае доступ да вашай прылады. Пазней @@ -1810,9 +1861,6 @@ Апошняе выкарыстанне - - Меню сартавання лагінаў - Меню сартавання пароляў @@ -1821,27 +1869,17 @@ Аўтазапаўненне Адрасы - - Крэдытныя карты Спосабы аплаты - Захоўваць і аўтаматычна запаўняць карты - Захоўваць і запаўняць спосабы аплаты - - Дадзеныя зашыфраваны Сінхранізаваць карты паміж прыладамі Сінхранізаваць карты - - Дадаць крэдытную карту Дадаць карту - - Кіраванне захаванымі картамі Кіраваць картамі @@ -1849,13 +1887,9 @@ Дадаць адрас Кіраваць адрасамі - - Захоўваць і аўтаматычна запаўняць адрасы Захоўваць і запаўняць адрасы - - Уключаць звесткі, такія як нумары, адрасы электроннай пошты і дастаўкі Уключае нумары тэлефонаў і адрасы электроннай пошты @@ -1880,8 +1914,6 @@ Выдаліць карту - Вы ўпэўнены, што жадаеце выдаліць гэту крэдытную карту? - Выдаліць карту? Выдаліць @@ -1894,23 +1926,14 @@ Захаваныя карты - - Калі ласка, увядзіце сапраўдны нумар крэдытнай карты - Увядзіце сапраўдны нумар карты - - Калі ласка, запоўніце гэтае поле Дадайце імя Разблакуйце, каб пабачыць захаваныя карты - Абараніце свае крэдытныя карты - Абараніце захаваныя спосабы аплаты - - Наладзьце графічны ключ, пін або пароль для блакавання прылады, каб абараніць захаваныя крэдытныя карты, калі хтось іншы атрымае доступ да вашай прылады. Наладзіць зараз @@ -1918,8 +1941,6 @@ Разблакуйце сваю прыладу - - Разблакуйце, каб выкарыстаць захаваную інфармацыю крэдытнай карты Разблакуйце, каб выкарыстаць захаваныя спосабы аплаты @@ -1929,12 +1950,6 @@ Змяніць адрас Кіраваць адрасамі - - Імя - - Імя па бацьку - - Прозвішча Назва @@ -1960,8 +1975,6 @@ Выдаліць адрас - - Вы ўпэўнены, што жадаеце выдаліць гэты адрас? Выдаліць гэты адрас? @@ -2066,49 +2079,29 @@ Выдаліць Змяніць - - Вы сапраўды хочаце выдаліць гэтае лагін? Вы ўпэўнены, што хочаце выдаліць гэты пароль? Выдаліць Адмяніць - - Налады лагіна Параметры пароля - - Тэкставае поле для рэдагавання вэб-адраса для ўваходу ў сістэму. Тэкставае поле для рэдагавання адраса сайта. - - Тэкставае поле для рэдагавання імені карыстальніка для ўваходу ў сістэму. Тэкставае поле для рэдагавання імя карыстальніка. - Тэкставае поле для рэдагавання пароля для ўваходу ў сістэму. - Тэкставае поле для рэдагавання пароля. - - Захаваць змены ва ўваходных даных. Захаваць змены. - - Рэдагаванне Змяніць пароль - - Дадаць новы лагін Дадаць пароль - - Патрабуецца пароль Увядзіце пароль - Патрабуецца імя карыстальніка - Увядзіце імя карыстальніка Патрабуецца імя хоста @@ -2474,7 +2467,7 @@ На жаль, мы пакуль не падтрымліваем %1$s. - Падрабязней + Падрабязней @@ -2490,7 +2483,9 @@ - Параметры перакладу + Параметры перакладу + + Параметры перакладу Заўсёды прапаноўваць пераклад @@ -2608,12 +2603,14 @@ Інструменты адладкі Перайсці назад + + Інструменты картак Колькасць картак - Дзейныя + Дзейныя Неактыўныя @@ -2630,4 +2627,35 @@ Дадаць да неактыўных картак Дадаць да прыватных картак - + + + + + Працягнуць + + Паведамленне аб прыватнасці + + Падаць + + Закрыць + + Дзякуй за ваш водгук! + + Вельмі задаволены + + Задаволены + + Нейтральны + + Незадаволены + + Вельмі незадаволены + + + + Лагіны + + Бягучы дамен: %s + + Дадаць несапраўдны лагін для гэтага дамена + diff --git a/mobile/android/fenix/app/src/main/res/values-bg/strings.xml b/mobile/android/fenix/app/src/main/res/values-bg/strings.xml index 63fe480125..721c694545 100644 --- a/mobile/android/fenix/app/src/main/res/values-bg/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-bg/strings.xml @@ -201,6 +201,10 @@ Добавки Разширения + + Управление на добавки + + Открийте още разширения Информация за профила @@ -219,6 +223,8 @@ Отваряне в обикновен раздел Добавяне към екрана + + Добавяне към началния екран… Инсталиране @@ -230,9 +236,13 @@ Превеждане на страницатa + Запазване в списък… + Добавяне към списък Споделяне + + Споделяне… Отваряне в %1$s @@ -287,6 +297,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Запазване + + Отбележете тази страница + + Промяна на отметка + + Запазване като PDF… + + Включете изгледа за четене + + Изключете изгледа за четене + + Превеждане на страницата… + + Преведено на %1$s + + Отпечатване… + Няма разширения @@ -383,8 +411,6 @@ бележка за поверителността на Firefox - - Научете повече в нашата политика за поверителност Обичаме да ви пазим в безопасност Език - Превод + Превод + + Преводи Избор на данни @@ -664,10 +692,6 @@ Задължително По желание - - Четене и промяна на данни на уебсайта - - Изтриване на уебсайт Разрешено за всички сайтове @@ -793,8 +817,6 @@ История Отметки - - Регистрации Пароли @@ -820,8 +842,6 @@ and the third is the device model. --> %1$s на %2$s %3$s - - Банкови карти Начини на плащане @@ -837,6 +857,14 @@ Раздел от %s + + + %1$s затворени раздела: %2$d + + Преглед на наскоро затворените раздели + Изключения @@ -1776,13 +1804,9 @@ Можете лесно да добавите тази страница към началния екран, за да имате бърз достъп до нея, подобно на приложение. - - Регистрации и пароли Пароли - Запазване на регистрации и пароли - Запазване на пароли Питане за запазване @@ -1797,47 +1821,29 @@ Попълване в други приложения Попълват се потребителски имена и пароли в други приложения на устройството. - - Добавяне на регистрация Добавяне на парола - - Синхронизиране на регистрации Синхронизиране на пароли - - Синхронизиране на регистрации между устройства Синхронизиране на пароли между устройства - - Запазени регистрации Запазени пароли - Тук се показват нещата, които запазвате или синхронизирате във %s. - Паролите, които запазите или синхронизирате с %s ще бъдат изброени тук. Всички запазени пароли са шифровани. - - Научете повече за Sync. Научете повече за синхронизирането Изключения - - Тук се показват регистрации и пароли, които не са запазени. %s няма да запазва пароли за сайтове, изброени тук. - - Следните страници няма да запазват регистрации и пароли. %s няма да запази пароли за тези сайтове. Изтриване на всички - - Търсене на регистрация Търсене на пароли @@ -1867,17 +1873,11 @@ Показване на парола Скриване на парола - - Отключете, за да видите запазените регистрации Отключете, за да видите запазените пароли - Защитете вашите данни за вход - Защитете запазените пароли - Настройте фигура, PIN или парола за отключване на устройството, за да защитите запазените регистрации и пароли, в случай че някой друг има достъп до него. - Настройте фигура, PIN или парола за отключване на устройството, за да защитите запазените пароли от достъп, ако някой друг има вашето устройство. По-късно @@ -1893,8 +1893,6 @@ Име (А-Я) Последно използване - - Меню за сортиране на регистрации Меню за сортиране на пароли @@ -1904,28 +1902,18 @@ Автоматично попълване Адреси - - Банкови карти Начини на плащане - Запазване и автоматично попълване на карти - Запазване и попълване на начини на плащане - - Данните са криптирани %s шифрова всички запазени от вас начини на плащане Синхронизиране на карти между устройства Синхронизиране на карти - - Добавяне на карта Добавяне на карта - - Управление на карти Управление на карти @@ -1934,12 +1922,8 @@ Управление на адреси - - Запазване и попълване на адреси Запазване и попълване на адреси - - Включително номера, електронни адреси и адреси за доставка Включва телефонни номера и имейл адреси @@ -1963,8 +1947,6 @@ Изтриване на картата - Сигурни ли сте, че искате да премахнете тази банкова карта? - Изтриване на картата? Премахване @@ -1976,23 +1958,15 @@ Отказ Запазени карти - - Моля, въведете валиден номер на банкова карта Въведете валиден номер на карта - - Попълнете полето Добавяне на име Отключете, за да видите запазените карти - Защитете банковите си карти - Защитете запазените начини на плащане - Настройте фигура, PIN или парола за отключване на устройството, за да защитите запазените банкови карти, в случай че някой друг има достъп до него. - Настройте фигура, PIN или парола за отключване на устройството, за да защитите запазените методи на плащане от достъп, ако някой друг има вашето устройство. Настройване @@ -2001,9 +1975,6 @@ Отключете устройството си - - Отключете, за да използвате запазената банкова карта - Отключете, за да използвате запазените методи на плащане @@ -2012,13 +1983,7 @@ Промяна на адрес Управление на адреси - - Първо име - - Презиме - - Фамилия Име @@ -2045,8 +2010,6 @@ Премахване на адреса - Сигурни ли сте, че искате да премахнете този адрес? - Изтриване на този адрес? Премахване @@ -2146,49 +2109,29 @@ Изтриване Редактиране - - Сигурни ли сте, че искате да изтриете тези данни за вход? Сигурни ли сте, че искате да изтриете тази парола? Изтриване Отказ - - Настройки за вход Опции за парола - - Текстовото поле за редактиране на адреса на регистрация. Текстовото поле за редактиране на адреса на уебсайта. - - Текстовото поле за редактиране на потребителското име на регистрация. Текстовото поле за редактиране на потребителското име. - Текстовото поле за редактиране на паролата на регистрация. - Текстовото поле за редактиране на паролата. - - Запазване на промените на регистрацията. Запазване на промените. - - Редактиране Редактиране на паролата - - Добавете регистрация Добавяне на парола - - Паролата е задължителна Въведете парола - Потребителското име е задължително - Въведете потребителско име Името на хоста е задължително @@ -2592,6 +2535,9 @@ Затваряне на листа Преводи + + Някои настройки временно не са налични. + Преводи @@ -2614,6 +2560,9 @@ Изберете език, който да управлявате „винаги превежда“ и „never translate“. + + Езиците не можаха да бъдат заредени. Моля, проверете отново по-късно. + Предложение за превод (по подразбиране) @@ -2636,6 +2585,8 @@ Премахване на %1$s + + Сайтовете не можаха да бъдат заредени. Моля, проверете отново по-късно. Изтриване на %1$s? @@ -2713,13 +2664,18 @@ Връщане назад + + Отваряне на чекмеджето за отстраняване на грешки + Инструменти за раздели Брой на разделите - Включен + Включен + + Включен Изключени @@ -2730,6 +2686,16 @@ Инструмент за създаване на раздели Количество на разделите за създаване + + Текстовото поле е празно + + Моля, въведете само положителни цели числа + + Моля, въведете число, по-голямо от нула + + Надвишен е максималният брой раздели (%1$s), които могат да бъдат генерирани с една операция Добавяне към активните раздели @@ -2746,11 +2712,11 @@ Политика на поверителност - Изпращане + Изпращане - Затваряне + Затваряне - Благодарим ви за отзива! + Благодарим ви за отзива! Много доволен @@ -2762,6 +2728,14 @@ Много недоволен + + + Отваряне на анкета + + Затваряне на анкетата + + Затваряне + Регистрации diff --git a/mobile/android/fenix/app/src/main/res/values-br/strings.xml b/mobile/android/fenix/app/src/main/res/values-br/strings.xml index 053dc5cc07..4b32c7a51c 100644 --- a/mobile/android/fenix/app/src/main/res/values-br/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-br/strings.xml @@ -46,11 +46,19 @@ - Enrollet nevez ’zo + Enrollet nevez ’zo - Diskouez an holl sinedoù enrollet + Diskouez an holl sinedoù enrollet - Dilemel + Dilemel + + + + Sinedoù + + Diskouez an holl sinedoù + + Dilemel Gant Mozilla eo produet %1$s. @@ -100,6 +108,11 @@ Gouzout hiroc’h a-zivout ar gwarez toupinoù hollek + + + Stokit amañ da gregiñ un estez prevez nevez. Dilemel a raio ho roll istor, ho toupinoù — pep tra. + + Ezhomm a zo haeziñ ar c’hamera. Kit e Arventennoù Android, stokit war an aotreoù ha stokit war aotren. @@ -185,6 +198,10 @@ Askouezhioù Askouezhioù + + Merañ an askouezhioù + + Dizoloit muioc’h a askouezhioù Titouroù ar gont @@ -203,18 +220,26 @@ Digeriñ en un ivinell ordinal Ouzhpennañ dʼar skramm degemer + + Ouzhpennañ d\'ar skramm degemer… Staliañ Adgoubredañ Kavout er bajennad + + Kavout er bajennad… Treiñ ar bajenn + Enrollañ en dastumad… + Enrollañ en dastumad Rannañ + + Rannañ… Digeriñ e %1$s @@ -243,11 +268,46 @@ Kennaskañ + + Goubredit gerioù-tremen, ivinelloù ha muioc’h c’hoazh + + Kennaskañ en-dro evit goubredañ + + Ehanet eo ar goubredañ Ivinell prevez nevez Gerioù-tremen + + Nevez e %1$s + + Mont d\'ar stumm evit an urzhiataer + + Ostilhoù + + Enrollañ + + Lakaat ur sined war ar bajenn-mañ + + Kemmañ ar sined + + Enrollañ evel PDF… + + Enaouiñ ar mod lenn + + Kuitaat ar mod lenn + + + Treiñ ar bajenn… + + Troet e %1$s + + Moullañ… + Askouezh ebet da gaout amañ @@ -262,6 +322,11 @@ Treiñ ar bajenn + + Pajenn troet eus %1$s betek %2$s. + Yezh dibabet @@ -334,8 +399,24 @@ Ket bremañ + + + Politikerezh prevezded Firefox Ho surentez a zo talvoudus deomp + + Dizoloit perak e plij Firefox da vilionoù a dud + + Merdeiñ diogel ha muioc\'h a zibaboù + + Hor merdeer harpet gant un aozadur hep gounidoù a sikour da herzel an embregerezhioù d\'ho heuliañ pep lec\'h er web. + + Ouzhpenn 100 milion a dud a warez o buhez prevez en ur zibab ur merdeer a zo harpet gant un aozadur hep gounidoù. + + Heulierien anavezet? Stanket diouzhtu. Askouezhioù? Esaeit 700 anezho. PDFoù? Hol lenner enkorfet a lako anezho aes da verañ. + + Hor merdeer harpet gant un aozadur hep gounidoù a sikour da herzel an embregerezhioù d\'ho heuliañ pep lec\'h er web.\n\nGouzout hiroc’h en hor reolennoù a-fet buhez prevez. evezhiadennoù a-fet buhez prevez @@ -343,6 +424,11 @@ Lakaat da verdeer dre ziouer Ket bremañ + + Chomit enrineget pa tremenit eus un trevnad d\'egile + + Pa kennaskit hag e weredekait ar goubredañ eo kreñvoc\'h ho surentez. Fiefox a enrineg ho kerioù-tremen, ho sinedoù hag muioc’h c’hoazh. Kennaskañ @@ -350,11 +436,23 @@ Gant ar rebuzadurioù e c’hallit chom suroc\'h gant Firefox + + Kas ivinelloù etre ho trevnadoù ha dizoloit keweriusterioù prevezded all Firefox. Gweredekaat ar rebuzadurioù Ket bremañ + + Esaeit widjet klask Firefox + + Gant Firefox war ho skramm degemer e c’halloc’h digeriñ ar merdeer a lak ho puhez prevez da gentañ hag a stank an heulierien etre al lec’hiennoù. + + Ouzhpennañ ur widjet FIrefox Ket bremañ @@ -391,6 +489,8 @@ Luskerioù klask Kinnigoù al luskerioù enklask + + Gwellvezioù ar varrenn chomlec’h Barrenn chomlec\'hioù - Alioù Firefox @@ -421,6 +521,10 @@ Mod HTTPS-hepken + + Stanker banniel an toupinoù + + Stanker banniel an toupinoù er merdeiñ prevez Diweredekaet evit al lec’hienn-mañ @@ -437,9 +541,22 @@ Goulenn skor kaset N’eo ket skoret al lec’hienn-mañ c’hoazh + + Gweredekaat stanker banniel an toupinoù evit %1$s? + + Diweredekaat stanker banniel an toupinoù evit %1$s? %1$s n’hall ket nac’hañ ar goulennoù toupinoù war al lec’hienn-mañ. Gallout a rit goulenn e vefe skoret al lec’hienn-mañ en dazont. + + Diweredekaat ha skarzhet e vo an toupinoù hag adkarget al lec’hienn gant %1$s. Digennaskañ a raio ac’hanoc’h ha goullonderiñ a raio ho paner. + + Gweredekaat ha %1$s a glasko nac’hañ bannieloù an toupinoù ent emgefreek war al lec’hienn-mañ. + + %1$s en deus nac’het toupinoù evidoc’h + + Nebeutoc’h a draoù diezhomm, nebeutoc’h a doupinoù ho’ch heuliañ ac’hanoc’h war al lec’hienn-mañ. + Klask kennaskañ ent emgefreek gant ar c’homenad HTTPS evit muioc’h a surentez. @@ -466,6 +583,8 @@ Dafariad kont Mozilla personelaet Dafariad Sync personelaet + + Kemmet eo bet an dafariad kontoù Mozilla/Sync. Serriñ a raio an arload evit kemer ar c\'hemmoù e kont… Kont @@ -486,6 +605,10 @@ Adkennaskit evit kendercʼhel gant ar goubredañ Yezh + + Troidigezh + + Troidigezhioù Dibaboù roadennoù @@ -512,9 +635,14 @@ Leuniañ an ereoù ent emgefreek Kinnigoù paeroniet + + Harpit %1$s gant kuzulioù kevelet diskouezet ur wech an amzer Alioù %1$s + + Kaout alioù eus ar web hervez ho klaskoù Digeriñ ereoù en arloadoù @@ -534,8 +662,12 @@ Askouezhioù + + Askouezhioù Staliañ un askouezh diouzh ar restr + + Staliañ an askouezh adalek ur restr Rebuzadurioù @@ -549,8 +681,6 @@ Rediet Diret - - Dilemel al lec’hienn Aotren evit an holl lec’hiennoù @@ -574,7 +704,9 @@ Distreiñ - Ivinelloù nevez + Ivinelloù nevez + + Sinedoù Gweladennet nevez ’zo @@ -634,6 +766,8 @@ Diweredekaet eo an askouezhioù evit ar mare Klask adloc’hañ an askouezhioù + + Klaskit adloc’hañ an askouezhioù Kenderc’hel gant an askouezhioù bet diweredekaet @@ -650,8 +784,6 @@ Roll istor Sinedoù - - Titouroù kennaskañ Gerioù-tremen @@ -679,8 +811,6 @@ and the third is the device model. --> %1$s war %2$s %3$s - - Kartennoù kred Doareoù paeañ @@ -696,6 +826,14 @@ Ivinell eus %s + + + %1$s ivinell serret: %2$d + + Diskouez an ivinelloù bet serret nevez zo + Nemedennoù @@ -1610,13 +1748,9 @@ Gallout a rit ouzhpennañ al lec’hienn-mañ da bennbajenn ho trevnad evit mont war-eeun ha merdeiñ primoc’h evel ma vefe un arload. - - Titouroù kennaskañ Gerioù-tremen - Enrollañ an titouroù kennaskañ - Enrollañ ar gerioù-tremen Goulenn a-raok enrollañ @@ -1632,36 +1766,18 @@ Leuniañ anvioù-implijer ha gerioù-tremen en arloadoù-all war ho trevnad. - - Ouzhpennañ titouroù kennaskañ Ouzhpennañ ur ger-tremen - - Goubredañ an titouroù kennaskañ Goubredañ ar gerioù-tremen - - Goubredañ an titouroù kennaskañ dre an trevnadoù - - Titouroù kennaskañ enrollet Gerioù-tremen enrollet - - An titouroù kennaskañ a enrollit pe c’houbredit e %s a vo diskouezet amañ. - - Gouzout hiroc’h diwar-benn Sync Nemedennoù - - An titouroù kennaskañ n’int ket enrollet a vo diskouezet amañ. - - Ne vo ket enrollet an titouroù kennaskañ evit al lec’hiennoù-mañ. Dilemel an holl nemedennoù - - Klask titouroù kennaskañ Klask er gerioù-tremen @@ -1691,14 +1807,8 @@ Diskouez ar ger-tremen Kuzhat ar ger-tremen - - Dibrennit evit gwelet ho titouroù kennaskañ enrollet - - Diogelit ho titouroù kennaskañ Diogelit ho kerioù-tremen enrollet - - Arventennit ur patrom morailh, ur PIN pe ur ger-tremen evit gwareziñ ho titouroù kennaskañ enrollet da vezañ lennet gant unan bennak all. Diwezhatoc’h @@ -1713,34 +1823,22 @@ Anv (A-Z) Arver diwezhañ - - Lañser rummañ an titouroù kennaskañ Leuniañ ent emgefreek Chomlec’hioù - - Kartennoù kred Doareoù paeañ - Enrollañ ha leuniañ ar c’hartennoù en un doare emgefreek - Enrollañ ha leuniañ an doareoù paeañ - - Rineget eo ar roadennoù Goubredañ ar c’hartennoù kred etre ho trevnadoù Goubredañ ar c\'hartennoù - - Ouzhpennañ ur gartenn gred Ouzhpennañ ur gartenn - - Merañ ar c’hartennoù enrollet Merañ ar c’hartennoù @@ -1749,12 +1847,8 @@ Merañ ar chomlec’hioù - - Enrollañ ha leuniañ ar chomlec’hioù en un doare emgefreek Enrollañ ha leuniañ ar chomlec’hioù - - Enkorfañ a ra titouroù evel niverennoù, posteloù ha chomlec’hioù Ouzhpennañ ur gartenn @@ -1776,8 +1870,6 @@ Dilemel ar gartenn - Ha fellout a ra deocʼh dilemel ar gartenn gred-mañ? - Dilemel ar gartenn? Dilemel @@ -1790,24 +1882,15 @@ Kartennoù enrollet - - Biziatait un niverenn kartenn gred talvoudek mar plij. - Enankit un niverenn gartenn reizh - - Mar plij, leugnit ar maez-mañ Ouzhpennañ un anv Dibrennit da welet ho kartennoù enrollet - - Diogelit ho kartennoù kred Diogelit ho toareoù paeañ enrollet - - Arventennit ur patrom prennañ, ur PIN pe ur ger-tremen evit gwareziñ ho kartennoù kred enrollet ma vez ho trevnad gant unan bennak all. Arventennañ bremañ @@ -1815,21 +1898,12 @@ Dibrennit ho trevnad - - Dibrennit evit implijout titouroù kartenn gred enrollet - Ouzhpennañ ur chomlec’h Embann ur chomlec’h Merañ ar chomlec’hioù - - Añv bihan - - Anv kreiz - - Anv familh Anv @@ -1855,8 +1929,6 @@ Dilemel ar chomlec’h - - Ha sur oc’h e fell deoc’h dilemel ar chomlec’h-mañ? Dilemel ar chomlec’h-mañ? @@ -1954,41 +2026,21 @@ Dilemel Embann - - Sur oc’h e fell deoc’h dilemel an titour-kennaskañ-mañ? Sur ocʼh e fell deocʼh dilemel ar ger-tremen-mañ? Dilemel Nullañ - - Dibarzhioù an titouroù kennaskañ - - Ar vaezienn destenn kemmus evit chomlec’h web an titour kennaskañ. - - Ar vaezienn destenn kemmus evit anv arveriad an titour kennaskañ. - - Ar vaezienn destenn kemmus evit ger-tremen an titour kennaskañ. - - Enrollañ ar c’hemmoù d’an titouroù kennaskañ. Enrollañ ar cʼhemmoù. - - Embann Aozañ ar ger-tremen - - Ouzhpennañ titouroù kennaskañ nevez Ouzhpennañ ur ger-tremen - - Ger-tremen azgoulennet Enankit ur ger-tremen - Anv-implijer rekis - Enankit un anv-implijer Anv an ostiz rekis @@ -2249,6 +2301,9 @@ Ereoù + + Liammoù da gaout + @@ -2262,6 +2317,8 @@ Treiñ e Diwezhatoc’h + + Diskouez an destenn orin Graet @@ -2293,6 +2350,8 @@ Na dreiñ biken %1$s Na dreiñ biken al lec’hienn-mañ + + Arventennoù an droidigezh Diwar-benn an treiñ e-barzh %1$s @@ -2390,7 +2449,9 @@ Niver a ivinelloù - Oberiant + Oberiant + + Oberiant Dizoberiant @@ -2398,4 +2459,16 @@ Hollad + + + + Kenderc’hel + + + Serriñ + + Trugarez da vezañ roet hoc’h ali! + + + Domani a-vremañ: %s diff --git a/mobile/android/fenix/app/src/main/res/values-bs/strings.xml b/mobile/android/fenix/app/src/main/res/values-bs/strings.xml index d32edb20bf..fcd01b9df4 100644 --- a/mobile/android/fenix/app/src/main/res/values-bs/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-bs/strings.xml @@ -47,11 +47,19 @@ - Nedavno sačuvano + Nedavno sačuvano - Prikaži sve sačuvane oznake + Prikaži sve sačuvane oznake - Ukloni + Ukloni + + + + Oznake + + Prikaži sve oznake + + Ukloni %1$s je razvila Mozilla. @@ -185,6 +193,10 @@ Add-oni Ekstenzije + + Upravljanje ekstenzijama + + Otkrijte više ekstenzija Podaci o računu @@ -203,18 +215,26 @@ Otvori u standardnom tabu Dodaj na Početni ekran + + Dodaj na početni ekran… Instaliraj Ponovo sinhronizuj Pronađi na stranici + + Pronađi na stranici… Prevedi stranicu + Sačuvaj u kolekciju… + Spasi u kolekciju Podijeli + + Dijeli… Otvori u %1$s @@ -254,6 +274,34 @@ Lozinke + + Novo u %1$s + + Prebacite se na desktop stranicu + + Alati + + Sačuvaj + + Označi ovu stranicu + + Uredi oznaku + + Sačuvaj kao PDF… + + Uključite Prikaz za čitanje + + Isključite Prikaz za čitanje + + Prevedi stranicu… + + Prevedeno na %1$s + + Štampaj… + Ovdje nema ekstenzija @@ -345,8 +393,6 @@ Firefox obavijest o privatnosti - - Saznajte više u našoj obavijesti o privatnosti Volimo da vas čuvamo Jezik + + Prijevod + + Prijevodi Izbori podataka @@ -625,10 +675,6 @@ Obavezno Opcionalno - - Čitajte i mijenjajte podatke web stranice - - Izbriši web stranicu Dozvoli za sve stranice @@ -657,7 +703,9 @@ Vrati se nazad - Nedavne oznake + Nedavne oznake + + Oznake Nedavno posjećeno @@ -750,8 +798,6 @@ Historiju Zabilješke - - Prijave Lozinke @@ -778,8 +824,6 @@ and the third is the device model. --> %1$s na %2$s %3$s - - Kreditne kartice Načini plaćanja @@ -795,6 +839,14 @@ Tab iz %s + + + %1$s zatvorenih tabova: %2$d + + Pregledajte nedavno zatvorene tabove + Izuzeci @@ -1715,13 +1767,9 @@ Ovu web stranicu možete lahko dodati na Početni ekran uređaja za brz pristup istoj i da surfate brže s iskustvom sličnom aplikaciji. - - Prijave i lozinke Lozinke - Spašene prijave i lozinke - Sačuvaj lozinke Pitaj za spašavanje @@ -1736,47 +1784,29 @@ Automatski ispuni u drugim aplikacijama Unesite korisnička imena i lozinke u druge aplikacije na svom uređaju. - - Dodaj prijavu Dodaj lozinku - - Sinkroniziraj prijave Sinhronizuj lozinke - - Sinhronizirajte prijave na svim uređajima Sinhronizuj lozinke na svim uređajima - - Spašene prijave Sačuvane lozinke - Prijave koje spasite ili sinhronizujete sa %s će se prikazati ovdje. - Lozinke koje sačuvate ili sinhronizujete sa %s će biti navedene ovdje. Sve lozinke koje sačuvate su šifrovane. - - Saznajte više o Syncu. Saznajte više o sinhronizaciji Izuzeci - - Prijave i lozinke koje nisu spašene će biti prikazane ovdje. %s neće sačuvati lozinke za web stranice navedene ovdje. - - Prijave i lozinke neće biti spašene za ove web stranice. %s neće sačuvati lozinke za ove stranice. Obriši sve izuzetke - - Pretraži prijave Traži lozinke @@ -1806,17 +1836,11 @@ Prikaži lozinku Sakrij lozinku - - Otključajte za pregled spašenih prijava Otključajte da vidite sačuvane lozinke - Osigurajte svoje prijave i lozinke - Osigurajte svoje sačuvane lozinke - Podesite pattern za zaključavanje uređaja, PIN ili lozinku da zaštitite vaše spašene prijave i lozinke od pristupa druge osobe koja ima vaš uređaj. - Postavite obrazac za zaključavanje uređaja, PIN ili lozinku da zaštitite svoje sačuvane lozinke od pristupa ako neko drugi ima vaš uređaj. Kasnije @@ -1833,9 +1857,6 @@ Zadnja upotreba - - Sortiraj meni prijava - Meni za sortiranje lozinki @@ -1844,16 +1865,10 @@ Automatsko popunjavanje Adrese - - Kreditne kartice Načini plaćanja - Sačuvajte i automatski popunjavajte kartice - Sačuvaj i popuni načine plaćanja - - Podaci su šifrovani %s šifruje sve načine plaćanja koje sačuvate @@ -1861,24 +1876,16 @@ Sinhronizujte kartice na svim uređajima Sinhronizuj kartice - - Dodaj kreditnu karticu Dodaj karticu - - Upravljaj sačuvanim karticama Upravljajte karticama Dodaj adresu Upravljaj adresama - - Sačuvajte i automatski popunjavajte adrese Sačuvajte i popunite adrese - - Uključite informacije kao što su brojevi, e-mail i adrese za dostavu Uključuje brojeve telefona i email adrese @@ -1902,8 +1909,6 @@ Izbriši karticu - Jeste li sigurni da želite izbrisati ovu kreditnu karticu? - Izbrisati karticu? Izbriši @@ -1915,23 +1920,15 @@ Otkaži Sačuvane kartice - - Unesite važeći broj kreditne kartice Unesite važeći broj kartice - - Molimo popunite ovo polje Dodaj ime Otključajte da vidite svoje sačuvane kartice - Osigurajte svoje kreditne kartice - Osigurajte svoje sačuvane načine plaćanja - Postavite obrazac za zaključavanje uređaja, PIN ili lozinku da zaštitite svoje sačuvane kreditne kartice od pristupa ako neko drugi ima vaš uređaj. - Postavite obrazac za zaključavanje uređaja, PIN ili lozinku da zaštitite sačuvane načine plaćanja od pristupa ako neko drugi ima vaš uređaj. Podesi odmah @@ -1939,8 +1936,6 @@ Kasnije Otključajte svoj uređaj - - Otključajte za korištenje sačuvanih podataka o kreditnoj kartici Otključajte da koristite sačuvane načine plaćanja @@ -1950,12 +1945,6 @@ Uredi adresu Upravljaj adresama - - Ime - - Srednje ime - - Prezime Ime @@ -1981,8 +1970,6 @@ Izbriši adresu - Jeste li sigurni da želite izbrisati ovu adresu? - Izbrisati ovu adresu? Izbriši @@ -2080,50 +2067,30 @@ Obriši Uredi - - Da li ste sigurni da želite obrisati ovu prijavu? Jeste li sigurni da želite izbrisati ovu lozinku? Obriši Otkaži - - Opcije prijave Opcije lozinke - - Izmjenjivo tekstualno polje za web adresu prijave. Tekstualno polje koje se može uređivati za adresu web stranice. - - Izmjenjivo tekstualno polje za korisničko ime prijave. Tekstualno polje koje se može uređivati za korisničko ime. - Izmjenjivo tekstualno polje za lozinku prijave. - Tekstualno polje koje se može uređivati za lozinku. - - Spasite izmjene za prijavu. Sačuvaj promjene. - - Uredi Uredi lozinku - - Dodaj novu prijavu Dodaj lozinku - - Potrebna lozinka Unesite lozinku - Korisničko ime je obavezno - Unesite korisničko ime Ime hosta je obavezno @@ -2468,6 +2435,8 @@ Ne sada Prikaži original + + Originalna neprevedena stranica je učitana Gotovo @@ -2525,6 +2494,9 @@ Zatvorite list Prijevodi + + Neke postavke su privremeno nedostupne. + Prijevodi @@ -2548,6 +2520,9 @@ Odaberite jezik za upravljanje postavkama ”uvijek prevodi“ i ”nikad ne prevodi“. + + Učitavanje jezika nije uspjelo. Molimo provjerite kasnije. + Ponudi prijevod (zadano) @@ -2570,6 +2545,8 @@ Ukloni %1$s + + Nije moguće učitati web stranice. Molimo provjerite kasnije. Obrisati %1$s? @@ -2647,13 +2624,18 @@ Idite nazad + + Otvorite ladicu za otklanjanje grešaka + Tab alati Broj tabova - Aktivno + Aktivno + + Aktivno Neaktivno @@ -2664,6 +2646,16 @@ Alat za kreiranje tabova Količina tabova za kreiranje + + Polje za tekst je prazno + + Unesite samo pozitivne cijele brojeve + + Unesite broj veći od nule + + Premašen je maksimalan broj tabova (%1$s) koji se mogu generisati u jednoj operaciji Dodaj aktivnim tabovima @@ -2671,6 +2663,39 @@ Dodaj privatnim tabovima + + + + Nastavi + + Popunite ovu anketu + + Napomena o privatnosti + + Pošalji + + Zatvori + + Hvala na povratnim informacijama! + + Vrlo zadovoljan + + Zadovoljan + + Neutralno + + Nezadovoljan + + Vrlo nezadovoljan + + + + Otvori anketu + + Zatvori anketu + + Zatvori + Prijave diff --git a/mobile/android/fenix/app/src/main/res/values-cak/strings.xml b/mobile/android/fenix/app/src/main/res/values-cak/strings.xml index 6fb48978ef..74c64172bf 100644 --- a/mobile/android/fenix/app/src/main/res/values-cak/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-cak/strings.xml @@ -53,11 +53,19 @@ - K\'a b\'a keyak + K\'a b\'a keyak - Kek\'ut ronojel ri yakon taq yaketal + Kek\'ut ronojel ri yakon taq yaketal - Tiyuj + Tiyuj + + + + Taq yaketal + + Kek\'ut konojel taq yaketal + + Tiyuj %1$s b\'anon ruma Mozilla. @@ -146,8 +154,10 @@ K’ak’a’ ichinan ruwi’ - - Choj okem pa ewan tzij + + Ewan taq tzij + + Choj okem pa ewan tzij @@ -189,11 +199,13 @@ Tiq\'at - Taq tz\'aqat + Taq tz\'aqat + + Taq k\'amal Rutzijol rub\'i\' taqoya\'l - Majun tz\'aqat wakami + Majun tz\'aqat wakami To\'ïk @@ -210,11 +222,13 @@ Titz\'aqatisäx pa ri Rutikirib\'al ruwa - Tiyak + Tiyak Tixim chik Tikanöx pa ruxaq + + Tikanöx pa ruxaq… Titzalq\'omïx ruxaq @@ -249,6 +263,34 @@ Tawichinaj ri ruxaq atikirib\'al + + Titikirisäx molojri\'ïl + + + Kexim ewan taq tzij, taq ruwi\', chuqa\' ch\'aqa\' chik + + Titikirisäx chik molojri\'ïl richin ximojri\'ïl + + Q\'aton ximojri\'ïl + + K\'ak\'a\' ichinan ruwi\' + + Ewan taq tzij + + K\'ak\'a\' pa %1$s + + Tik\'ex rik\'in ruxaq kematz\'ib\' + + Samajib\'äl + + Tiyak + + + + Majun k\'amal k\'o wawe\' + Rutikirib\'al ruwäch @@ -548,6 +590,8 @@ Tokisäx chik richin nitikirisäx chik ri ximoj Ch\'ab\'äl + + Tzalq\'omanïk Rucha\'ik tzij @@ -598,10 +642,14 @@ Tajin nel pa ri okisanel richin yejikib\'äx taq jaloj… - Taq tz’aqat + Taq tz’aqat + + Taq k\'amal - Tiyak ri tz\'aqat rik\'in ri yakb\'äl + Tiyak ri tz\'aqat rik\'in ri yakb\'äl + + Tiyak k\'amal rik\'in yakb\'äl Taq rutzijol @@ -610,9 +658,13 @@ Man ya\'on ta q\'ij chi re + + + Rajowaxik + - Kimolik ichinan taq Tz\'aqat + Kimolik ichinan taq Tz\'aqat ÜTZ @@ -624,13 +676,13 @@ Rajaw Mol (Winäq ID) - Xjal kimolaj tz\'aqat. Nitz\'apïx chokoy richin yesamajïx ri taq jaloj… + Xjal kimolaj tz\'aqat. Nitz\'apïx chokoy richin yesamajïx ri taq jaloj… Titz\'et chik - K\'ak\'a\' taq yaketal + K\'ak\'a\' taq yaketal K\'a b\'a ketz\'et @@ -676,20 +728,20 @@ - K\'ak\'a\' taq tz\'aqa ewachel wakami + K\'ak\'a\' taq tz\'aqa ewachel wakami Ke\'awila\' +100 k\'ak\'a\' taq k\'amal ri nikiya\' q\'ij chawe\' nawichinaj Firefox. - Kenik\'öx taq tz\'aqat + Kenik\'öx taq tz\'aqat - + - Echupun jumej ri taq tz\'aqat + Echupun jumej ri taq tz\'aqat - Tatojtob\'ej ye\'atzïj chik ri taq tz\'aqat + Tatojtob\'ej ye\'atzïj chik ri taq tz\'aqat - Kesamajïx chik ri chupun taq tz\'aqat + Kesamajïx chik ri chupun taq tz\'aqat @@ -1859,6 +1911,8 @@ Titz\'aqatisäx jun b\'i\'aj Man tiq\'at chik richin ye\'atz\'ët ri tarjeta\' e\'ayakon + + Tab\'ana\' runuk\'ulem jun retal ruq\'atoj okisab\'äl, jun PIN o jun ewan tzij richin nachajij ri kre\'ito\' tarjeta\' e\'ayakon we xa ta k\'o xtichapo ri awokisab\'al. Tib\'an runuk\'ulem wakami @@ -2201,4 +2255,6 @@ Achi\'el: \nhttps://www.google.com/search?q=%s + + diff --git a/mobile/android/fenix/app/src/main/res/values-co/strings.xml b/mobile/android/fenix/app/src/main/res/values-co/strings.xml index 01c3c63d1c..29d8291625 100644 --- a/mobile/android/fenix/app/src/main/res/values-co/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-co/strings.xml @@ -595,7 +595,9 @@ Lingua - Traduzzione + Traduzzione + + Traduzzioni Scelta di dati @@ -2743,7 +2745,7 @@ Numeru d’unghjette - Attive + Attive Inattive diff --git a/mobile/android/fenix/app/src/main/res/values-cs/strings.xml b/mobile/android/fenix/app/src/main/res/values-cs/strings.xml index 2f50ff055d..66fc6aa503 100644 --- a/mobile/android/fenix/app/src/main/res/values-cs/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-cs/strings.xml @@ -204,6 +204,10 @@ Doplňky Rozšíření + + Správa rozšíření + + Objevit další rozšíření Informace o účtu @@ -222,6 +226,8 @@ Otevřít v běžném panelu Přidat na plochu + + Přidat na domovskou obrazovku… Nainstalovat @@ -233,9 +239,13 @@ Přeložit stránku + Uložit do sbírky… + Uložit do sbírky Sdílet + + Sdílet… Otevřít v aplikaci %1$s @@ -290,6 +300,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Uložit + + Přidat stránku do záložek + + Upravit záložku + + Uložit jako PDF… + + Zapnout zobrazení čtečky + + Vypnout zobrazení čtečky + + Přeložit stránku… + + Přeloženo do jazyka %1$s + + Tisk… + Nejsou tu žádná rozšíření @@ -387,8 +415,6 @@ Zásady ochrany osobních údajů - - Další informace naleznete v našich oznámeních o ochraně osobních údajů Rádi vás udržujeme v bezpečí Jazyk - Překladač + Překladač + + Překlady Možnosti hlášení @@ -668,10 +696,6 @@ Vyžadováno Volitelné - - Čtení a změna údajů webových stránek - - Smazat webovou stránku Povolit pro všechny stránky @@ -796,8 +820,6 @@ Historii Záložky - - Přihlašovací údaje Hesla @@ -825,8 +847,6 @@ and the third is the device model. --> %1$s na %2$s %3$s - - Platební karty Platební metody @@ -842,6 +862,14 @@ Panel z %s + + + V aplikaci %1$s bylo zavřeno %2$d panelů + + Zobrazení nedávno zavřených panelů + Výjimky @@ -1782,13 +1810,9 @@ Tuto stránku si můžete snadno přidat na domovskou obrazovku svého zařízení. Budete k ní mít okamžitý přístup a prohlížení bude rychlejší se zážitkem jako v aplikaci. - - Přihlašovací údaje Hesla - Ukládat přihlašovací údaje - Ukládání hesel Před uložením se zeptat @@ -1803,46 +1827,27 @@ Vyplňování a ukládání uživatelských jmen a hesel v dalších aplikacích na vašem zařízení. - - Přidat přihlašovací údaje - Přidat heslo - - Synchronizovat přihlašovací údaje Synchronizovat hesla - - Synchronizovat přihlašovací údaje mezi zařízeními Synchronizovat hesla napříč zařízeními - - Uložené přihlašovací údaje Uložená hesla - Uložené nebo synchronizované údaje aplikace %s se zobrazí tady. - Hesla, která uložíte nebo synchronizujete s aplikací %s, budou uvedena zde. Všechna uložená hesla jsou šifrována. - - Zjistit více o službě Sync. Další informace o synchronizaci Výjimky - - Tady se zobrazí přihlašovací údaje, které se nebudou ukládat. Aplikace %s nebude nikdy ukládat hesla pro stránky uvedené v tomto seznamu. - - Pro následující servery se nebudou přihlašovací údaje ukládat. Aplikace %s nebude nikdy ukládat hesla pro tyto stránky. Odebrat všechny výjimky - - Hledat přihlašovací údaje Hledat v heslech @@ -1871,17 +1876,11 @@ Zobrazit heslo Skrýt heslo - - Odemknout pro zobrazení přihlašovacích údajů Pro zobrazení uložených hesel odemkněte - Zabezpečte své přihlašovací údaje a hesla - Zabezpečte svá uložená hesla - Nastavte si gesto, PIN nebo heslo zámku obrazovky, který ochrání vaše uložené přihlašovací údaje, pokud by se vaše zařízení dostalo do ruky někomu jinému. - Nastavte si gesto, PIN nebo heslo zámku obrazovky, který ochrání vaše uložená hesla, pokud by se vaše zařízení dostalo do ruky někomu jinému. Později @@ -1900,9 +1899,6 @@ Naposledy použito - - Seřadit podle - Nabídka pro řazení hesel @@ -1911,41 +1907,27 @@ Automatické vyplňování Adresy - - Platební karty Platební metody - Ukládat a automaticky vyplňovat karty - Uložení a vyplnění platebních metod - - Data jsou šifrována %s šifruje všechny vaše uložené platební metody Synchronizovat karty napříč zařízeními Synchronizovat platební karty - - Přidat platební kartu Přidat kartu - - Správa uložených karet Spravovat karty Přidat adresu Správa adres - - Ukládat a automaticky vyplňovat adresy Ukládat a vyplňovat adresy - - Zahrnuje informace jako čísla a e-mailové a doručovací adresy Zahrnuje telefonní čísla a e-mailové adresy @@ -1969,8 +1951,6 @@ Odstranit kartu - Opravdu chcete tuto platební kartu smazat? - Odstranit kartu? Smazat @@ -1984,24 +1964,15 @@ Uložené karty - - Zadejte prosím platné číslo platební karty - Zadejte platné číslo karty - - Vyplňte prosím toto pole Zadejte jméno Odemknout pro zobrazení uložených karet - Zabezpečte své platební karty - Zabezpečte vaše uložené platební metody - Nastavte si gesto, PIN nebo heslo zámku obrazovky, který ochrání vaše uložené platební karty, pokud by se vaše zařízení dostalo do ruky někomu jinému. - Nastavením vzoru zámku zařízení, kódu PIN nebo hesla ochráníte uložené platební metody před přístupem, pokud má vaše zařízení někdo jiný. Nastavit @@ -2010,9 +1981,6 @@ Odemkněte zařízení - - Odemkněte pro použití informací o platební kartě - Odemknout pro použití uložených způsobů platby @@ -2021,12 +1989,6 @@ Upravit adresu Správa adres - - Křestní jméno - - Prostřední jméno - - Příjmení Název @@ -2053,8 +2015,6 @@ Smazat adresu - Opravdu chcete tuto adresu smazat? - Smazat tuto adresu? Smazat @@ -2154,49 +2114,29 @@ Odstranit Upravit - - Opravdu chcete tyto přihlašovací údaje odstranit? Opravdu chcete smazat toto heslo? Odstranit Zrušit - - Možnosti přihlášení Možnosti hesel - - Textové pole pro webovou adresu pro přihlašovací údaje. Upravitelné textové pole pro adresu webové stránky. - - Textové pole pro uživatelské jméno z přihlašovacích údajů. Upravitelné textové pole pro uživatelské jméno. - Textové pole pro heslo z přihlašovacích údajů. - Upravitelné textové pole pro heslo. - - Uložit změny přihlašovacích údajů. Uložit změny. - - Upravit Upravit heslo - - Přidat nové přihlašovací údaje Přidat heslo - - Je vyžadováno heslo Zadejte heslo - Uživatelské je povinné - Zadejte uživatelské jméno Adresa serveru je povinná @@ -2603,6 +2543,9 @@ Zavřít Překlady + + Některá nastavení jsou dočasně nedostupná. + Překlady @@ -2626,6 +2569,9 @@ Vyberte jazyk, u kterého chcete spravovat předvolby „vždy překládat“ a „nikdy nepřekládat“. + + Nelze načíst jazyky. Zkuste to prosím později. + Nabídnout překlad (výchozí) @@ -2649,6 +2595,8 @@ Odebrat %1$s + + Nelze načíst stránky. Zkuste to prosím později. Smazat %1$s? @@ -2727,13 +2675,18 @@ Návrat zpět + + Otevřít ladicí panel + Nástroje pro panely Počet panelů - Aktivní + Aktivní + + Aktivní Neaktivní @@ -2744,6 +2697,16 @@ Nástroj na vytváření panelů Počet panelů, které chcete vytvořit + + Textové pole je prázdné + + Zadejte prosím pouze kladná celá čísla + + Zadejte prosím číslo větší než nula + + Byl překročen maximální počet panelů (%1$s), které mohou být vygenerovány v jedné operaci Přidat mezi aktivní panely @@ -2760,11 +2723,11 @@ Zásady ochrany osobních údajů - Odeslat + Odeslat - Zavřít + Zavřít - Děkujeme za vaši zpětnou vazbu! + Děkujeme za vaši zpětnou vazbu! Velmi spokojený @@ -2776,6 +2739,14 @@ Velmi nespokojený + + + Otevře průzkum + + Zavře průzkum + + Zavřít + Přihlašovací údaje diff --git a/mobile/android/fenix/app/src/main/res/values-cy/strings.xml b/mobile/android/fenix/app/src/main/res/values-cy/strings.xml index dda3ca243d..0aac4e2556 100644 --- a/mobile/android/fenix/app/src/main/res/values-cy/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-cy/strings.xml @@ -199,6 +199,10 @@ Ychwanegion Estyniadau + + Rheoli estyniadau + + Darganfod rhagor o estyniadau Manylion cyfrif @@ -217,6 +221,8 @@ Agorwch mewn tab arferol Ychwanegu i’r sgrin Cartref + + Ychwanegu i\'r sgrin Cartref… Gosod @@ -228,9 +234,13 @@ Cyfieithu\'r dudalen + Cadw i gasgliad… + Cadw i Gasgliad Rhannu + + Rhannu… Agor yn %1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Cadw + + Gosod nod tudalen i\'r dudalen hon + + Golygu nod tudalen + + Cadw fel PDF… + + Trowch y Golwg Darllen ymlaen + + Diffodd y Golwg Darllen + + Cyfieithu tudalen… + + Wedi\'i gyfieithu i %1$s + + Argraffu… + Dim estyniadau yma @@ -378,8 +406,6 @@ Hysbysiad preifatrwydd Firefox - - Darllenwch ein hysbysiad preifatrwydd Rydyn ni wrth ein bodd yn eich cadw chi’n ddiogel Iaith - Cyfieithu + Cyfieithu + + Cyfieithu Dewisiadau data @@ -663,10 +691,6 @@ Angenrheidiol Dewisol - - Darllen a newid data gwefan - - Dileu gwefan Caniatáu ar gyfer pob gwefan @@ -792,8 +816,6 @@ Hanes Nodau Tudalen - - Mewngofnodion Cyfrineiriau @@ -820,8 +842,6 @@ and the third is the device model. --> %1$s o %2$s %3$s - - Cardiau credyd Dulliau talu @@ -837,6 +857,14 @@ Tab o %s + + + %1$s tabiau wedi cau: %2$d + + Gweld tabiau a gaewyd yn ddiweddar + Eithriadau @@ -1760,13 +1788,9 @@ Gallwch ychwanegu’r wefan hon yn hawdd i sgrin Cartref eich ffôn i gael mynediad ar unwaith a phori’n gyflymach gyda phrofiad tebyg i ap. - - Mewngofnodion a chyfrineiriau Cyfrineiriau - Cadw mewngofnodion a chyfrineiriau - Cadw cyfrineiriau Gofyn i gadw @@ -1781,47 +1805,28 @@ Llenwi enwau defnyddwyr a chyfrineiriau mewn apiau eraill ar eich dyfais. - - Ychwanegu mewngofnod - Ychwanegu cyfrinair - - Cydweddu mewngofnodion Cydweddu cyfrineiriau - - Cydweddu mewngofnodion ar draws dyfeisiau Cydweddu cyfrineiriau ar draws dyfeisiau - - Mewngofnodion wedi’u cadw Cyfrineiriau wedi\'u cadw - Bydd y mewngofnodi rydych yn eu cadw neu’n cydweddu i %s i’w gweld yma. - Bydd y cyfrineiriau rydych yn eu cadw neu eu cydweddu i %s yn cael eu rhestru yma. Mae\'r holl gyfrineiriau rydych chi\'n eu cadw wedi\'u hamgryptio. - - Dysgu rhagor am Sync. Dysgwch fwy am gydweddu Eithriadau - - Bydd mewngofnodion a chyfrineiriau sydd heb eu cadw i’w gweld yma. Fydd %s ddim yn cadw cyfrineiriau ar gyfer gwefannau sy\'n cael eu rhestru yma. - - Ni fydd mewngofnodion a chyfrineiriau’n cael eu cadw ar gyfer y gwefannau hyn. Fydd %s ddim yn cadw cyfrineiriau ar gyfer y gwefannau hyn. Dileu pob eithriad - - Chwilio mewngofnodion Chwilio cyfrineiriau @@ -1851,17 +1856,11 @@ Dangos cyfrinair Cuddio cyfrinair - - Datgloi i weld eich mewngofnodi wedi’u cadw Datgloi i weld eich cyfrineiriau wedi\'u cadw - Diogelu mewngofnodion a chyfrineiriau - Diogelwch eich cyfrineiriau wedi\'u cadw - Creu patrwm cloi dyfais, PIN, neu gyfrinair i ddiogelu eich mewngofnodion a’ch cyfrineiriau sydd wedi’u cadw rhag i rhywun arall sydd â mynediad i’ch dyfais. - Gosodwch batrwm clo dyfais, PIN, neu gyfrinair i ddiogelu eich cyfrineiriau sydd wedi\'u cadw rhag i rywun arall sydd â\'ch dyfais gael mynediad iddyn nhw. Yn hwyrach @@ -1878,8 +1877,6 @@ Enw (A-Z) Defnyddiwyd Diwethaf - - Trefnu dewislen mewngofnodi Didoli dewislen cyfrineiriau @@ -1889,41 +1886,27 @@ Awtolanw Cyfeiriadau - - Cardiau credyd Dulliau talu - Cadw ac awtolanw cardiau - Cadw a llanw dulliau talu - - Mae data wedi’i amgryptio Mae %s yn amgryptio pob dull talu rydych yn ei gadw Cydweddu cardiau ar draws dyfeisiau Cydweddu cardiau - - Ychwanegu cerdyn credyd Ychwanegu cerdyn - - Rheoli cardiau wedi’u cadw Rheoli cardiau Ychwanegu cyfeiriad Rheoli cyfeiriadau - - Cadw ac awtolanw cyfeiriadau Cadw a llanw cyfeiriadau - - Cynhwyswch fanylion fel rhifau, cyfeiriadau e-bost a chludo Yn cynnwys rhifau ffôn a chyfeiriadau e-bost @@ -1947,8 +1930,6 @@ Dileu cerdyn - Ydych chi’n siŵr eich bod am ddileu’r cerdyn credyd yma? - Dileu cerdyn? Dileu @@ -1962,24 +1943,15 @@ Cardiau wedi’u cadw - - Rhowch rif cerdyn credyd dilys - Rhoi rif cerdyn dilys - - Llanwch y maes yma. Ychwanegu enw Datgloi i weld y cardiau rydych wedi’u cadw - Diogelwch eich cerdyn credyd - Diogelu eich dulliau talu sydd wedi\'u cadw - Gosodwch batrwm cloi dyfais, PIN, neu gyfrinair i ddiogelu eich mewngofnodion a’ch cyfrineiriau sydd wedi’u cadw rhag i rhywun arall sydd â mynediad i’ch dyfais. - Gosodwch batrwm clo dyfais, PIN, neu gyfrinair i ddiogelu eich dulliau talu rhag i rywun arall sydd â\'ch dyfais gael mynediad iddyn nhw. Gosod nawr @@ -1988,9 +1960,6 @@ Datgloi’ch dyfais - - Datglowch i ddefnyddio manylion cerdyn credyd wedi’i storio - Datgloi i ddefnyddio dulliau talu wedi\'u cadw @@ -1999,12 +1968,6 @@ Golygu cyfeiriad Rheoli cyfeiriadau - - Enw Cyntaf - - Enw Canol - - Enw Olaf Enw @@ -2030,8 +1993,6 @@ Dileu cyfeiriad - - Ydych chi’n siŵr eich bod am ddileu’r cyfeiriad hwn? Dileu\'r cyfeiriad hwn? @@ -2130,49 +2091,29 @@ Dileu Golygu - - Ydych chi’n siŵr eich bod eisiau dileu’r mewngofnod? Ydych chi\'n siŵr eich bod am ddileu\'r cyfrinair hwn? Dileu Diddymu - - Dewisiadau mewngofnodi Dewisiadau cyfrineiriau - - Maes testun golygadwy cyfeiriad gwe’r mewngofnodi. Maes testun golygadwy y wefan. - - Maes testun golygadwy enw defnyddiwr y mewngofnodi. Maes testun golygadwy yr enw defnyddiwr. - Maes testun golygadwy cyfrinair y mewngofnodi. - Maes testun golygadwy y cyfrinair. - - Cadw newidiadau i’r mewngofnodi. Cadw newidiadau. - - Golygu Golygu cyfrinair - - Ychwanegu mewngofnod newydd Ychwanegu cyfrinair - - Mae angen cyfrinair Rhowch gyfrinair - Mae angen enw defnyddiwr - Rhoi enw defnyddiwr Mae angen enw gwesteiwr @@ -2580,6 +2521,9 @@ Cau\'r ddalen Cyfieithiadau + + Nid yw rhai gosodiadau ar gael dros dro. + Cyfieithu @@ -2602,6 +2546,9 @@ Dewiswch iaith i reoli dewisiadau ”cyfieithu bob tro“ a ”peidio â chyfieithu“. + + Methu llwytho ieithoedd. Gwiriwch yn ôl yn nes ymlaen. + Cynnig i gyfieithu (rhagosodedig) @@ -2624,6 +2571,8 @@ Tynnu %1$s + + Methu llwytho gwefannau. Gwiriwch yn ôl yn nes ymlaen. Dileu %1$s? @@ -2701,13 +2650,18 @@ Symud nôl + + Agor drôr dadfygio + Offer Tab Cyfrif tab - Gweithredol + Gweithredol + + Gweithredol Anweithredol @@ -2718,6 +2672,16 @@ Teclyn creu tabiau Nifer y tabiau i\'w creu + + Mae maes testun yn wag + + Rhowch gyfanrifau positif yn unig + + Rhowch rif sy\'n fwy na sero + + Wedi mynd y tu hwnt i uchafswm nifer y tabiau (%1$s) y gellir eu cynhyrchu mewn un gweithrediad Ychwanegu at y tabiau gweithredol @@ -2734,11 +2698,11 @@ Hysbysiad Preifatrwydd - Cyflwyno + Cyflwyno - Cau + Cau - Diolch am eich adborth! + Diolch am eich adborth! Bodlon Iawn @@ -2750,6 +2714,14 @@ Anfodlon Iawn + + + Agor arolwg + + Cau\'r arolwg + + Cau + Mewngofnodion diff --git a/mobile/android/fenix/app/src/main/res/values-da/strings.xml b/mobile/android/fenix/app/src/main/res/values-da/strings.xml index 98609735d8..5759e3a0a6 100644 --- a/mobile/android/fenix/app/src/main/res/values-da/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-da/strings.xml @@ -49,12 +49,20 @@ - Gemt for nylig + Gemt for nylig - Vis alle gemte bogmærker + Vis alle gemte bogmærker - Fjern + Fjern + + + + Bogmærker + + Vis alle bogmærker + + Fjern %1$s er lavet af Mozilla. @@ -192,6 +200,10 @@ Tilføjelser Udvidelser + + Håndter udvidelser + + Opdag flere udvidelser Kontooplysninger @@ -210,6 +222,8 @@ Åbn i almindeligt faneblad Føj til startskærm + + Føj til startskærm… Installer @@ -221,9 +235,13 @@ Oversæt side + Gem til samling… + Gem til samling Del + + Del… Åbn i %1$s @@ -276,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Gem + + Bogmærk denne side + + Rediger bogmærke + + Gem som PDF… + + Slå læsevisning til + + Slå læsevisning fra + + Oversæt side… + + Oversat til %1$s + + Udskriv… + Ingen udvidelser her @@ -372,8 +408,6 @@ Privatlivserklæring for Firefox - - Læs mere i vores privatlivserklæring Vi elsker at holde dig sikker Sprog - Oversættelse + Oversættelse + + Oversættelser Valg for data @@ -654,10 +690,6 @@ Påkrævede Valgfrie - - Læse og ændre webstedsdata - - Slet websted Tillad for alle websteder @@ -686,7 +718,9 @@ Hop tilbage til - Seneste bogmærker + Seneste bogmærker + + Bogmærker Besøgt for nylig @@ -780,8 +814,6 @@ Historik Bogmærker - - Logins Adgangskoder @@ -808,8 +840,6 @@ and the third is the device model. --> %1$s på %2$s %3$s - - Betalingskort Betalingsmetoder @@ -825,6 +855,14 @@ Faneblad fra %s + + + %1$s-faneblade lukket: %2$d + + Vis nyligt lukkede faneblade + Undtagelser @@ -1745,13 +1783,9 @@ Du kan nemt føje dette websted til din enheds startskærm for at have hurtig adgang til det og browse hurtigere med en app-lignende oplevelse. - - Logins og adgangskoder Adgangskoder - Gem logins og adgangskoder - Gem adgangskoder Bed om at gemme @@ -1766,47 +1800,28 @@ Udfyld brugernavne og adgangskoder i andre apps på din enhed. - - Tilføj login - Tilføj adgangskode - - Synkroniser logins Synkroniser adgangskoder - - Synkroniser logins på tværs af enheder Synkroniser adgangskoder på tværs af enheder - - Gemte logins Gemte adgangskoder - De logins, du gemmer eller synkroniserer til %s, vises her. - Adgangskoderne, du gemmer i eller synkroniserer med %s vil blive vist her. Alle dine gemte adgangskoder bliver krypteret. - - Læs mere om Sync. Læs mere om synkronisering Undtagelser - - Logins og adgangskoder, der ikke er gemt, vises her. %s gemmer ikke adgangskoder til websteder vist her. - - Logins og adgangskoder vil ikke blive gemt for disse websteder. %s gemmer ikke adgangskoder til disse websteder. Slet alle undtagelser - - Søg efter logins Søg efter adgangskoder @@ -1835,17 +1850,11 @@ Vis adgangskode Skjul adgangskode - - Lås op for at se dine gemte logins Lås op for at se dine gemte adgangskoder - Gør dine logins og adgangskoder sikre - Gør dine gemte adgangskoder sikre - Indstil en pinkode, en adgangskode eller et låsemønster på din enhed for at forhindre, at andre mennesker får adgang til dine gemte logins og adgangskoder, hvis de har adgang til din enhed. - Indstil en pinkode, en adgangskode eller et låsemønster på din enhed for at forhindre, at andre mennesker får adgang til dine gemte adgangskoder, hvis de har adgang til din enhed. Senere @@ -1863,9 +1872,6 @@ Senest brugt - - Sortér menuen logins - Menuen sorter adgangskoder @@ -1874,40 +1880,26 @@ Autofyld Adresser - - Betalingskort Betalingsmetoder - Gem og autofyld betalingskort - Gem og udfyld betalingsmetoder - - Data er krypteret %s krypterer alle betalingsmetoder, du gemmer Synkroniser kort på tværs af enheder Synkroniser kort - - Tilføj betalingskort Tilføj kort - - Håndter gemte kort Håndter kort Tilføj adresse Håndter adresser - - Gem og autofyld adresser Gem og udfyld adresser - - Inkluderer oplysninger såsom telefonnumre, mail- og forsendelsesadresser Inkluderer telefonnumre og mailadresser @@ -1931,8 +1923,6 @@ Slet kort - Er du sikker på, at du vil slette dette betalingskort? - Slet kort? Slet @@ -1946,24 +1936,15 @@ Gemte kort - - Indtast et gyldigt betalingskortnummer - Indtast et gyldigt kortnummer - - Udfyld dette felt Tilføj navn Lås op for at se dine gemte betalingskort - Beskyt dine betalingskort - Gør dine gemte betalingsmetoder sikre - Indstil en pinkode, en adgangskode eller et låsemønster på din enhed for at forhindre, at andre mennesker får adgang til dine gemte betalingskort, hvis de har adgang til din enhed. - Indstil en pinkode, en adgangskode eller et låsemønster på din enhed for at forhindre, at andre mennesker får adgang til dine gemte betalingsmetoder, hvis de har adgang til din enhed. Indstil nu @@ -1971,8 +1952,6 @@ Senere Lås din enhed op - - Lås op for at anvende gemte informationer om betalingskort Lås op for at bruge gemte betalingsmetoder @@ -1982,12 +1961,6 @@ Rediger adresse Håndter adresser - - Fornavn - - Mellemnavn - - Efternavn Navn @@ -2013,8 +1986,6 @@ Slet adresse - Er du sikker på, at du vil slette denne adresse? - Slet denne adresse? Slet @@ -2113,49 +2084,29 @@ Slet Rediger - - Er du sikker på, at du vil slette dette login? Er du sikker på, at du vil slette denne adgangskode? Slet Annuller - - Login-indstillinger Adgangskode-indstillinger - - Det redigerbare tekstfelt for login’ets webadresse. Det redigerbare tekstfelt for webadressen. - - Det redigerbare tekstfelt for login’ets brugernavn. Det redigerbare tekstfelt for brugernavnet. - Det redigerbare tekstfelt for login’ets adgangskode. - Det redigerbare tekstfelt for adgangskoden. - - Gem ændringer til login. Gem ændringer. - - Rediger Rediger adgangskode - - Tilføj nyt login Tilføj adgangskode - - Adgangskode påkrævet Indtast en adgangskode - Brugernavn påkrævet - Indtast et brugernavn Værtsnavn påkrævet @@ -2505,6 +2456,8 @@ Ikke nu Vis oprindelig + + Original uoversat side indlæst Færdig @@ -2562,6 +2515,9 @@ Luk oversættelsesoversigten + + Nogle indstillinger er midlertidigt utilgængelige. + Oversættelser @@ -2584,6 +2540,9 @@ Vælg et sprog for at håndtere indstillingerne ”oversæt altid“ og ”oversæt aldrig“. + + Kunne ikke indlæse sprog. Prøv igen senere. + Tilbyd at oversætte (standard) @@ -2607,6 +2566,8 @@ Fjern %1$s + + Kunne ikke indlæse websteder. Prøv igen senere. Slet %1$s? @@ -2692,7 +2653,9 @@ Antal faneblade - Aktive + Aktive + + Aktive Inaktive @@ -2703,6 +2666,16 @@ Værktøj til oprettelse af faneblade Antal faneblade, der skal oprettes + + Tekstfeltet er tomt + + Indtast kun positive heltal + + Indtast et tal større end nul + + Overskredet det maksimale antal faneblade (%1$s), der kan oprettes i én operation Føj til aktive faneblade @@ -2719,11 +2692,11 @@ Privatlivserklæring - Indsend + Indsend - Luk + Luk - Tak for tilbagemeldingen! + Tak for tilbagemeldingen! Meget tilfreds @@ -2735,6 +2708,14 @@ Meget utilfreds + + + Åbn undersøgelse + + Luk undersøgelse + + Luk + Logins diff --git a/mobile/android/fenix/app/src/main/res/values-de/strings.xml b/mobile/android/fenix/app/src/main/res/values-de/strings.xml index ae6645a6fe..c78b9aa5fa 100644 --- a/mobile/android/fenix/app/src/main/res/values-de/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-de/strings.xml @@ -202,6 +202,10 @@ Add-ons Erweiterungen + + Erweiterungen verwalten + + Weitere Erweiterungen entdecken Kontoinformationen @@ -220,6 +224,8 @@ In normalem Tab öffnen Zum Startbildschirm hinzufügen + + Zum Startbildschirm hinzufügen… Installieren @@ -231,9 +237,13 @@ Seite übersetzen + In Sammlung speichern… + In Sammlung speichern Teilen + + Teilen… In %1$s öffnen @@ -288,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Speichern + + Lesezeichen für diese Seite hinzufügen + + Lesezeichen bearbeiten + + Als PDF speichern… + + Leseansicht aktivieren + + Leseansicht deaktivieren + + Seite übersetzen… + + Übersetzt in %1$s + + Drucken… + Keine Erweiterungen vorhanden @@ -385,8 +413,6 @@ Firefox-Datenschutzhinweis - - Weitere Informationen finden Sie in unserem Datenschutzhinweis Wir schützen Sie gerne Sprache - Übersetzung + Übersetzung + + Übersetzungen Datenübermittlung @@ -669,10 +697,6 @@ Erforderlich Optional - - Website-Daten lesen und ändern - - Website löschen Für alle Websites erlauben @@ -798,8 +822,6 @@ Chronik Lesezeichen - - Zugangsdaten Passwörter @@ -826,8 +848,6 @@ and the third is the device model. --> %1$s auf %2$s %3$s - - Kreditkarten Zahlungsmethoden @@ -844,6 +864,14 @@ Tab von %s + + + %1$s-Tabs geschlossen: %2$d + + Kürzlich geschlossene Tabs anzeigen + Ausnahmen @@ -1796,13 +1824,9 @@ Sie können diese Website einfach zum Startbildschirm Ihres Geräts hinzufügen, um unmittelbaren Zugriff darauf zu haben und sie wie eine App zu nutzen. - - Zugangsdaten und Passwörter Passwörter - Zugangsdaten und Passwörter speichern - Passwörter speichern Zum Speichern nachfragen @@ -1818,46 +1842,27 @@ Benutzernamen und Passwörter auf Webseite bei Nutzung von anderen Apps auf Ihrem Gerät automatisch ausfüllen. - - Zugangsdaten hinzufügen - Passwort hinzufügen - - Zugangsdaten synchronisieren Passwörter synchronisieren - - Zugangsdaten zwischen Geräten synchronisieren Passwörter geräteübergreifend synchronisieren - - Gespeicherte Zugangsdaten Gespeicherte Passwörter - Die Zugangsdaten, die Sie speichern oder mit %s synchronisieren, werden hier angezeigt. - Die Passwörter, die Sie speichern oder mit %s synchronisieren, werden hier aufgelistet. Alle gespeicherten Passwörter werden verschlüsselt. - - Erfahren Sie mehr über Sync. Weitere Informationen über Sync Ausnahmen - - Zugangsdaten und Passwörter, die nicht gespeichert werden, werden hier angezeigt. %s speichert keine Passwörter für die hier aufgeführten Websites. - - Zugangsdaten und Passwörter werden für diese Websites nicht gespeichert. %s speichert keine Passwörter für diese Websites. Alle Ausnahmen löschen - - Zugangsdaten durchsuchen Passwörter durchsuchen @@ -1886,17 +1891,11 @@ Passwort anzeigen Passwort verbergen - - Zum Anzeigen Ihrer gespeicherten Zugangsdaten entsperren Zum Anzeigen Ihrer gespeicherten Passwörter entsperren - Sichern Sie Ihre Zugangsdaten und Passwörter - Sichern Sie Ihre gespeicherten Passwörter - Richten Sie ein Gerätesperrmuster, eine PIN oder ein Passwort ein, um zu verhindern, dass auf Ihre gespeicherten Zugangsdaten und Passwörter zugegriffen wird, wenn jemand anderes über Ihr Gerät verfügt. - Richten Sie ein Gerätesperrmuster, eine PIN oder ein Passwort ein, um zu verhindern, dass auf Ihre gespeicherten Passwörter zugegriffen wird, wenn jemand anderes über Ihr Gerät verfügt. Später @@ -1914,9 +1913,6 @@ Zuletzt verwendet - - Menü mit Zugangsdaten sortieren - Menü „Passwörter sortieren“ @@ -1925,41 +1921,27 @@ Automatisch ausfüllen Adressen - - Kreditkarten Zahlungsmethoden - Autovervollständigung für Kreditkartendaten - Zahlungsmethoden speichern und ausfüllen - - Daten sind verschlüsselt %s verschlüsselt alle von Ihnen gespeicherten Zahlungsmethoden Karten zwischen Geräten synchronisieren Kreditkarten synchronisieren - - Kreditkarte hinzufügen Karte hinzufügen - - Gespeicherte Karten verwalten Karten verwalten Adresse hinzufügen Adressen verwalten - - Autovervollständigung für Adressen Adressen speichern und ausfüllen - - Dies beinhaltetet Nummern, E-Mail- und Lieferadressen Enthält Telefonnummern und E-Mail-Adressen @@ -1983,8 +1965,6 @@ Karte löschen - Soll diese Kreditkarte wirklich gelöscht werden? - Karte löschen? Löschen @@ -1998,24 +1978,15 @@ Gespeicherte Karten - - Bitte geben Sie eine gültige Kreditkartennummer ein - Geben Sie eine gültige Kartennummer ein - - Bitte füllen Sie dieses Feld aus Fügen Sie einen Namen hinzu Zum Anzeigen Ihrer gespeicherten Karten entsperren - Sichern Sie Ihre Kreditkarten - Sichern Sie Ihre gespeicherten Zahlungsmethoden - Richten Sie ein Gerätesperrmuster, eine PIN oder ein Passwort ein, um zu verhindern, dass auf Ihre gespeicherten Karten zugegriffen wird, wenn jemand anderes über Ihr Gerät verfügt. - Richten Sie ein Gerätesperrmuster, eine PIN oder ein Passwort ein, um zu verhindern, dass auf Ihre gespeicherten Zahlungsmethoden zugegriffen wird, wenn jemand anderes über Ihr Gerät verfügt. Jetzt einrichten @@ -2024,9 +1995,6 @@ Entsperren Sie Ihr Gerät - - Entsperren, um gespeicherte Kreditkartendaten zu verwenden - Zum Verwenden Ihrer gespeicherten Zahlungsmethoden entsperren @@ -2035,12 +2003,6 @@ Adresse bearbeiten Adressen verwalten - - Vorname - - Zweiter Vorname - - Nachname Name @@ -2066,8 +2028,6 @@ Adresse löschen - - Soll diese Adresse wirklich gelöscht werden? Diese Adresse löschen? @@ -2166,49 +2126,29 @@ Entfernen Bearbeiten - - Sollen diese Zugangsdaten wirklich gelöscht werden? Soll dieses Passwort wirklich gelöscht werden? Löschen Abbrechen - - Optionen für Zugangsdaten Passwort-Optionen - - Das bearbeitbare Textfeld für die Internetadresse der Zugangsdaten. Das bearbeitbare Textfeld für die Adresse der Website. - - Das bearbeitbare Textfeld für den Benutzernamen der Zugangsdaten. Das bearbeitbare Textfeld für den Benutzernamen. - Das bearbeitbare Textfeld für das Passwort der Zugangsdaten. - Das bearbeitbare Textfeld für das Passwort. - - Änderungen an Zugangsdaten speichern. Änderungen speichern. - - Bearbeiten Passwort bearbeiten - - Neue Zugangsdaten hinzufügen Passwort hinzufügen - - Passwort erforderlich Passwort eingeben - Benutzername erforderlich - Benutzername eingeben Hostname erforderlich @@ -2614,6 +2554,9 @@ Seite „Übersetzungen“ schließen + + Einige Einstellungen sind vorübergehend nicht verfügbar. + Übersetzungen @@ -2636,6 +2579,9 @@ Wählen Sie eine Sprache aus, um die Einstellungen für „Immer übersetzen“ und „Nie übersetzen“ zu verwalten. + + Sprachen konnten nicht geladen werden. Bitte versuchen Sie es später erneut. + Übersetzung anbieten (Standard) @@ -2658,6 +2604,8 @@ %1$s entfernen + + Websites konnten nicht geladen werden. Bitte versuchen Sie es später erneut. %1$s löschen? @@ -2735,13 +2683,18 @@ Rückwärts navigieren + + Debug-Schublade öffnen + Tab-Werkzeuge Tab-Anzahl - Aktiv + Aktiv + + Aktiv Inaktiv @@ -2752,6 +2705,16 @@ Werkzeug zur Tab-Erstellung Zu erstellende Tab-Anzahl + + Textfeld ist leer + + Bitte geben Sie nur positive ganze Zahlen ein + + Bitte geben Sie eine Zahl größer als 0 ein + + Die maximale Anzahl an Tabs (%1$s) wurde überschritten, die in einem Arbeitsschritt erzeugt werden kann Zu aktiven Tabs hinzufügen @@ -2768,11 +2731,11 @@ Datenschutzhinweis - Absenden + Absenden - Schließen + Schließen - Danke für Ihr Feedback! + Danke für Ihr Feedback! Sehr zufrieden @@ -2784,6 +2747,14 @@ Sehr unzufrieden + + + Umfrage öffnen + + Umfrage schließen + + Schließen + Zugangsdaten diff --git a/mobile/android/fenix/app/src/main/res/values-dsb/strings.xml b/mobile/android/fenix/app/src/main/res/values-dsb/strings.xml index 45de14681f..de2fba4cb7 100644 --- a/mobile/android/fenix/app/src/main/res/values-dsb/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-dsb/strings.xml @@ -200,6 +200,10 @@ Dodanki Rozšyrjenja + + Rozšyrjenja zastojaś + + Dalšne rozšyrjenja namakaś Kontowe informacije @@ -218,6 +222,8 @@ W normalnem rejtariku wócyniś Startowej wobrazowce pśidaś + + Startowej wobrazowce pśidaś… Instalěrowaś @@ -229,9 +235,13 @@ Bok pśełožyś + Do zběrki składowaś… + Do zběrki składowaś Źěliś + + Źěliś… W %1$s wócyniś @@ -284,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Składowaś + + Toś ten bok ako cytańske znamje składowaś + + Cytańske znamje wobźěłaś + + Ako PDF składowaś… + + Cytański naglěd zmóžniś + + Cytański naglěd znjemóžniś + + Bok pśełožyś… + + Pśełožony do rěcy %1$s + + Śišćaś… + Žedne rozšyrjenja how @@ -379,8 +407,6 @@ Powěźeńka priwatnosći Firefox - - Zgóńśo wěcej w našej powěźeńce priwatnosći Šćitamy was rad Rěc - Pśełožk + Pśełožk + + Pśełožki Datowy wuběrki @@ -661,10 +689,6 @@ Trjebny Na žycenje - - Websedłowe daty cytaś a změniś - - Websedło wulašowaś Za wšykne sedła dowóliś @@ -790,8 +814,6 @@ Historija Cytańske znamjenja - - Pśizjawjenja Gronidła @@ -818,8 +840,6 @@ and the third is the device model. --> %1$s wót %2$s %3$s - - Kreditowe kórty Płaśeńske metody @@ -835,6 +855,14 @@ Rajtark z %s + + + Rejtariki %1$s zacynjone: %2$d + + Rowno zacynjone rejtariki pokazaś + Wuwześa @@ -1762,13 +1790,9 @@ Móžośo startowej wobrazowce swójogo rěda toś to websedło lažko pśidaś, aby direktny pśistup měł a malsnjej z dožywjenim nałoženja pśeglědował. - - Pśizjawjenja a gronidła Gronidła - Pśizjawjenja a gronidła składowaś - Gronidła składowaś Pśed składowanim se pšašaś @@ -1783,46 +1807,27 @@ Wužywaŕske mjenja a gronidła w drugich nałoženjach na wašom rěźe zasajźiś. - - Pśizjawjenje pśidaś - Gronidło pśidaś - - Pśizjawjenja synchronizěrowaś Gronidła synchronizěrowaś - - Pśizjawjenja mjazy rědami synchronizěrowaś Gronidła pśez rědy synchronizěrowaś - - Skłaźone pśizjawjenja Skłaźone gronidła - Pśizjawjenja, kótarež składujośo abo z %s synchronizěrujośo, se how pokažu. - Gronidła, kótarež składujośo abo z %s synchronizěrujośo, se how nalicyju. Wšykne gronidła, kótarež składujośo, se koděruju. - - Zgóńśo wěcej wó Sync. Zgóńśo wěcej wó sync Wuwześa - - Pśizjawjenja a gronidła, kótarež se njeskładuju, se how pokažu. %s gronidła za sedła njeskładujo, kótarež su how nalicone. - - Pśizjawjenja a gronidła se za toś te sedła njeskładuju. %s gronidła za toś te sedła njeskładujo. Wšykne wuwześa wulašowaś - - Pśizjawjenja pytaś Gronidła pśepytaś @@ -1852,17 +1857,11 @@ Gronidło pokazaś Gronidła schowaś - - Za zwobraznjowanje wašych skłaźonych pśizjawjenjow wótwóriś Za zwobraznjowanje wašych skłaźonych kreditowych gronidłow wótwóriś - Zawěsććo swóje pśizjawjenja a gronidła - Zawěsććo swóje skłaźone gronidła - Nastajśo rědowy zastajeński muster, PIN abo gronidło, aby pśistupoju k swójim skłaźonym pśizjawjenjam a gronidłam zajźował, jolic něchten drugi ma waš rěd. - Nastajśo rědowy zastajeński muster, PIN abo gronidło, aby pśistupoju k swójim skłaźonym gronidłam zajźował, jolic něchten drugi ma waš rěd. Pózdźej @@ -1880,8 +1879,6 @@ Mjenju (A-Z) Slědnem wužyśu - - Meni pśizjawjeńskich datow sortěrowaś Meni „Gronidła sortěrowaś“ @@ -1891,41 +1888,27 @@ Awtomatiski wupołniś Adrese - - Kreditowe kórty Płaśeńske metody - Kórty składowaś a awtomatiski wupołniś - Płaśeńske metody składowaś a wupołniś - - Daty su skoděrowane %s wšykne płaśeńske metody koděrujo, kótarež składujośo Kórty pśez rědy synchronizěrowaś Kórty synchronizěrowaś - - Kreditowu kórtu pśidaś Kórtu pśidaś - - Skłaźone kórty zastojaś Kórty zastojaś Adresu pśidaś Adrese zastojaś - - Adrese składowaś a awtomatiski wupołniś Adrese składowaś a wupołniś - - Informacije ako licby, e-mailowe a rozpósłańske adrese zapśimjeś Wopśimujo telefonowe numery a e-mailowe adrese @@ -1949,8 +1932,6 @@ Kórtu wulašowaś - Cośo napšawdu toś tu kreditowu kórtu lašowaś? - Kórtu wulašowaś? Lašowaś @@ -1964,24 +1945,15 @@ Skłaźone kórty - - Pšosym zapódajśo płaśiwy numer kreditoweje kórty - Zapódajśo płaśiwy kórtowy numer - - Pšosym wupołńśo toś to pólo Pśidajśo mě Za zwobraznjowanje wašych skłaźonych kreditowych kórtow wótwóriś - Kreditowe kórty zawěsćiś - Zawěsććo swóje skłaźone płaśeńske metody - Nastajśo rědowy zastajeński muster, PIN abo gronidło, aby pśistupoju k swójim skłaźonym kreditowym kórtam zajźował, jolic něchten drugi ma waš rěd. - Nastajśo rědowy zastajeński muster, PIN abo gronidło, aby pśistupoju k swójim skłaźonym płaśeńskim metodam zajźował, jolic něchten drugi ma waš rěd. Něnto konfigurěrowaś @@ -1990,9 +1962,6 @@ Wótwóŕśo swój rěd - - Blokěrowanje wótpóraś, aby se skłaźone informacije kreditoweje kórty wužywali - Wótwóriś, aby wy skłaźone płaśeńske metody wužywał @@ -2001,12 +1970,6 @@ Adresu wobźěłaś Adrese zastojaś - - Pśedmě - - Druge pśedmě - - Familijowe mě @@ -2032,8 +1995,6 @@ Adresu lašowaś - - Cośo napšawdu toś tu adresu wulašowaś? Toś tu adresu lašowaś? @@ -2132,49 +2093,29 @@ Lašowaś Wobźěłaś - - Cośo napšawdu toś to pśizjawjenje lašowaś? Cośo napšawdu toś to gronidło lašowaś? Lašowaś Pśetergnuś - - Pśizjawjeńske nastajenja Gronidłowe nastajenja - - Wobźěłujobne tekstowe pólo za webadresu pśizjawjenja. Wobźěłujobne tekstowe pólo za adresu websedła. - - Wobźěłujobne tekstowe pólo za wužywaŕske mě pśizjawjenja. Wobźěłujobne tekstowe pólo za wužywaŕske mě. - Wobźěłujobne tekstowe pólo za gronidło pśizjawjenja. - Wobźěłujobne tekstowe pólo za gronidło. - - Změny pśizjawjenja składowaś Změny składowaś. - - Wobźěłaś Gronidło wobźěłaś - - Nowe pśizjawjenje pśidaś Gronidło pśidaś - - Gronidło trěbne Gronidło zapódaś - Wužywaŕske mě trěbne. - Wužywaŕske mě zapódaś Hostmě trěbne. @@ -2583,6 +2524,9 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Bok Pśełožki zacyniś + + Někotare nastajenja njejsu nachylu k dispoziciji. + Pśełožki @@ -2605,6 +2549,9 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Wubjeŕśo rěc, aby nastajeni „pśecej pśełožyś“ a „nigda njepśełožyś“ zastojał. + + Rěcy njejsu se dali zacytaś. Pšosym wopytajśo pózdźej hyšći raz. + Pśełožk póbitowaś (standard) @@ -2628,6 +2575,8 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt %1$s wótwónoźeś + + Sedła njejsu se dali zacytaś. Pšosym wopytajśo pózdźej hyšći raz. %1$s lašowaś? @@ -2705,13 +2654,18 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Slědk nawigěrowaś + + Debug drawer wócyniś + Rejtarikowe rědy Licba rejtarikow - Aktiwny + Aktiwny + + Aktiwny Njeaktiwny @@ -2722,6 +2676,16 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Rěd za napóranje rejtarikow Licba rejtarikow, kótarež se maju napóraś + + Tekstowe pólo jo prozne + + Pšosym zapódajśo jano pozitiwne cełe licby + + Pšosym zapódajśo licbu, kótaraž jo wětša ako nula + + Maksimalna licba rejtarikow (%1$s), kótarež daju se w jadnej operaciji generěrowaś, jo pśekšocona Aktiwnym rejtarikam pśidaś @@ -2738,11 +2702,11 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Powěźeńka priwatnosći - Wótpósłaś + Wótpósłaś - Zacyniś + Zacyniś - Źěkujomy se za waš komentar! + Źěkujomy se za waš komentar! Wjelgin spokojom @@ -2754,6 +2718,14 @@ To buźo jano pomagaś, kwalitu pógódnośenjow pósuźiś, nic kwalitu produkt Wjelgin njespokojom + + + Napšašowanje wócyniś + + Napšašowanje zacyniś + + Zacyniś + Pśizjawjenja diff --git a/mobile/android/fenix/app/src/main/res/values-el/strings.xml b/mobile/android/fenix/app/src/main/res/values-el/strings.xml index ede381ca29..cf34620a47 100644 --- a/mobile/android/fenix/app/src/main/res/values-el/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-el/strings.xml @@ -205,6 +205,10 @@ Πρόσθετα Επεκτάσεις + + Διαχείριση επεκτάσεων + + Ανακαλύψτε περισσότερες επεκτάσεις Πληροφορίες λογαριασμού @@ -223,6 +227,8 @@ Άνοιγμα σε κανονική καρτέλα Προσθήκη στην αρχική οθόνη + + Προσθήκη στην αρχική οθόνη… Εγκατάσταση @@ -235,9 +241,13 @@ Μετάφραση σελίδας + Αποθήκευση σε συλλογή… + Αποθήκευση στη συλλογή Κοινή χρήση + + Κοινή χρήση… Άνοιγμα σε %1$s @@ -291,6 +301,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Αποθήκευση + + Προσθήκη στους σελιδοδείκτες + + Επεξεργασία σελιδοδείκτη + + Αποθήκευση ως PDF… + + Ενεργοποίηση προβολής ανάγνωσης + + Απενεργοποίηση προβολής ανάγνωσης + + Μετάφραση σελίδας… + + Μεταφράστηκε σε %1$s + + Εκτύπωση… + Δεν υπάρχουν επεκτάσεις @@ -388,8 +416,6 @@ Σημείωση απορρήτου του Firefox - - Μάθετε περισσότερα στη σημείωση απορρήτου μας Μας αρέσει να σας προστατεύουμε Γλώσσα - Μετάφραση + Μετάφραση + + Μεταφράσεις Επιλογές δεδομένων @@ -672,10 +700,6 @@ Απαιτείται Προαιρετικό - - Ανάγνωση και αλλαγή δεδομένων ιστοτόπων - - Διαγραφή ιστοτόπου Αποδοχή για όλους τους ιστοτόπους @@ -804,8 +828,6 @@ Ιστορικό Σελιδοδείκτες - - Συνδέσεις Κωδικοί πρόσβασης @@ -832,8 +854,6 @@ and the third is the device model. --> %1$s στο %2$s %3$s - - Πιστωτικές κάρτες Μέθοδοι πληρωμής @@ -849,6 +869,14 @@ Καρτέλα από %s + + + Έκλεισαν %2$d καρτέλες του %1$s + + Προβολή πρόσφατα κλεισμένων καρτελών + Εξαιρέσεις @@ -1784,13 +1812,9 @@ Μπορείτε εύκολα να προσθέσετε αυτόν τον ιστότοπο στην αρχική οθόνη για άμεση πρόσβαση και ταχύτερη περιήγηση, σαν να ήταν εφαρμογή. - - Συνδέσεις και κωδικοί πρόσβασης Κωδικοί πρόσβασης - Αποθήκευση συνδέσεων και κωδικών πρόσβασης - Αποθήκευση κωδικών πρόσβασης Ερώτηση για αποθήκευση @@ -1806,46 +1830,27 @@ Συμπλήρωση στοιχείων σύνδεσης σε άλλες εφαρμογές της συσκευής σας. - - Προσθήκη σύνδεσης - Προσθήκη κωδικού πρόσβασης - - Συγχρονισμός συνδέσεων Συγχρονισμός κωδικών πρόσβασης - - Συγχρονισμός συνδέσεων μεταξύ συσκευών Συγχρονισμός κωδικών πρόσβασης μεταξύ των συσκευών σας - - Αποθηκευμένες συνδέσεις Αποθηκευμένοι κωδικοί πρόσβασης - Οι συνδέσεις που αποθηκεύετε ή συγχρονίζετε στο %s θα εμφανίζονται εδώ. - Οι κωδικοί πρόσβασης που αποθηκεύετε ή συγχρονίζετε στο %s θα εμφανίζονται εδώ. Όλοι οι κωδικοί πρόσβασης που αποθηκεύετε κρυπτογραφούνται. - - Μάθετε περισσότερα σχετικά με το Sync. Μάθετε περισσότερα σχετικά με τον συγχρονισμό Εξαιρέσεις - - Εδώ εμφανίζονται οι συνδέσεις και οι κωδικοί πρόσβασης που δεν αποθηκεύονται. Το %s δεν θα αποθηκεύει κωδικούς πρόσβασης για τους ιστοτόπους που αναφέρονται εδώ. - - Δεν θα αποθηκεύονται στοιχεία σύνδεσης για τους εξής ιστοτόπους. Το %s δεν θα αποθηκεύει κωδικούς πρόσβασης για αυτούς τους ιστοτόπους. Διαγραφή όλων των εξαιρέσεων - - Αναζήτηση συνδέσεων Αναζήτηση κωδικών πρόσβασης @@ -1874,17 +1879,11 @@ Εμφάνιση κωδικού πρόσβασης Απόκρυψη κωδικού πρόσβασης - - Ξεκλειδώστε για να δείτε τις αποθηκευμένες συνδέσεις σας Ξεκλειδώστε για να δείτε τους αποθηκευμένους κωδικούς πρόσβασής σας - Προστασία στοιχείων σύνδεσης - Προστασία των αποθηκευμένων κωδικών πρόσβασής σας - Ορίστε ένα μοτίβο κλειδώματος συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για προστασία των αποθηκευμένων στοιχείων σύνδεσης, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. - Ορίστε ένα μοτίβο κλειδώματος συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για την προστασία των αποθηκευμένων κωδικών πρόσβασής σας, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. Αργότερα @@ -1901,9 +1900,6 @@ Τελευταία χρήση - - Ταξινόμηση μενού σύνδεσης - Μενού ταξινόμησης κωδικών πρόσβασης @@ -1912,42 +1908,28 @@ Αυτόματη συμπλήρωση Διευθύνσεις - - Πιστωτικές κάρτες Μέθοδοι πληρωμής - Αποθήκευση και αυτόματη συμπλήρωση στοιχείων καρτών - Αποθήκευση και συμπλήρωση μεθόδων πληρωμής - - Τα δεδομένα κρυπτογραφούνται Το %s κρυπτογραφεί όλες τις μεθόδους πληρωμής που αποθηκεύετε Συγχρονισμός καρτών μεταξύ συσκευών Συγχρονισμός καρτών - - Προσθήκη πιστωτικής κάρτας Προσθήκη κάρτας - - Διαχείριση αποθηκευμένων καρτών Διαχείριση καρτών Προσθήκη διεύθυνσης Διαχείριση διευθύνσεων - - Αποθήκευση και αυτόματη συμπλήρωση διευθύνσεων Αποθήκευση και συμπλήρωση διευθύνσεων - - Συμπεριλάβετε πληροφορίες, όπως αριθμούς, email και διευθύνσεις αποστολής Περιλαμβάνει αριθμούς τηλεφώνου και διευθύνσεις email @@ -1971,8 +1953,6 @@ Διαγραφή κάρτας - Θέλετε σίγουρα να διαγράψετε αυτήν την πιστωτική κάρτα; - Διαγραφή κάρτας; Διαγραφή @@ -1986,24 +1966,15 @@ Αποθηκευμένες κάρτες - - Παρακαλώ εισαγάγετε έναν έγκυρο αριθμό πιστωτικής κάρτας - Εισαγάγετε έγκυρο αριθμό κάρτας - - Παρακαλώ συμπληρώστε αυτό το πεδίο Προσθήκη ονόματος Ξεκλειδώστε για να δείτε τις αποθηκευμένες κάρτες σας - Ασφαλίστε τις πιστωτικές κάρτες σας - Προστασία των αποθηκευμένων μεθόδων πληρωμής σας - Ορίστε ένα μοτίβο κλειδώματος συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για την προστασία των αποθηκευμένων πιστωτικών καρτών σας, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. - Ορίστε ένα μοτίβο κλειδώματος της συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για την προστασία των αποθηκευμένων καρτών σας, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. Ρύθμιση τώρα @@ -2012,8 +1983,6 @@ Ξεκλειδώστε τη συσκευή σας - - Ξεκλειδώστε για χρήση των στοιχείων πιστωτικής κάρτας Ξεκλειδώστε για χρήση των αποθηκευμένων μεθόδων πληρωμής @@ -2023,12 +1992,6 @@ Επεξεργασία διεύθυνσης Διαχείριση διευθύνσεων - - Όνομα - - Μεσαίο όνομα - - Επώνυμο Όνομα @@ -2054,8 +2017,6 @@ Διαγραφή διεύθυνσης - - Θέλετε σίγουρα να διαγράψετε αυτήν τη διεύθυνση; Διαγραφή διεύθυνσης; @@ -2157,49 +2118,29 @@ Διαγραφή Επεξεργασία - - Θέλετε σίγουρα να διαγράψετε αυτή τη σύνδεση; Θέλετε σίγουρα να διαγράψετε αυτόν τον κωδικό πρόσβασης; Διαγραφή Ακύρωση - - Επιλογές σύνδεσης Επιλογές κωδικών πρόσβασης - - Το επεξεργάσιμο πεδίο κειμένου της διεύθυνσης ιστού της σύνδεσης. Το επεξεργάσιμο πεδίο κειμένου της διεύθυνσης ιστοτόπου. - - Το επεξεργάσιμο πεδίο κειμένου για το όνομα χρήστη της σύνδεσης. Το επεξεργάσιμο πεδίο κειμένου για το όνομα χρήστη. - Το επεξεργάσιμο πεδίο κειμένου για τον κωδικό πρόσβασης της σύνδεσης. - Το επεξεργάσιμο πεδίο κειμένου για τον κωδικό πρόσβασης. - - Αποθήκευση αλλαγών στη σύνδεση. Αποθήκευση αλλαγών. - - Επεξεργασία Επεξεργασία κωδικού πρόσβασης - - Προσθήκη νέας σύνδεσης Προσθήκη κωδικού πρόσβασης - - Απαιτείται κωδικός πρόσβασης Εισαγάγετε έναν κωδικό πρόσβασης - Απαιτείται όνομα χρήστη - Εισαγάγετε ένα όνομα χρήστη Απαιτείται όνομα κεντρικού υπολογιστή @@ -2548,6 +2489,8 @@ Όχι τώρα Εμφάνιση πρωτότυπου + + Φορτώθηκε η αρχική, αμετάφραστη σελίδα Τέλος @@ -2605,6 +2548,9 @@ Κλείσιμο σελίδας μεταφράσεων + + Ορισμένες ρυθμίσεις δεν είναι προσωρινά διαθέσιμες. + Μεταφράσεις @@ -2628,6 +2574,9 @@ Επιλέξτε μια γλώσσα για να διαχειριστείτε τις προτιμήσεις «πάντα μετάφραση» και «ποτέ μετάφραση». + + Δεν ήταν δυνατή η φόρτωση των γλωσσών. Ελέγξτε ξανά αργότερα. + Πρόταση για μετάφραση (προεπιλογή) @@ -2651,6 +2600,8 @@ Αφαίρεση %1$s + + Δεν ήταν δυνατή η φόρτωση των ιστοτόπων. Ελέγξτε ξανά αργότερα. Διαγραφή του %1$s; @@ -2728,13 +2679,18 @@ Πλοήγηση προς τα πίσω + + Άνοιγμα μενού εντοπισμού σφαλμάτων + Εργαλεία καρτελών Αριθμός καρτελών - Ενεργές + Ενεργές + + Ενεργές Ανενεργές @@ -2745,6 +2701,16 @@ Εργαλείο δημιουργίας καρτελών Πλήθος καρτελών προς δημιουργία + + Το πεδίο κειμένου είναι κενό + + Παρακαλώ εισαγάγετε μόνο θετικούς ακέραιους αριθμούς + + Παρακαλώ εισαγάγετε έναν αριθμό μεγαλύτερο του μηδέν + + Υπέρβαση του μέγιστου αριθμού καρτελών (%1$s) που μπορούν να δημιουργηθούν με μία ενέργεια Προσθήκη στις ενεργές καρτέλες @@ -2761,11 +2727,11 @@ Σημείωση απορρήτου - Υποβολή + Υποβολή - Κλείσιμο + Κλείσιμο - Ευχαριστούμε για τα σχόλιά σας! + Ευχαριστούμε για τα σχόλιά σας! Πολύ ικανοποιημένος/-η @@ -2778,6 +2744,14 @@ Πολύ δυσαρεστημένος/-η + + + Άνοιγμα έρευνας + + Κλείσιμο έρευνας + + Κλείσιμο + Συνδέσεις diff --git a/mobile/android/fenix/app/src/main/res/values-en-rCA/strings.xml b/mobile/android/fenix/app/src/main/res/values-en-rCA/strings.xml index 5ec62ac882..dac62c0821 100644 --- a/mobile/android/fenix/app/src/main/res/values-en-rCA/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-en-rCA/strings.xml @@ -200,6 +200,10 @@ Add-ons Extensions + + Manage extensions + + Discover more extensions Account info @@ -218,6 +222,8 @@ Open in regular tab Add to Home screen + + Add to Home screen… Install @@ -229,9 +235,13 @@ Translate page + Save to collection… + Save to collection Share + + Share… Open in %1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Save + + Bookmark this page + + Edit bookmark + + Save as PDF… + + Turn on Reader View + + Turn off Reader View + + Translate page… + + Translated to %1$s + + Print… + No extensions here @@ -377,8 +405,6 @@ Firefox privacy notice - - Learn more in our privacy notice We love keeping you safe Language - Translation + Translation + + Translations Data choices @@ -658,10 +686,6 @@ Required Optional - - Read and change website data - - Delete website Allow for all sites @@ -787,8 +811,6 @@ History Bookmarks - - Logins Passwords @@ -815,8 +837,6 @@ and the third is the device model. --> %1$s on %2$s %3$s - - Credit cards Payment methods @@ -832,6 +852,14 @@ Tab from %s + + + %1$s tabs closed: %2$d + + View recently closed tabs + Exceptions @@ -1750,13 +1778,9 @@ You can easily add this website to your device’s Home screen to have instant access and browse faster with an app-like experience. - - Logins and passwords Passwords - Save logins and passwords - Save passwords Ask to save @@ -1771,46 +1795,27 @@ Fill usernames and passwords in other apps on your device. - - Add login - Add password - - Sync logins Sync passwords - - Sync logins across devices Sync passwords across devices - - Saved logins Saved passwords - The logins you save or sync to %s will show up here. - The passwords you save or sync to %s will be listed here. All passwords you save are encrypted. - - Learn more about Sync. Learn more about sync Exceptions - - Logins and passwords that are not saved will be shown here. %s won’t save passwords for sites listed here. - - Logins and passwords will not be saved for these sites. %s won’t save passwords for these sites. Delete all exceptions - - Search logins Search passwords @@ -1839,17 +1844,11 @@ Show password Hide password - - Unlock to view your saved logins Unlock to view your saved passwords - Secure your logins and passwords - Secure your saved passwords - Set up a device lock pattern, PIN, or password to protect your saved logins and passwords from being accessed if someone else has your device. - Set up a device lock pattern, PIN, or password to protect your saved passwords from being accessed if someone else has your device. Later @@ -1866,8 +1865,6 @@ Name (A-Z) Last used - - Sort logins menu Sort passwords menu @@ -1877,29 +1874,19 @@ Autofill Addresses - - Credit cards Payment methods - Save and autofill cards - Save and fill payment methods - - Data is encrypted %s encrypts all payment methods you save Sync cards across devices Sync cards - - Add credit card Add card - - Manage saved cards Manage cards @@ -1907,12 +1894,8 @@ Manage addresses - - Save and autofill addresses Save and fill addresses - - Include information like numbers, email and shipping addresses Includes phone numbers and email addresses @@ -1936,8 +1919,6 @@ Delete card - Are you sure you want to delete this credit card? - Delete card? Delete @@ -1951,24 +1932,15 @@ Saved cards - - Please enter a valid credit card number - Enter a valid card number - - Please fill out this field Add a name Unlock to view your saved cards - Secure your credit cards - Secure your saved payment methods - Set up a device lock pattern, PIN, or password to protect your saved credit cards from being accessed if someone else has your device. - Set up a device lock pattern, PIN, or password to protect your saved payment methods from being accessed if someone else has your device. Set up now @@ -1976,8 +1948,6 @@ Later Unlock your device - - Unlock to use stored credit card information Unlock to use saved payment methods @@ -1987,12 +1957,6 @@ Edit address Manage addresses - - First Name - - Middle Name - - Last Name Name @@ -2018,8 +1982,6 @@ Delete address - - Are you sure you want to delete this address? Delete this address? @@ -2118,49 +2080,29 @@ Delete Edit - - Are you sure you want to delete this login? Are you sure you want to delete this password? Delete Cancel - - Login options Password options - - The editable text field for the web address of the login. The editable text field for the website address. - - The editable text field for the username of the login. The editable text field for the username. - The editable text field for the password of the login. - The editable text field for the password. - - Save changes to login. Save changes. - - Edit Edit password - - Add new login Add password - - Password required Enter a password - Username required - Enter a username Hostname required @@ -2566,6 +2508,9 @@ Close Translations sheet + + Some settings are temporarily unavailable. + Translations @@ -2588,6 +2533,9 @@ Select a language to manage ”always translate“ and ”never translate“ preferences. + + Couldn’t load languages. Please check back later. + Offer to translate (default) @@ -2610,6 +2558,8 @@ Remove %1$s + + Couldn’t load sites. Please check back later. Delete %1$s? @@ -2687,13 +2637,18 @@ Navigate back + + Open debug drawer + Tab Tools Tab count - Active + Active + + Active Inactive @@ -2704,6 +2659,16 @@ Tab creation tool Tab quantity to create + + Text field is empty + + Please enter positive integers only + + Please enter a number greater than zero + + Exceeded the maximum number of tabs (%1$s) that can be generated in one operation Add to active tabs @@ -2720,11 +2685,11 @@ Privacy Notice - Submit + Submit - Close + Close - Thanks for your feedback! + Thanks for your feedback! Very Satisfied @@ -2736,6 +2701,14 @@ Very Dissatisfied + + + Open survey + + Close survey + + Close + Logins diff --git a/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml b/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml index b04058b493..8b41ad2e50 100644 --- a/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml @@ -198,6 +198,10 @@ Add-ons Extensions + + Manage extensions + + Discover more extensions Account info @@ -216,6 +220,8 @@ Open in regular tab Add to Home screen + + Add to Home screen… Install @@ -227,9 +233,13 @@ Translate page + Save to collection… + Save to collection Share + + Share… Open in %1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Save + + Bookmark this page + + Edit bookmark + + Save as PDF… + + Turn on Reader View + + Turn off Reader View + + Translate page… + + Translated to %1$s + + Print… + No extensions here @@ -379,8 +407,6 @@ Firefox privacy notice - - Learn more in our privacy notice We love keeping you safe Language - Translation + Translation + + Translations Data choices @@ -662,10 +690,6 @@ Required Optional - - Read and change web site data - - Delete web site Allow for all sites @@ -790,8 +814,6 @@ History Bookmarks - - Logins Passwords @@ -818,8 +840,6 @@ and the third is the device model. --> %1$s on %2$s %3$s - - Credit cards Payment methods @@ -835,6 +855,14 @@ Tab from %s + + + %1$s tabs closed: %2$d + + View recently closed tabs + Exceptions @@ -1755,13 +1783,9 @@ You can easily add this web site to your device’s Home screen to have instant access and browse faster with an app-like experience. - - Logins and passwords Passwords - Save logins and passwords - Save passwords Ask to save @@ -1777,47 +1801,28 @@ Fill usernames and passwords in other apps on your device. - - Add login - Add password - - Synchronise logins Synchronise passwords - - Synchronise logins across devices Synchronise passwords across devices - - Saved logins Saved passwords - The logins you save or synchronise to %s will show up here. - The passwords you save or synchronise to %s will be listed here. All passwords you save are encrypted. - - Learn more about Sync. Learn more about Sync Exceptions - - Logins and passwords that are not saved will be shown here. %s won’t save passwords for sites listed here. - - Logins and passwords will not be saved for these sites. %s won’t save passwords for these sites. Delete all exceptions - - Search logins Search passwords @@ -1846,17 +1851,11 @@ Show password Hide password - - Unlock to view your saved logins Unlock to view your saved passwords - Secure your logins and passwords - Secure your saved passwords - Set up a device lock pattern, PIN, or password to protect your saved logins and passwords from being accessed if someone else has your device. - Set up a device lock pattern, PIN, or password to protect your saved passwords from being accessed if someone else has your device. Later @@ -1873,8 +1872,6 @@ Name (A-Z) Last used - - Sort logins menu Sort passwords menu @@ -1884,41 +1881,27 @@ Autofill Addresses - - Credit cards Payment methods - Save and autofill cards - Save and fill payment methods - - Data is encrypted %s encrypts all payment methods you save Synchronise cards across devices Synchronise cards - - Add credit card Add card - - Manage saved cards Manage cards Add address Manage addresses - - Save and autofill addresses Save and fill addresses - - Include information like numbers, email and shipping addresses Includes phone numbers and email addresses @@ -1942,8 +1925,6 @@ Delete card - Are you sure you want to delete this credit card? - Delete card? Delete @@ -1957,24 +1938,15 @@ Saved cards - - Please enter a valid credit card number - Enter a valid card number - - Please fill out this field Add a name Unlock to view your saved cards - Secure your credit cards - Secure your saved payment methods - Set up a device lock pattern, PIN, or password to protect your saved credit cards from being accessed if someone else has your device. - Set up a device lock pattern, PIN, or password to protect your saved payment methods from being accessed if someone else has your device. Set up now @@ -1982,8 +1954,6 @@ Later Unlock your device - - Unlock to use stored credit card information Unlock to use saved payment methods @@ -1993,12 +1963,6 @@ Edit address Manage addresses - - First Name - - Middle Name - - Last Name Name @@ -2024,8 +1988,6 @@ Delete address - - Are you sure you want to delete this address? Delete this address? @@ -2124,49 +2086,29 @@ Delete Edit - - Are you sure you want to delete this login? Are you sure you want to delete this password? Delete Cancel - - Login options Password options - - The editable text field for the web address of the login. The editable text field for the web site address. - - The editable text field for the username of the login. The editable text field for the username. - The editable text field for the password of the login. - The editable text field for the password. - - Save changes to login. Save changes. - - Edit Edit password - - Add new login Add password - - Password required Enter a password - Username required - Enter a username Hostname required @@ -2513,6 +2455,8 @@ Not now Show original + + Original untranslated page loaded Done @@ -2570,6 +2514,9 @@ Close Translations sheet + + Some settings are temporarily unavailable. + Translations @@ -2592,6 +2539,9 @@ Select a language to manage ”always translate“ and ”never translate“ preferences. + + Couldn’t load languages. Please check back later. + Offer to translate (default) @@ -2614,6 +2564,8 @@ Remove %1$s + + Couldn’t load sites. Please check back later. Delete %1$s? @@ -2691,13 +2643,18 @@ Navigate backwards + + Open debug drawer + Tab Tools Tab count - Active + Active + + Active Inactive @@ -2708,6 +2665,16 @@ Tab creation tool Tab quantity to create + + Text field is empty + + Please enter positive integers only + + Please enter a number greater than zero + + Exceeded the maximum number of tabs (%1$s) that can be generated in one operation Add to active tabs @@ -2724,11 +2691,11 @@ Privacy Notice - Submit + Submit - Close + Close - Thanks for your feedback! + Thanks for your feedback! Very Satisfied @@ -2740,6 +2707,14 @@ Very Dissatisfied + + + Open survey + + Close survey + + Close + Logins diff --git a/mobile/android/fenix/app/src/main/res/values-eo/strings.xml b/mobile/android/fenix/app/src/main/res/values-eo/strings.xml index 0b365e2616..962dc6a81c 100644 --- a/mobile/android/fenix/app/src/main/res/values-eo/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-eo/strings.xml @@ -48,11 +48,19 @@ - Ĵuse konservitaj + Ĵuse konservitaj - Montri ĉiujn konservitajn legosignojn + Montri ĉiujn konservitajn legosignojn - Forigi + Forigi + + + + Legosignoj + + Montri ĉiujn legosignojn + + Forigi %1$s estas kreita de Mozilla. @@ -140,8 +148,10 @@ Nova privata langeto - - Ŝparvojo por la pasvortoj + + Pasvortoj + + Ŝparvojo por la pasvortoj @@ -182,11 +192,13 @@ Haltigi - Aldonaĵoj + Aldonaĵoj + + Etendaĵoj Informoj pri konto - Neniu aldonaĵo estas ĉi tie + Neniu aldonaĵo estas ĉi tie Helpo @@ -202,12 +214,14 @@ Aldoni al hejmekrano - Instali + Instali Respeguli Serĉi en paĝo + + Serĉi en paĝo… Traduki paĝon @@ -240,6 +254,33 @@ Personecigi ekan paĝon + + Komenci seancon + + Speguli pasvortojn, langetojn, kaj pli + + Rekomencu seancon por speguli + + Spegulado paŭzinta + + Nova privata langeto + + Pasvortoj + + Novaĵoj en %1$s + + Iri al versio por komputiloj + + Iloj + + Konservi + + + + Ne estas etendaĵoj ĉi tie + Hejmekrano @@ -543,6 +584,8 @@ Rekonektiĝu por daŭrigi la speguladon Lingvo + + Traduko Elekto de datumoj @@ -594,10 +637,14 @@ Fino de programo pro apliko de ŝanĝoj… - Aldonaĵoj + Aldonaĵoj + + Etendaĵoj - Instali aldonaĵon el dosiero + Instali aldonaĵon el dosiero + + Instali etendaĵon el dosiero Sciigoj @@ -606,9 +653,25 @@ Ne permesita + + + Postulata + + Elektebla + + Legi kaj ŝanĝi retejajn datumojn + + Forigi retejon + + Permesi por ĉiuj retejoj + + Se vi fidas tiun ĉi etendaĵon vi povas doni al ĝi tiun permeson por ĉiuj retejoj. + - Personecigita kolekto de aldonaĵoj + Personecigita kolekto de aldonaĵoj + + Personecigita kolekto de etendaĵoj Akcepti @@ -618,13 +681,18 @@ Posedanto de kolekto (identigilo de uzanto) - La kolekto de aldonaĵoj estis ŝanĝita. La programo nun finiĝos por apliki la ŝanĝojn… + La kolekto de aldonaĵoj estis ŝanĝita. La programo nun finiĝos por apliki la ŝanĝojn… + + + La kolekto de etendaĵoj estis ŝanĝita. La programo nun finiĝos por apliki la ŝanĝojn… Reiri - Ĵusaj legosignoj + Ĵusaj legosignoj + + Legosignoj Ĵusaj retpaĝoj @@ -674,21 +742,37 @@ - Novaj aldonaĵoj disponeblaj + Novaj aldonaĵoj disponeblaj + + Novaj etendaĵoj disponeblaj Konu pli ol 100+ novajn etendaĵojn, kiuj permesas al vi personecigi Firefox. - Esplori aldonaĵojn + Esplori aldonaĵojn - + + Esplori etendaĵoj + + - La aldonaĵoj estas provizore malaktivigitaj + La aldonaĵoj estas provizore malaktivigitaj + + La etendaĵoj estas provizore malaktivigitaj - Unu aŭ pli da aldonaĴoj ĉesis funkcii, kaj tio igas vian sistemon nestabila. %1$s malsukcese klopodis restartigi la aldonaĵo(j)n.\n\nAldonaĵoj ne estos restartigitaj dum via nuna seanco.\n\nForigo aŭ malaktivigo de aldonaĵo(j) povus solvi tiun ĉi problemo. + Unu aŭ pli da aldonaĴoj ĉesis funkcii, kaj tio igas vian sistemon nestabila. %1$s malsukcese klopodis restartigi la aldonaĵo(j)n.\n\nAldonaĵoj ne estos restartigitaj dum via nuna seanco.\n\nForigo aŭ malaktivigo de aldonaĵo(j) povus solvi tiun ĉi problemo. + + Unu aŭ pli da etendaĵoj ĉesis funkcii, kaj tio igas vian sistemon nestabila. %1$s malsukcese klopodis restartigi la etendaĵo(j)n.\n\nEtendaĵoj ne estos restartigitaj dum via nuna seanco.\n\nForigo aŭ malaktivigo de aldonaĵo(j) povus solvi tiun ĉi problemo. - Provi restartigi aldonaĵojn + Provi restartigi aldonaĵojn + + Provi restartigi etendaĵojn - Daŭrigi kun aldonaĵoj malaktivigitaj + Daŭrigi kun aldonaĵoj malaktivigitaj + + + Daŭrigi kun etendaĵoj malaktivigitaj @@ -2441,10 +2525,14 @@ Traduki el Traduki en + + Provi alian originan lingvon Ne nun Montri originalon + + Originala netradukita paĝo ŝargita Farita @@ -2464,7 +2552,7 @@ Bedaŭrinde ni ankoraŭ ne subtenas %1$s. - Pli da informo + Pli da informo @@ -2478,7 +2566,9 @@ - Tradukaj elektebloj + Tradukaj elektebloj + + Tradukaj elektebloj Ĉiam proponi tradukon @@ -2496,6 +2586,9 @@ Pri tradukoj en %1$s + + Fermi panelon de tradukoj + Tradukoj @@ -2603,6 +2696,8 @@ Ĉu elŝuti dum datumŝpara reĝimo (%1$s)? Ni elŝutas partojn de lingvoj al via staplo por gardi viajn tradukojn privataj. + + Ni elŝutas partojn de lingvoj por gardi viajn tradukojn privataj. Ĉiam elŝuti en datumŝpara regïmo @@ -2617,6 +2712,8 @@ Erarserĉilaj iloj Iri reen + + Langetaj iloj @@ -2639,4 +2736,39 @@ Aldoni al malaktivaj langetoj Aldoni al privataj langetoj + + + + + Daŭrigi + + Respondi tiun ĉi demandaron + + Rimarko pri privateco + + Sendi + + Fermi + + Dankon pro via opinio! + + Tre kontentiga + + Kontentiga + + Neŭtra + + Nekontentiga + + Tute nekontentiga + + + + Legitimiloj + + Nuna nomregno: %s + + Aldoni neveran konton al tiu ĉi nomregno + + Forigi legitimilon kun la nomo de uzanto %s diff --git a/mobile/android/fenix/app/src/main/res/values-es-rAR/strings.xml b/mobile/android/fenix/app/src/main/res/values-es-rAR/strings.xml index 785d99207d..ad85fcab17 100644 --- a/mobile/android/fenix/app/src/main/res/values-es-rAR/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-es-rAR/strings.xml @@ -201,6 +201,10 @@ Complementos Extensiones + + Administrar extensiones + + Descubrir más extensiones Información de la cuenta @@ -219,6 +223,8 @@ Abrir en una pestaña normal Agregar a pantalla de inicio + + Agregar a la pantalla de inicio… Instalar @@ -230,9 +236,13 @@ Traducir página + Guardar en la colección… + Guardar en la colección Compartir + + Compartir… Abrir en %1$s @@ -287,6 +297,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Guardar + + Marcar esta página + + Editar marcador + + Guardar como PDF… + + Activar vista de lectura + + Desactivar vista de lectura + + Traducir página… + + Traducido al %1$s + + Imprimir… + No hay extensiones aquí @@ -387,8 +415,6 @@ Aviso de privacidad de Firefox - - Conocé más en nuestra nota de privacidad Nos encanta mantenerte seguro Idioma - Traducción + Traducción + + Traducciones Elección de datos @@ -670,10 +698,6 @@ Necesarios Opcional - - Leer y cambiar los datos de sitios web - - Eliminar sitio web Permitir para todos los sitios @@ -798,8 +822,6 @@ Historial Marcadores - - Inicios de sesión Contraseñas @@ -828,8 +850,6 @@ and the third is the device model. --> %1$s en %2$s %3$s - - Tarjetas de crédito Métodos de pago @@ -845,6 +865,14 @@ Pestaña de %s + + + Pestañas cerradas en %1$s: %2$d + + Ver pestañas cerradas recientemente + Excepciones @@ -1786,13 +1814,9 @@ Podés agregar este sitio a la pantalla de inicio del dispositivo fácilmente para tener acceso instantáneo y navegar más rápido con una experiencia similar a la de una aplicación. - - Inicios de sesión y contraseñas Contraseñas - Guardar inicios de sesión y contraseñas - Guardar contraseñas Solicitar guardar @@ -1807,46 +1831,27 @@ Completar nombres de usuario y contraseñas en otras aplicaciones de tu dispositivo. - - Agregar inicio de sesión - Agregar contraseña - - Sincronizar inicios de sesión Sincronizar contraseñas - - Sincronizar inicios de sesión entre dispositivos Sincronizar contraseñas entre dispositivos - - Inicios de sesión guardados Contraseñas guardadas - Aquí se van a ver los inicios de sesión que guardes o sincronices con %s. - Las contraseñas que guardés o sincronicés con %s aparecerán acá. Todas las contraseñas que guardés están encriptadas. - - Conocé más acerca de Sync. Conocé más acerca de la sincronización Excepciones - - Los inicios de sesión y las contraseñas que no se guardan se mostrarán aquí. %s no guardará las contraseñas de los sitios listados acá. - - Los inicios de sesión y las contraseñas no se van a guardar para estos sitios. %s no guardará las contraseñas de estos sitios. Eliminar todas las excepciones - - Buscar inicios de sesión Buscar contraseñas @@ -1875,17 +1880,11 @@ Mostrar contraseña Ocultar contraseña - - Desbloqueá para ver tus inicios de sesión guardados Desbloqueá para ver las contraseñas guardadas - Asegurá inicios de sesión y contraseñas - Asegurá tus contraseñas guardadas - Configurá un patrón de bloqueo del dispositivo, un PIN o una contraseña para proteger el acceso a tus inicios de sesión y contraseñas guardadas por si alguien más tiene tu dispositivo. - Configurá un patrón de bloqueo del dispositivo, PIN o contraseña para proteger el acceso a tus contraseñas guardadas si alguien más tiene tu dispositivo. Más tarde @@ -1902,8 +1901,6 @@ Nombre (A-Z) Usado por última vez - - Ordenar menú de inicio de sesión Menú ordenar contraseñas @@ -1913,41 +1910,27 @@ Autocompletar Direcciones - - Tarjetas de crédito Métodos de pago - Guardar y autocompletar tarjetas - Guardar y completar métodos de pago - - Los datos están cifrados %s cifra todos los métodos de pago guardados Sincronizar tarjetas entre dispositivos Sincronizar tarjetas - - Agregar tarjeta de crédito Agregar tarjeta - - Administrar tarjetas guardadas Administrar tarjetas Agregar dirección Administrar direcciones - - Guardar y autocompletar direcciones Guardar y completar direcciones - - Incluir información como números, correos electrónicos y direcciones de envíos Incluye números de teléfono y direcciones de correo electrónico @@ -1971,8 +1954,6 @@ Borrar tarjeta - ¿Estás seguro de querer borrar esta tarjeta de crédito? - ¿Borrar tarjeta? Borrar @@ -1986,25 +1967,16 @@ Tarjetas guardadas - - Ingresá un número de tarjeta de crédito válido - Ingresá un número de tarjeta válido - - Se debe completar este campo Agregar un nombre Desbloqueá para ver las tarjetas guardadas - - Asegurá tus tarjetas de crédito Asegurá tus métodos de pago guardados - Configurá un patrón de bloqueo del dispositivo, PIN o contraseña para proteger el acceso a tus tarjetas de crédito guardadas si alguien más tiene tu dispositivo. - Configurá un patrón de bloqueo del dispositivo, un PIN o una contraseña para proteger el acceso a tus métodos de pago guardados por si otra persona accede a tu dispositivo. Configurar ahora @@ -2013,9 +1985,6 @@ Desbloqueá tu dispositivo - - Desbloquear para usar la información almacenada de la tarjeta de crédito - Desbloquear para usar los métodos de pago guardados @@ -2024,12 +1993,6 @@ Editar dirección Administrar direcciones - - Primer Nombre - - Segundo nombre - - Apellido Nombre @@ -2055,8 +2018,6 @@ Borrar dirección - - ¿Estás seguro de querer borrar esta dirección? ¿Borrar esta dirección? @@ -2157,49 +2118,29 @@ Eliminar Editar - - ¿Estás seguro de que querés eliminar este inicio de sesión? ¿Estás seguro de que querés eliminar esta contraseña? Eliminar Cancelar - - Opciones de inicio de sesión Opciones de contraseña - - El campo de texto editable para la dirección web del inicio de sesión. El campo de texto editable para la dirección del sitio web. - - El campo de texto editable para el nombre de usuario del inicio de sesión. El campo de texto editable para el nombre de usuario. - El campo de texto editable para la contraseña del inicio de sesión. - El campo de texto editable para la contraseña. - - Guardar cambios para el inicio de sesión. Guardar cambios. - - Editar Editar contraseña - - Agregar nuevo inicio de sesión Agregar contraseña - - Se necesita contraseña Ingresar una contraseña - Se requiere nombre de usuario - Ingresar un nombre de usuario Se requiere nombre de host @@ -2608,6 +2549,9 @@ Cerrar la hoja de traducciones + + Algunas configuraciones no están disponibles temporariamente. + Traducciones @@ -2630,6 +2574,9 @@ Seleccioná un idioma para administrar las preferencias de ”traducir siempre“ y ”nunca traducir“. + + No se pudieron cargar los idiomas. Intentalo de nuevo más tarde. + Ofrecer traducción (predeterminado) @@ -2653,6 +2600,8 @@ Eliminar %1$s + + No se pudieron cargar los sitios. Intentalo de nuevo más tarde. ¿Eliminar %1$s? @@ -2730,13 +2679,18 @@ Navegar hacia atrás + + Abrir cajón de depuración + Herramientas de pestañas Cantidad de pestañas - Activa + Activa + + Activo Inactiva @@ -2747,6 +2701,16 @@ Herramienta de creación de pestañas Cantidad de pestañas a crear + + El campo de texto está vacío + + Ingresar solamente enteros positivos + + Ingresar un número mayor que cero + + Se superó el número máximo de pestañas (%1$s) que se pueden generar en una operación Agregar a pestañas activas @@ -2763,11 +2727,11 @@ Nota de privacidad - Enviar + Enviar - Cerrar + Cerrar - ¡Gracias por tus comentarios! + ¡Gracias por tus comentarios! Muy satisfecho @@ -2779,6 +2743,14 @@ Muy insatisfecho + + + Abrir encuesta + + Cerrar encuesta + + Cerrar + Inicios de sesión diff --git a/mobile/android/fenix/app/src/main/res/values-es-rCL/strings.xml b/mobile/android/fenix/app/src/main/res/values-es-rCL/strings.xml index 099a7d1850..32f5170529 100644 --- a/mobile/android/fenix/app/src/main/res/values-es-rCL/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-es-rCL/strings.xml @@ -47,12 +47,20 @@ - Guardados recientemente + Guardados recientemente - Mostrar todos los marcadores guardados + Mostrar todos los marcadores guardados - Eliminar + Eliminar + + + + Marcadores + + Mostrar todos los marcadores + + Eliminar %1$s es producido por Mozilla. @@ -191,6 +199,10 @@ Complementos Extensiones + + Gestionar extensiones + + Descubrir más extensiones Información de la cuenta @@ -209,6 +221,8 @@ Abrir en pestaña normal Añadir a pantalla de inicio + + Añadir a la pantalla de inicio… Instalar @@ -220,9 +234,13 @@ Traducir página + Guardar en la colección… + Guardar en la colección Compartir + + Compartir… Abrir en %1$s @@ -275,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Guardar + + Guardar esta página en marcadores + + Editar marcador + + Guardar como PDF… + + Activar la vista de lector + + Desactivar la vista de lector + + Traducir página… + + Traducido al %1$s + + Imprimir… + No hay extensiones aquí @@ -371,8 +407,6 @@ Aviso de privacidad de Firefox - - Aprende más en nuestra política de privacidad Nos encanta mantenerte a salvo Idioma - Traducción + Traducción + + Traducciones Elección de datos @@ -654,10 +690,6 @@ Requerido Opcional - - Leer y cambiar datos del sitio web - - Eliminar sitio web Permitir para todos los sitios @@ -686,7 +718,9 @@ Regresar a - Marcadores recientes + Marcadores recientes + + Marcadores Visitados recientemente @@ -781,8 +815,6 @@ Historial Marcadores - - Credenciales Contraseñas @@ -809,8 +841,6 @@ and the third is the device model. --> %1$s en %2$s %3$s - - Tarjetas de crédito Métodos de pago @@ -826,6 +856,14 @@ Pestaña de %s + + + %1$s pestañas cerradas: %2$d + + Ver pestañas cerradas recientemente + Excepciones @@ -1749,13 +1787,9 @@ Puedes añadir fácilmente este sitio web a tu pantalla de inicio de tu dispositivo para tener acceso instantáneo y navegar rápidamente, consiguiendo una experiencia similar a la de una aplicación real. - - Credenciales y contraseñas Contraseñas - Guardar credenciales y contraseñas - Guardar contraseñas Preguntar si guardar @@ -1770,47 +1804,28 @@ Completa los nombres de usuario y contraseñas en otras aplicaciones de tu dispositivo. - - Añadir conexión - Añadir contraseña - - Sincronizar credenciales Sincronizar contraseñas - - Sincronizar credenciales en todos los dispositivos Sincronizar contraseñas entre dispositivos - - Credenciales guardadas Contraseñas guardadas - Las credenciales que guardas o sincronizas con %s serán mostradas aquí. - Las contraseñas que guardes o sincronices con %s aparecerán aquí. Todas las contraseñas que guardes quedan cifradas. - - Aprender más acerca de Sync. Aprende más acerca de la sincronización Excepciones - - Las credenciales y contraseñas que no son guardadas serán mostradas aquí. %s no guardará las contraseñas de los sitios listados aquí. - - Las credenciales y contraseñas no serán guardadas para estos sitios. %s no guardará las contraseñas para estos sitios. Eliminar todas las excepciones - - Buscar credenciales Buscar contraseñas @@ -1839,17 +1854,11 @@ Mostrar contraseña Ocultar contraseña - - Desbloquea para ver tus credenciales guardadas Desbloquea para ver tus contraseñas guardadas - Asegura tus credenciales y contraseñas - Asegura tus contraseñas guardadas - Configura un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus credenciales y contraseñas guardadas, y así no sean accedidas si alguien más tiene tu dispositivo. - Configura un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus contraseñas guardadas, y así no sean accedidas si alguien más tiene tu dispositivo. Más tarde @@ -1867,8 +1876,6 @@ Nombre (A-Z) Último uso - - Menú para ordenar credenciales Menú ordenar contraseñas @@ -1878,41 +1885,27 @@ Autollenado Direcciones - - Tarjetas de crédito Métodos de pago - Guardar y autocompletar tarjetas - Guardar y completar métodos de pago - - Los datos están encriptados %s cifra todos los métodos de pago que guardas Sincronizar tarjetas entre dispositivos Sincronizar tarjetas - - Añadir tarjeta de crédito Añadir tarjeta - - Gestionar tarjetas guardadas Gestionar tarjetas Añadir dirección Administrar direcciones - - Guardar y autocompletar direcciones Guardar y completar direcciones - - Incluye información como números, correos electrónicos y direcciones Incluye números de teléfono y direcciones de correo electrónico @@ -1936,8 +1929,6 @@ Eliminar tarjeta - ¿De verdad quieres eliminar esta tarjeta de crédito? - ¿Eliminar tarjeta? Eliminar @@ -1951,24 +1942,15 @@ Tarjetas guardadas - - Por favor, ingresa un número de tarjeta de crédito válido - Ingresa un número de tarjeta válido - - Complete este campo Añadir un nombre Desbloquea para ver tus tarjetas guardadas - Asegura tus tarjetas de crédito - Asegura tus métodos de pago guardados - Configura un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus tarjetas de crédito guardadas, y así no sean accedidas si alguien más tiene tu dispositivo. - Configura un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus métodos de pago guardados, y así no sean accedidos si alguien más tiene tu dispositivo. Configurar ahora @@ -1976,8 +1958,6 @@ Más tarde Desbloquea tu dispositivo - - Desbloquea para usar información de la tarjeta de crédito almacenada Desbloquea para usar métodos de pago guardados @@ -1987,12 +1967,6 @@ Editar dirección Administrar direcciones - - Primer nombre - - Segundo nombre - - Apellidos Nombre @@ -2018,8 +1992,6 @@ Eliminar dirección - - ¿De verdad quieres eliminar esta dirección? ¿Eliminar esta dirección? @@ -2118,49 +2090,29 @@ Eliminar Editar - - ¿De verdad quieres eliminar esta conexión? ¿Estas seguro de eliminar esta contraseña? Eliminar Cancelar - - Opciones de credenciales Opciones de contraseña - - El campo de texto editable para la dirección web de la credencial. El campo de texto editable para la dirección del sitio web. - - El campo de texto editable para el nombre de usuario de la credencial. El campo de texto editable para el nombre de usuario. - El campo de texto editable para la contraseña de la credencial. - El campo de texto editable para la contraseña. - - Guardar cambios a la credencial. Guardar cambios. - - Editar Editar contraseña - - Añadir nueva credencial Añadir contraseña - - Contraseña requerida Ingresar una contraseña - Nombre de usuario requerido - Ingresar un nombre de usuario Nombre de servidor requerido @@ -2508,6 +2460,8 @@ Ahora no Mostrar original + + Página original sin traducir cargada Hecho @@ -2565,6 +2519,9 @@ Cerrar hoja de traducciones + + Algunos ajustes no están disponibles temporalmente. + Traducciones @@ -2587,6 +2544,9 @@ Selecciona un idioma para administrar las preferencias de ”traducir siempre“ y ”nunca traducir“. + + No se pudieron cargar idiomas. Por favor, vuelve más tarde. + Ofrecer traducción (predeterminado) @@ -2609,6 +2569,8 @@ Eliminar %1$s + + No se pudieron cargar sitios. Por favor, vuelve más tarde. ¿Eliminar %1$s? @@ -2686,13 +2648,18 @@ Navegar hacia atrás + + Abrir cajón de depuración + Herramientas de pestaña Recuento de pestañas - Activa + Activa + + Activo Inactiva @@ -2703,6 +2670,16 @@ Herramienta de creación de pestañas Cantidad de pestañas a crear + + El campo de texto está vacío + + Por favor, ingresa solo números enteros positivos + + Por favor, introduce un número mayor que cero + + Se superó el número máximo de pestañas (%1$s) que se pueden generar en una operación Añadir a pestañas activas @@ -2719,11 +2696,11 @@ Política de privacidad - Enviar + Enviar - Cerrar + Cerrar - ¡Gracias por tus comentarios! + ¡Gracias por tus comentarios! Muy satisfecho @@ -2735,6 +2712,14 @@ Muy insatisfecho + + + Abrir encuesta + + Cerrar encuesta + + Cerrar + Conexiones diff --git a/mobile/android/fenix/app/src/main/res/values-es-rES/strings.xml b/mobile/android/fenix/app/src/main/res/values-es-rES/strings.xml index 5896187516..42ee948d2a 100644 --- a/mobile/android/fenix/app/src/main/res/values-es-rES/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-es-rES/strings.xml @@ -51,12 +51,20 @@ - Guardado recientemente + Guardado recientemente - Mostrar todos los marcadores guardados + Mostrar todos los marcadores guardados - Eliminar + Eliminar + + + + Marcadores + + Mostrar todos los marcadores + + Eliminar %1$s es producido por Mozilla. @@ -194,6 +202,10 @@ Complementos Extensiones + + Gestionar extensiones + + Descubrir más extensiones Información de la cuenta @@ -212,6 +224,8 @@ Abrir en una pestaña normal Agregar a la pantalla de Inicio + + Añadir a la pantalla de inicio… Instalar @@ -223,9 +237,13 @@ Traducir página + Guardar en colección… + Guardar en la colección Compartir + + Compartir… Abrir en %1$s @@ -280,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Guardar + + Añadir esta página a marcadores + + Editar marcador + + Guardar como PDF… + + Activar vista de lectura + + Desactivar vista de lectura + + Traducir página… + + Traducido al %1$s + + Imprimir… + No hay extensiones aquí @@ -377,8 +413,6 @@ Aviso de privacidad de Firefox - - Saber más en nuestro aviso de privacidad Nos encanta mantenerte a salvo Idioma - Traducción + Traducción + + Traducciones Elección de datos @@ -662,10 +698,6 @@ Requerido Opcional - - Leer y cambiar los datos de sitios web - - Eliminar sitio web Permitir para todos los sitios @@ -695,7 +727,9 @@ Volver a esta pestaña - Marcadores recientes + Marcadores recientes + + Marcadores Visitados recientemente @@ -789,8 +823,6 @@ Historial Marcadores - - Inicios de sesión Contraseñas @@ -817,8 +849,6 @@ and the third is the device model. --> %1$s en %2$s %3$s - - Tarjetas de crédito Métodos de pago @@ -836,6 +866,14 @@ Pestaña de %s + + + %1$s pestañas cerradas: %2$d + + Ver pestañas cerradas recientemente + Excepciones @@ -1786,13 +1824,9 @@ Puedes añadir fácilmente este sitio web a la pantalla de inicio de tu dispositivo para tener acceso instantáneo y navegar rápidamente, consiguiendo una experiencia similar a la de una aplicación real. - - Inicios de sesión y contraseñas Contraseñas - Guardar inicios de sesión y contraseñas - Guardar contraseñas Preguntar antes de guardar @@ -1809,46 +1843,27 @@ Completar nombres de usuarios y contraseñas en otras aplicaciones de tu dispositivo. - - Añadir cuenta - Añadir contraseña - - Inicios de sesión sincronizados Sincronizar contraseñas - - Sincronizar inicios de sesión entre dispositivos Sincronizar contraseñas entre dispositivos - - Inicios de sesión guardados Contraseñas guardadas - Los inicios de sesión que guardes o sincronices con %s se mostrarán aquí. - Las contraseñas que guardes o sincronices con %s aparecerán aquí. Todas las contraseñas que guardes quedan cifradas. - - Saber más sobre Sync. Descubre más sobre Sync Excepciones - - Los inicios de sesión y contraseñas no guardados aparecerán aquí. %s no guardará contraseñas para los sitios que se listen aquí. - - No se guardarán los inicios de sesión y contraseñas para estos sitios. %s no guardará las contraseñas para estos sitios. Eliminar todas las excepciones - - Buscar inicios de sesión Buscar contraseñas @@ -1877,17 +1892,11 @@ Mostrar contraseña Ocultar contraseña - - Desbloquear para ver tus inicios de sesión guardados Desbloquea para ver tus contraseñas guardadas - Asegurar tus usuarios y contraseñas - Asegura tus contraseñas guardadas - Configura un patrón de bloqueo del dispositivo, un PIN o una contraseña para proteger el acceso a tus usuarios y contraseñas guardados si alguien más tiene tu dispositivo. - Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus contraseñas guardadas y evitar que sean accedidas por otras personas en caso de que alguien más tenga tu dispositivo. Más tarde @@ -1906,9 +1915,6 @@ Usado por última vez - - Ordenar menú de inicio de sesión - Menú ordenar contraseñas @@ -1917,16 +1923,10 @@ Autocompletado Direcciones - - Tarjetas de crédito Métodos de pago - Guardar y autocompletar tarjetas - Guardar y completar métodos de pago - - Los datos están cifrados %s cifra todos los métodos de pago que guardes @@ -1934,25 +1934,17 @@ Sincronizar tarjetas entre dispositivos Sincronizar tarjetas - - Añadir tarjeta de crédito Añadir tarjeta - - Administrar tarjetas guardadas Administrar tarjetas Añadir dirección Administrar direcciones - - Guardar y autocompletar direcciones Guardar y completar direcciones - - Incluir información como números, correos electrónicos y direcciones de envío Incluye números de teléfono y direcciones de correo electrónico @@ -1976,8 +1968,6 @@ Eliminar tarjeta - ¿Seguro que quieres eliminar esta tarjeta de crédito? - ¿Eliminar tarjeta? Eliminar @@ -1991,24 +1981,15 @@ Tarjetas guardadas - - Por favor, escriba un número válido de tarjeta de crédito - Introduce un número de tarjeta válido - - Por favor, rellena este campo Añadir un nombre Desbloquear para ver tus tarjetas guardadas - Asegurar tus tarjetas de crédito - Protege tus métodos de pago guardados - Configura un patrón de bloqueo, PIN o contraseña para proteger el acceso a tus tarjetas guardadas si alguien más accede a tu dispositivo. - Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus métodos de pago guardados y evitar que sean accedidos por otras personas en caso de que alguien más tenga tu dispositivo. Configurar ahora @@ -2016,8 +1997,6 @@ Más tarde Desbloquear tu dispositivo - - Desbloquear para usar la información de la tarjeta de crédito almacenada Desbloquea para utilizar métodos de pago guardados @@ -2027,12 +2006,6 @@ Editar dirección Administrar direcciones - - Nombre - - Segundo nombre - - Apellidos Nombre @@ -2058,8 +2031,6 @@ Eliminar dirección - - ¿Seguro que quieres eliminar esta dirección? ¿Eliminar esta dirección? @@ -2159,49 +2130,29 @@ Eliminar Editar - - ¿Seguro que quieres eliminar este inicio de sesión? ¿Estás seguro de que quieres eliminar esta contraseña? Eliminar Cancelar - - Opciones de inicio de sesión Opciones de contraseña - - El campo de texto editable para la dirección web del inicio de sesión. El campo de texto editable para la dirección del sitio web. - - El campo de texto editable para el nombre de usuario del inicio de sesión. El campo de texto editable para el nombre de usuario. - El campo de texto editable para la contraseña del inicio de sesión. - El campo de texto editable para la contraseña. - - Guardar cambios para el inicio de sesión. Guardar cambios. - - Editar Editar contraseña - - Añadir nueva cuenta Añadir contraseña - - Se necesita contraseña Introduce una contraseña - Se requiere nombre de usuario - Introduce un nombre de usuario Se requiere nombre de servidor @@ -2549,6 +2500,8 @@ Ahora no Mostrar original + + Se ha cargado la página original sin traducir Hecho @@ -2606,6 +2559,9 @@ Cerrar hoja de traducciones + + Algunos ajustes no están disponibles temporalmente. + Traducciones @@ -2628,6 +2584,9 @@ Selecciona un idioma para administrar las preferencias de ”traducir siempre“ y ”no traducir nunca“. + + No se han podido cargar los idiomas. Inténtalo de nuevo más tarde. + Ofrecer traducción (predeterminado) @@ -2651,6 +2610,8 @@ Eliminar %1$s + + No se han podido cargar los sitios. Inténtalo de nuevo más tarde. ¿Eliminar %1$s? @@ -2728,13 +2689,18 @@ Ir a la página anterior + + Abrir panel de depuración + Herramientas de pestañas Número de pestañas - Activa + Activa + + Activo Inactiva @@ -2745,6 +2711,16 @@ Herramienta de creación de pestañas Cantidad de pestañas a crear + + El campo de texto está vacío + + Introduce solo números enteros positivos + + Introduce un número mayor que cero + + Se ha superado el número máximo de pestañas (%1$s) que se pueden generar en una operación Añadir a pestañas activas @@ -2761,11 +2737,11 @@ Aviso de privacidad - Enviar + Enviar - Cerrar + Cerrar - ¡Gracias por tu opinión! + ¡Gracias por tu opinión! Muy satisfecho @@ -2777,6 +2753,14 @@ Muy insatisfecho + + + Abrir la encuesta + + Cerrar la encuesta + + Cerrar + Inicios de sesión diff --git a/mobile/android/fenix/app/src/main/res/values-es/strings.xml b/mobile/android/fenix/app/src/main/res/values-es/strings.xml index 5896187516..42ee948d2a 100644 --- a/mobile/android/fenix/app/src/main/res/values-es/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-es/strings.xml @@ -51,12 +51,20 @@ - Guardado recientemente + Guardado recientemente - Mostrar todos los marcadores guardados + Mostrar todos los marcadores guardados - Eliminar + Eliminar + + + + Marcadores + + Mostrar todos los marcadores + + Eliminar %1$s es producido por Mozilla. @@ -194,6 +202,10 @@ Complementos Extensiones + + Gestionar extensiones + + Descubrir más extensiones Información de la cuenta @@ -212,6 +224,8 @@ Abrir en una pestaña normal Agregar a la pantalla de Inicio + + Añadir a la pantalla de inicio… Instalar @@ -223,9 +237,13 @@ Traducir página + Guardar en colección… + Guardar en la colección Compartir + + Compartir… Abrir en %1$s @@ -280,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Guardar + + Añadir esta página a marcadores + + Editar marcador + + Guardar como PDF… + + Activar vista de lectura + + Desactivar vista de lectura + + Traducir página… + + Traducido al %1$s + + Imprimir… + No hay extensiones aquí @@ -377,8 +413,6 @@ Aviso de privacidad de Firefox - - Saber más en nuestro aviso de privacidad Nos encanta mantenerte a salvo Idioma - Traducción + Traducción + + Traducciones Elección de datos @@ -662,10 +698,6 @@ Requerido Opcional - - Leer y cambiar los datos de sitios web - - Eliminar sitio web Permitir para todos los sitios @@ -695,7 +727,9 @@ Volver a esta pestaña - Marcadores recientes + Marcadores recientes + + Marcadores Visitados recientemente @@ -789,8 +823,6 @@ Historial Marcadores - - Inicios de sesión Contraseñas @@ -817,8 +849,6 @@ and the third is the device model. --> %1$s en %2$s %3$s - - Tarjetas de crédito Métodos de pago @@ -836,6 +866,14 @@ Pestaña de %s + + + %1$s pestañas cerradas: %2$d + + Ver pestañas cerradas recientemente + Excepciones @@ -1786,13 +1824,9 @@ Puedes añadir fácilmente este sitio web a la pantalla de inicio de tu dispositivo para tener acceso instantáneo y navegar rápidamente, consiguiendo una experiencia similar a la de una aplicación real. - - Inicios de sesión y contraseñas Contraseñas - Guardar inicios de sesión y contraseñas - Guardar contraseñas Preguntar antes de guardar @@ -1809,46 +1843,27 @@ Completar nombres de usuarios y contraseñas en otras aplicaciones de tu dispositivo. - - Añadir cuenta - Añadir contraseña - - Inicios de sesión sincronizados Sincronizar contraseñas - - Sincronizar inicios de sesión entre dispositivos Sincronizar contraseñas entre dispositivos - - Inicios de sesión guardados Contraseñas guardadas - Los inicios de sesión que guardes o sincronices con %s se mostrarán aquí. - Las contraseñas que guardes o sincronices con %s aparecerán aquí. Todas las contraseñas que guardes quedan cifradas. - - Saber más sobre Sync. Descubre más sobre Sync Excepciones - - Los inicios de sesión y contraseñas no guardados aparecerán aquí. %s no guardará contraseñas para los sitios que se listen aquí. - - No se guardarán los inicios de sesión y contraseñas para estos sitios. %s no guardará las contraseñas para estos sitios. Eliminar todas las excepciones - - Buscar inicios de sesión Buscar contraseñas @@ -1877,17 +1892,11 @@ Mostrar contraseña Ocultar contraseña - - Desbloquear para ver tus inicios de sesión guardados Desbloquea para ver tus contraseñas guardadas - Asegurar tus usuarios y contraseñas - Asegura tus contraseñas guardadas - Configura un patrón de bloqueo del dispositivo, un PIN o una contraseña para proteger el acceso a tus usuarios y contraseñas guardados si alguien más tiene tu dispositivo. - Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus contraseñas guardadas y evitar que sean accedidas por otras personas en caso de que alguien más tenga tu dispositivo. Más tarde @@ -1906,9 +1915,6 @@ Usado por última vez - - Ordenar menú de inicio de sesión - Menú ordenar contraseñas @@ -1917,16 +1923,10 @@ Autocompletado Direcciones - - Tarjetas de crédito Métodos de pago - Guardar y autocompletar tarjetas - Guardar y completar métodos de pago - - Los datos están cifrados %s cifra todos los métodos de pago que guardes @@ -1934,25 +1934,17 @@ Sincronizar tarjetas entre dispositivos Sincronizar tarjetas - - Añadir tarjeta de crédito Añadir tarjeta - - Administrar tarjetas guardadas Administrar tarjetas Añadir dirección Administrar direcciones - - Guardar y autocompletar direcciones Guardar y completar direcciones - - Incluir información como números, correos electrónicos y direcciones de envío Incluye números de teléfono y direcciones de correo electrónico @@ -1976,8 +1968,6 @@ Eliminar tarjeta - ¿Seguro que quieres eliminar esta tarjeta de crédito? - ¿Eliminar tarjeta? Eliminar @@ -1991,24 +1981,15 @@ Tarjetas guardadas - - Por favor, escriba un número válido de tarjeta de crédito - Introduce un número de tarjeta válido - - Por favor, rellena este campo Añadir un nombre Desbloquear para ver tus tarjetas guardadas - Asegurar tus tarjetas de crédito - Protege tus métodos de pago guardados - Configura un patrón de bloqueo, PIN o contraseña para proteger el acceso a tus tarjetas guardadas si alguien más accede a tu dispositivo. - Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus métodos de pago guardados y evitar que sean accedidos por otras personas en caso de que alguien más tenga tu dispositivo. Configurar ahora @@ -2016,8 +1997,6 @@ Más tarde Desbloquear tu dispositivo - - Desbloquear para usar la información de la tarjeta de crédito almacenada Desbloquea para utilizar métodos de pago guardados @@ -2027,12 +2006,6 @@ Editar dirección Administrar direcciones - - Nombre - - Segundo nombre - - Apellidos Nombre @@ -2058,8 +2031,6 @@ Eliminar dirección - - ¿Seguro que quieres eliminar esta dirección? ¿Eliminar esta dirección? @@ -2159,49 +2130,29 @@ Eliminar Editar - - ¿Seguro que quieres eliminar este inicio de sesión? ¿Estás seguro de que quieres eliminar esta contraseña? Eliminar Cancelar - - Opciones de inicio de sesión Opciones de contraseña - - El campo de texto editable para la dirección web del inicio de sesión. El campo de texto editable para la dirección del sitio web. - - El campo de texto editable para el nombre de usuario del inicio de sesión. El campo de texto editable para el nombre de usuario. - El campo de texto editable para la contraseña del inicio de sesión. - El campo de texto editable para la contraseña. - - Guardar cambios para el inicio de sesión. Guardar cambios. - - Editar Editar contraseña - - Añadir nueva cuenta Añadir contraseña - - Se necesita contraseña Introduce una contraseña - Se requiere nombre de usuario - Introduce un nombre de usuario Se requiere nombre de servidor @@ -2549,6 +2500,8 @@ Ahora no Mostrar original + + Se ha cargado la página original sin traducir Hecho @@ -2606,6 +2559,9 @@ Cerrar hoja de traducciones + + Algunos ajustes no están disponibles temporalmente. + Traducciones @@ -2628,6 +2584,9 @@ Selecciona un idioma para administrar las preferencias de ”traducir siempre“ y ”no traducir nunca“. + + No se han podido cargar los idiomas. Inténtalo de nuevo más tarde. + Ofrecer traducción (predeterminado) @@ -2651,6 +2610,8 @@ Eliminar %1$s + + No se han podido cargar los sitios. Inténtalo de nuevo más tarde. ¿Eliminar %1$s? @@ -2728,13 +2689,18 @@ Ir a la página anterior + + Abrir panel de depuración + Herramientas de pestañas Número de pestañas - Activa + Activa + + Activo Inactiva @@ -2745,6 +2711,16 @@ Herramienta de creación de pestañas Cantidad de pestañas a crear + + El campo de texto está vacío + + Introduce solo números enteros positivos + + Introduce un número mayor que cero + + Se ha superado el número máximo de pestañas (%1$s) que se pueden generar en una operación Añadir a pestañas activas @@ -2761,11 +2737,11 @@ Aviso de privacidad - Enviar + Enviar - Cerrar + Cerrar - ¡Gracias por tu opinión! + ¡Gracias por tu opinión! Muy satisfecho @@ -2777,6 +2753,14 @@ Muy insatisfecho + + + Abrir la encuesta + + Cerrar la encuesta + + Cerrar + Inicios de sesión diff --git a/mobile/android/fenix/app/src/main/res/values-eu/strings.xml b/mobile/android/fenix/app/src/main/res/values-eu/strings.xml index cd905135c3..80955bc9b8 100644 --- a/mobile/android/fenix/app/src/main/res/values-eu/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-eu/strings.xml @@ -49,12 +49,20 @@ - Gordetako azkenak + Gordetako azkenak - Erakutsi gordetako laster-marka guztiak + Erakutsi gordetako laster-marka guztiak - Kendu + Kendu + + + + Laster-markak + + Erakutsi laster-marka guztiak + + Kendu %1$s Mozillak egina da. @@ -144,8 +152,10 @@ Fitxa pribatu berria - - Pasahitzen lasterbidea + + Pasahitzak + + Pasahitzen lasterbidea @@ -186,11 +196,17 @@ Gelditu - Gehigarriak + Gehigarriak + + Hedapenak + + Kudeatu hedapenak + + Deskubritu hedapen gehiago Kontuaren informazioa - Gehigarririk ez hemen + Gehigarririk ez hemen Laguntza @@ -205,19 +221,27 @@ Ireki fitxa arruntean Gehitu hasierako pantailan + + Gehitu hasierako pantailan… - Instalatu + Instalatu Sinkronizatu berriro Bilatu orrian + + Bilatu orrian… Itzuli orria + Gorde bilduman… + Gorde bilduman Partekatu + + Partekatu… Ireki %1$s nabigatzailean @@ -246,6 +270,51 @@ Pertsonalizatu hasiera-orria + + Hasi saioa + + + Sinkronizatu pasahitzak, fitxak eta gehiago + + Hasi berriro saioa sinkronizatzeko + + Sinkronizazioa pausatuta + + Fitxa pribatu berria + + Pasahitzak + + Berria %1$s(e)n + + Aldatu mahaigaineko gunera + + Tresnak + + Gorde + + Egin orriaren laster-marka + + Editatu laster-marka + + Gorde PDF gisa… + + Aktibatu irakurtzeko ikuspegia + + Desaktibatu irakurtzeko ikuspegia + + Itzuli orria… + + %1$s hizkuntzara itzulita + + Inprimatu… + + + + Hedapenik ez hemen + Hasiera-pantaila @@ -551,6 +620,10 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Birkonektatu sinkronizatzeari berrekiteko Hizkuntza + + Itzulpena + + Itzulpenak Datu-aukerak @@ -603,10 +676,14 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Aplikaziotik irteten aldaketak aplikatzeko… - Gehigarriak + Gehigarriak + + Hedapenak - Instalatu gehigarria fitxategitik + Instalatu gehigarria fitxategitik + + Instalatu hedapena fitxategitik Jakinarazpenak @@ -615,9 +692,25 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Ez dago baimenduta + + + Beharrezkoa + + Aukerakoa + + Webguneetako datuak irakurri eta aldatzea + + Ezabatu webgunea + + Baimendu gune guztietarako + + Hedapena fidagarritzat baduzu, webgune guztietarako baimena eman diezaiokezu. + - Gehigarrien bilduma pertsonalizatua + Gehigarrien bilduma pertsonalizatua + + Hedapenen bilduma pertsonalizatua Ados @@ -627,13 +720,18 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Bildumaren jabea (erabiltzaile-IDa) - Gehigarrien bilduma aldatu egin da. Aplikaziotik irteten aldaketak aplikatzeko… + Gehigarrien bilduma aldatu egin da. Aplikaziotik irteten aldaketak aplikatzeko… + + + Hedapenen bilduma aldatu egin da. Aplikaziotik irteten aldaketak aplikatzeko… Itzuli zeudenera - Azken laster-markak + Azken laster-markak + + Laster-markak Bisitatutako azkenak @@ -682,21 +780,37 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. - Gehigarri berriak erabilgarri orain + Gehigarri berriak erabilgarri orain + + Hedapen berriak erabilgarri orain Eman begiratua Firefox zure egiten lagunduko dizuten 100 gehigarri berri baino gehiagori. - Esploratu gehigarriak + Esploratu gehigarriak - + + Arakatu hedapenak + + - Gehigarriak aldi baterako desgaitu dira + Gehigarriak aldi baterako desgaitu dira + + Hedapenak aldi baterako desgaitu dira - Gehigarri bat edo gehiago ez dabil eta zure sistema desegonkortzen ari da. %1$s saiatu da gehigarriak berrabiarazten, arrakastarik gabe.\n\nEz da gehigarririk berrabiaraziko zure uneko saioan.\n\nGeghiarriak kendu edo desgaitzeak arazoa konpon lezake. + Gehigarri bat edo gehiago ez dabil eta zure sistema desegonkortzen ari da. %1$s saiatu da gehigarriak berrabiarazten, arrakastarik gabe.\n\nEz da gehigarririk berrabiaraziko zure uneko saioan.\n\nGeghiarriak kendu edo desgaitzeak arazoa konpon lezake. + + Hedapen bat edo gehiago ez dabil eta zure sistema desegonkortzen ari da. %1$s saiatu da hedapena(k) berrabiarazten, arrakastarik gabe.\n\nEz da hedapenik berrabiaraziko zure uneko saioan.\n\nHedapenak kendu edo desgaitzeak arazoa konpon lezake. - Probatu gehigarriak berrabiarazten + Probatu gehigarriak berrabiarazten + + Saiatu hedapenak berrabiarazten - Jarraitu gehigarriak desgaituta + Jarraitu gehigarriak desgaituta + + + Jarraitu hedapenak desgaituta @@ -757,6 +871,14 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. %s gailuko fitxa + + + %1$s fitxa itxita: %2$d + + Ikusi itxitako azken fitxak + Salbuespenak @@ -2443,10 +2565,14 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Itzuli hemendik Itzuli hona + + Probatu beste iturburu-hizkuntza bat Une honetan ez Erakutsi jatorrizkoa + + Jatorrizko itzuli gabeko orria kargatuta Eginda @@ -2467,7 +2593,7 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Barkatu, %1$s ez dugu onartzen oraindik. - Argibide gehiago + Argibide gehiago Itzultzen… @@ -2481,7 +2607,9 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. - Itzulpenen aukerak + Itzulpenen aukerak + + Itzulpenen aukerak Eskaini beti itzultzea @@ -2500,6 +2628,9 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. %1$s(e)ko itzulpenei buruz + + Itxi itzulpenen orria + Itzulpenak @@ -2604,6 +2735,8 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Deskargatu datuak aurrezteko moduan egotean (%1$s)? Hizkuntzak erdizka deskargatzen ditugu zure cachera itzulpenak pribatu manten daitezen. + + Hizkuntzak erdizka deskargatzen ditugu itzulpenak pribatu manten daitezen. Deskargatu beti datuak aurrezteko moduan @@ -2618,12 +2751,19 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Arazketa-tresnak Nabigatu atzera + + + Ireki arazketarako menu lerrakorra + + Fitxen tresnak Fitxa kopurua - Aktibo + Aktibo + + Aktibo Inaktibo @@ -2640,4 +2780,39 @@ zure pasahitzak, laster-markak eta gehiago zifratzen du. Gehitu fitxa inaktiboetara Gehitu fitxa pribatuetara + + + + + Jarraitu + + Burutu inkesta + + Pribatutasun-oharra + + Bidali + + Itxi + + Eskerrik asko zure iritziagatik! + + Oso kontent + + Kontent + + Neutrala + + Kontentagaitz + + Oso kontentagaitz + + + + Saio-hasierak + + Uneko domeinua: %s + + Gehitu saio-hasiera faltsua domeinu honetarako + + Ezabatu %s erabiltzaile-izena duen saio-hasiera diff --git a/mobile/android/fenix/app/src/main/res/values-fi/strings.xml b/mobile/android/fenix/app/src/main/res/values-fi/strings.xml index aa7be14543..004d8494cd 100644 --- a/mobile/android/fenix/app/src/main/res/values-fi/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-fi/strings.xml @@ -201,6 +201,10 @@ Lisäosat Laajennukset + + Hallitse laajennuksia + + Löydä lisää laajennuksia Tilin tiedot @@ -219,6 +223,8 @@ Avaa tavallisessa välilehdessä Lisää aloitusnäytölle + + Lisää aloitusnäytölle… Asenna @@ -230,10 +236,14 @@ Käännä sivu + Tallenna kokoelmaan… + Tallenna kokoelmaan Jaa + + Jaa… Avaa sovelluksessa %1$s @@ -288,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Tallenna + + Lisää tämä sivu kirjanmerkkeihin + + Muokkaa kirjanmerkkiä + + Tallenna PDF-muodossa… + + Avaa lukunäkymä + + Sulje lukunäkymä + + Käännä sivu… + + Käännetty kielelle %1$s + + Tulosta… + Täällä ei ole laajennuksia @@ -385,8 +413,6 @@ Firefoxin tietosuojakäytäntö - - Lue lisää tietosuojakäytännöstämme Turvaamisesi on tärkeää meille Kieli - Käännös + Käännös + + Käännökset Tietovalinnat @@ -670,10 +698,6 @@ Pakollinen Valinnainen - - Lue ja muuta verkkosivuston dataa - - Poista verkkosivusto Salli kaikille sivustoille @@ -799,8 +823,6 @@ Historia Kirjanmerkit - - Kirjautumistiedot Salasanat @@ -827,8 +849,6 @@ and the third is the device model. --> %1$s laitteella %2$s %3$s - - Luottokortit Maksutavat @@ -844,6 +864,14 @@ Välilehti laitteesta %s + + + %1$s-välilehtiä suljettu: %2$d + + Näytä viimeksi suljetut välilehdet + Poikkeukset @@ -1782,13 +1810,9 @@ Voit lisätä tämän sivuston laitteesi aloitusnäytölle, jolloin sivuston käyttö onnistuu nopeasti ja tarjoaa sovelluksen kaltaisen kokemuksen. - - Käyttäjätunnukset ja salasanat Salasanat - Tallenna käyttäjätunnukset ja salasanat - Tallenna salasanat Kysy tallennusta @@ -1804,46 +1828,27 @@ Täytä käyttäjätunnukset ja salasanat muissa laitteesi sovelluksissa. - - Lisää kirjautumistieto - Lisää salasana - - Synkronoi kirjautumistiedot Synkronoi salasanat - - Synkronoi kirjautumistiedot laitteiden välillä Synkronoi salasanat eri laitteiden välillä - - Tallennetut kirjautumistiedot Tallennetut salasanat - %siin tallentamasi tai synkronoimasi kirjautumistiedot näkyvät täällä. - %siin tallentamasi tai synkronoimasi salasanat näkyvät tässä. Kaikki tallentamasi salasanat ovat salattuja. - - Lue lisää Syncista. Lisätietoja synkronoinnista Poikkeukset - - Käyttäjätunnukset ja salanat, joita ei tallenneta, näytetään täällä. %s ei tallenna tässä lueteltujen sivustojen salasanoja. - - Käyttäjätunnuksia ja salasanoja ei tallenneta näille sivustoille. %s ei tallenna näiden sivustojen salasanoja. Poista kaikki poikkeukset - - Etsi kirjautumistiedoista Etsi salasanoja @@ -1872,17 +1877,11 @@ Näytä salasana Piilota salasana - - Avaa lukitus nähdäksesi tallennetut kirjautumistiedot Avaa lukitus nähdäksesi tallennetut salasanat - Suojaa käyttäjätunnuksesi ja salasanasi - Suojaa tallennetut salasanat - Aseta laitteen avaukseen tarkoitettu kuvio, PIN-koodi tai salasana suojataksesi tallennetut kirjautumistiedot ja salasanat siltä varalta, että joku saa laitteesi haltuunsa. - Määritä laitteen lukituskuvio, PIN-koodi tai salasana suojataksesi tallennettuja salasanojasi, jos laitteesi on jollain toisella. Myöhemmin @@ -1900,8 +1899,6 @@ Nimi (A-Ö) Viimeksi käytetty - - Järjestä kirjautumistietojen valikko Salasanojen järjestysvalikko @@ -1911,41 +1908,27 @@ Automaattinen täyttö Osoitteet - - Luottokortit Maksutavat - Tallenna ja täytä kortit automaattisesti - Tallenna ja täytä maksutavat - - Tiedot on salattu %s salaa kaikki tallentamasi maksutavat Synkronoi kortit laitteiden välillä Synkronoi kortit - - Lisää luottokortti Lisää kortti - - Hallinnoi tallennettuja kortteja Hallitse kortteja Lisää osoite Hallitse osoitteita - - Tallenna ja täytä osoitteet automaattisesti Tallenna ja täytä osoitteet - - Sisällytä tiedot kuten numerot, sähköpostiosoitteet ja toimitusosoitteet Sisältää puhelinnumerot ja sähköpostiosoitteet @@ -1969,8 +1952,6 @@ Poista kortti - Haluatko varmasti poistaa tämän luottokortin? - Poistetaanko kortti? Poista @@ -1984,24 +1965,15 @@ Tallennetut kortit - - Kirjoita kelvollinen luottokortin numero - Anna kelvollinen kortin numero - - Täytä tämä kenttä Lisää nimi Avaa lukitus nähdäksesi tallennetut kortit - Suojaa luottokorttisi - Suojaa tallennetut maksutavat - Aseta laitteen avaukseen tarkoitettu kuvio, PIN-koodi tai salasana suojataksesi tallennetut luottokorttitiedot siltä varalta, että joku saa laitteesi haltuunsa. - Määritä laitteen lukituskuvio, PIN-koodi tai salasana suojataksesi tallennettuja maksutapojasi, jos laitteesi on jollain toisella. Aseta nyt @@ -2010,9 +1982,6 @@ Avaa laitteen lukitus - - Avaa lukitus käyttääksesi tallennettuja luottokorttitietoja - Avaa lukitus käyttääksesi tallennettuja maksutapoja @@ -2021,12 +1990,6 @@ Muokkaa osoitetta Hallitse osoitteita - - Etunimi - - Toinen nimi - - Sukunimi Nimi @@ -2052,8 +2015,6 @@ Poista osoite - - Haluatko varmasti poistaa tämän osoitteen? Poistetaanko tämä osoite? @@ -2153,49 +2114,29 @@ Poista Muokkaa - - Haluatko varmasti poistaa tämän kirjautumistiedon? Haluatko varmasti poistaa tämän salasanan? Poista Peruuta - - Kirjautumistietojen valinnat Salasanojen asetukset - - Muokattava tekstikenttä kirjautumisen verkkosivua varten. Verkkosivuston muokattava tekstikenttä. - - Muokattava tekstikenttä kirjautumisen käyttäjätunnusta varten. Käyttäjätunnuksen muokattava tekstikenttä. - Muokattava tekstikenttä kirjautumisen salasanaa varten. - Salasanan muokattava tekstikenttä. - - Tallenna muutokset kirjautumistietoihin. Tallenna muutokset. - - Muokkaa Muokkaa salasanaa - - Lisää uusi kirjautumistieto Lisää salasana - - Salasana vaaditaan Kirjoita salasana - Käyttäjätunnus vaaditaan - Kirjoita käyttäjätunnus Isäntänimi vaaditaan @@ -2582,6 +2523,11 @@ Tietoja käännöksistä %1$sissa + + Sulje käännössivu + + Jotkut asetukset eivät ole tilapäisesti käytettävissä. + Käännökset @@ -2606,6 +2552,9 @@ Valitse kieli hallitaksesi "käännä aina"- ja "älä käännä koskaan"-asetuksia. + + Kieliä ei voitu ladata. Tarkista myöhemmin uudelleen. + Tarjoa käännöstä (oletus) @@ -2629,6 +2578,8 @@ Poista %1$s + + Sivustoja ei voitu ladata. Tarkista myöhemmin uudelleen. Poistetaanko %1$s? @@ -2706,13 +2657,18 @@ Siirry taaksepäin + + Avaa vianjäljitysvalikko + Välilehtityökalut Välilehtien määrä - Aktiivinen + Aktiivinen + + Aktiivinen Passiivinen @@ -2723,6 +2679,16 @@ Välilehtien luontityökalu Luotavien välilehtien määrä + + Tekstikenttä on tyhjä + + Anna vain positiivisia kokonaislukuja + + Anna numero, joka on suurempi kuin nolla + + Yhdellä toiminnolla luotavien välilehtien enimmäismäärä (%1$s) täynnä Lisää aktiivisiin välilehtiin @@ -2739,11 +2705,11 @@ Tietosuojakäytäntö - Lähetä + Lähetä - Sulje + Sulje - Kiitos palautteestasi! + Kiitos palautteestasi! Hyvin tyytyväinen @@ -2755,6 +2721,14 @@ Hyvin tyytymätön + + + Avaa kysely + + Sulje kysely + + Sulje + Kirjautumistiedot diff --git a/mobile/android/fenix/app/src/main/res/values-fr/strings.xml b/mobile/android/fenix/app/src/main/res/values-fr/strings.xml index 59aafe7a2d..d39900ddce 100644 --- a/mobile/android/fenix/app/src/main/res/values-fr/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-fr/strings.xml @@ -202,6 +202,10 @@ Modules complémentaires Extensions + + Gérer les extensions + + Découvrir d’autres extensions Informations du compte @@ -220,6 +224,8 @@ Ouvrir dans un onglet classique Ajouter à l’écran d’accueil + + Ajouter à l’écran d’accueil… Installer @@ -231,9 +237,13 @@ Traduire la page + Enregistrer dans une collection… + Enregistrer dans une collection Partager + + Partager… Ouvrir avec %1$s @@ -288,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Enregistrer + + Marquer cette page + + Modifier le marque-page + + Enregistrer en PDF… + + Passer en mode lecture + + Quitter le mode lecture + + Traduire la page… + + Traduit en %1$s + + Imprimer… + Aucune extension disponible @@ -385,8 +413,6 @@ Politique de confidentialité de Firefox - - En savoir plus dans notre politique de confidentialité Votre protection compte pour nous Langues - Traduction + Traduction + + Traductions Choix de données @@ -670,10 +698,6 @@ Obligatoire Facultatif - - Consulter et modifier les données de sites web - - Supprimer le site web Autoriser pour tous les sites @@ -799,8 +823,6 @@ Historique Marque-pages - - Identifiants Mots de passe @@ -827,8 +849,6 @@ and the third is the device model. --> %1$s sur %2$s %3$s - - Cartes bancaires Moyens de paiement @@ -846,6 +866,14 @@ Onglet provenant de %s + + + %2$d onglets fermés sur %1$s + + Afficher les onglets récemment fermés + Exceptions @@ -1797,13 +1825,9 @@ Vous pouvez facilement ajouter ce site à l’écran d’accueil de votre appareil pour y avoir accès directement et naviguer plus rapidement, comme si vous utilisiez une application. - - Identifiants et mots de passe Mots de passe - Enregistrer les identifiants et les mots de passe - Enregistrer les mots de passe Demander pour enregistrer @@ -1819,46 +1843,27 @@ Remplit les noms d’utilisateur et les mots de passe dans d’autres applications sur votre appareil. - - Ajouter un identifiant - Ajouter un mot de passe - - Synchroniser les identifiants Synchroniser les mots de passe - - Synchroniser les identifiants entre vos appareils Synchronisez les mots de passe entre vos appareils - - Identifiants enregistrés Mots de passe enregistrés - Les identifiants que vous enregistrez ou synchronisez avec %s s’afficheront ici. - Les mots de passe que vous enregistrez ou synchronisez avec %s seront répertoriés ici. Tous les mots de passe que vous enregistrez sont chiffrés. - - En savoir plus sur Sync. En savoir plus sur la synchronisation Exceptions - - Les identifiants et les mots de passe qui ne sont pas enregistrés seront affichés ici. %s n’enregistrera pas les mots de passe pour les sites listés ici. - - Les identifiants et les mots de passe ne seront pas enregistrés pour ces sites. %s n’enregistrera pas les mots de passe pour ces sites. Supprimer toutes les exceptions - - Rechercher des identifiants Rechercher des mots de passe @@ -1887,17 +1892,11 @@ Afficher le mot de passe Masquer le mot de passe - - Déverrouillez pour afficher vos identifiants enregistrés Déverrouillez pour afficher vos mots de passe enregistrés - Protégez vos identifiants et mots de passe - Sécurisez les mots de passe enregistrés - Configurez un schéma de verrouillage, un code PIN ou un mot de passe pour protéger vos identifiants de connexion et mots de passe enregistrés pour le cas où quelqu’un accède à votre appareil. - Configurez un schéma de verrouillage, un code PIN ou un mot de passe pour protéger vos mots de passe enregistrés si jamais quelqu’un accède à votre appareil. Plus tard @@ -1914,8 +1913,6 @@ Nom (A-Z) Dernière utilisation - - Menu de tri des identifiants Menu de tri des mots de passe @@ -1925,41 +1922,27 @@ Remplissage automatique Adresses - - Cartes bancaires Moyens de paiement - Enregistrer et remplir automatiquement les cartes - Enregistrer et renseigner les moyens de paiement - - Les données sont chiffrées %s chiffre tous les moyens de paiement que vous enregistrez Synchroniser les cartes entre vos appareils Synchroniser les cartes - - Ajouter une carte bancaire Ajouter une carte - - Gérer les cartes enregistrées Gérer les cartes Ajouter une adresse Gérer les adresses - - Enregistrer et remplir automatiquement les adresses Enregistrer et remplir automatiquement les adresses - - Cela comprend des informations telles que des numéros, des adresses e-mail et des adresses d’expédition Y compris les numéros de téléphone et les adresses e-mail @@ -1983,8 +1966,6 @@ Supprimer la carte - Voulez-vous vraiment supprimer cette carte bancaire ? - Supprimer la carte ? Supprimer @@ -1998,24 +1979,15 @@ Cartes enregistrées - - Veuillez saisir un numéro de carte bancaire valide - Saisissez un numéro de carte valide - - Veuillez compléter ce champ Ajouter un nom Déverrouillez pour afficher vos cartes enregistrées - Protégez vos cartes bancaires - Sécurisez vos moyens de paiement enregistrés - Configurez un schéma de verrouillage, un code PIN ou un mot de passe pour protéger vos cartes bancaires enregistrées si jamais quelqu’un accède à votre appareil. - Configurez un schéma de verrouillage, un code PIN ou un mot de passe pour protéger vos moyens de paiement enregistrés si jamais quelqu’un accède à votre appareil. Configurer maintenant @@ -2023,8 +1995,6 @@ Plus tard Déverrouillez votre appareil - - Déverrouillez pour utiliser les informations de cartes bancaires enregistrées Déverrouillez pour utiliser des moyens de paiement enregistrés @@ -2034,12 +2004,6 @@ Modifier l’adresse Gérer les adresses - - Prénom - - Deuxième prénom - - Nom de famille Nom complet @@ -2065,8 +2029,6 @@ Supprimer l’adresse - - Voulez-vous vraiment supprimer cette adresse ? Supprimer cette adresse ? @@ -2165,49 +2127,29 @@ Supprimer Modifier - - Voulez-vous vraiment supprimer cet identifiant ? Voulez-vous vraiment supprimer ce mot de passe ? Supprimer Annuler - - Options de l’identifiant Options de mot de passe - - Le champ de texte modifiable pour l’adresse web de l’identifiant. Le champ de texte modifiable pour l’adresse du site web. - - Le champ de texte modifiable pour le nom d’utilisateur de l’identifiant. Le champ de texte modifiable pour le nom d’utilisateur. - Le champ de texte modifiable pour le mot de passe de l’identifiant. - Le champ de texte modifiable pour le mot de passe. - - Enregistrez les modifications de l’identifiant. Enregistrer les modifications. - - Modifier Modifier le mot de passe - - Ajouter un nouvel identifiant Ajouter un mot de passe - - Mot de passe requis Saisissez un mot de passe - Le nom d’utilisateur doit être renseigné. - Saisissez un nom d’utilisateur Le nom d’hôte est requis @@ -2617,6 +2559,9 @@ Fermer l’onglet Traductions + + Certains paramètres sont temporairement indisponibles. + Traductions @@ -2640,6 +2585,9 @@ Sélectionnez une langue pour gérer les préférences « Toujours traduire » et « Ne jamais traduire ». + + Impossible de charger les langues. Veuillez réessayer plus tard. + Proposer de traduire (par défaut) @@ -2662,6 +2610,8 @@ Supprimer %1$s + + Impossible de charger des sites. Veuillez réessayer plus tard. Supprimer %1$s ? @@ -2739,13 +2689,18 @@ Revenir + + Ouvrir le panneau de débogage + Outils d’onglets Nombre d’onglets - Actifs + Actifs + + Actif Inactifs @@ -2756,6 +2711,16 @@ Outil de création d’onglets Nombre d’onglets à créer + + Le champ de texte est vide + + Saisissez uniquement des nombres entiers positifs + + Saisissez un nombre supérieur à zéro + + Vous avez dépassé le nombre maximal d’onglets (%1$s) qui peuvent être générés en une seule opération Ajouter aux onglets actifs @@ -2772,11 +2737,11 @@ Politique de confidentialité - Envoyer + Envoyer - Fermer + Fermer - Merci de votre retour ! + Merci de votre retour ! Très satisfait·e @@ -2788,6 +2753,14 @@ Très insatisfait·e + + + Ouvrir le sondage + + Fermer le sondage + + Fermer + Identifiants diff --git a/mobile/android/fenix/app/src/main/res/values-fur/strings.xml b/mobile/android/fenix/app/src/main/res/values-fur/strings.xml index 8f59de5cbf..20676406c0 100644 --- a/mobile/android/fenix/app/src/main/res/values-fur/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-fur/strings.xml @@ -199,6 +199,10 @@ Components adizionâi Estensions + + Gjestìs estensions + + Scuvierç altris estensions Informazions account @@ -217,6 +221,8 @@ Vierç intune schede normâl Zonte a schermade principâl + + Zonte a scherm. di inizi… Instale @@ -228,9 +234,13 @@ Tradûs pagjine + Salve intune racuelte… + Salve intune racuelte Condivît + + Condivît… Vierç in %1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Salve + + Zonte pagjine ai segnelibris + + Modifiche segnelibri + + Salve come PDF… + + Ative Viodude di leture + + Disative Viodude di leture + + Tradûs pagjine… + + Voltade par %1$s + + Stampe… + Nissune estension achì @@ -377,8 +405,6 @@ Informative su la riservatece di Firefox - - Plui informazions te nestre informative su la riservatece Nus plâs tignîti di cont - Traduzion + Traduzion + + Traduzions Sielte di dâts @@ -665,10 +693,6 @@ Obligatori Facoltatîf - - Lei e modificâ i dâts dai sîts web - - Elimine sît web Permet par ducj i sîts @@ -797,8 +821,6 @@ Cronologjie Segnelibris - - Credenziâls Passwords @@ -823,8 +845,6 @@ The first parameter is the application name, the second is the device manufacturer name and the third is the device model. --> %1$s su %2$s %3$s - - Cjartis di credit Metodis di paiament @@ -841,6 +861,14 @@ Schede di %s + + + Schedis di %1$s sieradis: %2$d + + Viôt schedis sieradis di resint + Ecezions @@ -1769,13 +1797,9 @@ Al è pussibil zontâ chest sît web ae schermade principâl dal dispositîf par doprâle cun plui sveltece, tant che e fos une aplicazion. - - Credenziâls e passwords Passwords - Salve credenziâls e passwords - Salve passwords Domande prime di salvâ @@ -1789,48 +1813,30 @@ Compile in automatic in altris aplicazions Compile i nons utent e lis passwords in altris aplicazions dal to dispositîf. - - Zonte credenziâl Zonte password - - Sincronize lis credenziâls Sincronize passwords - - Sincronize lis credenziâls tra dispositîfs Sincronize passwords tra i tiei dispositîfs - - Credenziâls salvadis Passwords salvadis - Lis credenziâls che tu salvis o sincronizis su %s a vignaran fûr achì. - Lis passwords salvadis o sincronizadis su %s a vignaran listadis achì. Dutis lis passwords che tu salvis a vegnin cifradis. - - Plui informazions su Sync. Plui informazions su la sincronizazion Ecezions - - Lis credenziâls e lis passwords che no tu âs salvât a vignaran fûr achì. %s nol salvarà lis passwords pai sîts listâts achì. - - Lis credenziâls e lis passwords no vignaran salvadis par chescj sîts. %s nol salvarà lis passwords par chescj sîts. Elimine dutis lis ecezions - - Cîr credenziâls Cîr tes passwords @@ -1859,17 +1865,11 @@ Mostre password Plate password - - Sbloche par visualizâ lis credenziâls salvadis Sbloche par viodi lis passwords salvadis - Protêç lis tôs credenziâls di acès - Protêç lis passwords salvadis - Configure une secuence di bloc, PIN o password par protezi lis tôs credenziâls e lis passwords salvadis, cussì che se cualchidun altri al varà il to dispositîf nol rivarà a doprâlis. - Configure une secuence di bloc, PIN o password par protezi lis passwords salvadis, cussì che se cualchidun altri al varà il to dispositîf nol rivarà a doprâlis. Plui indenant @@ -1885,8 +1885,6 @@ Non (A-Z) Ultime doprade - - Ordene il menù des credenziâls Menù par ordenâ lis passwords @@ -1896,41 +1894,27 @@ Compilazion automatiche Recapits - - Cjartis di credit Metodis di paiament - - Salve e compile in automatic lis cjartis Salve e compile i metodis di paiament - - I dâts a son cifrâts %s al cifre ducj i metodis di paiaments salvâts Sincronize lis cjartis tra plui dispositîfs Sincronize cjartis di credit - - Zonte cjarte di credit Zonte cjarte - - Gjestìs cjartis salvadis Gjestìs cjartis Zonte recapit Gjestìs recapits - - Salve e compile in automatic i recapits Salve e compile direzions - - Includi informazions come numars, e-mail e recapits di spedizion Inclût numars di telefon e direzions e-mail @@ -1954,8 +1938,6 @@ Elimine cjarte - Eliminâ pardabon cheste cjarte di credit? - Eliminâ la cjarte? Elimine @@ -1967,24 +1949,16 @@ Anule Cjartis salvadis - - Inserìs un numar di cjarte di credit valit Inserìs un numar di cjarte valit - - Compile chest cjamp Zonte un non Sbloche par visualizâ lis cjartis di credit salvadis - - Protêç lis tôs cjartis di credit Protêç i metodis di paiament salvâts - Configure une secuence di bloc, PIN o password par protezi lis tôs cjartis di credit salvadis, cussì che se cualchidun altri al varà il to dispositîf nol rivarà a doprâlis. - Configure une secuence di bloc, PIN o password par protezi i tiei metodis di paiament salvâts, cussì che se cualchidun altri al varà il to dispositîf nol rivarà a doprâju. Configure cumò @@ -1992,8 +1966,6 @@ Plui indenant Sbloche il dispositîf - - Sbloche par doprâ lis informazions des cjartis di credit memorizadis Sbloche par doprâ i metodis di paiament @@ -2002,12 +1974,6 @@ Modifiche recapit Gjestìs recapits - - Non - - Secont non - - Cognon Non @@ -2033,8 +1999,6 @@ Elimine recapit - Eliminâ pardabon chest recapit? - Eliminâ cheste direzion? Elimine @@ -2132,49 +2096,29 @@ Elimine Modifiche - - Eliminâ pardabon cheste credenziâl? Eliminâ pardabon cheste password? Elimine Anule - - Opzions credenziâls Opzions password - - Il cjamp di test modificabil pe direzion web de credenziâl. Il cjamp di test che si pues modificâ pe direzion dal sît web. - - Il cjamp di test modificabil pal non utent de credenziâl. Il cjamp di test che si pues modificâ pal non utent. - Il cjamp di test modificabil pe password de credenziâl. - Il cjamp di test che si pues modificâ pe password. - - Salve lis modifichis ae credenziâl. Salve modifichis. - - Modifiche Modifiche password - - Zonte gnove credenziâl Zonte password - - Password necessarie Inserìs une password - Non utent necessari - Inserìs un non utent Non servidôr necessari @@ -2524,6 +2468,8 @@ No cumò Mostre origjinâl + + Cjariade pagjine origjinâl no tradote Fat @@ -2582,6 +2528,9 @@ Siere il sfuei des traduzions + + Cualchi impostazion no je pal moment disponibile. + Traduzions @@ -2604,6 +2553,9 @@ Selezione une lenghe par gjestî lis preferencis ”tradûs simpri“ e ”no sta tradusi mai“. + + Impussibil cjariâ lis lenghis. Torne controle plui tart. + Propon la traduzion (predefinide) @@ -2626,6 +2578,8 @@ Gjave %1$s + + Impussibil cjariâ i sîts. Torne controle plui tart. Eliminâ %1$s? @@ -2707,13 +2661,18 @@ Torne indaûr + + Vierç scansel pal debug + Struments des schedis Conte des schedis - Ative + Ative + + Ativis Inative @@ -2724,6 +2683,16 @@ Strument pe creazion di schedis Numar di schedis di creâ + + Il cjamp di test al è vueit + + Inserìs dome numars intîrs positîfs + + Inserìs un numar plui grant di zero + + Al è stât superât il numar massim di schedis (%1$s) che al è pussibil gjenerâ cuntune sole operazion Zonte a schedis ativis @@ -2740,11 +2709,11 @@ Informative su la riservatece - Invie + Invie - Siere + Siere - Graciis di vê condividude la tô opinion. + Graciis di vê condividude la tô opinion. Une vore sodisfat(e) @@ -2756,6 +2725,14 @@ Une vore insodisfat(e) + + + Vierç sondaç + + Siere sondaç + + Siere + Credenziâls diff --git a/mobile/android/fenix/app/src/main/res/values-fy-rNL/strings.xml b/mobile/android/fenix/app/src/main/res/values-fy-rNL/strings.xml index 0e0ce000f9..201fbd157c 100644 --- a/mobile/android/fenix/app/src/main/res/values-fy-rNL/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-fy-rNL/strings.xml @@ -200,6 +200,10 @@ Add-ons Utwreidingen + + Utwreidingen beheare + + Mear útwreidingen ûntdekke Accountynformaasje @@ -219,6 +223,8 @@ Iepenje yn gewoan ljepblêd Tafoegje oan startskerm + + Tafoegje oan startskerm… Ynstallearje @@ -230,9 +236,13 @@ Side oersette + Yn kolleksje bewarje… + Yn kolleksje bewarje Diele + + Diele… Iepenje yn %1$s @@ -285,6 +295,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Bewarje + + Blêdwizer foar dizze side meitsje + + Blêdwizer bewurkje + + Bewarje as PDF… + + Lêzerwerjefte ynskeakelje + + Lêzerwerjefte útskeakelje + + Side oersette… + + Oerset nei %1$s + + Ofdrukke… + Gjin útwreidingen hjir @@ -382,8 +410,6 @@ Firefox-privacyferklearring - - Lês mear yn ús privacyferklearring Wy hâlde jo graach feilich Taal - Oersetting + Oersetting + + Oersettingen Gegevenskarren @@ -665,10 +693,6 @@ Fereaske Opsjoneel - - Websitegegevens lêze en wizigje - - Website fuortsmite Tastean foar alle websites @@ -793,8 +817,6 @@ Skiednis Blêdwizers - - Oanmeldingen Wachtwurden @@ -821,8 +843,6 @@ and the third is the device model. --> %1$s op %2$s %3$s - - Creditcards Betellingsmetoaden @@ -838,6 +858,14 @@ Ljepblêd fan %s + + + %1$s-ljepblêden sluten: %2$d + + Koartlyn sluten ljepblêden besjen + Utsûnderingen @@ -1759,13 +1787,9 @@ Jo kinne dizze website ienfâldich oan it startskerm fan jo apparaat tafoegje, om sa daliks tagong te hawwen en flugger te navigearjen mei in app-eftige ûnderfining. - - Oanmeldingen en wachtwurden Wachtwurden - Oanmeldingen en wachtwurden bewarje - Wachtwurden bewarje Freegje om te bewarjen @@ -1780,47 +1804,28 @@ Brûkersnammen en wachtwurden yn oare apps op jo apparaat ynfolje. - - Oanmelding tafoegje - Wachtwurd tafoegje - - Oanmeldingen syngronisearje Wachtwurden syngronisearje - - Oanmeldingen op apparaten syngronisearje Wachtwurden syngronisearje tusken apparaten - - Bewarre oanmeldingen Bewarre wachtwurden - De oanmeldingen dy’t jo bewarje of syngronisearje mei %s wurde hjir toand. - De wachtwurden dy’t jo bewarje of syngronisearje mei %s sille hjir fermeld wurde. Alle wachtwurden dy’t jo bewarje binne fersifere. - - Mear ynfo oer Sync. Mear ynfo oer syngronisaasje Utsûnderingen - - Net-bewarre oanmeldingen en wachtwurden wurde hjir werjûn. %s sil gjin wachtwurden foar de hjir fermelde websites bewarje. - - Oanmeldingen en wachtwurden wurde foar dizze websites net bewarre. %s sil gjin wachtwurden foar dizze websites bewarje. Alle útsûnderingen fuortsmite - - Oanmeldingen sykje Wachtwurden sykje @@ -1849,17 +1854,11 @@ Wachtwurd toane Wachtwurd ferstopje - - Untskoattelje om jo bewarre oanmeldingen te besjen Untskoattelje om jo bewarre wachtwurden te besjen - Befeiligje jo oanmeldingen en wachtwurden - Befeiligje jo bewarre wachtwurden - Stel in beskoattelingspatroan, pinkoade of wachtwurd foar jo apparaat yn om jo bewarre oanmeldingen en wachtwurden te beskermjen tsjin tagong as in oar jo apparaat hat. - Stel in beskoattelingspatroan, pinkoade of wachtwurd foar jo apparaat yn om jo bewarre wachtwurden te beskermjen tsjin tagong as in oar jo apparaat hat. Letter @@ -1876,8 +1875,6 @@ Namme (A-Z) Lêst brûkt - - Menu Oanmeldingen sortearje Wachtwurdmenu sortearje @@ -1887,41 +1884,27 @@ Automatysk ynfolje Adressen - - Creditcards Betellingsmetoaden - Kaarten bewarje en automatysk ynfolje - Betellingsmetoaden bewarje en ynfolje - - Gegevens binne fersifere %s fersiferet alle betellingsmetoaden dy’t jo bewarje Kaarten syngronisearje tusken apparaten Kaarten syngronisearje - - Creditcard tafoegje Kaart tafoegje - - Bewarre kaarten beheare Kaarten beheare Adres tafoegje Adressen beheare - - Adressen bewarje en automatysk ynfolje Adressen bewarje en ynfolje - - Ynformaasje lykas nûmers, e-mail- en ferstjoeradressen tafoegje Ynklusyf telefoannûmers en e-mailadressen @@ -1946,8 +1929,6 @@ Kaart fuortsmite - Binne jo wis dat jo dizze creditcard fuortsmite wolle? - Kaart fuortsmite? Fuortsmite @@ -1961,23 +1942,14 @@ Bewarre kaarten - - Fier in jildich creditkaartnûmer yn - Fier in jildich kaartnûmer yn - - Folje dit fjild yn Foegje in namme ta Untskoattelje om jo bewarre kaarten te besjen - Befeiligje jo creditcards - Befeiligje jo bewarre betelmetoaden - - Stel in beskoattelingspatroan, pinkoade of wachtwurd foar jo apparaat yn om jo bewarre creditcards te beskermjen tsjin tagong as in oar jo apparaat hat. Stel in beskoattelingspatroan, pinkoade of wachtwurd foar jo apparaat yn om jo bewarre betellingsmetoaden te beskermjen tsjin tagong as in oar jo apparaat hat. @@ -1988,9 +1960,6 @@ Untskoattelje jo apparaat - - Untskoattelje om bewarre creditkaartynformaasje te brûken - Untskoattelje om bewarre betellingsmetoaden te brûken @@ -1999,12 +1968,6 @@ Adres bewurkje Adressen beheare - - Foarnamme - - Twadde namme - - Efternamme Namme @@ -2030,8 +1993,6 @@ Adres fuortsmite - - Binne jo wis dat jo dit adres fuortsmite wolle? Dit adres fuortsmite? @@ -2130,49 +2091,29 @@ Fuortsmite Bewurkje - - Binne jo wis dat jo dizze oanmelding fuortsmite wolle? Binne jo wis dat jo dit wachtwurd fuortsmite wolle? Fuortsmite Annulearje - - Oanmeldopsjes Wachtwurdopsjes - - It bewurkbere tekstfjild foar it webadres fan de oanmelding. It bewurkbere tekstfjild foar it websiteadres. - - It bewurkbere tekstfjild foar de brûkersnamme fan de oanmelding. It bewurkbere tekstfjild foar de brûkersnamme. - It bewurkbere tekstfjild foar it wachtwurd fan de oanmelding. - It bewurkbere tekstfjild foar it wachtwurd. - - Wizigingen oan oanmelding bewarje. Wizigingen bewarje. - - Bewurkje Wachtwurd bewurkje - - Nije oanmelding tafoegje Wachtwurd tafoegje - - Wachtwurd fereaske Folje in wachtwurd yn - Brûkersnamme fereaske - Folje in brûkersnamme yn Hostnamme fereaske @@ -2524,6 +2465,8 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen No net Orizjineel toane + + Oarspronklik net-oersette side laden Dien @@ -2580,6 +2523,9 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Blêd Oersettingen slute + + Guon ynstellingen binne tydlik net beskikber. + Oersettingen @@ -2602,6 +2548,9 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Selektearje in taal om de foarkarren ‘Altyd oersette’ en ‘Nea oersette’ te behearen. + + Kin talen net lade. Probearje it letter nochris. + Oanbiede om oer te setten (standert) @@ -2624,6 +2573,8 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen %1$s fuortsmite + + Kin websites net lade. Probearje it letter nochris. %1$s fuortsmite? @@ -2702,13 +2653,18 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Tebek blêdzje + + Debuglade iepenje + Ljepblêdhelpmiddelen Oantal ljepblêden - Aktyf + Aktyf + + Aktyf Ynaktyf @@ -2719,6 +2675,16 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Helpmiddel foar it oanmeitsjen fan ljepblêden Oantal oan te meitsjen ljepblêden + + Tekstfjild is leech + + Fier allinnich positive hiele getallen yn + + Fier in getal grutter dan nul yn + + Dit binne mear as it maksimale oantal ljepblêden (%1$s) dat yn ien bewurking oanmakke wurde kin Tafoegje oan aktive ljepblêden @@ -2735,11 +2701,11 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Privacyferklearring - Yntsjinje + Yntsjinje - Slute + Slute - Tank foar jo kommentaar! + Tank foar jo kommentaar! Hiel tefreden @@ -2751,6 +2717,14 @@ Dizze analyze sil jo allinnich helpe om de beoardielingskwaliteit te beoardielen Hiel ûntefreden + + + Enkête iepenje + + Enkête slute + + Slute + Oanmeldingen diff --git a/mobile/android/fenix/app/src/main/res/values-gl/strings.xml b/mobile/android/fenix/app/src/main/res/values-gl/strings.xml index 10e350b1c0..8f2f036b83 100644 --- a/mobile/android/fenix/app/src/main/res/values-gl/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-gl/strings.xml @@ -196,6 +196,10 @@ Complementos Extensións + + Xestionar as extensións + + Descubre máis extensións Información da conta @@ -214,6 +218,8 @@ Abrir na pestana normal Engadir á páxina de inicio + + Engadir á pantalla de inicio… Instalar @@ -225,9 +231,13 @@ Traducir a páxina + Gardar na colección… + Gardar na colección Compartir + + Compartir… Aberto en %1$s @@ -279,6 +289,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Gardar + + Marcar esta páxina + + Editar marcador + + Gardar como PDF… + + Activa a vista de lector + + Desactiva a vista de lector + + Traducir a páxina… + + Traducido a %1$s + + Imprimir… + Non hai extensións aquí @@ -374,8 +402,6 @@ Política de privacidade de Firefox - - Obtéña máis información no noso aviso de privacidade Encántanos telo seguro Idioma - Tradución + Tradución + + Traducións Escollas de datos @@ -655,10 +683,6 @@ Obrigatorio Opcional - - Le e cambia os datos do sitio web - - Eliminar sitio web Permitir para todos os sitios @@ -783,8 +807,6 @@ Historial Marcadores - - Identificacións Contrasinais @@ -810,8 +832,6 @@ and the third is the device model. --> %1$s en %2$s %3$s - - Tarxetas de crédito Métodos de pago @@ -827,6 +847,14 @@ Lapela de %s + + + %1$s pestanas pechadas: %2$d + + Ver pestanas pechadas recentemente + Excepcións @@ -1747,13 +1775,9 @@ Pode engadir facilmente este sitio web á pantalla de inicio do dispositivo para ter acceso instantáneo e navegar máis rápido cunha experiencia semellante á dunha aplicación. - - Identificacións e contrasinais Contrasinais - Gardar Inicios de sesión e contrasinais - Gardar contrasinais Preguntar para gardar @@ -1768,46 +1792,28 @@ Completado automático noutras aplicacións Encher os nomes de usuario e os contrasinais noutras aplicacións do dispositivo. - - Engadir credenciais Engadir contrasinal - - Sincronizar os inicios de sesión Sincronizar contrasinais - - Sincronizar as credenciais entre dispositivos Sincronizar os contrasinais entre dispositivos - - Identificacións gardadas Contrasinais gardados - Os inicios de sesión que garde ou sincronice co %s aparecerán aquí. - Os contrasinais que garde ou sincronice con %s listaranse aquí. Todos os contrasinais que garde están cifrados. - - Obteña máis información sobre Sync. Obteña máis información sobre Sync Excepcións - - Aquí mostraranse os inicios de sesión e os contrasinais que non se garden. %s non gardará os contrasinais dos sitios que aparecen aquí. - - Non se gardarán os inicios de sesión e os contrasinais para estes sitios. %s non gardará os contrasinais destes sitios. Eliminar todas as excepcións - - Buscar inicios de sesións Buscar contrasinais @@ -1836,17 +1842,11 @@ Mostrar contrasinal Agochar contrasinal - - Desbloquee para ver os seus inicios de sesión gardados Desbloquear para ver os seus contrasinais gardados - Protexa os seus inicios de sesión e contrasinais - Protexa os seus contrasinais gardados - Configure un padrón de bloqueo de dispositivo, PIN ou contrasinal para protexer os accesos e contrasinais gardados de que non sexan accedidos se outra persoa ten o seu dispositivo. - Configure un padrón de bloqueo do dispositivo, un PIN ou un contrasinal para protexer o acceso aos seus contrasinais gardados se outra persoa ten o seu dispositivo. Máis tarde @@ -1862,8 +1862,6 @@ Nome (A-Z) Usado por última vez - - Ordenar o menú de inicio de sesión Menú de ordenar contrasinais @@ -1873,16 +1871,10 @@ Completado automático Enderezos - - Tarxetas de crédito Métodos de pagamento - Gardar e completar automaticamente as tarxetas - Gardar e cubrir os métodos de pago - - Os datos están cifrados %s cifra todos os métodos de pago que garda @@ -1890,12 +1882,8 @@ Sincronizar as tarxetas entre dispositivos Sincronizar as tarxetas - - Engadir unha tarxeta de crédito Engadir a tarxeta - - Xestionar as tarxetas gardadas Xestionar tarxetas @@ -1904,12 +1892,8 @@ Xestionar enderezos - - Gardar e encher automaticamente enderezos Gardar e completar os enderezos - - Inclúe información como números, correo electrónico e enderezos de envío Inclúe números de teléfono e enderezos de correo electrónico @@ -1933,8 +1917,6 @@ Eliminar a tarxeta - Seguro de que quere eliminar esta tarxeta de crédito? - Eliminar a tarxeta? Eliminar @@ -1946,23 +1928,15 @@ Cancelar Tarxetas gardadas - - Introduza un número de tarxeta válido Introduza un número de tarxeta válido - - Cubra este campo Engadir un nome Desbloquee para ver as súas tarxetas gardadas - Asegure as súas tarxetas de crédito - Protexa os seus métodos de pago gardados - Configure un padrón de bloqueo de dispositivo, PIN ou contrasinal para evitar que outra persoa co seu dispositivo poida acceder a elas. - Configure un padrón de bloqueo do dispositivo, un PIN ou un contrasinal para protexer o acceso aos seus métodos de pago gardados se outra persoa ten o seu dispositivo. Configurar agora @@ -1970,8 +1944,6 @@ Máis tarde Desbloquear o dispositivo - - Desbloquee para empregar a información almacenada de tarxetas de crédito Desbloquee para usar os métodos de pago gardados @@ -1980,12 +1952,6 @@ Editar enderezo Xestionar enderezos - - Nome - - Segundo nome - - Apelidos Nome @@ -2011,8 +1977,6 @@ Eliminar enderezo - Seguro de que quere eliminar este enderezo? - Eliminar este enderezo? Eliminar @@ -2112,49 +2076,29 @@ Eliminar Editar - - Confirma que desexa eliminar este inicio de sesión? Seguro que quere eliminar este contrasinal? Eliminar Cancelar - - Opcións de inicio de sesión Opcións de contrasinal - - O campo de texto editábel para o enderezo web do inicio de sesión. O campo de texto editable para o enderezo do sitio web. - - O campo de texto editábel para o nome de usuario do inicio de sesión. O campo de texto editable para o nome de usuario. - O campo de texto editábel para o contrasinal do inicio de sesión. - O campo de texto editable para o contrasinal. - - Gardar os cambios para iniciar sesión. Gardar cambios. - - Editar Editar contrasinal - - Engadir novas credenciais Engadir contrasinal - - Contrasinal obrigatorio Introduza un contrasinal - Requírese un nome de usuario - Introduza un nome de usuario Requírese un nome de servidor @@ -2556,6 +2500,9 @@ Pechar a folla de traducións + + Algunhas opcións de configuración non están dispoñibles temporalmente. + Traducións @@ -2579,6 +2526,9 @@ Seleccionar un idioma para xestionar as preferencias de «traducir sempre» e «nunca traducir». + + Non se puideron cargar os idiomas. Volve a comprobar máis tarde. + Ofrecer traducción (predeterminado) @@ -2602,6 +2552,8 @@ Retirar %1$s + + Non se puideron cargar os sitios. Volve a comprobar máis tarde. Eliminar %1$s? @@ -2679,13 +2631,18 @@ Retroceder no historial + + Abre o caixón de depuración + Ferramentas de lapelas Número de lapelas - Activo + Activo + + Activo Inactivo @@ -2696,6 +2653,16 @@ Ferramenta de creación de lapelas Cantidade de lapelas a crear + + O campo de texto está baleiro + + Introduce só números enteiros positivos + + Introduce un número maior que cero + + Superouse o número máximo de pestanas (%1$s) que se poden xerar nunha soa operación Engadir ás lapelas activas @@ -2712,11 +2679,11 @@ Política de privacidade - Enviar + Enviar - Pechar + Pechar - Grazas pola súa opinión + Grazas pola súa opinión Moi satisfeito @@ -2728,6 +2695,14 @@ Moi insatisfeito + + + Enquisa aberta + + Pecha enquisa + + Pechar + Identificacións diff --git a/mobile/android/fenix/app/src/main/res/values-gn/strings.xml b/mobile/android/fenix/app/src/main/res/values-gn/strings.xml index a83bce8a53..ae524cde42 100644 --- a/mobile/android/fenix/app/src/main/res/values-gn/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-gn/strings.xml @@ -203,6 +203,10 @@ Moĩmbaha Jepysokue + + Eñangareko jepysokuére + + Ejuhuvéta jepysokue Mba’ete marandu @@ -222,6 +226,8 @@ Embojuruja tendayke Embojuaju mba’erechaha ñepyrũgua + + Embojuaju mba’erechaha ñepyrũgua… Mohenda @@ -233,9 +239,13 @@ Emoñe’ẽasa kuatiarogue + Eñongatu mbyatyhápe… + Eñongatu mbyatyhápe Moherakuã + + Moherakuã… Embojuruja %1$s-pe @@ -289,6 +299,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Ñongatu + + Emongurusu ko kuatiarogue + + Embosako’i techaukaha + + Eñongatu PDF ramo… + + Emyandy moñe’ẽha jehecha + + Embogue moñe’ẽha jehecha + + Emoñe’ẽasa kuatiarogue… + + %1$s-pe oñemoñe’ẽasapyre + + Mbokuatia… + Ndaipóri jepysokue ápe @@ -385,8 +413,6 @@ Firefox marandu’i ñemigua - - Eikuaave ore marandu’i ñemigua rupive Rovy’ã eimére tekorosãme Ñe’ẽ - Ñemoñe’ẽasa + Ñemoñe’ẽasa + + Ñemoñe’ẽasa Mba’ekuaarã jeporavo @@ -672,10 +700,6 @@ Jeporavorã - - Emoñe’ẽ ha emoambue ñanduti renda mba’ekuaarã - - Emboguete ñanduti renda Emoneĩ opaite tendápe g̃uarã @@ -807,8 +831,6 @@ Techaukaha - - Tembiapo ñepyrũ Ñe’ẽñemi @@ -835,8 +857,6 @@ and the third is the device model. --> %1$s %2$s %3$s-pe - - Kuatia’atã ñemurã Mba’éicha ehepyme’ẽta @@ -852,6 +872,14 @@ Tendayke %s mba’e + + + %1$s tendayke mbotypyre: %2$d-pe + + Ehecha tendayke oñembotyramóva + Oĩ’ỹva @@ -1803,13 +1831,9 @@ Ikatu embojuaju ko ñanduti renda ne mba’e’oka mba’erechaha ñepyrũgua rehe eike hag̃ua ha eikundaha pya’eve, peteĩ tembiporu’ícharamo. - - Tembiapo ñepyrũ ha ñe’ẽñemi Ñe’ẽñemi - Eñongatu tembiapo ñepyrũ ha ñe’ẽñemi - Eñongatu ñe’ẽñemi Ejerure ñemoneĩ eñongatu hag̃ua @@ -1826,46 +1850,27 @@ Emoĩmba ha eñongatu poruhára réra ha ñe’ẽñemi ambue tembiporu’ípe ne mba’e’oka pegua. - - Embojuaju jeike - Embojuaju ñe’ẽñemi - - Embojuehe tembiapo ñepyrũ Embojuehe ñe’ẽñemi - - Embojuehe tembiapo ñepyrũ mba’e’oka pa’ũme Embojuehe ñe’ẽñemi mba’e’oka pa’ũme - - Tembiapo ñepyrũ ñongatupyre Ñe’ẽñemi ñongatupyre - Ko’ápe ojehecháta tembiapo ñepyrũ eñongatu térã embojuehéva %s ndive. - Umi ñe’ẽñemi eñongatu térã embojuehéva %s ndive ojehecháta ko’ápe. Opaite ñe’ẽñemi eñongatúva ipapapýta. - - Eikuaave Sync rehegua Eikuaave ñembojuehe rehegua Oĩ’ỹva - - Ápe ojehecháta tembiapo ñepyrũ ha ñe’ẽñemi oñeñongatu’ỹva. %s noñongatúi ñe’ẽñemi tendakuéra hysýiva ko’ápe g̃uarãme. - - Noñeñongatumo’ãi tembiapo ñepyrũ ha ñe’ẽñemi ko’ã tendápe g̃uarã. %s noñongatúi ñe’ẽñemi ko’ã tendápe g̃uarã. Emboguete opaite oĩ’ỹva - - Eheka tembiapo ñepyrũ Eheka ñe’ẽñemi @@ -1894,17 +1899,11 @@ Ehechauka ñe’ẽñemi Emokañy ñe’ẽñemi - - Emyandyjey ehecha hag̃ua ne rembiapo ñepyrũ ñongatupyre Embojuruja ehecha hag̃ua ñe’ẽñemi ñongatupyre - Erekokuaa nde poruhára ha ñe’ẽñemi - Emo’ã ne ñe’ẽñemi ñongatupyre - Emboheko pe patrõ jokoháva mba’e’oka pegua, PIN térã ñe’ẽñemi emo’ã hag̃ua nde poruhára ha ñe’ẽñemi ñongatupyrépe jeike ambue orekórõ ne mba’e’oka. - Emboheko mba’e’oka jekokoha rape, PIN térã ñe’ẽñemi emo’ã hag̃ua ñe’ẽñemi ñongatupyre ha emboyke ani hag̃ua oike ambuéva orekórõ ne mba’e’oka. Upéi @@ -1923,8 +1922,6 @@ Téra (A-Z) Jejuru paha - - Emoĩporã poravorã tembiapo ñepyrũgua Emoĩporã ñe’ẽñemi poravorã @@ -1934,43 +1931,29 @@ Myanyhẽ jehegui Kundaharape - - Kuatia’atã ñemurã Mba’éicha ehepyme’ẽta - - Eñongatu ha emyanyhẽ kuatia’atã Eñongatu ha emyanyhẽ mba’éicha ehepyme’ẽta - - Mba’ekuaarã ipapapypa %s ombopapapypa ne ñehepyme’ẽrã ñongatupyre Embojuehe kuatia’atã mba’e’oka pa’ũme Embojuehe kuatia’atã - - Embojuaju kuatia’atã ñemurã Embojuaju kuatia’atã - - Eñangareko kuatia’atã ñongatupyrére Eñangareko kuatia’atã Embojuaju kundaharape Kundaharape ñangareko - - Eñongatu ha emyanyhẽ kundaharape Eñongatu ha emyanyhẽ kundaharape - - Emoĩ marandu ipapapy, ñanduti veve ha kundaharape ñemondorãva Oike pumbyry papapy ha ñanduti veve kundaharape @@ -1994,8 +1977,6 @@ Emboguete kuatia’atã - ¿Emboguesépa añetehápe ko kuatia’atã ñemurã? - ¿Emboguete kuatia’atã? Mboguete @@ -2009,24 +1990,15 @@ Kuatia’atã ñongatupyre - - Ikatúpiko ehai kuatia’atã ñemurã papapy oikóva - Emoinge kuatia’atã papapy oikóva - - Ikatúpiko emyanyhẽ ko kora Embojuaju téra Embojuruja ehecha hag̃ua kuatia’atã ñongatupyre - Emohekorosã nde kuatia’atã ñemurã - Emo’ã ne ñehepyme’ẽrã ñongatupyre - Emboheko peteĩ jekokoha rape, PIN térã ñe’ẽñemi emo’ã hag̃ua nde kuatia’atã ñongatupyre ambue oikeséramo ne mba’e’okápe. - Emboheko mba’e’oka jekokoha rape, PIN térã ñe’ẽñemi emo’ã hag̃ua ñehepyme’ẽrã ñongatupyre ha emboyke ani hag̃ua oike ambuéva orekórõ ne mba’e’oka. Emboheko ko’ág̃a @@ -2035,9 +2007,6 @@ Embojuruja ne mba’e’oka - - Embojuruja eiporu hag̃ua kuatia’atã ñemurã marandu mbyatypyre - Embojuruja eiporu hag̃ua ne ñehepyme’ẽrã ñongatupyre @@ -2046,12 +2015,6 @@ Embosako’i kundaharape Kundaharape ñangareko - - Téra Peteĩha - - Téra Mokõiha - - Terajoapy Téra @@ -2077,8 +2040,6 @@ Embogue kundaharape - - ¿Añetehápepa remboguese ko kundaharape? ¿Embogue kundaharape? @@ -2179,49 +2140,29 @@ Mboguete Mbosako’i - - ¿Emboguese añetehápe ko tembiapo ñepyrũ? Emboguesetépa añetehápe ko ñe’ẽñemi Mboguete Heja - - Jeporavorã tembiapo ñepyrũgua Ñe’ẽñemi jeporavorã - - Moñe’ẽha kora isako’ikuaáva ñanduti kundaharape rembiapo ñepyrũme. Moñe’ẽrã kora ehaikuaáva kundaharape ñanduti rendápe g̃uarã. - - Moñe’ẽha kora isako’ikuaáva poruhára réra rembiapo ñepyrũme. Moñe’ẽrã kora ehaikuaáva poruhára rérape g̃uarã. - Moñe’ẽha kora isako’ikuaáva ñe’ẽñemi rembiapo ñepyrũme. - Moñe’ẽrã kora ehaikuaáva ñe’ẽñemíme g̃uarã. - - Eñongatu moambue tembiapo ñepyrũme. Moambue ñongatu. - - Mbosako’i Embosako’i ñe’ẽñemi - - Embojuaju terarenda pyahu Embojuaju ñe’ẽñemi - - Tekotevẽ ñe’ẽñemi Emoinge ñe’ẽñemi - Poruhára réra jerurepyre - Ehai poruhára réra Mohendahavusu réra jerurepyre @@ -2573,6 +2514,8 @@ Ani ko’ág̃a Ehechauka ypykuéva + + Oñemyanyhẽ kuatiarogue oñemoñe’ẽasa’ỹre Oĩma @@ -2632,6 +2575,9 @@ Emboty ñe’ẽasaha rogue + + Ñemoĩporãha ndojeporukuaáiva ko’ag̃aite. + Ñemoñe’ẽasa @@ -2657,6 +2603,9 @@ Eiporavo ñe’ẽ eñangareko hag̃ua umi erohoryvéva “emoñe’ẽasameme” y “ani emoñe’ẽasa”. + + Noñemyanyhẽkuaái ñe’ẽnguéra. Eha’ãjey ag̃amieve. + Eikuave’ẽ ñe’ẽasa (ypyguáva) @@ -2680,6 +2629,8 @@ Emboguete %1$s + + Noñemyanyhẽkuaái tendakuéra. Eha’ãjey ag̃ave. ¿Emboguete %1$s? @@ -2757,13 +2708,18 @@ Eikundaha tapykuévo + + Embojuruja kahõ mopotĩha + Tendayke rembiporu Tendayke papapy - Myandy + Myandy + + Myandy Jokopyre @@ -2774,6 +2730,16 @@ Tembiporu tendayke moheñoiha Mboýpa emoheñóita tendayke + + Moñe’ẽrã kora inandi + + Emoinge papapy entero positivo año + + Emoinge pateĩ papapy sérogui tuichavéva + + Ohasáma pe tendayke oñekotevẽva (%1$s) ikatúva oñemongu’e peteĩjeýpe Embojuaju tendayke oikóva @@ -2790,11 +2756,11 @@ Marandu’i ñemiguáva - Mondo + Mondo - Mboty + Mboty - ¡Aguyje nde jehaipyrére! + ¡Aguyje nde jehaipyrére! Avy’aiterei @@ -2807,6 +2773,14 @@ Nachembovy’ái + + + Embojuruja ñeporandu + + Emboty ñeporandu + + Mboty + Tembiapo moñepyrũ diff --git a/mobile/android/fenix/app/src/main/res/values-hr/strings.xml b/mobile/android/fenix/app/src/main/res/values-hr/strings.xml index d1834444f2..a89e528b94 100644 --- a/mobile/android/fenix/app/src/main/res/values-hr/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-hr/strings.xml @@ -2,7 +2,7 @@ - Privatni %s + Privatne %s %s (privatno) @@ -49,12 +49,12 @@ - Nedavno spremljeno + Nedavno spremljeno - Prikaži sve spremljene zabilješke + Prikaži sve spremljene zabilješke - Ukloni + Ukloni %1$s proizvodi Mozilla. @@ -144,8 +144,8 @@ Nova privatna kartica - - Prečac za lozinke + + Prečac za lozinke @@ -187,11 +187,11 @@ Zaustavi - Dodaci + Dodaci Informacije računa - Ovdje nema dodataka + Ovdje nema dodataka Pomoć @@ -207,7 +207,7 @@ Dodaj na početni ekran - Instaliraj + Instaliraj Ponovna sinkronizacija @@ -313,22 +313,22 @@ - Obavijesti vam pomažu da učinite više s %s + Obavijesti vam pomažu da učinite više s %s - Sinkronizirajte svoje kartice između uređaja, upravljajte preuzimanjima, dobivajte savjete o tome kako najbolje iskoristiti %s zaštitu privatnosti i još mnogo toga. + Sinkronizirajte svoje kartice između uređaja, upravljajte preuzimanjima, dobivajte savjete o tome kako najbolje iskoristiti %s zaštitu privatnosti i još mnogo toga. - Nastavi + Nastavi - Ne sada + Ne sada Volimo vas čuvati - Naš neprofitni preglednik pomaže spriječiti tvrtke da vas tajno prate na webu.\n\nSaznajte više u našoj obavijesti o privatnosti. + Naš neprofitni preglednik pomaže spriječiti tvrtke da vas tajno prate na webu.\n\nSaznajte više u našoj obavijesti o privatnosti. - politika privatnosti + politika privatnosti Postavi kao zadani preglednik @@ -511,7 +511,7 @@ Zatvaranje aplikacije radi primjene promjena… - Dodaci + Dodaci Obavijesti @@ -523,7 +523,7 @@ - Prilagođena kolekcija dodataka + Prilagođena kolekcija dodataka U redu @@ -533,13 +533,13 @@ Vlasnik kolekcije (ID korisnika) - Izmijenjena je kolekcija dodataka. Napuštanje aplikacije za primjenu izmjena… + Izmijenjena je kolekcija dodataka. Napuštanje aplikacije za primjenu izmjena… Vrati se natrag - Nedavne zabilješke + Nedavne zabilješke Nedavno posjećeno @@ -587,15 +587,15 @@ Istražite više pozadina - + - Dodaci su privremeno onemogućeni + Dodaci su privremeno onemogućeni - Jedan ili više dodataka prestalo je raditi, što je vaš sustav učinilo nestabilnim. %1$s je bezuspješno pokušao ponovno pokrenuti dodatke.\n\nDodaci se neće ponovno pokrenuti tijekom vaše trenutačne sesije.\n\nUklanjanje ili onemogućavanje dodataka može riješiti ovaj problem. + Jedan ili više dodataka prestalo je raditi, što je vaš sustav učinilo nestabilnim. %1$s je bezuspješno pokušao ponovno pokrenuti dodatke.\n\nDodaci se neće ponovno pokrenuti tijekom vaše trenutačne sesije.\n\nUklanjanje ili onemogućavanje dodataka može riješiti ovaj problem. - Pokušajte ponovno pokrenuti dodatke + Pokušajte ponovno pokrenuti dodatke - Nastavite s onemogućenim dodacima + Nastavite s onemogućenim dodacima @@ -608,8 +608,6 @@ Povijest Zabilješke - - Prijave Otvorene kartice @@ -635,8 +633,6 @@ and the third is the device model. --> %1$s na %2$s %3$s - - Kreditne kartice Adrese @@ -1571,10 +1567,6 @@ Ovu stranicu lako možeš dodati na početni zaslon svog uređaja za brže pretraživanje s iskustvom sličnim aplikacijama. - - Prijave i lozinke - - Spremi prijave i lozinke Pitaj treba li se spremiti @@ -1588,30 +1580,11 @@ Ispuni korisnička imena i lozinke u drugim aplikacijama na tvom uređaju. - - Dodaj prijavu - - - Sinkroniziraj prijave - - Sinkroniziraj prijave na svim uređajima - - Spremljene prijave - - Ovdje će se prikazati prijave koje spremaš ili sinkroniziraš s programom %s. - - Saznaj više o sinkronizaciji. Iznimke - - Ovdje će se prikazati prijave i lozinke koje nisu spremljene. - - Prijave i lozinke neće se spremiti za te web-stanice. Obriši sve iznimke - - Traži prijave Stranica @@ -1638,13 +1611,7 @@ Prikaži lozinku Sakrij lozinku - - Otključaj za pregled tvojih spremljenih prijava - - Osiguraj svoje prijave i lozinke - - Postavi način za zaključavanje uređaja, PIN-a ili lozinke, kako bi se spriječio pristup spremljenim prijavama i lozinkama, kad netko drugi radi s tvojim uređajem. Kasnije @@ -1661,40 +1628,22 @@ Naziv (A-Z) Zadnja upotreba - - Izbornik sortiranja prijava Automatsko ispunjavanje Adrese - - Kreditne kartice - - Spremi i automatski ispuni polja kartice - - Podaci su šifrirani Sinkroniziraj kartice na uređajima Sinkroniziraj kartice - - Dodaj kreditnu karticu - - - Upravljaj spremljenim karticama Dodaj adresu Upravljaj adresama - - Spremi i automatski ispuni adrese - - Uključi podatke kao što su brojevi, e-pošta i adresa dostave - Dodaj karticu @@ -1713,8 +1662,6 @@ Izbriši karticu Izbriši karticu - - Jeste li sigurni da želite obrisati ovu karticu? Izbriši @@ -1727,17 +1674,8 @@ Spremljene kartice - - Unesi važeći broj kreditne kartice - - - Molimo ispuni ovo polje Otključaj za prikaz spremljenih kreditnih kartica - - Osiguraj svoje kreditne kartice - - Postavi uzorak za otključavanje uređaja, PIN ili lozinku da bi tvoje spremljene kreditne kartice bile zaštićene od neovlaštenog pristupa drugih osoba. Postavi sada @@ -1745,21 +1683,12 @@ Otključaj svoj uređaj - - Otključ za korištenje spremljenih podataka o kreditnim karticama - Dodaj adresu Uredi adresu Upravljaj adresama - - Ime - - Srednje ime - - Prezime Ulica @@ -1783,8 +1712,6 @@ Odustani Izbriši adresu - - Stvano želiš izbrisati ovu adresu? Izbriši @@ -1883,30 +1810,10 @@ Izbriši Uredi - - Sigurno izbrisati ovu prijavu? Izbriši Odustani - - Opcije prijave - - Tekstualno polje za web adresu prijave koje se može uređivati. - - Tekstualno polje za korisničko ime koje se može uređivati. - - Tekstualno polje za lozinku koje se može uređivati. - - Spremi izmjene prijave. - - Uređivanje - - Dodaj novu prijavu - - Potrebna je lozinka - - Korisničko ime je potrebno Domena je potrebna @@ -2004,7 +1911,7 @@ %s pretraga - + Postavi automatsko otvaranje poveznica web stranica, e-pošte i poruka u Firefoxu. @@ -2014,7 +1921,7 @@ Klikni za više informacija - Navigiraj gore + Navigiraj gore Zatvori @@ -2056,4 +1963,6 @@ + + diff --git a/mobile/android/fenix/app/src/main/res/values-hsb/strings.xml b/mobile/android/fenix/app/src/main/res/values-hsb/strings.xml index b366c59fcd..561485718b 100644 --- a/mobile/android/fenix/app/src/main/res/values-hsb/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-hsb/strings.xml @@ -200,6 +200,10 @@ Přidatki Rozšěrjenja + + Rozšěrjenja rjadować + + Dalše rozšěrjenja namakać Kontowe informacije @@ -218,6 +222,8 @@ W normalnym rajtarku wočinić Startowej wobrazowce přidać + + Startowej wobrazowce přidać… Instalować @@ -229,9 +235,13 @@ Stronu přełožić + Do zběrki składować… + Do zběrki składować Dźělić + + Dźělić… W %1$s wočinić @@ -284,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Składować + + Tutu stronu jako zapołožku składować + + Zapołožku wobdźěłać + + Jako PDF składować… + + Čitanski napohlad zmóžnić + + Čitanski napohlad znjemóžnić + + Stronu přełožić… + + Přełoženy do rěče %1$s + + Ćišćeć… + Žane rozšěrjenja tu @@ -379,8 +407,6 @@ Zdźělenka priwatnosće Firefox - - Zhońće wjace w našej zdźělence priwatnosće Škitamy was rady Rěč - Přełožk + Přełožk + + Přełožki Datowe wuběry @@ -661,10 +689,6 @@ Trěbny Na přeće - - Websydłowe daty čitać a změnić - - Websydło zhašeć Za wšě sydła dowolić @@ -791,8 +815,6 @@ Historija Zapołožki - - Přizjewjenja Hesła @@ -820,8 +842,6 @@ and the third is the device model. --> %1$s wot %2$s %3$s - - Kreditne karty Płaćenske metody @@ -837,6 +857,14 @@ Rajtark z %s + + + Rajtarki %1$s začinjene: %2$d + + Runje začinjene rajtarki pokazać + Wuwzaća @@ -1768,13 +1796,9 @@ Móžeće startowej wobrazowce swojeho grata tute websydło lochko přidać, zo byšće direktny přistup měł a spěšnišo z dožiwjenjom nałoženja přehladował. - - Přizjewjenja a hesła Hesła - Přizjewjenja a hesła składować - Hesła składować Před składowanjom so prašeć @@ -1790,46 +1814,27 @@ Wužiwarske mjena a hesła w druhich nałoženjach na wašim graće zasadźić. - - Přizjewjenje přidać - Hesło přidać - - Přizjewjenja synchronizować Hesła synchronizować - - Přizjewjenja mjez gratami synchronizować Hesła přez graty synchronizować - - Składowane přizjewjenja Składowane hesła - Přizjewjenja, kotrež składujeće abo z %s synchronizujeće, so tu pokazaja. - Hesła, kotrež składujeće abo z %s synchronizujeće, so tu nalistuja. Wšě hesła, kotrež składujeće, so zaklučuja. - - Zhońće wjace wo Sync. Zhońće wjace wo sync Wuwzaća - - Přizjewjenja a hesła, kotrež so njeskładuja, so tu pokazaja. %s hesła za sydła njeskładuje, kotrež su tu nalistowane. - - Přizjewjenja a hesła so za tute sydła njeskładuja. %s hesła za tute sydła njeskładuje. Wšě wuwzaća zhašeć - - Přizjewjenja pytać Hesła přepytać @@ -1858,17 +1863,11 @@ Hesło pokazać Hesło schować - - Za zwobraznjenje wašich składowanych přizjewjenjow wotewrěć Za zwobraznjenje wašich składowanych hesłow wotewrěć - Zawěsćće swoje přizjewjenja a hesła - Zawěsćće swoje składowane hesła - Nastajće gratowy zawrjenski muster, PIN abo hesło, zo byšće přistupej k swojim składowanym přizjewjenjam a hesłam zadźěwał, jeli něchtó druhi waš grat ma. - Nastajće gratowy zawrjenski muster, PIN abo hesło, zo byšće přistupej k swojim składowanym hesłam zadźěwał, jeli něchtó druhi waš grat ma. Pozdźišo @@ -1887,9 +1886,6 @@ Poslednim wužiću - - Meni přizjewjenskich datow sortěrować - Meni „Hesła sortěrować“ @@ -1898,41 +1894,27 @@ Awtomatisce wupjelnić Adresy - - Kreditne karty Płaćenske metody - Karty składować a awtomatisce wupjelnić - Płaćenske metody składować a wupjelnić - - Daty su zaklučowane %s wšě płaćenske metody zaklučuje, kotrež składujeće Karty přez graty synchronizować Karty synchronizować - - Kreditnu kartu přidać Kartu přidać - - Składowane karty rjadować Karty rjadować Adresu přidać Adresy rjadować - - Adresy składować a awtomatisce wupjelnić Adresy składować a wupjelnić - - Informacije kaž ličby, e-mejlowe a rozsyłanske adresy zapřijeć Wobsahuje telefonowe čisła a e-mejlowe adresy @@ -1956,8 +1938,6 @@ Kartu zhašeć - Chceće woprawdźe tutu kreditnu kartu zhašeć? - Kartu zhašeć? Zhašeć @@ -1971,24 +1951,15 @@ Składowane karty - - Prošu zapodajće płaćiwe čisło kreditneje karty - Zapodajće płaćiwe kartowe čisło - - Prošu wupjelńće tute polo Přidajće mjeno Za zwobraznjenje wašich składowanych kartow wotewrěć - Waše kreditne karty zawěsćić - Zawěsćće swoje składowane płaćenske metody - Nastajće gratowy zawrjenski muster, PIN abo hesło, zo byšće přistupej k swojim składowanym kreditnym kartam zadźěwał, jeli něchtó druhi waš grat ma. - Nastajće gratowy zawrjenski muster, PIN abo hesło, zo byšće přistupej k swojim składowanym płácenskim metodam zadźěwał, jeli něchtó druhi waš grat ma. Nětko konfigurować @@ -1997,9 +1968,6 @@ Wotewriće swój grat - - Blokowanje zběhnyć, zo bychu so składowane informacije kreditneje karty wužiwali - Wotewrěć, zo byšće składowane płaćenske metody wužiwał @@ -2008,12 +1976,6 @@ Adresu wobdźěłać Adresy rjadować - - Předmjeno - - Druhe předmjeno - - Swójbne mjeno Mjeno @@ -2039,8 +2001,6 @@ Adresu zhašeć - - Chceće woprawdźe tutu adresu zhašeć? Tutu adresu zhašeć? @@ -2140,49 +2100,29 @@ Zhašeć Wobdźěłać - - Chceće woprawdźe tute přizjewjenje zhašeć? Chceće woprawdźe tute hesło zhašeć? Zhašeć Přetorhnyć - - Přizjewjenske nastajenja Hesłowe nastajenja - - Wobdźěłujomne tekstowe polo za webadresu přizjewjenja. Wobdźěłujomne tekstowe polo za adresu websydła. - - Wobdźěłujomne tekstowe polo za wužiwarske mjeno přizjewjenja. Wobdźěłujomne tekstowe polo za wužiwarske mjeno. - Wobdźěłujomne tekstowe polo za hesło přizjewjenja. - Wobdźěłujomne tekstowe polo za hesło. - - Změny přizjewjenja składować Změny składować. - - Wobdźěłać Hesło wobdźěłać - - Nowe přizjewjenje přidać Hesło přidać - - Hesło trěbne Hesło zapodać - Wužiwarske mjeno trěbne - Wužiwarske mjeno zapodać Hostmjeno trěbne @@ -2590,6 +2530,9 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Stronu Přełožki začinić + + Někotre nastajenja na chwilu k dispoziciji njejsu. + Přełožki @@ -2613,6 +2556,9 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Wubjerće rěč, zo byšće nastajeni „přeco přełožić“ a „ženje njepřełožić“ rjadował. + + Rěče njedachu so začitać. Prošu spytajće pozdźišo hišće raz. + Přełožk poskićić (standard) @@ -2636,6 +2582,8 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk %1$s wotstronić + + Sydła njedachu so začitać. Prošu spytajće pozdźišo hišće raz. %1$s zhašeć? @@ -2713,13 +2661,18 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Wróćo nawigěrować + + Debug drawer wočinić + Rajtarkowe nastroje Ličba rajtarkow - Aktiwny + Aktiwny + + Aktiwny Njeaktiwny @@ -2730,6 +2683,16 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Nastroj za wutworjenje rajtarkow Ličba rajtarkow, kotrež so maja wutworić + + Tekstowe polo je prózdne + + Prošu zapodajće jenož pozitiwne cyłe ličby + + Prošu zapodajće ličbu, kotraž je wjetša hač nula + + Maksimalna ličba rajtarkow (%1$s), kotrež dadźa so w jednej operaciji generěrować, je překročena Aktiwnym rajtarkam přidać @@ -2746,11 +2709,11 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Zdźělenka priwatnosće - Wotpósłać + Wotpósłać - Začinić + Začinić - Dźakujemy so za waš komentar! + Dźakujemy so za waš komentar! Jara spokojny @@ -2762,6 +2725,14 @@ To budźe jenož pomhać, kwalitu pohódnoćenjow posudźić, nic kwalitu produk Jara njespokojny + + + Naprašowanje wočinić + + Naprašowanje začinić + + Začinić + Přizjewjenja diff --git a/mobile/android/fenix/app/src/main/res/values-hu/strings.xml b/mobile/android/fenix/app/src/main/res/values-hu/strings.xml index 4131faf3bb..efffda262e 100644 --- a/mobile/android/fenix/app/src/main/res/values-hu/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-hu/strings.xml @@ -48,12 +48,20 @@ - Nemrég mentett + Nemrég mentett - Összes mentett könyvjelző megjelenítése + Összes mentett könyvjelző megjelenítése - Eltávolítás + Eltávolítás + + + + Könyvjelzők + + Összes könyvjelző megjelenítése + + Eltávolítás A %1$s a Mozilla terméke. @@ -191,6 +199,10 @@ Kiegészítők Kiegészítők + + Kiegészítők kezelése + + További kiegészítők felfedezése Fiókinformáció @@ -209,6 +221,8 @@ Megnyitás szokásos lapon Kezdőképernyőhöz adás + + Hozzáadás a kezdőképernyőhöz… Telepítés @@ -220,9 +234,13 @@ Oldal fordítása + Mentés gyűjteménybe… + Gyűjteménybe mentés Megosztás + + Megosztás… Megnyitás ezzel: %1$s @@ -277,6 +295,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Mentés + + Oldal könyvjelzőzése + + Könyvjelző szerkesztése + + Mentés PDF-ként… + + Olvasó nézetet bekapcsolása + + Olvasó nézet kikapcsolása + + Oldal fordítása… + + Lefordítva erre: %1$s + + Nyomtatás… + Itt nincsenek kiegészítők @@ -582,6 +618,10 @@ Újracsatlakozás a szinkronizálás folytatásához Nyelv + + Fordítás + + Fordítások Adatküldések @@ -689,7 +729,9 @@ Ugrás vissza - Friss könyvjelzők + Friss könyvjelzők + + Könyvjelzők Nemrég felkeresett @@ -829,6 +871,14 @@ Lap innen: %s + + + %1$s lap bezárva: %2$d + + Nemrég bezárt lapok megtekintése + Kivételek @@ -2529,6 +2579,8 @@ Most nem Eredeti megjelenítése + + Eredeti, lefordítatlan oldal betöltve Kész @@ -2712,13 +2764,18 @@ Navigálás visszafelé + + Hibakereső fiók megnyitása + Lapeszközök Lapok száma - Aktív + Aktív + + Aktív Inaktív diff --git a/mobile/android/fenix/app/src/main/res/values-hy-rAM/strings.xml b/mobile/android/fenix/app/src/main/res/values-hy-rAM/strings.xml index 4b5ebebaef..7ae4420a7b 100644 --- a/mobile/android/fenix/app/src/main/res/values-hy-rAM/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-hy-rAM/strings.xml @@ -24,7 +24,7 @@ Որոնել ներդիրներ - Մուտքագրեք որոնվող բառը + Մուտքագրեք որոնման եզրույթներ Ձեր բաց ներդիրները կցուցադրվեն այստեղ: @@ -199,6 +199,10 @@ Հավելումներ Ընդլայնումներ + + Կառավարել ընդլայնումները + + Բացահայտեք ավելի շատ ընդլայնումներ Հաշվի տվյալներ @@ -217,6 +221,8 @@ Բացել կանոնավոր ներդիրում Ավելացնել Տնային էկրանին + + Ավելացնել Տուն էկրանին… Տեղադրել @@ -229,9 +235,13 @@ Թարգմանել էջը + Պահել հավաքածույում… + Պահպանել հավաքածուում Տարածել + + Համօգտագործել… Բացել %1$s-ում @@ -284,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Պահպանել + + Էջանշել այս էջը + + Խմբագրել Էջանիշը + + Պահել որպես PDF… + + Միացնել Ընթերցելու եղանակը + + Անջատել Ընթերցելու եղանակը + + Թարգմանել էջը… + + Թարգմանված է %1$s + + Տպել… + Այստեղ ընդլայնումներ չկան @@ -379,8 +407,6 @@ Firefox֊ի գաղտնիության ծանուցում - - Իմացեք ավելին մեր գաղտնիության ծանուցման մեջ Մենք սիրում ենք ձեզ ապահով պահել - Թարգմանություն + Թարգմանություն + + Թարգմանություններ Տվյալների ընտրություն @@ -662,10 +690,6 @@ Պահանջված Կամընտրական - - Կարդալ և փոխել վեբկայքի տվյալները - - Ջնջել վեբկայքը Թույլատրել բոլոր կայքերի համար @@ -790,8 +814,6 @@ Պատմություն Էջանիշեր - - Մուտքանուններ Գաղտնաբառեր @@ -818,8 +840,6 @@ and the third is the device model. --> %1$s-ը %2$s %3$s-ում - - Բանկային քարտեր Վճարամիջոցներ @@ -835,6 +855,14 @@ Ներդիր %s-ից + + + %1$s-ի փակված ներդիրները՝ %2$d + + Դիտել վերջերս փակված ներդիրները + Բացառություններ @@ -869,7 +897,7 @@ - firefox.com/pair-ում ցուցադրված QR կոդը]]> + firefox.com/pair-ում ցուցադրված QR կոդը]]> @@ -1022,7 +1050,7 @@ Չեղարկել - Փոփոխությունները գործադրելու համար դուրս է գալիս հավելվածից… + Փոփոխությունները կիրառելու համար լքում է հավելվածը… @@ -1091,7 +1119,7 @@ - Մուտքագրեք որոնվող բառը + Մուտքագրեք որոնման եզրույթներ Ջնջել պատմությունը @@ -1134,7 +1162,7 @@ - Ներողություն. %1$s-ը չի կարող բեռնել այդ էջը: + Ներողություն: %1$s-ը չի կարող բեռնել այդ էջը: Ուղարկել վթարի զեկույցը Mozilla-ին @@ -1210,7 +1238,7 @@ ՀԵՏԱՐԿԵԼ - Մուտքագրեք որոնվող բառը + Մուտքագրեք որոնման եզրույթներ @@ -1266,7 +1294,7 @@ Անջ. - Ստանդարտ + Սովորական Խիստ @@ -1390,7 +1418,7 @@ Հասկացա - Հնարավոր չէ տարածել այս հավելվածին + Չստացվեց տարածել այս հավելվածին Ուղարկել սարքի @@ -1586,7 +1614,7 @@ https://firefox.com/pair]]> - Պատրաստ է սկանավորել + Պատրաստ է սկանավորման Մուտք գործել Ձեր տեսախցիկով @@ -1614,7 +1642,7 @@ Իմանալ ավելին - Ստանդարտ (սկզբնադիր) + Սովորական (սկզբնադիր) Էջերը նորմալ կբեռնվեն, բայց արգելափակվելու են ավելի քիչ հետագծիչներ: @@ -1760,13 +1788,9 @@ Հեշտությամբ կարող եք ավելացնել այս կայքը ձեր հեռախոսի Տնային էկրանին՝ ակնթարթորեն մատչելու և արագ դիտարկելու համար: - - Մուտքանուններ և գաղտնաբառեր Գաղտնաբառեր - Պահպանել մուտքանունները և գաղտնաբառերը - Պահել գաղտնաբառերը Հարցնել պահպանելիս @@ -1781,46 +1805,27 @@ Լրացրեք օգտանունները և գաղտնաբառերը Ձեր սարքի այլ հավելվածներում: - - Հավելել մուտքանուն - Հավելել գաղտնաբառ - - Համաժամեցնել մուտքանունները Համաժամեցնել գաղտնաբառերը - - Համաժամեցնել մուտքանունները սարքերի միջև Համաժամեցնել գաղտնաբառերը սարքերի միջև - - Պահպանված մուտքանուններ Պահված գաղտնաբառեր - %s-ի հետ ձեր պահպանած կամ համաժամեցրած մուտքանունները կցուցադրվեն այստեղ: - Այն գաղտնաբառերը, որոնք դուք պահում կամ համաժամացնում եք %s-ի հետ, կցուցադրվեն այստեղ: Ձեր պահած բոլոր գաղտնաբառերը գաղտնագրված են: - - Իմանալ ավելին համաժամեցման մասին: Իմանալ ավելին Սինքի մասին Բացառություններ - - Մուտքանունները և գաղտնաբառերը, որոնք չեն պահպանվել, կցուցադրվեն այստեղ: %s-ը չի պահի այստեղ թվարկված կայքերի գաղտնաբառերը: - - Մուտքանունները և գաղտնաբառերը չեն պահպանվի այս կայքերի համար: %s-ը չի պահի այս կայքերի գաղտնաբառերը: Ջնջել բոլոր բացառությունները - - Որոնել մուտքանուններ Որոնել գաղտնաբառեր @@ -1850,17 +1855,11 @@ Ցուցադրել գաղտնաբառը Թաքցնել գաղտնաբառը - - Ապակողպեք՝ դիտելու համար պահպանված մուտքանունները Ապակողպեք՝ դիտելու համար պահված գաղտնաբառերը - Անվտանգ դարձրեք ձեր մուտքանունները և գաղտնաբառերը - Անվտանգ դարձրեք ձեր պահված գաղտնաբառերը - Ստեղծեք սարքի կողպեքի նմուշ, PIN կամ գաղտնաբառ՝ ձեր պահպանված մուտքանունները և գաղտնաբառերը մուտք գործելուց պաշտպանելու համար, եթե ձեր սարքը ուրիշի ձեռքն ընկնի: - Տեղակայեք սարքի կողպման նախշ, PIN կամ գաղտնաբառ՝ պաշտպանելու պահված գաղտնաբառերն այն դեպքում, եթե որևէ մեկը ևս մուտք ունենա Ձեր սարքին: Հետո @@ -1878,51 +1877,35 @@ Անվան (Ա-Ֆ) Վերջին օգտագործվածը - - Տեսակավորել մուտագրումների ցանկը Տեսակավորել գաղտնաբառերի ցանկը - Ինքնալցում + Ինքնալրացում Հասցեներ - - Բանկային քարտեր Վճարամիջոցներ - Պահել և ինքնալրացնել քարտերը - Պահել և լրացնել վճարամիջոցները - - Տվյալները գաղտնագրված են %s-ը գաղտնագրում է ձեր պահած բոլոր վճարամիջոցները Համաժամեցնել քարտերը սարքերի միջև Համաժամեցնել քարտերը - - Ավելացնել բանկային քարտ Հավելել քարտ - - Կառավարել պահված քարտերը Կառավարել քարտերը Ավելացնել հասցե Կառավարեք հասցեները - - Պահել և ինքնալրացնել քարտերը Պահել և լրացնել հասցեները - - Ներառել տեղեկություններ, ինչպիսիք են համարները, էլ.փոստը և առաքման հասցեները Ներառում է հեռախոսահամարներ և էլ. փոստի հասցեներ @@ -1946,8 +1929,6 @@ Ջնջել քարտը - Համոզվա՞ծ եք, որ ցանկանում եք ջնջել այս բանկային քարտը: - Ջնջե՞լ քարտը Ջնջել @@ -1961,24 +1942,15 @@ Պահպանված քարտեր - - Մուտքագրեք բանկային վավեր քարտի համար - Մուտքագրեք ճիշտ քարտի համար - - Լրացրեք այս դաշտը Հավելել անուն Ապակողպեք՝ դիտելու համար պահպանված քարտերը - Անվտանգ դարձրեք ձեր բանկային քարտերը - Անվտանգ դարձրեք ձեր պահած վճարամիջոցները - Տեղակայեք սարքի կողպման նախշ, PIN կամ գաղտնաբառ՝ պաշտպանելու պահված բանկային քարտերը այն դեպքում, եթե որևէ մեկը ևս մուտք ունի Ձեր սարքին: - Տեղակայեք սարքի կողպման նախշ, PIN կամ գաղտնաբառ՝ պաշտպանելու ձեր քարտերն այն դեպքում, եթե որևէ մեկը ևս մուտք ունենա Ձեր սարքին: Տեղակայել հիմա @@ -1986,8 +1958,6 @@ Ավելի ուշ Ապակողպեք ձեր սարքը - - Ապակողպեք՝ օգտագործելու համար բանկային քարտերի պահված տեղեկությունները Ապակողպեք՝ պահված վճարամիջոցն օգտագործելու համար @@ -1997,12 +1967,6 @@ Խմբագրել հասցեն Կառավարեք հասցեները - - Անուն - - Հայրանուն - - Ազգանուն Անուն @@ -2028,8 +1992,6 @@ Ջնջել հասցեն - Ջնջե՞լ այս հասցեն: - Ջնջե՞լ այս հասցեն: Ջնջել @@ -2126,49 +2088,29 @@ Ջնջել Խմբագրել - - Համոզվա՞ծ եք, որ ցանկանում եք ջնջել այս մուտքանունը: Համոզվա՞ծ եք, որ ցանկանում եք ջնջել այս գաղտնաբառը: Ջնջել Չեղարկել - - Մուտքի ընտրանքներ Գաղտնաբառի ընտրանքներ - - Խմբագրելի տեքստի դաշտը մուտքի վեբ հասցեի համար: Խմբագրելի տեքստի դաշտ կայքի հասցեի համար: - - Խմբագրելի տեքստի դաշտը մուտք գործողի համար: Խմբագրելի տեքստի դաշտ օգտվողի անվան համար: - Խմբագրելի տեքստի դաշտը մուտքի գաղտնաբառի համար: - Խմբագրելի տեքստի դաշտ գաղտնաբառի համար: - - Պահպանել փոփոխությունները մուտքագրման համար: Պահել փոփոխությունները: - - Խմբագրել Խմբագրել գաղտնաբառը - - Հավելել նոր մուտքանուն Հավելել գաղտնաբառ - - Գաղտնաբառ է պահանջվում Մուտքագրեք գաղտնաբառ - Օգտվողի անունը պարտադիր է - Մուտքագրեք օգտվողի անունը Հոսթի անունը պարտադիր է @@ -2576,6 +2518,9 @@ Փակել թարգմանությունների թերթիկը + + Որոշ կարգավորումներ ժամանակավորապես անհասանելի են։ + Թարգմանություններ @@ -2598,6 +2543,9 @@ Ընտրեք լեզուն՝ «միշտ թարգմանել» և «երբեք չթարգմանել» նախապատվությունները կառավարելու համար: + + Չստացվեց բեռնել լեզուները։ Խնդրում ենք կրկին ստուգել՝ ավելի ուշ։ + Թարգմանելու առաջարկ (սկզբնադիր) @@ -2620,6 +2568,8 @@ Հեռացնել %1$s-ը + + Չստացվեց բեռնել կայքերը։ Խնդրում ենք կրկին ստուգել՝ ավելի ուշ։ Ջնջե՞լ %1$s-ը: @@ -2697,13 +2647,18 @@ Նավարկել հետ + + Բացել վրիպազերծման գզրոցը + Ներդիրի գործիքներ Ներդիրների քանակը - Ակտիվ + Ակտիվ + + Ակտիվ Անգործուն @@ -2714,6 +2669,16 @@ Ներդիրների ստեղծման գործիք Ներդիրների քանակ ստեղծելու համար + + Տեքստային դաշտը դատարկ է + + Խնդրում ենք մուտքագրել միայն դրական ամբողջ թվեր + + Խնդրում ենք մուտքագրել զրոյից մեծ թիվ + + Գերազանցվել է ներդիրների առավելագույն քանակը (%1$s), որոնք կարող են ստեղծվել մեկ գործողության ընթացքում Հավելել ակտիվ ներդիրներին @@ -2730,11 +2695,11 @@ Գաղտնիության դրույթներ - Ուղարկել + Ուղարկել - Փակել + Փակել - Շնորհակալությո՜ւն Ձեր արձագանքի համար: + Շնորհակալությո՜ւն Ձեր արձագանքի համար: Շատ գոհ @@ -2746,6 +2711,14 @@ Շատ դժգոհ + + + Բացել հարցումը + + Փակել հարցումը + + Փակել + Մուտքանուններ diff --git a/mobile/android/fenix/app/src/main/res/values-ia/strings.xml b/mobile/android/fenix/app/src/main/res/values-ia/strings.xml index 8aed75c096..91b8cce44c 100644 --- a/mobile/android/fenix/app/src/main/res/values-ia/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ia/strings.xml @@ -202,6 +202,10 @@ Additivos Extensiones + + Gerer extensiones + + Discoperir altere extensiones Informationes del conto @@ -220,6 +224,8 @@ Aperir in un scheda regular Adder al pagina initial + + Adder al pagina initial… Installar @@ -231,9 +237,13 @@ Traducer le pagina + Salvar al collection… + Salvar al collection Compartir + + Compartir… Aperir in %1$s @@ -287,6 +297,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Salvar + + Adder un marcapaginas pro iste pagina + + Rediger marcapagina + + Salvar como PDF… + + Activar modo lectura + + Disactivar modo lectura + + Traducer le pagina… + + Traducite in %1$s + + Imprimer… + Nulle extensiones hic @@ -385,8 +413,6 @@ Aviso de confidentialitate de Firefox - - Lege nostre aviso de confidentialitate A nos place mantener te secur Lingua - Traduction + Traduction + + Traductiones Selectiones de datos @@ -669,10 +697,6 @@ Necessari Optional - - Leger e cambiar datos del sito web - - Deler le sito web Permitter a tote le sitos @@ -801,8 +825,6 @@ Chronologia Marcapaginas - - Credentiales Contrasignos @@ -829,8 +851,6 @@ and the third is the device model. --> %1$s sur %2$s %3$s - - Cartas de credito Methodos de pagamento @@ -848,6 +868,14 @@ Scheda veniente de %s + + + Schedas de %1$s clause: %2$d + + Vider schedas recentemente claudite + Exceptiones @@ -1809,14 +1837,9 @@ Tu pote facilemente adder iste sito web al Pagina initial de tu apparato pro haber accesso instantanee e navigar plus veloce con un experientia simile al app. - - Credentiales e contrasignos - Contrasignos - Salvar credentiales e contrasignos - Salvar contrasignos Demandar pro salvar @@ -1833,46 +1856,27 @@ Plenar nomines de usator e contrasignos in altere apps sur tu apparato. - - Adder credentiales - Adder contrasigno - - Synchronisar credentiales Synchronisar contrasignos - - Synchronisar credentiales inter le apparatos Synchronisar contrasignos inter apparatos - - Credentiales salvate Contrasignos salvate - Le credentiales que tu salva o synchronisa a in %s apparera hic. - Le contrasignos que tu salva o synchronisa in %s sera listate ci. Tote le contrasignos que tu salva es cryptate. - - Apprender plus re Sync. Apprender plus re le synchronisation Exceptiones - - Credentiales e contrasignos que non es salvate essera monstrate hic. %s non salvara le password pro le sitos ci listate. - - Credentiales e contrasignos non sera salvate pro iste sitos. %s non salvara le contrasignos pro iste sitos. Deler tote le exceptiones - - Cercar credentiales Cercar contrasignos @@ -1901,17 +1905,11 @@ Monstrar contrasigno Celar contrasigno - - Disbloca pro vider tu credentiales salvate Disbloca pro vider tu contrasignos salvate - Assecurar tu credentiales e contrasignos - Protege tu contrasignos salvate - Implementa un patrono de blocada apparato, PIN o contrasigno pro proteger tu credentiales e contrasignos salvate de esser accedite, si alcuno altere ha tu apparato. - Implementa un patrono de blocada apparato, PIN o contrasigno pro proteger tu contrasignos de esser accedite, si alcuno altere ha tu apparato. Plus tarde @@ -1929,9 +1927,6 @@ Ultimemente usate - - Ordinar menu de credentiales - Ordinar le menu del contrasignos @@ -1940,16 +1935,10 @@ Autoplenar Adresses - - Cartas de credito Methodos de pagamento - Gerer le cartas salvate - Salvar e compilar methodos de pagamento automaticamente - - Datos es cryptate %s crypta tote tu methodos de pagamento salvate @@ -1957,12 +1946,8 @@ Synchronisar cartas - - Adde un carta de credito Adder carta - - Gerer le cartas salvate Gerer le cartas @@ -1970,13 +1955,8 @@ Gerer adresses - - Salvar e autoplenar adresses - Salvar e compilar automaticamente adresses - - Includer informationes como numeros, email e adresses de expedition Include numeros de telephono e adresses email @@ -2000,8 +1980,6 @@ Deler carta - Desira tu vermente deler iste carta de credito? - Deler carta? Deler @@ -2015,24 +1993,15 @@ Cartas salvate - - Insere un numero valide de carta de credito - Insere un numero de carta valide - - Completa iste campo Adde un nomine Disbloca pro vider tu cartas salvate - Protege tu carta de credito - Protege tu methodo de pagamento salvate - Implementa un patrono de blocada apparato, PIN o contrasigno pro proteger tu cartas de credito de esser accedite, si alcuno altere ha tu apparato. - Implementa un patrono de blocada apparato, PIN o contrasigno pro proteger tu methodos de pagamento salvate de esser accedite, si alcun altere ha tu apparato. Implementar ora @@ -2041,9 +2010,6 @@ Disbloca tu apparato - - Disbloca pro usar le informationes de carta de credito immagazinate - Disblocar pro usar le methodo de pagamento salvate @@ -2052,12 +2018,6 @@ Modificar le adresse Gerer addresses - - Prenomine - - Nomine intermedie - - Nomine de familia Nomine @@ -2083,8 +2043,6 @@ Deler adresses - - Desira tu vermente deler iste adresses? Deler iste adresses? @@ -2185,50 +2143,30 @@ Deler Rediger - - Desira tu vermente deler iste credentiales? Desira tu vermente deler iste contrasigno? Deler Cancellar - - Optiones de apertura de session Optiones de contrasigno - - Le campo de texto redigibile pro le adresse web del credentiales. Le campo de texto redigibile pro le adresse del sito web. - - Le campo de texto redigibile pro le nomine de usator del accesso. Le campo de texto redigibile pro le nomine de usator. - Le campo de texto redigibile pro le contrasigno del credentiales. - Le campo de texto redigibile pro le contrasigno. - - Salvar cambiamentos a credentiales. Salvar le cambiamentos. - - Rediger Modificar le contrasigno - - Adder nove credential Adder contrasigno - - Contrasigno requirite Insere un contrasigno - Nomine de usator necesse. - Insere un nomine de usator Nomine de servitor necesse. @@ -2636,6 +2574,9 @@ Eliger folio de traductiones + + Alcun parametros es temporarimente indisponibile. + Traduction @@ -2658,6 +2599,9 @@ Elige un lingua pro gerer le preferentias “sempre traducer” e “jammais traducer”. + + Impossibile cargar le linguas. Re-controla plus tarde. + Offerer le traduction (predefinite) @@ -2680,6 +2624,8 @@ Remover %1$s + + Impossibile cargar sitos. Retenta plus tarde. Deler %1$s? @@ -2757,13 +2703,18 @@ Naviga a retro + + Aperir tiratorio de depuration + Numero de schedas Numero de schedas - Active + Active + + Active Inactive @@ -2774,6 +2725,16 @@ Utensile pro creation de schedas Numero de schedas a crear + + Le campo de texto es vacue + + Solo insere numeros integre positive + + Insere un numero major que zero + + Excedite le maxime numero de schedas (%1$s) que pote esser generate in un operation Adder a schedas active @@ -2790,11 +2751,11 @@ Aviso de confidentialitate - Inviar + Inviar - Clauder + Clauder - Gratias pro tu commentario! + Gratias pro tu commentario! Absolutemente satisfacite @@ -2806,6 +2767,14 @@ Absolutemente non satisfacite + + + Aperir sondage + + Clauder le questionario + + Clauder + Authenticationes diff --git a/mobile/android/fenix/app/src/main/res/values-is/strings.xml b/mobile/android/fenix/app/src/main/res/values-is/strings.xml index 2cbbea4ff7..8c5d707a9c 100644 --- a/mobile/android/fenix/app/src/main/res/values-is/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-is/strings.xml @@ -198,6 +198,10 @@ Viðbætur Forritsaukar + + Sýsla með forritsauka + + Uppgötvaðu fleiri forritsauka Upplýsingar um reikning @@ -217,6 +221,8 @@ Opna í venjulegum flipa Bæta við á ræsisíðu + + Bæta við á upphafsskjá… Setja upp @@ -228,9 +234,13 @@ Þýða síðu + Vista í safn… + Vista í safn Deila + + Deila… Opna með %1$s @@ -284,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Vista + + Setja síðu í bókamerki + + Breyta bókamerki + + Vista sem PDF… + + Kveikja á lesham + + Slökkva á lesham + + Þýða síðu… + + Þýtt á %1$s + + Prenta… + Engir forritsaukar hér @@ -380,8 +408,6 @@ Meðferð persónuupplýsinga í Firefox - - Sjáðu nánar í persónuverndaryfirlýsingu okkar Við fáum kikk út úr því að halda þér öruggum Tungumál - Þýðing + Þýðing + + Þýðingar Gagnamöguleikar @@ -663,10 +691,6 @@ Nauðsynlegt Valkvætt - - Lesa og breyta vefsíðugögnum - - Eyða vefsvæði Leyfa fyrir öll vefsvæði @@ -792,8 +816,6 @@ Feril Bókamerki - - Innskráningar Lykilorð @@ -820,8 +842,6 @@ and the third is the device model. --> %1$s á %2$s %3$s - - Greiðslukort Greiðslumátar @@ -837,6 +857,14 @@ Flipi frá %s + + + %1$s flipum lokað: %2$d + + Skoða nýjustu flipa sem var lokað + Undanþágur @@ -1758,13 +1786,9 @@ Þú getur á auðveldan hátt bætt þessu vefsvæði á upphafsskjáinn þinn fyrir auðveldara aðgengi og hraðara vafur. - - Innskráning og lykilorð Lykilorð - Vista innskráningu og lykilorð - Vista lykilorð Biðja um að vista @@ -1779,48 +1803,30 @@ Sjálfvirk útfylling í öðrum forritum Fylltu út notendanöfn og lykilorð í öðrum forritum á tækinu þínu. - - Bæta við innskráningu Bæta við lykilorði - - Samstilla innskráningar Samstilla lykilorð - - Samstilla innskráningar milli tækja Samstilla lykilorð milli tækja - - Vistaðar innskráningar Vistuð lykilorð - - Innskráningarnar sem þú vistar eða samstillir við %s birtast hér. Lykilorðin sem þú vistar eða samstillir við %s verða skráð hér. Öll lykilorð sem þú vistar eru dulrituð. - - Læra meira um samstillingu. Frekari upplýsingar um samstillingu Undanþágur - - Innskráningar og lykilorð sem ekki eru vistuð sjást hér. %s mun ekki vista lykilorð fyrir vefsvæði sem skráð eru hér. - - Innskráningar og lykilorð verða ekki vistuð fyrir þessi vefsvæði. %s mun ekki vista lykilorð fyrir þessi vefsvæði. Eyða öllum undantekningum - - Leita að innskráningu Leita að lykilorðum @@ -1849,17 +1855,11 @@ Sýna lykilorð Fela lykilorð - - Aflæstu til að skoða vistaðar innskráningar Aflæstu til að skoða vistuð lykilorð - Verndaðu innskráningar þínar og lykilorð - Tryggðu öryggi vistuðu lykilorðanna þinna - Settu upp læsimynstur, PIN eða lykilorð til að vernda vistaðar innskráningar og lykilorð ef ske kynni að einhver annar komist yfir tækið þitt. - Settu upp læsimynstur, PIN-númer eða lykilorð til að vernda vistuðu lykilorðin þín ef ske kynni að einhver annar komist yfir tækið þitt. Síðar @@ -1877,8 +1877,6 @@ Nafn (A-Z) Síðast notað - - Raða innskráningarvalmyndinni Raða lykilorðavalmynd @@ -1888,40 +1886,26 @@ Sjálfvirk útfylling Tölvupóstföng - - Greiðslukort Greiðslumátar - Vista og fylla sjálfkrafa út í greiðslukort - Vista og fylla út greiðslumáta - - Gögn eru dulrituð %s dulritar alla greiðslumáta sem þú vistar Samstilla greiðslukort milli tækja Samstilla kort - - Bæta við greiðslukorti Bæta við korti - - Sýsla með vistuð greiðslukort Sýsla með greiðslukort Bæta við tölvupóstfangi Sýsla með tölvupóstföng - - Vista og fylla sjálfkrafa út heimilisföng Vista og fylla út í heimilisföng - - Láta upplýsingar eins og símanúmer, tölvupóstföng og heimilisföng fylgja með Þar með talin símanúmer og tölvupóstföng @@ -1946,8 +1930,6 @@ Eyða korti - Ertu viss um að þú viljir eyða þessu greiðslukorti? - Eyða korti? Eyða @@ -1959,23 +1941,15 @@ Hætta við Vistuð greiðslukort - - Settu inn gilt kortanúmer Settu inn gilt kortanúmer - - Fylltu út í þennan reit Bættu við nafni Aflæsa til að skoða vistuð greiðslukort - Haltu kreditkortunum þínum öruggum - Tryggðu öryggi vistuðu greiðslumátanna þinna - Settu upp læsimynstur, PIN-númer eða lykilorð til að vernda vistuðu greiðslukortin þín ef ske kynni að einhver annar komist yfir tækið þitt. - Settu upp læsimynstur, PIN-númer eða lykilorð til að vernda vistuðu greiðslumátana þína ef ske kynni að einhver annar komist yfir tækið þitt. Setja upp núna @@ -1983,8 +1957,6 @@ Síðar Aflæstu tækinu þínu - - Aflæstu til að nota geymdar kreditkortaupplýsingar Aflæstu til að nota vistaða greiðslumáta @@ -1994,12 +1966,6 @@ Breyt tölvupóstfangi Sýsla með tölvupóstföng - - Skírnarnafn - - Millinafn - - Eftirnafn Nafn @@ -2025,8 +1991,6 @@ Eyða heimilisfangi - - Ertu viss um að þú viljir eyða þessu póstfangi? Eyða þessu heimilisfangi? @@ -2126,49 +2090,29 @@ Eyða Breyta - - Ertu viss um að þú viljir eyða þessari innskráningu? Ertu viss um að þú viljir eyða þessu lykilorði? Eyða Hætta við - - Innskráningarvalkostir Valkostir lykilorðs - - Breytilegi textareiturinn fyrir veffang þessarar innskráningarinnar. Breytanlegi textareiturinn fyrir vistfang vefsvæðisins. - - Breytilegi textareiturinn fyrir notandanafn innskráningarinnar. Breytanlegi textareiturinn fyrir notandanafnið. - Breytilegi textareiturinn fyrir lykilorð innskráningarinnar. - Breytanlegi textareiturinn fyrir lykilorðið. - - Vista breytingar á innskráningu. Vista breytingar. - - Breyta Breyta lykilorði - - Bæta við nýrri innskráningu Bæta við lykilorði - - Lykilorðs krafist Settu inn lykilorð - Notandanafn er nauðsynlegt - Settu inn notandanafn Hýsingarheiti er nauðsynlegt @@ -2577,6 +2521,9 @@ Loka þýðingarblaði + + Sumar stillingar eru ekki aðgengilegar eins og stendur. + Þýðingar @@ -2599,6 +2546,9 @@ Veldu tungumál til að stjórna stillingum á „alltaf þýða“ og „aldrei þýða“. + + Ekki tókst að hlaða tungumálum. Athugaðu aftur síðar. + Bjóðast til að þýða (sjálfgefið) @@ -2622,6 +2572,8 @@ Fjarlægja %1$s + + Ekki tókst að hlaðið inn vefsvæðum. Athugaðu aftur síðar. Eyða %1$s? @@ -2699,13 +2651,18 @@ Fara til baka + + Opna villuleitarspjald + Flipaverkfæri Fjöldi flipa - Virkir + Virkir + + Virkt Óvirkir @@ -2716,6 +2673,16 @@ Verkfæri til að búa til flipa Fjöldi flipa sem á að búa til + + Textareiturinn er tómur + + Settu aðeins inn jákvæðar heiltölur + + Settu inn tölu sem er stærri en núll + + Farið yfir hámarksfjölda flipa (%1$s) sem hægt er að búa til í einni aðgerð Bæta við virka flipa @@ -2732,11 +2699,11 @@ Meðferð persónuupplýsinga - Senda inn + Senda inn - Loka + Loka - Takk fyrir álit þitt! + Takk fyrir álit þitt! Mjög ánægð/ur @@ -2748,6 +2715,14 @@ Mjög óánægð/ur + + + Opna könnun + + Loka könnun + + Loka + Innskráningar diff --git a/mobile/android/fenix/app/src/main/res/values-it/strings.xml b/mobile/android/fenix/app/src/main/res/values-it/strings.xml index 36c423b38d..84154422c7 100644 --- a/mobile/android/fenix/app/src/main/res/values-it/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-it/strings.xml @@ -201,6 +201,10 @@ Componenti aggiuntivi Estensioni + + Gestisci estensioni + + Scopri altre estensioni Informazioni sull’account @@ -219,6 +223,8 @@ Apri in scheda normale Agg. a schermata principale + + Agg. a schermata principale… Installa @@ -230,10 +236,14 @@ Traduci pagina + Salva in una raccolta… + Salva in una raccolta Condividi + + Condividi… Apri in %1$s @@ -288,6 +298,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Salva + + Aggiungi pagina ai segnalibri + + Modifica segnalibro + + Salva come PDF… + + Attiva Modalità lettura + + Disattiva Modalità lettura + + Traduci pagina… + + Tradotta in %1$s + + Stampa… + Nessuna estensione disponibile @@ -385,8 +413,6 @@ Informativa sulla privacy di Firefox - - Ulteriori informazioni nella nostra informativa sulla privacy Ci piace mantenerti al sicuro Lingua - Traduzione + Traduzione + + Traduzioni Condivisione dati @@ -671,10 +699,6 @@ Obbligatorio Facoltativo - - Leggere e modificare i dati dei siti web - - Elimina sito web Consenti per tutti i siti @@ -802,8 +826,6 @@ Segnalibri - - Credenziali Password @@ -830,8 +852,6 @@ and the third is the device model. --> %1$s su %2$s %3$s - - Carte di credito Metodi di pagamento @@ -848,6 +868,14 @@ Scheda da %s + + + Schede di %1$s chiuse: %2$d + + Visualizza schede chiuse di recente + Eccezioni @@ -1801,13 +1829,9 @@ È possibile aggiungere questo sito web alla schermata principale del dispositivo per accedervi più rapidamente, come se si trattasse di un’app. - - Credenziali e password Password - Salva credenziali e password - Salva password Chiedi prima di salvare @@ -1823,46 +1847,27 @@ Compila nomi utente e password nelle altre app del tuo dispositivo. - - Aggiungi credenziali - Aggiungi password - - Sincronizza le credenziali Sincronizza password - - Sincronizza credenziali tra dispositivi Sincronizza le password tra i tuoi dispositivi - - Credenziali salvate Password salvate - Le credenziali salvate o sincronizzate in %s verranno visualizzate qui. - Le password salvate o sincronizzate con %s verranno visualizzate qui. Tutte le password salvate sono crittate. - - Ulteriori informazioni su Sync. Ulteriori informazioni sulla sincronizzazione Eccezioni - - Le credenziali e le password non salvate verranno mostrate qui. %s non salverà le password per i siti elencati qui. - - Le credenziali e le password non verranno salvate per questi siti. %s non salverà le password per questi siti. Elimina tutte le eccezioni - - Cerca credenziali Cerca nelle password @@ -1891,17 +1896,11 @@ Mostra password Nascondi password - - Sblocca per visualizzare le credenziali salvate Sblocca per visualizzare le password salvate - Proteggi le credenziali di accesso - Proteggi le password salvate - Imposta una sequenza di blocco, PIN o password per impedire a chi si impossessa del dispositivo di visualizzare le tue credenziali. - Imposta una sequenza di blocco, PIN o password per impedire a chi si impossessa del dispositivo di visualizzare le password salvate. Più tardi @@ -1919,8 +1918,6 @@ Nome (A-Z) Ultimo utilizzo - - Ordina il menu delle credenziali di accesso Menu per ordinare le password @@ -1930,41 +1927,27 @@ Compilazione automatica Indirizzi - - Carte di credito Metodi di pagamento - Salvare e compilare automaticamente le carte - Salva e compila i metodi di pagamento - - I dati sono crittati %s critta tutti i metodi di pagamento salvati Sincronizza le carte tra più dispositivi Sincronizza carte di credito - - Aggiungi una carta di credito Aggiungi carta - - Gestione carte salvate Gestisci carte Aggiungi indirizzo Gestione indirizzi - - Salvare e compilare automaticamente gli indirizzi Salva e compila indirizzi - - Includere informazioni come numeri, email e indirizzi di spedizione Include numeri di telefono e indirizzi email @@ -1989,8 +1972,6 @@ Elimina carta - Eliminare questa carta di credito? - Eliminare la carta? Elimina @@ -2004,24 +1985,15 @@ Carte salvate - - Inserire un numero di carta di credito valido - Inserisci un numero di carta valido - - Compilare questo campo Aggiungi un nome Sblocca per visualizzare le carte di credito salvate - Proteggi le tue carte di credito - Proteggi i metodi di pagamento salvati - Imposta una sequenza di blocco, PIN o password per impedire a chi si impossessa del dispositivo di visualizzare le carte di credito salvate. - Imposta una sequenza di blocco, PIN o password per impedire a chi si impossessa del dispositivo di visualizzare i metodi di pagamento salvati. Imposta adesso @@ -2030,9 +2002,6 @@ Sblocca il dispositivo - - Sblocca per utilizzare le informazioni delle carte di credito salvate - Sblocca per utilizzare i metodi di pagamento salvati @@ -2041,12 +2010,6 @@ Modifica indirizzo Gestione indirizzi - - Nome - - Secondo nome - - Cognome Nome @@ -2073,8 +2036,6 @@ Elimina indirizzo - - Eliminare questo indirizzo? Eliminare questo indirizzo? @@ -2173,49 +2134,29 @@ Elimina Modifica - - Rimuovere queste credenziali? Eliminare questa password? Rimuovi Annulla - - Opzioni credenziali Opzioni password - - Il campo di testo modificabile per l’indirizzo web delle credenziali Il campo di testo modificabile per l’indirizzo del sito web. - - Il campo di testo modificabile per il nome utente delle credenziali Il campo di testo modificabile per il nome utente. - Il campo di testo modificabile per la password delle credenziali - Il campo di testo modificabile per la password. - - Salva le modifiche alle credenziali. Salva modifiche. - - Modifica Modifica password - - Aggiungi nuove credenziali Aggiungi password - - Password obbligatoria Inserisci una password - Nome utente obbligatorio - Inserisci un nome utente Nome server obbligatorio @@ -2627,6 +2568,9 @@ Chiudi il pannello per le traduzioni + + Alcune impostazioni sono temporaneamente non disponibili. + Traduzioni @@ -2649,6 +2593,9 @@ Seleziona una lingua per gestire le preferenze “Traduci sempre” e “Non tradurre mai“. + + Impossibile caricare le lingue. Ricontrolla più tardi. + Proponi la traduzione (predefinita) @@ -2672,6 +2619,8 @@ Rimuovi %1$s + + Impossibile caricare i siti. Ricontrolla più tardi. Rimuovere %1$s? @@ -2749,13 +2698,18 @@ Torna indietro + + Apri riquadro di navigazione a scomparsa per il debug + Strumenti per le schede Conteggio schede - Attive + Attive + + Attive Inattive @@ -2766,6 +2720,16 @@ Strumento per la creazione di schede Numero di schede da creare + + Il campo di testo è vuoto + + Inserire solo numeri interi positivi + + Inserire un numero maggiore di zero + + Superato il numero massimo di schede (%1$s) che è possibile generare in una singola operazione Aggiungi a schede attive @@ -2782,11 +2746,11 @@ Informativa sulla privacy - Invia + Invia - Chiudi + Chiudi - Grazie per aver condiviso la tua opinione. + Grazie per aver condiviso la tua opinione. Molto soddisfatto/a @@ -2798,6 +2762,14 @@ Molto insoddisfatto/a + + + Apri sondaggio + + Chiudi sondaggio + + Chiudi + Credenziali diff --git a/mobile/android/fenix/app/src/main/res/values-iw/strings.xml b/mobile/android/fenix/app/src/main/res/values-iw/strings.xml index 6afb865f1c..934a428d3c 100644 --- a/mobile/android/fenix/app/src/main/res/values-iw/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-iw/strings.xml @@ -199,6 +199,10 @@ תוספות הרחבות + + ניהול הרחבות + + גילוי הרחבות נוספות מידע על החשבון @@ -217,6 +221,8 @@ פתיחה בלשונית רגילה הוספה למסך הבית + + הוספה למסך הבית… התקנה @@ -228,9 +234,13 @@ תרגום הדף + שמירה לאוסף… + שמירה לאוסף שיתוף + + שיתוף… פתיחה ב־%1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> שמירה + + יצירת סימנייה לדף זה + + עריכת סימנייה + + שמירה כ־PDF… + + הפעלת תצוגת קריאה + + כיבוי תצוגת קריאה + + תרגום הדף… + + תורגם ל%1$s + + הדפסה… + אין כאן הרחבות @@ -379,8 +407,6 @@ הצהרת הפרטיות של Firefox - - מידע נוסף בהצהרת הפרטיות שלנו אנחנו אוהבים לשמור עליך שפה - תרגום + תרגום + + תרגומים בחירות נתונים @@ -660,10 +688,6 @@ נדרש לא חובה - - קריאה ושינוי נתוני אתרים - - מחיקת אתר לאפשר לכל האתרים @@ -794,8 +818,6 @@ היסטוריה סימניות - - כניסות ססמאות @@ -822,8 +844,6 @@ and the third is the device model. --> ‏%1$s על %2$s %3$s - - כרטיסי אשראי אמצעי תשלום @@ -839,6 +859,14 @@ לשונית מהמכשיר %s + + + %2$d לשוניות נסגרו מ־%1$s + + הצגת לשוניות שנסגרו לאחרונה + חריגות @@ -1769,13 +1797,9 @@ באפשרותך להוסיף בקלות אתר זה למסך הבית של המכשיר שלך כדי לקבל גישה מיידית ולגלוש מהר יותר עם חוויה שמדמה שימוש ביישומון. - - כניסות וססמאות ססמאות - שמירת כניסות וססמאות - שמירת ססמאות לבקש לשמור @@ -1792,46 +1816,27 @@ מילוי ושמירת שמות משתמשים וססמאות ביישומונים אחרים במכשיר שלך. - - הוספת כניסה - הוספת ססמה - - סנכרון כניסות סנכרון ססמאות - - סנכרון כניסות בין מכשירים סנכרון ססמאות בין מכשירים - - כניסות שמורות ססמאות שמורות - הכניסות שיישמרו או יסתנכרנו אל %s יופיעו כאן. - הססמאות שיישמרו או יסונכרנו עם %s יופיעו כאן. כל הססמאות השמורות הינן מוצפנות. - - מידע נוסף על Sync. מידע נוסף על סנכרון חריגות - - כניסות וססמאות שאינן שמורות יופיעו כאן. ‏%s לא ישמור ססמאות לאתרים המפורטים כאן. - - כניסות וססמאות לא יישמרו עבור אתרים אלו. ‏%s לא ישמור ססמאות לאתרים אלו. מחיקת כל החריגות - - חיפוש כניסות חיפוש ססמאות @@ -1860,17 +1865,11 @@ הצגת ססמה הסתרת ססמה - - יש לבטל את הנעילה כדי להציג את הכניסות השמורות שלך יש לבטל את הנעילה כדי להציג את הססמאות השמורות שלך - שמירה מאובטחת של הכניסות והססמאות שלך - אבטחת הססמאות השמורות שלך - ניתן להגדיר תבנית נעילת מכשיר, קוד או ססמה כדי להגן על פרטי הגישה והססמאות השמורות שלך מפני גורמים בלתי מהימנים שמחזיקים במכשיר שלך. - ניתן להגדיר תבנית נעילת מכשיר, קוד או ססמה כדי להגן על הססמאות השמורות שלך מפני גורמים בלתי מהימנים שמחזיקים במכשיר שלך. מאוחר יותר @@ -1888,8 +1887,6 @@ שם (A-Z) שימוש אחרון - - תפריט מיון כניסות תפריט מיון ססמאות @@ -1899,42 +1896,28 @@ מילוי אוטומטי כתובות - - כרטיסי אשראי אמצעי תשלום - שמירה ומילוי אוטומטי של כרטיסים - שמירה ומילוי אמצעי תשלום - - הנתונים מוצפנים ‏%s מצפין את כל אמצעי התשלום השמורים סנכרון כרטיסים בין מכשירים סנכרון כרטיסים - - הוספת כרטיס אשראי הוספת כרטיס - - ניהול כרטיסים שמורים ניהול כרטיסים הוספת כתובת ניהול כתובות - - שמירה ומילוי אוטומטי של כתובות שמירה ומילוי כתובות - - לכלול מידע כמו מספרים, כתובות דוא״ל וכתובות למשלוח כולל מספרי טלפון וכתובות דוא״ל @@ -1958,8 +1941,6 @@ מחיקת כרטיס - האם ברצונך למחוק את כרטיס האשראי הזה? - למחוק את הכרטיס? מחיקה @@ -1973,24 +1954,15 @@ כרטיסים שמורים - - נא להכניס מספר כרטיס אשראי תקין - נא להכניס מספר כרטיס תקני - - נא למלא שדה זה הוספת שם יש לבטל את הנעילה כדי להציג את הכרטיסים השמורים שלך - אבטחת כרטיסי האשראי שלך - אבטחת אמצעי התשלום השמורים שלך - ניתן להגדיר תבנית נעילת מכשיר, קוד או ססמה כדי להגן על כרטיסי האשראי השמורים שלך מפני גורמים בלתי מהימנים שמחזיקים במכשיר שלך. - ניתן להגדיר תבנית נעילת מכשיר, קוד או ססמה כדי להגן על אמצעי התשלום השמורים שלך מפני גורמים בלתי מהימנים שמחזיקים במכשיר שלך. להגדיר כעת @@ -1999,9 +1971,6 @@ שחרור נעילת המכשיר שלך - - יש לבטל את הנעילה כדי להשתמש בפרטי כרטיס האשראי השמור - יש לבטל את הנעילה כדי להשתמש באמצעי התשלום השמורים שלך @@ -2010,12 +1979,6 @@ עריכת כתובת ניהול כתובות - - שם פרטי - - שם אמצעי - - שם משפחה שם @@ -2039,9 +2002,6 @@ מחיקת כתובת - - האם ברצונך למחוק את כתובת זו? - למחוק את הכתובת הזאת? @@ -2141,49 +2101,29 @@ מחיקה עריכה - - האם ברצונך למחוק את כניסה זו? האם ברצונך למחוק ססמה זו? מחיקה ביטול - - אפשרויות כניסה אפשרויות ססמה - - שדה הטקסט הניתן לעריכה עבור כתובת האתר של הכניסה. שדה הטקסט הניתן לעריכה עבור כתובת האתר. - - שדה הטקסט הניתן לעריכה עבור שם המשתמש של הכניסה. שדה הטקסט הניתן לעריכה עבור שם המשתמש. - שדה הטקסט הניתן לעריכה עבור הססמה של הכניסה. - שדה הטקסט הניתן לעריכה עבור הססמה. - - שמירת השינויים של הכניסה. שמירת שינויים. - - עריכה עריכת הססמה - - הוספת כניסה חדשה הוספת ססמה - - נדרשת ססמה נא להכניס ססמה - דרוש שם משתמש - נא להכניס שם משתמש דרוש שם מארח @@ -2592,6 +2532,9 @@ סגירת גיליון התרגומים + + זמנית, חלק מההגדרות אינן זמינות. + תרגומים @@ -2614,6 +2557,9 @@ יש לבחור שפה כדי לנהל את ההעדפות של ״תמיד לתרגם״ ו״לעולם לא לתרגם״. + + לא ניתן היה לטעון שפות. נא לנסות שוב מאוחר יותר. + להציע לתרגם (ברירת מחדלץ) @@ -2636,6 +2582,8 @@ להסיר את %1$s + + לא ניתן היה לטעון אתרים. נא לנסות שוב מאוחר יותר. למחוק את %1$s? @@ -2714,13 +2662,18 @@ ניווט אחורה + + פתיחת מגירת ניפוי השגיאות + כלי לשוניות ספירת לשוניות - פעיל + פעיל + + פעילים לא פעיל @@ -2731,6 +2684,16 @@ כלי ליצירת לשוניות כמות הלשוניות ליצירה + + שדה הטקסט ריק + + נא להכניס מספרים שלמים חיוביים בלבד + + נא להכניס מספר הגדול מאפס + + חריגה ממספר הלשוניות המירבי (%1$s) שניתן ליצור בפעולה אחת הוספה ללשוניות פעילות @@ -2747,11 +2710,11 @@ הצהרת פרטיות - שליחה + שליחה - סגירה + סגירה - תודה על המשוב שלך! + תודה על המשוב שלך! מאוד מרוצה @@ -2763,6 +2726,14 @@ מאוד לא מרוצה + + + פתיחת סקר + + סגירת סקר + + סגירה + כניסות diff --git a/mobile/android/fenix/app/src/main/res/values-ja/strings.xml b/mobile/android/fenix/app/src/main/res/values-ja/strings.xml index 5040b99f60..cad2df70fa 100644 --- a/mobile/android/fenix/app/src/main/res/values-ja/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ja/strings.xml @@ -51,12 +51,20 @@ - 最近保存 + 最近保存 - 保存したブックマークをすべて表示します + 保存したブックマークをすべて表示します - 削除 + 削除 + + + + ブックマーク + + すべてのブックマークを表示 + + 削除 %1$s は Mozilla の製品です。 @@ -147,8 +155,10 @@ 新しいプライベートタブ - - パスワードのショートカット + + パスワード + + パスワードのショートカット @@ -193,6 +203,10 @@ アドオン 拡張機能 + + 拡張機能を管理 + + 拡張機能を探す アカウント情報 @@ -211,18 +225,26 @@ 通常タブで開く ホーム画面に追加 + + ホーム画面に追加... インストール 再同期 ページ内検索 + + ページ内を検索... ページを翻訳 + コレクションに保存... + コレクションに保存 共有 + + 共有... %1$s で開く @@ -252,9 +274,47 @@ ホームページをカスタマイズ - ログイン - - パスワード、タブなどを同期します + ログイン + + パスワード、タブなどを同期します + + + ログインして Sync に戻る + + 同期を一時停止中 + + 新しいプライベートタブ + + パスワード + + + %1$s の新着情報 + + PC 版サイトに切り替える + + ツール + + 保存 + + + このページをブックマーク + + ブックマークを編集 + + PDF として保存... + + リーダービューで開く + + リーダービューを閉じる + + ページを翻訳... + + %1$s に翻訳しました + + 印刷... @@ -353,8 +413,6 @@ Firefox のプライバシー通知 - - 詳細はプライバシー通知をご覧ください 私たちはあなたの安全を守りたいと願っています 言語 + + 翻訳 + + 翻訳 データの選択 @@ -633,6 +695,17 @@ 許可されていません + + + 必須 + + 任意 + + すべてのサイトで許可する + + + この拡張機能を信頼する場合は、すべてのウェブサイトに対して権限を与えられます。 + カスタムアドオンコレクション @@ -656,7 +729,9 @@ 以前表示したタブ - 最近追加したブックマーク + 最近追加したブックマーク + + ブックマーク 最近訪れたサイト @@ -751,8 +826,6 @@ 表示履歴 ブックマーク - - ログイン情報 パスワード @@ -779,8 +852,6 @@ and the third is the device model. --> %2$s %3$s 上の %1$s - - クレジットカード情報 支払い方法 @@ -797,6 +868,14 @@ %s からのタブ + + + %1$s のタブを %2$d 個閉じました + + 最近閉じたタブを表示 + 例外 @@ -1735,13 +1814,9 @@ このウェブサイトを端末のホーム画面に簡単な操作で追加できます。アプリのような感覚で素早くアクセスして閲覧しましょう。 - - ログイン情報とパスワード パスワード - ログイン情報を保存する - パスワードを保存 保存するか確認する @@ -1757,46 +1832,27 @@ 端末上の他のアプリにユーザー名とパスワードを入力します。 - - ログイン情報を追加 - パスワードを追加 - - ログイン情報を同期 パスワードを同期 - - 端末間でログイン情報を同期します 端末間でパスワードを同期 - - 保存されたログイン情報 保存されたパスワード - 端末に保存または %s と同期したログイン情報がここに表示されます。 - %s に保存または同期したパスワードがこのリストに表示されます。保存されたすべてのパスワードは暗号化されます。 - - Sync についての詳細情報。 Sync についての詳細情報 例外 - - ログイン情報が保存されないサイトがここに表示されます。 %s は、このリストに表示されているサイトのパスワードを保存しません。 - - これらのサイトではログイン情報が保存されません。 %s はこれらのサイトのパスワードを保存しません。 すべての例外を削除 - - ログイン情報を検索 パスワードを検索 @@ -1825,17 +1881,11 @@ パスワードを表示 パスワードを隠す - - ロック解除して保存されたログイン情報を表示します 保存されたパスワードを表示するにはロック解除してください - ログイン情報とパスワードの保護 - 保存されたパスワードを保護してください - 端末のロックパターンや PIN、パスワードを設定して、保存されたログイン情報とパスワードを他人の不正なアクセスから保護しましょう。 - 端末のロックパターンや PIN、パスワードを設定して、保存されたパスワードを他人の不正なアクセスから保護しましょう。 後で @@ -1853,8 +1903,6 @@ 名前 (昇順) 最終使用日時 - - ログイン情報メニューの並べ替え パスワードを並べ替えます @@ -1864,29 +1912,19 @@ 自動入力 所在地フォーム - - クレジットカード 支払い方法 - カード情報を保存して自動入力する - 支払い方法を保存して入力する - - データは暗号化されています %s は保存したすべての支払い方法を暗号化します 端末間でカード情報を同期する クレジットカード情報を同期 - - クレジットカードを追加 カード情報を追加 - - 保存したカードを管理 カード情報を管理 @@ -1894,12 +1932,8 @@ アドレスの管理 - - アドレスを保存して自動入力する 住所を保存して入力する - - カード番号、メールアドレス、配送先などの情報を含める 電話番号とメールアドレスを含みます @@ -1924,8 +1958,6 @@ カードを削除 - 本当にこのクレジットカード情報を削除してもよろしいですか? - カード情報を削除しますか? 削除 @@ -1939,25 +1971,16 @@ 保存したカード - - 有効なクレジットカード番号を入力してください - 正しいカード番号を入力してください - - このフィールドは入力必須です 名前を追加してください 保存されたカード情報を表示するにはロック解除してください - - クレジットカード情報の保護 保存された支払い方法を保護してください - 端末のロックパターンや PIN、パスワードを設定して、保存されたクレジットカード情報とパスワードを他人の不正なアクセスから保護しましょう。 - 端末のロックパターンや PIN、パスワードを設定して、保存された支払い方法を他人の不正なアクセスから保護しましょう。 今すぐ設定 @@ -1966,9 +1989,6 @@ 端末のロック解除 - - ロックを解除して保存したクレジットカード情報を使用します - 保存された支払い方法を使用するにはロック解除してください @@ -1977,12 +1997,6 @@ 住所の編集 アドレスの管理 - - - - ミドルネーム - - 氏名 @@ -2008,8 +2022,6 @@ アドレスを削除 - - 本当にこの住所を削除してもよろしいですか? このアドレスを削除しますか? @@ -2108,8 +2120,6 @@ 削除 編集 - - このログイン情報を削除してもよろしいですか? 本当にこのパスワードを削除してもよろしいですか? @@ -2117,41 +2127,23 @@ キャンセル - - ログインオプション パスワードのオプション - - ログイン情報のウェブアドレスの編集可能なテキストフィールド。 ウェブサイトのアドレスの編集可能なテキストフィールド。 - - ログイン情報のユーザー名の編集可能なテキストフィールド。 ユーザー名の編集可能なテキストフィールド。 - ログイン情報のパスワードの編集可能なテキストフィールド。 - パスワードの編集可能なテキストフィールド。 - - 変更を保存してログインします。 変更を保存します。 - - 編集 パスワードを編集 - - 新しいログイン情報の追加 パスワードを追加 - - パスワードが必要です パスワードを入力してください - ユーザー名は必須です - ユーザー名を入力してください ホスト名は必須です @@ -2497,10 +2489,14 @@ 翻訳元 翻訳先 + + 別の翻訳元言語を試す 後で 元の言語で表示 + + 翻訳されていない元のページが読み込まれました 完了 @@ -2520,7 +2516,7 @@ 申し訳ありませんが、%1$s はまだサポートされていません。 - 詳細情報 + 詳細情報 翻訳中… @@ -2535,7 +2531,9 @@ - 翻訳オプション + 翻訳オプション + + 翻訳オプション 常に翻訳機能を提供する @@ -2553,6 +2551,12 @@ %1$s の翻訳機能について + + 翻訳シートを閉じる + + + 一部の設定が一時的に利用できません。 + 翻訳 @@ -2576,6 +2580,9 @@ 設定で「常に翻訳する」言語と「翻訳しない」言語を選択します。 + + 言語を読み込めませんでした。後でもう一度ご確認ください。 + 翻訳を通知する (既定) @@ -2598,6 +2605,8 @@ %1$s を削除します + + サイトを読み込めませんでした。後でもう一度ご確認ください。 %1$s を削除しますか? @@ -2675,12 +2684,19 @@ デバッグツール 前のページへ戻ります + + + デバッグパネルを開く + + タブツール タブ数 - 使用中 + 使用中 + + 使用中 休止中 @@ -2691,10 +2707,63 @@ タブ作成ツール 作成するタブの数 + + テキストフィールドが空です + + 正の整数を入力してください + + 0 より大きい数字を入力してください + + 1 回の操作で生成できるタブの上限 (%1$s 個) に達しました 使用中のタブに追加 休止中のタブに追加 プライベートタブに追加 + + + + + 続ける + + このアンケートにご回答ください + + プライバシー通知 + + 送信 + + 閉じる + + フィードバックのご提供ありがとうございます。 + + とても満足 + + 満足 + + どちらでもない + + 不満 + + 非常に不満 + + + + アンケートを開く + + アンケートを閉じる + + 閉じる + + + + ログイン情報 + + 現在のドメイン: %s + + このドメインの疑似ログイン情報を追加 + + ユーザー名 %s のログイン情報を削除します diff --git a/mobile/android/fenix/app/src/main/res/values-kab/strings.xml b/mobile/android/fenix/app/src/main/res/values-kab/strings.xml index 65fa7f8a0c..0c17e45335 100644 --- a/mobile/android/fenix/app/src/main/res/values-kab/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-kab/strings.xml @@ -51,12 +51,20 @@ - Yettwasekles melmi kan + Yettwasekles melmi kan - Sken akk ticraḍ n yisebtar yettwaskelsen + Sken akk ticraḍ n yisebtar yettwaskelsen - Kkes + Kkes + + + + Ticraḍ n yisebtar + + Sken akk ticraḍ n yisebtar + + Kkes %1$s d afares n Mozilla. @@ -196,6 +204,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Izegrar Isiɣzaf + + Sefrek isiɣzaf Talut n umiḍan @@ -220,12 +230,18 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ales amtawi Nadi deg usebter + + Nadi deg usebter… Suqel asebter + Sekles deg tegrumma… + Sekles ɣer tegrumma Bḍu + + Bḍu… Ldi di %1$s @@ -257,6 +273,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kcem Mtawi awalen n uεeddi, accaren akked wayen niḍen + + Ales qqen i wakken ad tesnekreḍ amtawi + + Amtawa yeḥbes Iccer uslig amaynut @@ -265,6 +285,28 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara The first parameter is the name of the app defined in app_name (for example: Fenix)--> Amaynut deg %1$s + + Ddu ɣer usmel n tnarit + + Ifecka + + Sekles + + + Creḍ asebter-a + + Ẓreg tacreḍt n usebter + + Kles d PDF… + + Suqel asebter… + + Suqqel ɣer %1$s + + Siggez… + Ulac isiɣzaf dagi @@ -280,6 +322,11 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Suqel asebter + + Asebter yettwasuqqel seg %1$s ɣer %2$s. + Fren tutlayt @@ -358,8 +405,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tasertit tabaḍnit n Firefox - - Issin ugar deg tsertit-nneɣ n tbaḍnit Nḥemmel ad teqqimeḍ d aɣellsan @@ -550,6 +595,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ales tuqqna akken ad ikemmel umtawi Tutlayt + + Tasuqilt + + Tisuqilin Afran n yisefka @@ -604,6 +653,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Izegrar + + Isiɣzaf Sebded azegrir seg ufaylu @@ -621,10 +672,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Yettawsra Afrayan - - Γeṛ syen tesnefleḍ isefka n usmel web - - Kkes asmel web + + + Sireg meṛṛa ismal @@ -645,7 +695,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Uɣal ɣer deffir - Ticraḍ n yisebtar n melmi kan + Ticraḍ n yisebtar n melmi kan + + Ticraḍ n yisebtar Yemmẓer melmi kan @@ -697,18 +749,27 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Izegrar imaynuten llan tura + + Llan akka tura isiɣzaf imaynuten Snirem ugar n 100 yisiɣzaf imaynuten ara ak·akem-yeǧǧen ad tsagneḍ Firefox. Snirem izegrar + + Snirem isiɣzaf + Azegrir yensa i kra n wakud + + Isiɣzaf nsan i kra n wakud Yiwen neɣ ugar n yizegrar ḥebsen, rran anagraw-ik d arurkid. %1$s ur yessaweḍ ara ad yales asenker n uzegrir(yizegrar).\n\nIzegrar ugin ad alsen tanekra ɣef teɣzi n tiɣimit-a.\n\Kkes neɣ sens izegrar-a, ahat ad yefru wugur-a. Ɛreḍ asenker n uzegrir + + Ԑreḍ allus n usenker n yisiɣzaf Kemml s uzegrir yensa @@ -725,8 +786,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Azray Ticraḍ n yisebtar - - Inekcam Awalen uffiren @@ -754,8 +813,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara and the third is the device model. --> %1$s deg %2$s %3$s - - Tikarḍiwin n usmad Tarrayin n uxelleṣ @@ -1708,13 +1765,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tzemreḍ s sshal ad ternuḍ asmel-a web ɣer ugdil agejdan n yibenk-inek·inem i wakken ad tesɛuḍ anekcum askudan d tunigin taruradt s termt i icuban asnas. - - Inekcam d wawalen uffiren Awalen uffiren - Sekles inekcam d wawalen uffiren - Sekles awalen uffiren Suter asekles @@ -1730,38 +1783,19 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ččar ismawen n useqdac d wawalen uffiren deg yisnasen-nniḍen ɣef yibenk-ik·im. - - Rnu anekcum - Rnu awal uffir - - Mtawi inekcam Mtawi awalen uffiren - - Mtawi inekcam gqr yibenkan - - Inekcam yettwakelsen Awalen uffiren yettwakelsen - - Anekcum i tḥerzeḍ ɣer %s ad d-ittwasken da. - - Issin ugar ɣef umtawi. Issin ugar ɣef umtawi Tisuraf - - Inekcam akked wawalen uffiren ur yettwaskelsen ara ad ttwaseknen dagi. - - Inekcam akked wawalen uffiren ur ttwaseklasen ara i yismal-a. Kkes akk tisuraf - - Nadi inekcam Nadi awalen uffiren @@ -1790,14 +1824,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sken awal uffir Ffer awal uffir - - Serreḥ akken ad tsekneḍ inekcam-ik yettwaskelsen - - Mmesten inekcam d wawalen uffiren Seɣles awalen-ik·im uffiren i yettwaskelsen - - Sbadu azenziɣ n usekkeṛ, tangal PIN, neɣ awal uffir akken ad temmesteneḍ inekcam-ik akked wawlen-ik uffiren yettwaskelsen ticki yella win ikecmen ɣer yibenk-ik. Ticki @@ -1815,44 +1843,34 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Aseqdec aneggaru - - Umuɣ n usemyizwer n yinekcam + + Umuɣ n usmizwer n wawalen n uεeddi Taččart tawurmant Tansiwin - - Tikarḍiwin n usmad Tarrayin n uxelleṣ - Asekles d taččart tawurmant n tkarḍiwin - - Isefka ttwawgelhen + Sekles syen ččar tarrayin n uxelleṣ + + %s yettewgellih akk tarrayin n uxelleṣ i teskelseḍ Mtawi tikarḍiwin gar yibenkan Mtawi tikarḍiwin - - Rnu takarḍa n usmad Rnu takarḍa - - Sefrek tikerḍiwin yettwaskelsen Sefrek tikarḍiwin Rnu tansa Sefrek tansiwin - - Asekles d taččart tawurmant n tansiwin Sekles; teččareḍ tansiwin - - Seddu talɣut am wuṭṭunen, imayl akked tansiwin n usiweḍ Seddu ula uṭṭunen n tiliɣri d tansiwin n yimayl @@ -1876,8 +1894,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kkes takarḍa - Tebɣiḍ s tidet ad tekkseḍ takarḍa-a n usmad? - Kkes takarḍa? Kkes @@ -1890,42 +1906,31 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tikerḍiwin yettwasekles - - Ma ulac aɣilif sekcem uṭṭun ameɣtu n tkarḍa n usmad - Sekcem uṭṭun n tkarḍa ameɣtu - - Ttxil-k·m ččar urti-a Rnu isem Kkes asekkeṛ i wakken ad twaliḍ tikerḍiwin-ik·im yettwaskelsen - Seɣles tikerḍiwin-ik·im n usmad + Seɣles tarrayin-ik n uxelleṣ i yettwaskelsen - Sbadu taneɣruft n usekkeṛ n yibenk, tangalt PIN neq awal uffir i ummesten n tkerḍiwin-ik·im n usmad yettwaskelsen ticki yella win ikecmen ɣer yibenk-inek·inem. + Sbadu taneɣruft n usekkeṛ n yibenk, tangalt PIN neq awal uffir i ummesten n tarrayin-ik·im n usmad yettwaskelsen ticki yella win ikecmen ɣer yibenk-inek·inem. Sbadu tura Ticki Serreḥ i ibenk-inek·inem - - Kkes asekkeṛ i wakken ad tesqedceḍ talɣut n tkarḍa n usmad + + Kkes asekkeṛ tarrayin-ik n uxelleṣ i yettwaskelsen Rnu tansa Ẓreg tansa Sefrek tansiwin - - Aɣara - - Isem alemmas - - Isem Isem @@ -1951,8 +1956,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kkes tansa - - D tidet tebɣiḍ ad tekkseḍ tansa-a? Kkes tansa-a? @@ -2053,41 +2056,29 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kkes Ẓreg - - Tebɣiḍ ad tekseḍ anekcum-agi? + + D tidet tebɣiḍ ad tekkseḍ awal-a n uεeddi? Kkes Sefsex - - Iɣewwaren n unekcum Tixtiṛiyin n wawal uffir - - Urti n uḍris yettusenfal i tansa n unekcum n web. - - Urti n uḍris yettusenfal i yisem n useqdac n unekcum. + + Urti n uḍris yettusenfal i tansa n usmel web. + + Urti n uḍris yettusenfal i yisem n useqdac. - Urti n uḍris yettusenfal i wawal uffir n unekcum. - - Sekles isenfaln unekcum. + Urti n uḍris yettusenfal i wawal n uεeddi. Sekles isenfal. - - Ẓreg Ẓreg awal uffir - - Rnu anekcum amaynut Rnu awal uffir - - Awal uffir yettusra Sekcem awal uffir - Isem n useqdac yettwasra - Sekcem isem n useqdac Asenneftaɣ yettwasra @@ -2510,7 +2501,7 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Amḍan n waccaren - Urmid + Urmid Insa @@ -2518,12 +2509,50 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Asemday + + Rnu ɣer waccaren urmiden + + Rnu ɣer waccaren irurmiden + + Rnu ɣer waccaren usligen + + + Kemmel + + Smed aḥedqis-a + + Tasertit n tbaḍnit + + Azen + + Mdel + + Tanemmirt ɣef tekti yinek! + + Yumer aṭas + + Y·Tumer War tamawt + + Ur yumir ara + + Texseṛ-as akk nniya + + + + Ldi aḥedqis + + Mdel aḥedqis + + Mdel + Inekcam + + Taɣult-a: %s diff --git a/mobile/android/fenix/app/src/main/res/values-kk/strings.xml b/mobile/android/fenix/app/src/main/res/values-kk/strings.xml index eec9afe73c..cb9da8fe5c 100644 --- a/mobile/android/fenix/app/src/main/res/values-kk/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-kk/strings.xml @@ -198,6 +198,10 @@ Қосымшалар Кеңейтулер + + Кеңейтулерді басқару + + Көбірек кеңейтулерді табу Тіркелгі ақпараты @@ -216,6 +220,8 @@ Қалыпты бетте ашу Үй экранына қосу + + Үй экранына қосу… Орнату @@ -227,9 +233,13 @@ Парақты аудару + Жинаққа сақтау… + Жинаққа сақтау Бөлісу + + Бөлісу… %1$s көмегімен ашу @@ -282,6 +292,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Сақтау + + Бұл бетті бетбелгілерге қосу + + Бетбелгіні түзету + + PDF ретінде сақтау… + + Оқу көрінісін іске қосу + + Оқу көрінісін сөндіру + + Парақты аудару… + + %1$s тіліне аударылған + + Баспаға шығару… + Мұнда кеңейтулер жоқ @@ -379,8 +407,6 @@ Firefox жекелік ескертуі - - Біздің жекелік ескертуімізден көбірек біліңіз Біз сіздің қауіпсіздігіңізді қамтамасыз етуді жақсы көреміз Тіл - Аударма + Аударма + + Аудармалар Деректер таңдауы @@ -663,10 +691,6 @@ Міндетті Міндетті емес - - Веб-сайт деректерін оқу және өзгерту - - Веб-сайтты өшіру Барлық сайттар үшін рұқсат ету @@ -792,8 +816,6 @@ Шолу тарихы Бетбелгілер - - Логиндер Парольдер @@ -819,8 +841,6 @@ and the third is the device model. --> %1$s, %2$s %3$s - - Несиелік карталар Төлем әдістері @@ -836,6 +856,14 @@ %s құрылғысынан бет + + + %1$s бет жабылды: %2$d + + Жақында жабылған беттерді қарау + Ережеден бөлек @@ -1761,13 +1789,9 @@ Бұл веб-сайтты жылдам қатынау және қолданба тектес режимде жылдам шолу мақсатымен құрылғыңыздың үй бетіңізге қосуға болады. - - Логиндер және парольдер Парольдер - Логиндер және парольдерді сақтау - Парольдерді сақтау Сақтау алдында сұрау @@ -1784,46 +1808,27 @@ Құрылғыңыздағы басқа қолданбаларда пайдаланушы аттары мен парольдерін толтыру. - - Логинді қосу - Парольді қосу - - Логиндерді синхрондау Парольдерді синхрондау - - Логиндерді құрылғылар арасында синхрондау Парольдерді құрылғылар арасында синхрондау - - Сақталған логиндер Сақталған парольдер - Сіз %s ішінде сақтаған немесе синхрондаған логиндер осында көрсетіледі. - Сіз %s ішіне сақтаған немесе синхрондаған парольдер осында тізіліп көрсетіледі. Сіз сақтаған барлық парольдер шифрленген. - - Синхрондау туралы көбірек біліңіз. Синхрондау туралы көбірек біліңіз Ережеден бөлек - - Сақталмаған логиндер мен парольдер осында көрсетіледі. %s осы жерде тізімделген сайттар үшін парольдерді сақтамайтын болады. - - Бұл сайттар үшін логиндер мен парольдер сақталмайды. %s бұл сайттар үшін парольдерді сақтамайтын болады. Барлық ережеден тыс жағдайларды өшіру - - Логиндерден іздеу Парольдерді іздеу @@ -1852,17 +1857,11 @@ Парольді көрсету Парольді жасыру - - Сақталған логиндеріңізді көру үшін бұғаттауын ашыңыз Сақталған парольдеріңізді көру үшін бұғаттауын ашыңыз - Логиндер және парольдерді қорғаңыз - Сақталған парольдерді қорғаңыз - Бөтен адам сіздің құрылғыңызда болса, одан сақталған логиндер мен парольдерді қорғау үшін құрылғының бұғаттау үлгісін, PIN-кодын немесе парольді орнатыңыз. - Бөтен адам сіздің құрылғыңызда болса, одан сақталған парольдеріңізді қорғау үшін құрылғының бұғаттау үлгісін, PIN-кодын немесе парольді орнатыңыз. Кейінірек @@ -1879,8 +1878,6 @@ Аты (А-Я) Соңғы қолданылған - - Логиндерді сұрыптау мәзірі Парольдерді сұрыптау мәзірі @@ -1890,41 +1887,27 @@ Автотолтыру Адрестер - - Несиелік карталар Төлем әдістері - Карталарды сақтау және автотолтыру - Төлем әдістерін сақтау және толтыру - - Деректер шифрленген %s сіз сақтаған барлық төлем әдістерін шифрлейді Карталарды құрылғылар арасында синхрондау Карталарды синхрондау - - Несиелік картаны қосу Картаны қосу - - Сақталған карталарды басқару Карталарды басқару Адресті қосу Адрестерді басқару - - Адрестерді сақтау және автотолтыру Адрестерді сақтау және толтыру - - Нөмірлер, эл. пошта және жеткізу адрестері сияқты ақпаратты қосу Телефон нөмірлері мен электрондық пошта адрестерін қамтиды @@ -1948,8 +1931,6 @@ Картаны өшіру - Бұл несие картасын өшіруді шынымен қалайсыз ба? - Картаны өшіру керек пе? Өшіру @@ -1963,24 +1944,15 @@ Сақталған карталар - - Несиелік картаның жарамды нөмірін енгізіңіз - Жарамды карта нөмірін енгізіңіз - - Бұл өрісті толтырыңыз Атын қосыңыз Сақталған карталарыңызды көру үшін бұғаттауын ашыңыз - Несиелік карталарыңызды қорғаңыз - Сақталған төлем әдістерін қорғаңыз - Бөтен адам сіздің құрылғыңызда болса, одан сақталған несиелік карталарды қорғау үшін құрылғының бұғаттау үлгісін, PIN-кодын немесе парольді орнатыңыз. - Бөтен адам сіздің құрылғыңызда болса, одан сақталған төлем әдістерін қорғау үшін құрылғының бұғаттау үлгісін, PIN-кодын немесе парольді орнатыңыз. Қазір баптау @@ -1989,9 +1961,6 @@ Құрылғының бұғаттауын ашыңыз - - Сақталған несиелік карта ақпаратын қолдану үшін бұғаттауын ашыңыз - Сақталған төлем әдістерін қолдану үшін құлыптан босатыңыз @@ -2000,12 +1969,6 @@ Адресті түзету Адрестерді басқару - - Аты - - Әкесінің аты - - Тегi Аты @@ -2032,8 +1995,6 @@ Адресті өшіру - - Бұл адресті өшіруді шынымен қалайсыз ба? Бұл адресті өшіру керек пе? @@ -2131,50 +2092,30 @@ Өшіру Түзету - - Бұл логинді өшіруді қалайсыз ба? Бұл парольді өшіруді шынымен қалайсыз ба? Өшіру Бас тарту - - Логин баптаулары Пароль опциялары - - Логиннің веб-адресі үшін түзетуге болатын мәтіндік өрісі. Веб-сайт адресі үшін түзетуге болатын мәтіндік өрісі. - - Логиннің пайдаланушы аты үшін түзетуге болатын мәтіндік өрісі. Пайдаланушы аты үшін түзетуге болатын мәтіндік өрісі. - - Логиннің паролі үшін түзетуге болатын мәтіндік өрісі. Пароль үшін түзетуге болатын мәтіндік өрісі. - - Логин өзгерістерін сақтау. Өзгерістерді сақтау. - - Түзету Парольді түзету - - Жаңа логинді қосу Парольді қосу - - Пароль керек Парольді енгізіңіз - Пайдаланушы аты керек - Пайдаланушы атын енгізіңіз Хост атауы керек @@ -2524,6 +2465,8 @@ Қазір емес Түпнұсқаны көрсету + + Аударылмаған түпнұсқа бет жүктелді Дайын @@ -2582,6 +2525,9 @@ Аудармалар парағын жабу + + Кейбір баптаулар уақытша қолжетімсіз. + Аудармалар @@ -2604,6 +2550,9 @@ "әрқашан аудару" және "ешқашан аудармау" баптауларды басқару үшін тілді таңдаңыз. + + Тілдерді жүктеу мүмкін емес. Кейінірек қайта тексеріңіз. + Аударуды ұсыну (үнсіз келісім бойынша) @@ -2627,6 +2576,8 @@ %1$s өшіру + + Сайттарды жүктеу мүмкін емес. Кейінірек қайта тексеріңіз. %1$s өшіру керек пе? @@ -2705,13 +2656,18 @@ Артқа өту + + Жөндеу сөресін ашу + Беттер саймандары Беттер саны - Белсенді + Белсенді + + Белсенді Белсенді емес @@ -2722,6 +2678,16 @@ Беттерді жасау құралы Жасау үшін беттер саны + + Мәтін өрісі бос + + Тек оң сандарды енгізіңіз + + Нөлден үлкен санды енгізіңіз + + Беттер саны бір әрекетте жасауға болатын максималды санынан (%1$s) асты Белсенді беттерге қосу @@ -2738,11 +2704,11 @@ Жекелік ескертуі - Жіберу + Жіберу - Жабу + Жабу - Пікіріңізге рахмет! + Пікіріңізге рахмет! Өте жақсы @@ -2754,6 +2720,14 @@ Өте нашар + + + Сауалнаманы ашу + + Сауалнаманы жабу + + Жабу + Логиндер diff --git a/mobile/android/fenix/app/src/main/res/values-ko/strings.xml b/mobile/android/fenix/app/src/main/res/values-ko/strings.xml index 3d14dbadd0..5da118bfc3 100644 --- a/mobile/android/fenix/app/src/main/res/values-ko/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ko/strings.xml @@ -205,6 +205,10 @@ 부가 기능 확장 기능 + + 확장 기능 관리 + + 더 많은 확장 기능 살펴보기 계정 정보 @@ -223,6 +227,8 @@ 일반 탭에서 열기 홈 화면에 추가 + + 홈 화면에 추가… 설치 @@ -234,9 +240,13 @@ 페이지 번역 + 모음집에 저장… + 모음집에 저장 공유 + + 공유… %1$s에서 열기 @@ -291,6 +301,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> 저장 + + 이 페이지 북마크 + + 북마크 편집 + + PDF로 저장… + + 리더뷰 켜기 + + 리더뷰 끄기 + + 페이지 번역… + + %1$s 언어로 번역됨 + + 인쇄… + 확장 기능 없음 @@ -388,8 +416,6 @@ Firefox 개인정보처리방침 - - 개인정보 보호정책에서 자세히 알아보기 우리는 사용자를 안전하게 지키는 것을 좋아합니다 언어 - 번역 + 번역 + + 번역 데이터 선택 @@ -675,10 +703,6 @@ 선택 사항 - - 웹 사이트 데이터 읽기 및 변경 - - 웹 사이트 삭제 모든 사이트에 허용 @@ -805,8 +829,6 @@ 기록 북마크 - - 로그인 비밀번호 @@ -833,8 +855,6 @@ and the third is the device model. --> %2$s %3$s의 %1$s - - 신용 카드 결제 방법 @@ -851,6 +871,14 @@ %s에서 온 탭 + + + %1$s 탭 닫힘: %2$d개 + + 최근에 닫은 탭 보기 + 예외 목록 @@ -1812,13 +1840,9 @@ 이 웹 사이트를 기기의 홈 화면에 쉽게 추가하여 앱과 같은 경험을 통해 즉시 액세스하고 더 빠르게 탐색 할 수 있습니다. - - 로그인과 비밀번호 비밀번호 - 로그인과 비밀번호 저장 - 비밀번호 저장 저장할지 묻기 @@ -1834,46 +1858,27 @@ 기기의 다른 앱에서 사용자 이름과 비밀번호를 채웁니다. - - 로그인 추가 - 비밀번호 추가 - - Sync 로그인 비밀번호 동기화 - - 기기 간에 로그인 동기화 기기 간에 비밀번호 동기화 - - 저장된 로그인 저장된 비밀번호 - %s에 저장하거나 동기화한 로그인이 여기에 표시됩니다. - %s에 저장하거나 동기화한 비밀번호는 여기에 나열됩니다. 저장한 모든 비밀번호는 암호화됩니다. - - Sync에 대해 더 알아보기. 동기화에 대해 더 알아보기 예외 목록 - - 저장되지 않은 로그인과 비밀번호가 여기에 표시됩니다. %s는 여기에 나열된 사이트의 비밀번호를 저장하지 않습니다. - - 이 사이트에 대한 로그인과 비밀번호는 저장되지 않습니다. %s는 이 사이트의 비밀번호를 저장하지 않습니다. 모든 예외 삭제 - - 로그인 검색 비밀번호 검색 @@ -1902,17 +1907,11 @@ 비밀번호 보이기 비밀번호 숨기기 - - 저장된 로그인을 보려면 잠금 해제하세요 저장된 비밀번호를 보려면 잠금 해제하세요 - 로그인과 비밀번호 보안 - 저장된 비밀번호를 보호하세요 - 다른 사람이 내 기기를 가지고 있는 경우, 저장된 로그인과 비밀번호에 접근하지 못하도록 기기 잠금 패턴, PIN 또는 비밀번호를 설정하세요. - 다른 사람이 내 기기를 가지고 있는 경우, 저장된 비밀번호에 접근하지 못하도록 기기 잠금 패턴, PIN 또는 비밀번호를 설정하세요. 나중에 @@ -1930,8 +1929,6 @@ 이름 (A-Z) 마지막 사용 - - 로그인 정렬 메뉴 비밀번호 정렬 메뉴 @@ -1941,42 +1938,28 @@ 자동 채우기 주소 - - 신용 카드 결제 방법 - 카드 저장 및 자동 채우기 - 결제 방법 저장 및 채우기 - - 데이터가 암호화됨 %s는 저장한 모든 결제 방법을 암호화합니다 기기 간에 카드 동기화 카드 동기화 - - 신용 카드 추가 카드 추가 - - 저장된 카드 관리 카드 관리 주소 추가 주소 관리 - - 주소 저장 및 자동 채우기 주소 저장 및 채우기 - - 숫자, 이메일 및 배송 주소와 같은 정보 포함 전화번호와 이메일 주소가 포함됩니다 @@ -2001,8 +1984,6 @@ 카드 삭제 - 이 신용 카드를 삭제하시겠습니까? - 카드를 삭제하시겠습니까? 삭제 @@ -2016,24 +1997,15 @@ 저장된 카드 - - 유효한 신용 카드 번호를 입력해 주세요 - 유효한 카드 번호를 입력하세요 - - 이 항목을 입력하세요. 이름 추가 저장된 카드를 보려면 잠금 해제하세요 - 신용 카드 보안 - 저장된 결제 방법을 보호하세요 - 다른 사람이 내 기기를 가지고 있는 경우, 저장된 신용 카드에 접근하지 못하도록 기기 잠금 패턴, PIN 또는 비밀번호를 설정하세요. - 다른 사람이 내 기기를 가지고 있는 경우, 저장된 결제 방법에 접근하지 못하도록 기기 잠금 패턴, PIN 또는 비밀번호를 설정하세요. 지금 설정 @@ -2042,9 +2014,6 @@ 기기 잠금 해제 - - 저장된 신용 카드 정보를 사용하려면 잠금 해제하세요 - 저장된 결제 방법을 사용하려면 잠금 해제하세요 @@ -2053,12 +2022,6 @@ 주소 편집 주소 관리 - - 이름 - - 중간 이름 - - 이름 @@ -2084,8 +2047,6 @@ 주소 삭제 - - 이 주소를 삭제하시겠습니까? 이 주소를 삭제하시겠습니까? @@ -2185,49 +2146,29 @@ 삭제 편집 - - 이 로그인을 삭제하시겠습니까? 이 비밀번호를 삭제하시겠습니까? 삭제 취소 - - 로그인 옵션 비밀번호 옵션 - - 로그인 웹 주소의 편집 가능한 텍스트 필드입니다. 웹 사이트 주소에 대한 편집 가능한 텍스트 필드입니다. - - 로그인 사용자 이름의 편집 가능한 텍스트 필드입니다. 사용자 이름에 대한 편집 가능한 텍스트 필드입니다. - 로그인 비밀번호의 편집 가능한 텍스트 필드입니다. - 비밀번호에 대한 편집 가능한 텍스트 필드입니다. - - 변경 내용을 로그인에 저장. 변경 내용 저장. - - 편집 비밀번호 수정 - - 새 로그인 추가 비밀번호 추가 - - 비밀번호 필요 비밀번호 입력 - 사용자 이름은 필수입니다 - 사용자 이름 입력 호스트 이름은 필수입니다 @@ -2636,6 +2577,9 @@ 번역 시트 닫기 + + 일부 설정을 일시적으로 사용할 수 없습니다. + 번역 @@ -2659,6 +2603,9 @@ ”항상 번역“ 및 ”번역 안 함“ 설정을 관리할 언어를 선택하세요 + + 언어를 로드할 수 없습니다. 나중에 다시 확인해 주세요. + 번역 제안 (기본값) @@ -2682,6 +2629,8 @@ %1$s 제거 + + 사이트를 로드할 수 없습니다. 나중에 다시 확인해 주세요. %1$s 사이트를 삭제하시겠습니까? @@ -2761,13 +2710,18 @@ 뒤로 + + 디버그 서랍 열기 + 탭 도구 탭 수 - 활성 + 활성 + + 활성 비활성 @@ -2778,6 +2732,16 @@ 탭 생성 도구 생성할 탭 수량 + + 텍스트 필드가 비어 있음 + + 양의 정수만 입력하세요 + + 0보다 큰 숫자를 입력하세요 + + 한 번의 작업으로 생성할 수 있는 최대 탭 수(%1$s)를 초과했음 활성 탭에 추가 @@ -2794,12 +2758,12 @@ 개인정보처리방침 - 보내기 + 보내기 - 닫기 + 닫기 - 의견을 보내 주셔서 감사합니다! + 의견을 보내 주셔서 감사합니다! 매우 만족 @@ -2811,6 +2775,14 @@ 매우 불만족 + + + 설문조사 열기 + + 설문조사 닫기 + + 닫기 + 로그인 diff --git a/mobile/android/fenix/app/src/main/res/values-nb-rNO/strings.xml b/mobile/android/fenix/app/src/main/res/values-nb-rNO/strings.xml index 3479298722..d435330c22 100644 --- a/mobile/android/fenix/app/src/main/res/values-nb-rNO/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-nb-rNO/strings.xml @@ -48,12 +48,20 @@ - Nylig lagret + Nylig lagret - Vis alle lagrede bokmerker + Vis alle lagrede bokmerker - Fjern + Fjern + + + + Bokmerker + + Vis alle bokmerker + + Fjern %1$s er produsert av Mozilla. @@ -191,6 +199,10 @@ Tillegg Utvidelser + + Behandle utvidelser + + Oppdag flere utvidelser Kontoinformasjon @@ -210,18 +222,26 @@ Åpne i vanlig fane Legg til på startskjermen + + Legg til på startskjermen… Installer Synkroniser på nytt Finn på siden + + Finn på siden… Oversett siden + Lagre i samling… + Lagre i samling Del + + Del… Åpne i %1$s @@ -261,6 +281,34 @@ Passord + + Nytt i %1$s + + Bytt til datamaskinversjon + + Verktøy + + Lagre + + Bokmerk denne siden + + Rediger bokmerke + + Lagre som PDF… + + Slå på leservisning + + Slå av leservisning + + Oversett siden… + + Oversatt til %1$s + + Skriv ut… + Ingen utvidelser her @@ -358,8 +406,6 @@ Firefox personvernerklæring - - Les mer i vår personvernerklæring Vi beskytter deg gjerne Språk + + Oversettelse + + Oversettelser Datavalg @@ -638,10 +688,6 @@ Nødvendig Valgfri - - Les og endre nettstedsdata - - Slett nettsted Tillat for alle nettsteder @@ -671,7 +717,9 @@ Hopp inn igjen - Nylige bokmerker + Nylige bokmerker + + Bokmerker Nylig besøkt @@ -765,8 +813,6 @@ Historikk Bokmerker - - Innlogginger Passord @@ -793,8 +839,6 @@ and the third is the device model. --> %1$s på %2$s %3$s - - Betalingskort Betalingsmåter @@ -811,6 +855,14 @@ Fane fra %s + + + %1$s lukket %2$d faner + + Vis nylig lukkede faner + Unntak @@ -1742,13 +1794,9 @@ Du kan enkelt legge til dette nettstedet på enhetens startskjerm for å få øyeblikkelig tilgang og surfe raskere med en app-lignende opplevelse. - - Innlogginger og passord Passord - Lagre innlogginger og passord - Lagre passord Spør om å lagre @@ -1764,47 +1812,28 @@ Fyll inn brukernavn og passord i andre apper på enheten din. - - Legg til innlogging - Legg til passord - - Synkroniser innlogginger Synkroniser passord - - Synkroniser innlogginger på tvers av enheter Synkroniser passord på tvers av enheter - - Lagrede innlogginger Lagrede passord - De innlogginger du lagrer eller synkroniserer til %s vil vises her. - Passordene du lagrer eller synkroniserer med %s vil bli oppført her. Alle passord du lagrer er kryptert. - - Les mer om Sync. Les mer om synkronisering Unntak - - Innlogginger og passord som ikke er lagret vil vises her. %s vil ikke lagre passord for nettsteder som er oppført her. - - Innlogginger og passord vil ikke bli lagret for disse nettstedene. %s vil ikke lagre passord for disse nettstedene. Slett alle unntak - - Søk innlogginger Søk etter passord @@ -1834,17 +1863,11 @@ Vis passord Skjul passord - - Lås opp for å se dine lagrede innlogginger Lås opp for å se dine lagrede passord - Sikre dine innlogginger og passord - Sikre dine lagrede passord - Konfigurer en PIN-kode, et passord eller et låsemønster for å forhindre at andre mennesker får tilgang de lagrede innloggingene og passordene dine, hvis de har adgang til din enhet. - Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte dine lagrede passord om noen andre skulle få tak i enheten din. Senere @@ -1864,9 +1887,6 @@ Sist brukt - - Sorter innlogginger-meny - Sorter passord-menyen @@ -1875,41 +1895,27 @@ Autofyll Adresser - - Betalingskort Betalingsmåter - Lagre og fyll ut kort automatisk - Lagre og fyll inn betalingsmåter - - Data er kryptert %s krypterer alle betalingsmåter du lagrer Synkroniser kort på tvers av enheter Synkroniser kort - - Legg til betalingskort Legg til kort - - Behandle lagrede kort Behandle kort Legg til adresse Behandle adresser - - Lagre og autoutfyll adresser Lagre og fyll ut adresser - - Inkluderer informasjon som telefonnummer, e-post og leveringsadresser Inkluderer telefonnumre og e-postadresser @@ -1933,8 +1939,6 @@ Slett kort - Er du sikker på at du vil slette dette bankkortet? - Slett kort? Slett @@ -1947,24 +1951,15 @@ Lagrede kort - - Oppgi et gyldig betalingskortnummer - Skriv inn et gyldig kortnummer - - Fyll ut dette feltet Legg til et navn Lås opp for å se dine lagrede betalingskort - Sikre dine betalingskort - Sikre dine lagrede betalingsmåter - Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte de lagrede betalingskortene dine om noen andre skulle få tak i enheten din. - Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte dine lagrede betalingsmåter om noen andre skulle få tak i enheten din. Konfigurer nå @@ -1973,9 +1968,6 @@ Lås opp enheten din - - Lås opp for å bruke lagret betalingskortinformasjon - Lås opp for å bruke lagrede betalingsmåter @@ -1984,12 +1976,6 @@ Rediger adresse Behandle adresser - - Fornavn - - Mellomnavn - - Etternavn Navn @@ -2015,8 +2001,6 @@ Slett adresse - - Er du sikker på at du vil slette denne adressen? Slette denne adressen? @@ -2117,49 +2101,29 @@ Slett Rediger - - Er du sikker på at du ønsker å slette denne innloggingen? Er du sikker på at du ønsker å slette dette passordet? Slett Avbryt - - Innloggingsalternativer Passordalternativer - - Det redigerbare tekstfeltet for innloggingens nettadresse. Det redigerbare tekstfeltet for nettstedsadressen. - - Det redigerbare tekstfeltet for innloggingens brukernavn. Det redigerbare tekstfeltet for brukernavnet. - Det redigerbare tekstfeltet for innloggingens passord. - Det redigerbare tekstfeltet for passordet. - - Lagre endringer for innlogging. Lagre endringer. - - Rediger Rediger passord - - Legg til ny innlogging Legg til passord - - Passord kreves Skriv inn et passord - Brukernavn påkrevd - Skriv inn et brukernavn Servernavn påkrevd @@ -2508,6 +2472,8 @@ Ikke nå Vis original + + Original uoversatt side lastet inn Ferdig @@ -2564,6 +2530,9 @@ Lukk oversettelsesarket + + Noen innstillinger er midlertidig utilgjengelige. + Oversettelser @@ -2588,6 +2557,9 @@ Velg et språk for å behandle innstillinger for «oversett alltid» og «oversett aldri». + + Kunne ikke laste inn språk. Prøv igjen senere. + Tilby å oversette (standard) @@ -2610,6 +2582,8 @@ Fjern %1$s + + Kunne ikke laste inn nettsteder. Prøv igjen senere. Vil du slette %1$s? @@ -2687,13 +2661,18 @@ Naviger tilbake + + Åpne feilsøkingsskuffen + Faneverktøy Antall faner - Aktiv + Aktiv + + Aktiv Inaktiv @@ -2704,6 +2683,16 @@ Verktøy for å lage faner Antall faner som skal opprettes + + Tekstfeltet er tomt + + Angi bare positive heltall + + Skriv inn et tall større enn null + + Overskredet det maksimale antallet faner (%1$s) som kan genereres i én operasjon Legg til aktive faner @@ -2711,6 +2700,39 @@ Legg til private faner + + + + Fortsett + + Gjennomfør denne undersøkelsen + + Personvernbestemmelser + + Send + + Lukk + + Takk for tilbakemeldingen! + + Veldig fornøyd + + Fornøyd + + Nøytral + + Misfornøyd + + Veldig misfornøyd + + + + Åpen undersøkelse + + Lukk undersøkelse + + Lukk + Innlogginger diff --git a/mobile/android/fenix/app/src/main/res/values-night/colors.xml b/mobile/android/fenix/app/src/main/res/values-night/colors.xml index 2b7178b3d2..228a8df4af 100644 --- a/mobile/android/fenix/app/src/main/res/values-night/colors.xml +++ b/mobile/android/fenix/app/src/main/res/values-night/colors.xml @@ -26,11 +26,11 @@ @color/photonYellow70A77 - @color/photonGreen80 + @color/photonGreen80 - @color/photonPink80 + @color/photonPink80 - @color/photonBlue50A80 + @color/photonBlue50 @color/photonDarkGrey80 @@ -40,7 +40,7 @@ @color/photonViolet60A50 - @color/photonLightGrey30 + @color/photonDarkGrey05 @color/photonDarkGrey10 @@ -48,11 +48,11 @@ @color/photonYellow40A41 - @color/photonGreen70 + @color/photonGreen70 - @color/photonPink70A69 + @color/photonPink70A69 - @color/photonBlue30 + @color/photonBlue60 @color/photonLightGrey05 @@ -78,9 +78,9 @@ @color/photonLightGrey05A40 - @color/photonRed20 + @color/photonRed20 - @color/photonRed70 + @color/photonRed20 @color/photonViolet20 @@ -94,7 +94,7 @@ @color/photonLightGrey05A40 - @color/photonDarkGrey90 + @color/photonLightGrey05 @color/photonLightGrey05 @@ -117,9 +117,9 @@ @color/photonBlue30 @color/photonLightGrey05 - @color/photonRed20 + @color/photonRed20 - @color/photonRed70 + @color/photonRed20 @color/photonViolet20 @color/photonBlue20 @color/photonPink20 @@ -128,7 +128,7 @@ @color/photonLightGrey05 - @color/photonDarkGrey90 + @color/photonLightGrey05 @color/photonLightGrey05 @@ -149,7 +149,7 @@ @color/photonLightGrey05A40 - @color/photonRed40 + @color/photonRed20 @color/photonDarkGrey60 diff --git a/mobile/android/fenix/app/src/main/res/values-nl/strings.xml b/mobile/android/fenix/app/src/main/res/values-nl/strings.xml index 3277940ff0..8d3f1fd0b8 100644 --- a/mobile/android/fenix/app/src/main/res/values-nl/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-nl/strings.xml @@ -204,6 +204,10 @@ Add-ons Extensies + + Extensies beheren + + Meer extensies ontdekken Accountinformatie @@ -222,6 +226,8 @@ Openen in gewoon tabblad Toevoegen aan startscherm + + Toevoegen aan startscherm… Installeren @@ -233,9 +239,13 @@ Pagina vertalen + In collectie opslaan… + In collectie opslaan Delen + + Delen… Openen in %1$s @@ -290,6 +300,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Opslaan + + Bladwijzer voor deze pagina maken + + Bladwijzer bewerken + + Opslaan als PDF… + + Lezerweergave inschakelen + + Lezerweergave uitschakelen + + Pagina vertalen… + + Vertaald naar %1$s + + Afdrukken… + Geen extensies hier @@ -387,8 +415,6 @@ Firefox-privacyverklaring - - Lees meer in onze privacyverklaring We houden u graag veilig Taal - Vertaling + Vertaling + + Vertalingen Gegevenskeuzes @@ -670,10 +698,6 @@ Vereist Optioneel - - Websitegegevens lezen en wijzigen - - Website verwijderen Toestaan voor alle websites @@ -800,8 +824,6 @@ Geschiedenis Bladwijzers - - Aanmeldingen Wachtwoorden @@ -828,8 +850,6 @@ and the third is the device model. --> %1$s op %2$s %3$s - - Creditcards Betalingsmethoden @@ -845,6 +865,14 @@ Tabblad van %s + + + %1$s-tabbladen gesloten: %2$d + + Onlangs gesloten tabbladen bekijken + Uitzonderingen @@ -1776,13 +1804,9 @@ U kunt deze website eenvoudig aan het startscherm van uw apparaat toevoegen, om zo direct toegang te hebben en sneller te navigeren met een app-achtige ervaring. - - Aanmeldingen en wachtwoorden Wachtwoorden - Aanmeldingen en wachtwoorden opslaan - Wachtwoorden opslaan Vragen om op te slaan @@ -1797,46 +1821,27 @@ Gebruikersnamen en wachtwoorden in andere apps op uw apparaat invullen. - - Aanmelding toevoegen - Wachtwoord toevoegen - - Aanmeldingen synchroniseren Wachtwoorden synchroniseren - - Aanmeldingen op apparaten synchroniseren Wachtwoorden synchroniseren tussen apparaten - - Opgeslagen aanmeldingen Opgeslagen wachtwoorden - De aanmeldingen die u opslaat of synchroniseert met %s worden hier getoond. - De wachtwoorden die u opslaat of synchroniseert met %s zullen hier worden vermeld. Alle wachtwoorden die u opslaat, zijn versleuteld. - - Meer info over Sync. Meer info over synchronisatie Uitzonderingen - - Niet-opgeslagen aanmeldingen en wachtwoorden worden hier weergegeven. %s zal geen wachtwoorden voor de hier vermelde websites opslaan. - - Aanmeldingen en wachtwoorden worden voor deze websites niet opgeslagen. %s zal geen wachtwoorden voor deze websites opslaan. Alle uitzonderingen verwijderen - - Aanmeldingen zoeken Wachtwoorden zoeken @@ -1865,17 +1870,11 @@ Wachtwoord tonen Wachtwoord verbergen - - Ontgrendel om uw opgeslagen aanmeldingen te bekijken Ontgrendel om uw opgeslagen wachtwoorden te bekijken - Beveilig uw aanmeldingen en wachtwoorden - Beveilig uw opgeslagen wachtwoorden - Stel een vergrendelingspatroon, pincode of wachtwoord voor uw apparaat in om uw opgeslagen aanmeldingen en wachtwoorden te beschermen tegen toegang als iemand anders uw apparaat heeft. - Stel een vergrendelingspatroon, pincode of wachtwoord voor uw apparaat in om uw opgeslagen wachtwoorden te beschermen tegen toegang als iemand anders uw apparaat heeft. Later @@ -1892,8 +1891,6 @@ Naam (A-Z) Laatst gebruikt - - Menu Aanmeldingen sorteren Wachtwoordmenu sorteren @@ -1903,41 +1900,27 @@ Automatisch invullen Adressen - - Creditcards Betalingsmethoden - Kaarten opslaan en automatisch invullen - Betalingsmethoden opslaan en invullen - - Gegevens zijn versleuteld %s versleutelt alle betalingsmethoden die u opslaat Kaarten synchroniseren tussen apparaten Kaarten synchroniseren - - Creditcard toevoegen Kaart toevoegen - - Opgeslagen kaarten beheren Kaarten beheren Adres toevoegen Adressen beheren - - Adressen opslaan en automatisch invullen Adressen opslaan en invullen - - Informatie zoals nummers, e-mail- en verzendadressen toevoegen Inclusief telefoonnummers en e-mailadressen @@ -1961,8 +1944,6 @@ Kaart verwijderen - Weet u zeker dat u deze creditcard wilt verwijderen? - Kaart verwijderen? Verwijderen @@ -1976,24 +1957,15 @@ Opgeslagen kaarten - - Voer een geldig creditcardnummer in - Voer een geldig kaartnummer in - - Vul dit veld in Voeg een naam toe Ontgrendel om uw opgeslagen kaarten te bekijken - Beveilig uw creditcards - Beveilig uw opgeslagen betalingsmethoden - Stel een vergrendelingspatroon, pincode of wachtwoord voor uw apparaat in om uw opgeslagen creditcards te beschermen tegen toegang als iemand anders uw apparaat heeft. - Stel een vergrendelingspatroon, pincode of wachtwoord voor uw apparaat in om uw opgeslagen betalingsmethoden te beschermen tegen toegang als iemand anders uw apparaat heeft. Nu instellen @@ -2002,9 +1974,6 @@ Ontgrendel uw apparaat - - Ontgrendelen om opgeslagen creditcardgegevens te gebruiken - Ontgrendel om opgeslagen betalingsmethoden te gebruiken @@ -2013,12 +1982,6 @@ Adres bewerken Adressen beheren - - Voornaam - - Tweede naam - - Achternaam Naam @@ -2044,8 +2007,6 @@ Adres verwijderen - - Weet u zeker dat u dit adres wilt verwijderen? Dit adres verwijderen? @@ -2144,49 +2105,29 @@ Verwijderen Bewerken - - Weet u zeker dat u deze aanmelding wilt verwijderen? Weet u zeker dat u dit wachtwoord wilt verwijderen? Verwijderen Annuleren - - Aanmeldopties Wachtwoordopties - - Het bewerkbare tekstveld voor het webadres van de aanmelding. Het bewerkbare tekstveld voor het websiteadres. - - Het bewerkbare tekstveld voor de gebruikersnaam van de aanmelding. Het bewerkbare tekstveld voor de gebruikersnaam. - Het bewerkbare tekstveld voor het wachtwoord van de aanmelding. - Het bewerkbare tekstveld voor het wachtwoord. - - Wijzigingen aan aanmelding opslaan. Wijzigingen opslaan. - - Bewerken Wachtwoord bewerken - - Nieuwe aanmelding toevoegen Wachtwoord toevoegen - - Wachtwoord vereist Vul een wachtwoord in - Gebruikersnaam vereist - Vul een gebruikersnaam in Hostnaam vereist @@ -2537,6 +2478,8 @@ Niet nu Origineel tonen + + Oorspronkelijk onvertaalde pagina geladen Gereed @@ -2594,6 +2537,9 @@ Blad Vertalingen sluiten + + Sommige instellingen zijn tijdelijk niet beschikbaar. + Vertalingen @@ -2617,6 +2563,9 @@ Selecteer een taal om de voorkeuren ‘Altijd vertalen’ en ‘Nooit vertalen’ te beheren. + + Kan talen niet laden. Probeer het later nog eens. + Aanbieden om te vertalen (standaard) @@ -2639,6 +2588,8 @@ %1$s verwijderen + + Kan websites niet laden. Probeer het later nog eens. %1$s verwijderen? @@ -2716,13 +2667,18 @@ Terug bladeren + + Debuglade openen + Tabbladhulpmiddelen Aantal tabbladen - Actief + Actief + + Actief Inactief @@ -2733,6 +2689,16 @@ Hulpmiddel voor het aanmaken van tabbladen Aantal aan te maken tabbladen + + Tekstveld is leeg + + Voer alleen positieve gehele getallen in + + Voer een getal groter dan nul in + + Het maximale aantal tabbladen (%1$s) dat in één bewerking kan worden aangemaakt is overschreden Toevoegen aan actieve tabbladen @@ -2749,11 +2715,11 @@ Privacyverklaring - Indienen + Indienen - Sluiten + Sluiten - Bedankt voor uw feedback! + Bedankt voor uw feedback! Zeer tevreden @@ -2765,6 +2731,14 @@ Zeer ontevreden + + + Enquête openen + + Enquête sluiten + + Sluiten + Aanmeldingen diff --git a/mobile/android/fenix/app/src/main/res/values-nn-rNO/strings.xml b/mobile/android/fenix/app/src/main/res/values-nn-rNO/strings.xml index a85ae3472e..928e6f2594 100644 --- a/mobile/android/fenix/app/src/main/res/values-nn-rNO/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-nn-rNO/strings.xml @@ -51,12 +51,20 @@ - Nyleg lagra + Nyleg lagra - Vis alle lagra bokmerke + Vis alle lagra bokmerke - Fjern + Fjern + + + + Bokmerke + + Vis alle bokmerke + + Fjern %1$s er produsert av Mozilla. @@ -197,6 +205,10 @@ Tillegg Utvidingar + + Handsam utviding + + Oppdag fleire utvidingar Kontoinformasjon @@ -215,18 +227,26 @@ Opne i vanlig fane Legg til på startskjermen + + Legg til på startskjermen… Installer Synkroniser på nytt Finn på sida + + Finn på sida… Omset sida + Lagre i samling… + Lagre i samling Del + + Del… Opne i %1$s @@ -258,11 +278,44 @@ Synkroniser passord, faner, og meir + + Logg inn igjen for å synkronisere + + Synkronisering sett på pause Ny privat fane Passord + + Nytt i %1$s + + Byt til datamaskinversjon + + Verktøy + + Lagre + + Bokmerk denne sida + + Rediger bokmerke + + Lagre som PDF… + + + Slå på leservising + + Slå av leservising + + Omset sida… + + Omset til %1$s + + Skriv ut… + Ingen utvidingar her @@ -359,10 +412,15 @@ Firefox personvernerklæring - - Les meir i personvernerklæringa vår Vi vernar deg gjerne + + Finn ut kvifor millionar likar Firefox + + Sikker surfing med fleire val + + Den ideelle nettlesaren vår hindrar selskap i å spore aktiviteten din i hemmelegheit på nettet. Meir enn 100 millionar menneske beskyttar personvernet sitt ved å velje ein nettleser som er støtta av ein ideell organisasjon. @@ -385,6 +443,12 @@ Logg inn Ikkje no + + Varsel gjer deg tryggare med Firefox + + Send faner trygt mellom einingane dine og oppdag andre personvernfunksjonar i Firefox. Slå på varsel @@ -437,6 +501,8 @@ Søkjemotorar Forslag frå søkjemotorar + + Innstillingar for adresselinja Adresselinje - Firefox forslag @@ -553,6 +619,10 @@ Kople til igjen for å halde fram synkroniseringa Språk + + Omsetting + + Omsettingar Dataval @@ -611,6 +681,8 @@ Utvidingar Installer utviding frå fil + + Installer utviding frå fil Varsel @@ -623,9 +695,18 @@ Påkravd + + Valfri + + Tillat for alle nettstadar + + Viss du stolar på denne utvidinga, kan du gje henne løyve på kvar nettstad. + Tilpassa tilleggssamling + + Tilpassa utvidings-samling OK @@ -637,11 +718,16 @@ Tilleggssamling endra. Avsluttar applikasjonen for å bruke endringar… + + Utvidingssamling endra. Avsluttar applikasjonen for å leggje til endringar… + Hopp inn igjen - Nylege bokmerke + Nylege bokmerke + + Bokmerke Nyleg besøkte @@ -693,22 +779,38 @@ Nye utvidingar tilgjengelege no + + Nye utvidingar tilgjengelege no Sjekk ut 100+ nye utvidingar som lèt deg gjere Firefox til din eigen. Utforsk utvidingar + + Utforsk utvidingar + Tillegg er mellombels deaktivert + + Utvidinga er mellombels deaktivert Eitt eller fleire tillegg slutta å fungere, noko som gjorde systemet ditt ustabilt. %1$s prøvde utan hell å starte tillegget/tillegga på nytt.\n\nTillegg vil ikkje bli starta på nytt under gjeldande økt.\n\nDersom du fjernar eller deaktiverer tillegg, kan dette løyse problemet. + + Ei eller flere utvidingar slutta å fungere, noko som gjorde systemet ditt ustabilt. %1$s prøvde utan hell å starte utvidinga(ne) på nytt.\n\nUtvidingane vil ikkje bli starta på nytt under gjeldande økt.\n\nViss du fjernar eller deaktiverer utvidingar, kan dette løyse problemet. Prøv å starte tillegga på nytt + + Prøv å starte utvidingar på nytt Hald fram med deaktiverte tillegg + + Hald fram med utvidingar deaktiverte + Handsam kontoen @@ -722,8 +824,6 @@ Historikk Bokmerke - - Innloggingar Passord @@ -750,8 +850,6 @@ and the third is the device model. --> %1$s på %2$s %3$s - - Betalingskort Betalingsmåtar @@ -767,6 +865,15 @@ Fane frå %s + + + %1$s lét att %2$d faner + + + Vis nyleg attlatne faner + Unntak @@ -1347,6 +1454,8 @@ Late att private faner? + Trykk eller sveip dette varselet for å late att private faner. + Marknadsføring @@ -1700,13 +1809,9 @@ Du kan enkelt leggje til denne nettstaden på startskjermen til eininga for å, med ein gong, få tilgang og surfe raskare med ei app-liknande oppleving. - - Innloggingar og passord Passord - Lagre innloggingar og passord - Lagre passord Spør om å lagre @@ -1723,40 +1828,27 @@ Fyll inn brukarnamn og passord i andre appar på eininga di. - - Legg til innlogging - Legg til passord - - Synkroniser innloggingar Synkroniser passord - - Synkroniser innloggingar på tvers av einingar Synkroniser passord på tvers av einingar - - Lagra innloggingar Lagra passord - Innloggingane du lagrar eller synkroniserer til %s vil visast her. - - Les meir om Sync. + Passorda du lagrar eller synkroniserer med %s vil bli oppførte her. Alle passorda du lagrar er krypterte. Les meir om synkronisering Unntak - - Innloggingar og passord som ikkje er lagra vil visast her. - - Innloggingar og passord vil ikkje bli lagra for desse nettstadane. + + %s vil ikkje lagre passord for nettstadar som er oppførte her. + + %s vil ikkje lagre passord for desse nettstadane. Slett alle unntak - - Søk innloggingar Søk etter passord @@ -1786,12 +1878,12 @@ Vis passord Gøym passord - - Lås opp for å sjå dei lagra innloggingane dine + + Lås opp for å sjå dei lagra passorda dine - Gjer innloggingane og passorda dine trygge + Sikre dei lagra passorda dine - Lag ein PIN-kode, eit passord eller eit låsemønster for å hindre at andre får tilgang dei lagra innloggingane og passorda dine, dersom dei har tilgang til eininga di. + Konfigurer ein PIN-kode, eit passord eller eit låsemønster for å beskytte dei lagra passorda dine om nokon andre skulle få tak i eininga di. Seinare @@ -1807,8 +1899,6 @@ Namn (A-Å) Sist brukt - - Sorter inloggningsmenyen Sorter passord-menyen @@ -1818,27 +1908,19 @@ Autofyll Adresser - - Betalingskort Betalingsmåtar - Lagre og fyll ut kort automatisk - Lagre og fyll inn betalingsmåtar - - Data er kryptert + + %s krypterer alle betalingsmåtar du lagrar Synkroniser kort på tvers av einingar Synkroniser kort - - Legg til betalingskort Legg til kort - - Handsam lagra kort Handsam kort @@ -1846,12 +1928,8 @@ Handsam adresser - - Lagre og fyll ut adresser automatisk Lagre og fyll ut adresser - - Inkluderer informasjon som telefonnummer, e-post og leveringsadresser Inkluderer telefonnummer og e-postadresser @@ -1875,8 +1953,6 @@ Slett kort - Er du sikker på at du vil slette dette bankkortet? - Slette kort? Slett @@ -1890,21 +1966,16 @@ Lagra bankkort - - Skriv inn eit gyldig betalingskortnummer - Skriv inn eit gyldig kortnummer - - Fyll ut dette feltet Legg til eit namn Lås opp for å sjå lagra betalingskort - Sikre betalingskorta dine + Sikre dei lagra betalingsmåtane dine - Konfigurer eit låsemønster for eininga, PIN, eller passord, for å beskytte dei lagra betalingskorta dine om nokon andre skulle få tak i eininga di. + Konfigurer ein PIN-kode, eit passord eller eit låsemønster for å beskytte dei lagra betalingsmåtane dine om nokon annan skulle få tak i eininga di. Konfigurer no @@ -1912,21 +1983,14 @@ Lås opp eininga di - - Lås opp for å bruke lagra betalingskortinformasjon - + + Lås opp for å bruke lagra betalingsmåtar Legg til adresse Rediger adresse Handsam adresser - - Fornamn - - Mellomnamn - - Etternamn Namn @@ -1952,8 +2016,6 @@ Slett adresse - Er du sikker på at du vil slette denne adressa? - Slette denne adressa? Slett @@ -2055,41 +2117,29 @@ Slett Rediger - - Er du sikker på at du ønskjer å slette denne innlogginga? + + Er du sikker på at du ønskjer å slette dette passordet? Slett Avbryt - - Innloggingsalternativ Passordalternativ - - Det redigerbare tekstfeltet for innloggings-nettadressa. - - Det redigerbare tekstfeltet for innloggings-brukarnamnet. + + Det redigerbare tekstfeltet for nettstadadressa. + + Det redigerbare tekstfeltet for brukarnamnet. - Det redigerbare tekstfeltet for innloggings-passordet. - - Lagre endringar for innlogging. + Det redigerbare tekstfeltet for passordet. Lagre endringar. - - Rediger Rediger passord - - Legg til ny innlogging Legg til passord - - Passord påkravd Skriv inn passord - Brukarnamn påkravd - Skriv inn eit brukarnamn Vertsnamn påkravd @@ -2192,6 +2242,9 @@ Søk med %s + + + Endre standardnettlesar Vel at lenker frå nettstadar, e-postmeldingar og meldingar skal opnast automatisk i Firefox. @@ -2342,6 +2395,10 @@ Med krafta i %1$s frå Mozilla hjelper vi deg med å unngå einsidige og ikkje-truverdige vurderingar. AI-modellen vår vert alltid forbetra for å beskytte deg når du handlar. %2$s Les meir + + Ved å velje «Ja, prøv det» godtek du %1$s sine %2$s og %3$s sine %4$s. + + Ved å velje «Ja, prøv det» godtek du %1$s sine %2$s og %3$s sine %4$s. peronvernerklæring @@ -2413,16 +2470,28 @@ Omsetje denne sida? + + Sida er omsett frå %1$s til %2$s + + Prøv private omsettingar i %1$s + + Av omsyn til personvernet ditt forlét aldri omsettingar eininga di. Nye språk og forbetringar kjem snart! %1$s Les meir Omset frå Omset til + + Prøv eit anna kjeldespråk Ikkje no Vis original + + Original ikkje-omsett side lasta inn Ferdig @@ -2438,6 +2507,10 @@ Vel eit språk Det oppstod eit problem med å omsetje. Prøv på nytt. + + Klarte ikkje å laste inn språk. Sjekk Internett-tilkoplinga di og prøv igjen. + + Beklagar, vi stør ikkje %1$s enno. Les meir @@ -2445,9 +2518,18 @@ Omset… + + Last ned språk i datasparemodus (%1$s)? + + Innstillingar for omsetjing + + Innstillingar for omsetting Tilby alltid å omsetje @@ -2465,12 +2547,20 @@ Om omsetjingar i %1$s + + Lat att omsettingsarket + + + Nokre innstillingar er mellombels utilgjengelege. + Omsetjingar Tilby å omsetje når det er muleg + + Last alltid ned språk i datasparemodus Innstillingar for omsetjing @@ -2487,6 +2577,9 @@ Vel eit språk for å handsame innstillingar for «omset alltid» og «omset aldri». + + Klarte ikkje å laste inn språk. Prøv igjen seinare. + Tilby å omsetje (standard) @@ -2510,6 +2603,8 @@ Fjern %1$s + + Klarte ikkje å laste inn nettstadar. Prøv igjen seinare. Vil du slette %1$s? @@ -2551,11 +2646,28 @@ The first parameter is the name of the language, for example, "Spanish" and the second parameter is the size in kilobytes or megabytes of the language file. --> Slett %1$s (%2$s)? + + Viss du slettar dette språket, vil %1$s laste ned delar av språk til snøgglageret ditt medan du omset. + + Slette alle språk (%1$s)? + + Viss du slettar alle språk, vil %1$s laste ned delar av språk tilsnøgglageret ditt medan du omset. Slett Avbryt + + Laste ned medan du er i datasparemodus (%1$s)? + + Vi lastar ned delvise språk til snøgglageret ditt for å halde omsettingar private. + + Vi lastar ned delvise språk for å halde omsettingar private. Last alltid ned i datasparemodus @@ -2571,13 +2683,18 @@ Naviger tilbake + + Opne feilsøkingsskuffa + Faneverktøy Antal faner - Verksam + Verksam + + Aktiv Uverksam @@ -2588,6 +2705,16 @@ Verktøy for å lage faner Tal på faner som skal opprettast + + Text field is empty + + Skriv inn berre positive heiltall + + Skriv inn eit tal større enn null + + Overskreid det maksimale talet på faner (%1$s) som kan genererast i éin operasjon Legg til i verksame faner @@ -2599,16 +2726,43 @@ Hald fram + + Ver med i denne undersøkinga Personvernerklæring - Send inn + Send inn - Lat att + Lat att - Takk for tilbakemeldinga di! + Takk for tilbakemeldinga di! + + + Veldig fornøgd + + Fornøgd + + Nøytral + + Misfornøgd + + Veldig misfornøgd + + + + Opne undersøking + + Lat att undersøking + + Lat att Innloggingar - + + Gjeldande domene: %s + + Legg til ei falsk innlogging for dette domenet + + Slett innlogging med brukarnamn %s + diff --git a/mobile/android/fenix/app/src/main/res/values-oc/strings.xml b/mobile/android/fenix/app/src/main/res/values-oc/strings.xml index dadbc407db..93e80beb14 100644 --- a/mobile/android/fenix/app/src/main/res/values-oc/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-oc/strings.xml @@ -48,12 +48,20 @@ - Salvats recentament + Salvats recentament - Afichar totes los marcapaginas enregistrats + Afichar totes los marcapaginas enregistrats - Levar + Levar + + + + Marcapaginas + + Afichar totes los marcapaginas + + Suprimir %1$s es produch per Mozilla. @@ -191,6 +199,10 @@ Moduls Extensions + + Gerir las extensions + + Descobrir mai d’extensions Informacion compte @@ -209,18 +221,26 @@ Dobrir dins un onglet classic Apondre a l’ecran d’acuèlh + + Apondre a l’ecran d’acuèlh… Installar Tornar sincronizar Recercar dins la pagina + + Recercar dins la pagina… Traduire la pagina + Salvar a la colleccion… + Salvar a la colleccion Partejar + + Partejar… Dobrir amb %1$s @@ -262,6 +282,34 @@ Senhals + + Novetats dins %1$s + + Passar en version ordenador + + Aisinas + + Enregistrar + + Marcar aquesta pagina + + Modificar lo marcapagina + + Enregistrar en PDF… + + Passar a la vista lectura + + Quitar lo mòde lectura + + Traduire la pagina… + + Traduch en %1$s + + Imprimir… + Cap d’extension aquí @@ -358,8 +406,6 @@ Politica de confidencialitat de Firefox - - Ne saber mai tocant nòstra politica de confidencialitat Nos impòrta vòstra vida privada Lenga + + Traduccion + + Traduccions Donadas collectadas @@ -641,10 +691,6 @@ Requesida Facultativa - - Consultar e modificar las donadas de sites web - - Suprimir lo site web Autorizar per totes los sites @@ -675,7 +721,9 @@ Tornar a aqueste onglet - Marcats recentament + Marcats recentament + + Marcapaginas Visitats fa res @@ -772,8 +820,6 @@ Istoric Marcapaginas - - Identificants Senhals @@ -802,8 +848,6 @@ and the third is the device model. --> %1$s sus %2$s %3$s - - Cartas de crèdit Metòdes de pagament @@ -819,6 +863,14 @@ Onglet de %s + + + %2$d onglets tampats de %1$s + + Veire los onglets tampats recentament + Excepcions @@ -1766,13 +1818,9 @@ Podètz facilament apondre aqueste site a l’ecran d’acuèlh de vòstre periferic per i accedir dirèctament e i navegar coma se foguèsse una aplicacion. - - Identificants e senhals Senhals - Salvar los identificants e senhals - Salvar senhals Demandar per salvar @@ -1789,47 +1837,28 @@ Emplena los noms d’utilizaires e los senhals d’autres aplicacions sus vòstre periferic. - - Apondre un identificant - Apondre un senhal - - Sincronizar los identificants Sincronizar los senhals - - Sincronizar los identificants entre totes los periferics Sincronizar los senhals entre los periferics - - Identificants salvats Senhals salvats - Aquí se mòstran los identificants que salvatz o sincronizatz amb %s. - Los senhals que salvatz o sincronizatz amb %s seràn listats aquí. Totes los senhals qu’enregistratz son chifrats. - - Per ne saber mai sus Sync. Per ne saber mai sus sync Excepcions - - Los identificants e senhals pas salvats seràn mostrats aquí. %s enregistrarà pas los senhals pels sites listats aicí. - - Los identificants e senhals seràn pas salvats per aquestes sites. %s enregistrarà pas los senhals per aquestes sites. Suprimir totas las excepcions - - Recercar d’identificants Recercar de senhals @@ -1859,17 +1888,11 @@ Amagar lo senhal - - Desblocatz per veire los identificants enregistrats Desblocatz per veire los senhals salvats - Protegissètz vòstres identificants e senhals - Securizatz los senhals salvats - Configuratz un esquèma de verrolhatge, un còdi PIN o un senhal per protegir vòstres identificants de connexion e senhals enregistrats se per cas qualqu’un accedisca a vòstre periferic. - Configuratz un esquèma de desverrolhatge, un còdi PIN o un senhal per protegir vòstres senhals salvats se per cas qualqu’un accedisca a vòstre aparelh. Mai tard @@ -1886,9 +1909,6 @@ Darrièra utilizacion - - Menú per triar los identificants - Menú de tria dels senhals @@ -1897,42 +1917,28 @@ Emplenament automatic Adreças - - Cartas de crèdit Metòdes de pagament - Salvar e completar automaticament las cartas - Enregistrar e entresenhar los metòdes de pagament - - Donadas chifradas %s chifra totes los metòdes de pagament qu’enregistratz Sincronizar las cartas entre los periferics Sincronizar las cartas - - Apondre una cartas de crèdit Apondre una carta - - Gerir las cartas enregistradas Gerir las cartas Apondre una adreça Gerir las adreças - - Salvar e completar automaticament las adreças Salvar e emplenar automaticament las adreças - - Inclutz las informacions coma los numèros, las adreças electronicas e las adreças de liurason Incluses los numèros de telefòn e las adreças electronicas @@ -1956,8 +1962,6 @@ Suprimir la carta - Volètz vertadièrament suprimir aquesta carta bancària ? - Suprimir la carta ? Suprimir @@ -1971,25 +1975,16 @@ Cartas enregistradas - - Picatz un numèro de carta de crèdit valid - Picatz un numèro de carta valid - - Completatz aqueste camp Apondre un nom Desblocatz per veire las cartas enregistradas - - Securizatz vòstras cartas de crèdit Securizatz los metòdes de pagament enregistrats - Configuratz un esquèma de verrolhatge, un còdi PIN o un senhal per protegir vòstres identificants de cartas de crèdit enregistrats se per cas qualqu’un accedisca a vòstre periferic. - Configuratz un esquèma de desverrolhatge, un còdi PIN o un senhal per protegir vòstres mejans de pagament se per cas qualqu’un accedisca a vòstre aparelh. Configurar ara @@ -1998,9 +1993,6 @@ Desverrolhatz lo periferic - - Desverrolhatz per utilizar las informacions de cartas de crèdit enregistradas - Desblocatz per utilizar de metòdes de pagament enregistrats @@ -2009,12 +2001,6 @@ Modificar l’adreça Gestion de las adreças - - Pichon nom - - Segond pichon nom - - Nom d’ostal Nom @@ -2040,8 +2026,6 @@ Suprimir l’adreça - - Volètz vertadièrament suprimir aquesta adreça ? Suprimir aquesta adreça ? @@ -2142,8 +2126,6 @@ Exemple :\nhttps://suggestqueries.google.com/complete/search?client=firefox& Suprimir Modificar - - Volètz vertadièrament suprimir aqueste identificant ? Volètz vertadièrament suprimir aqueste senhal ? @@ -2151,41 +2133,23 @@ Exemple :\nhttps://suggestqueries.google.com/complete/search?client=firefox& Anullar - - Opcions de l’identificant Opcions de senhal - - Lo camp de tèxt modificable per l’adreça web de l’identificant. Lo camp de tèxt modificable per l’adreça del site web. - - Lo camp de tèxt modificable pel nom d’utilizaire de l’identificant. Lo camp de tèxt modificable pel nom d’utilizaire. - Lo camp de tèxt modificable pel senhal de l’identificant. - Lo camp de tèxt modificable pel senhal. - - Enregistratz las modificacions de l’identificant. Enregistrar las modificacions. - - Modificar Modificar lo senhal - - Apondre un identificant novèl Apondre un senhal - - Senhal requesit Picar lo senhal - Lo nom d’utilizaire es obligatòri - Picar un nom d’utilizaire Lo nom d’òste es obligatòri @@ -2535,6 +2499,8 @@ Exemple :\nhttps://suggestqueries.google.com/complete/search?client=firefox& Pas ara Veire la pagina originala + + Pagina originala non traducha cargada Acabat @@ -2718,13 +2684,18 @@ Exemple :\nhttps://suggestqueries.google.com/complete/search?client=firefox& Tornar + + Dobrir lo panèl de desvolopament + Aisinas d’onglet Nombre d’onglets - Actius + Actius + + Actiu Inactius @@ -2742,6 +2713,31 @@ Exemple :\nhttps://suggestqueries.google.com/complete/search?client=firefox& Apondre als onglets privats + + + + Contunhar + + Participar al sondatge + + Politica de confidencialitat + + Mandar + + Tampar + + Mercés per vòstre retorn ! + + Plan satisfach + + Satisfach + + Indiferent + + Insatisfach + + Plan insatisfach + Identificants diff --git a/mobile/android/fenix/app/src/main/res/values-pa-rIN/strings.xml b/mobile/android/fenix/app/src/main/res/values-pa-rIN/strings.xml index 2f5f2c0153..7991883f6d 100644 --- a/mobile/android/fenix/app/src/main/res/values-pa-rIN/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-pa-rIN/strings.xml @@ -205,6 +205,10 @@ ਐਡ-ਆਨ ਇਕਸਟੈਨਸ਼ਨਾਂ + + ਇਕਸਟੈਨਸ਼ਨਾਂ ਦਾ ਇੰਤਜ਼ਾਮ + + ਹੋਰ ਇਕਸਟੈਨਸ਼ਨਾਂ ਲੱਭੋ ਖਾਤਾ ਜਾਣਕਾਰੀ @@ -223,6 +227,8 @@ ਰੈਲੂਲਰ ਟੈਬ ਵਿੱਚ ਖੋਲ੍ਹੋ ਮੁੱਖ ਸਕਰੀਨ ‘ਤੇ ਜੋੜੋ + + …ਮੁੱਖ ਸਕਰੀਨ ‘ਤੇ ਜੋੜੋ ਇੰਸਟਾਲ ਕਰੋ @@ -235,9 +241,13 @@ ਸਫ਼ੇ ਦਾ ਉਲੱਥਾ ਕਰੋ + …ਭੰਡਾਰ ਵਿੱਚ ਸੰਭਾਲੋ + ਭੰਡਾਰ ‘ਚ ਸੰਭਾਲੋ ਸਾਂਝਾ ਕਰੋ + + …ਸਾਂਝਾ ਕਰੋ %1$s ‘ਚ ਖੋਲ੍ਹੋ @@ -291,6 +301,25 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> ਸੰਭਾਲੋ + + ਇਹ ਸਫ਼ੇ ਨੂੰ ਬੁੱਕਮਾਰਕ ਕਰੋ + + ਬੁੱਕਮਾਰਕ ਨੂੰ ਸੋਧੋ + + …PDF ਵਜੋਂ ਸੰਭਾਲੋ + + ਪੜ੍ਹਨ ਝਲਕ ਨੂੰ ਚਾਲੂ ਕਰੋ + + ਪੜ੍ਹਨ ਝਲਕ ਨੂੰ ਬੰਦ ਕਰੋ + + …ਸਫ਼ੇ ਦਾ ਉਲੱਥਾ ਕਰੋ + + + %1$s ਵਿੱਚ ਉਲੱਥਾ ਕੀਤਾ + + …ਪਰਿੰਟ ਕਰੋ + ਇੱਥੇ ਕੋਈ ਇਕਸਟੈਨਸ਼ਨ ਨਹੀਂ ਹੈ @@ -388,8 +417,6 @@ Firefox ਪਰਦੇਦਾਰੀ ਸੂਚਨਾ - - ਸਾਡੀ ਪਰਦੇਦਾਰੀ ਸੂਚਨਾ ਵਿੱਚ ਹੋਰ ਜਾਣਕਾਰੀ ਲਵੋ ਅਸੀਂ ਤੁਹਾਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹਾਂ ਭਾਸ਼ਾ - ਉਲੱਥਾ + ਉਲੱਥਾ + + ਅਨੁਵਾਦ ਡਾਟਾ ਚੋਣਾਂ @@ -675,10 +704,6 @@ ਲੋੜੀਂਦਾ ਚੋਣਵਾਂ - - ਵੈੱਬਸਾਈਟ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਬਦਲਣ - - ਵੈੱਬਸਾਈਟ ਨੂੰ ਹਟਾਉਣ ਸਭ ਸਾਈਟਾਂ ਲਈ ਮਨਜ਼ੂਰੀ @@ -805,8 +830,6 @@ ਅਤੀਤ ਬੁੱਕਮਾਰਕ - - ਲਾਗਇਨ ਪਾਸਵਰਡ @@ -834,8 +857,6 @@ and the third is the device model. --> %2$s %3$s ਉੱਤੇ %1$s - - ਕਰੈਡਿਟ ਕਾਰਡ ਭੁਗਤਾਨ ਦੇ ਢੰਗ @@ -851,6 +872,14 @@ %s ਤੋਂ ਟੈਬ + + + %1$s ਟੈਬਾਂ ਬੰਦ ਕੀਤੀਆਂ: %2$d + + ਤਾਜ਼ਾ ਬੰਦ ਕੀਤੀਆਂ ਟੈਬਾਂ ਨੂੰ ਵੇਖੋ + ਛੋਟਾਂ @@ -1787,13 +1816,9 @@ ਤੁਸੀਂ ਇਸ ਵੈੱਬਸਾਈਟ ਨੂੰ ਫ਼ੌਰੀ ਤੌਰ ਉੱਤੇ ਵਰਤਣ ਤੇ ਐਪ ਵਾਂਗ ਤੇਜ਼ ਬਰਾਊਜ਼ ਕਰਨ ਲਈ ਸੌਖੀ ਤਰ੍ਹਾਂ ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਸੌਖੀ ਤਰ੍ਹਾਂ ਜੋੜ ਸਕਦੇ ਹੋ। - - ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡ ਪਾਸਵਰਡ - ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡ ਸੰਭਾਲੋ - ਪਾਸਵਰਡਾਂ ਨੂੰ ਸੰਭਾਲੋ ਸੰਭਾਲਣ ਲਈ ਪੁੱਛੋ @@ -1808,46 +1833,28 @@ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਹੋਰ ਐਪਾਂ ਵਿੱਚ ਵਰਤੋਂਕਾਰ-ਨਾਂ ਅਤੇ ਪਾਸਵਰਡ ਭਰੋ। - - ਲਾਗਇਨ ਜੋੜੋ ਪਾਸਵਰਡ ਜੋੜੋ - - ਲਾਗਇਨ ਸਿੰਕ ਕਰੋ ਪਾਸਵਰਡ ਸਿੰਕ ਕਰੋ - - ਡਿਵਾਈਸਾਂ ਵਿਚਾਲੇ ਲਾਗਇਨਾਂ ਨੂੰ ਸਿੰਕ ਕਰੋ ਪਾਸਵਰਡ ਡਿਵਾਈਸਾਂ ਵਿਚਾਲੇ ਸਿੰਕ ਕਰੋ - - ਸੰਭਾਲੇ ਹੋਏ ਲਾਗਇਨ ਸੰਭਾਲੇ ਹੋਏ ਪਾਸਵਰਡ - ਤੁਹਾਡੇ ਵਲੋਂ ਸੰਭਾਲੇ ਲਾਗਇਨ ਜਾਂ %s ਨਾਲ ਸਿੰਕ ਕੀਤੇ ਇੱਥੇ ਵੇਖਾਏ ਜਾਣਗੇ। - ਤੁਹਾਡੇ ਵਲੋਂ %s ਵਿੱਚ ਸੰਭਾਲੇ ਜਾਂ ਸਿੰਕ ਕੀਤੇ ਪਾਸਵਰਡਾਂ ਨੂੰ ਇੱਥੇ ਦਿਖਾਇਆ ਜਾਵੇਗਾ। ਤੁਹਾਡੇ ਸਾਰੇ ਸੰਭਾਲੇ ਪਾਸਵਰਡ ਇੰਕ੍ਰਿਪਟ ਕੀਤੇ ਹੁੰਦੇ ਹਨ। - - ਸਿੰਕ ਬਾਰੇ ਹੋਰ ਜਾਣੋ। ਸਿੰਕ ਬਾਰੇ ਹੋਰ ਜਾਣੋ ਛੋਟਾਂ - - ਨਾ ਸੰਭਾਲੇ ਹੋਏ ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡਾਂ ਨੂੰ ਇੱਥੇ ਸੰਭਾਲਿਆ ਜਾਵੇਗਾ। %s ਇੱਥੇ ਦਿੱਤੀਆਂ ਹੋਈਆਂ ਸਾਈਟਾਂ ਲਈ ਪਾਸਵਰਡ ਨਹੀਂ ਸੰਭਾਲੇਗਾ। - - ਇਹਨਾਂ ਸਾਈਟਾਂ ਲਈ ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡ ਨਹੀਂ ਸੰਭਾਲੇ ਜਾਣਗੇ। %s ਇਹਨਾਂ ਸਾਈਟਾਂ ਲਈ ਪਾਸਵਰਡ ਨਹੀਂ ਸੰਭਾਲੇਗਾ। ਸਾਰੀਆਂ ਛੋਟਾਂ ਹਟਾ ਦਿਓ - - ਲਾਗਇਨ ਖੋਜੋ ਪਾਸਵਰਡ ਖੋਜੋ @@ -1876,17 +1883,11 @@ ਪਾਸਵਰਡ ਵੇਖਾਓ ਪਾਸਵਰਡ ਲੁਕਾਓ - - ਆਪਣੇ ਸੰਭਾਲੇ ਲਾਗਇਨ ਵੇਖਣ ਲਈ ਅਣ-ਲਾਕ ਕਰੋ ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਪਾਸਵਰਡ ਵੇਖਣ ਲਈ ਅਣ-ਲਾਕ ਕਰੋ - ਆਪਣੇ ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡ ਸੁਰੱਖਿਅਤ ਕਰੋ - ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਪਾਸਵਰਡਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ - ਆਪਣੇ ਲਾਗਇਨਾਂ ਅਤੇ ਪਾਸਵਰਡਾਂ ਨੂੰ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਨੂੰ ਰੱਖਣ ਵਾਲੇ ਕਿਸੇ ਦੀ ਪਹੁੰਚ ਤੋਂ ਸੁਰੱਖਿਅਤ ਰੱਖਣ ਲਈ ਡਿਵਾਈਸ ਲਾਕ ਪੈਟਰਨ, ਪਿੰਨ ਜਾਂ ਪਾਸਵਰਡ ਸੈਟ ਅੱਪ ਕਰੋ। - ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਪਾਸਵਰਡਾਂ ਨੂੰ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਕਿਸੇ ਹੋਰ ਵੱਲੋਂ ਪਹੁੰਚ ਕਰਨ ਤੋਂ ਬਚਾਉਣ ਤੋਂ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਡਿਵਾਈਸ ਲਾਕ ਪੈਟਰਨ, ਪਿੰਨ ਜਾਂ ਪਾਸਵਰਡ ਸੈੱਟ ਕਰੋ। ਬਾਅਦ \'ਚ @@ -1904,8 +1905,6 @@ ਨਾਂ (A-Z) ਆਖਰੀ ਵਰਤੋਂ - - ਲਾਗਇਨ ਲੜੀਬਧ ਮੇਨੂ ਪਾਸਵਰਡ ਮੇਨੂ ਨੂੰ ਲੜੀਬੱਧ ਕਰੋ @@ -1915,40 +1914,26 @@ ਆਪੇ-ਭਰੋ ਸਿਰਨਾਵੇਂ - - ਕਰੈਡਿਟ ਕਾਰਡ ਭੁਗਤਾਨ ਦੇ ਢੰਗ - ਕਾਰਡ ਸੰਭਾਲੋ ਅਤੇ ਆਪਣੇ-ਆਪ ਭਰੋ - ਭੁਗਤਾਨ ਦੇ ਢੰਗ ਸੰਭਾਲੋ ਅਤੇ ਭਰੋ - - ਡਾਟਾ ਇੰਕ੍ਰਿਪਟ ਕੀਤਾ ਹੈ %s ਤੁਹਾਡੇ ਵਲੋਂ ਸੰਭਾਲੇ ਸਾਰੇ ਭੁਗਤਾਨ ਢੰਗਾਂ ਨੂੰ ਇੰਕ੍ਰਿਪਟ ਕਰਦਾ ਹੈ ਡਿਵਾਈਸਾਂ ਵਿਚਾਲੇ ਕਾਰਡਾਂ ਨੂੰ ਸਿੰਕ ਕਰੋ ਕਾਰਡ ਸਿੰਕ ਕਰੋ - - ਕਰੈਡਿਟ ਕਾਰਡ ਜੋੜੋ ਕਾਰਡ ਜੋੜੋ - - ਸੰਭਾਲੇ ਹੋਏ ਕਾਰਡਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ ਕਾਰਡਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ ਸਿਰਨਾਵਾਂ ਜੋੜੋ ਸਿਰਨਾਵਿਆਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ - - ਸਿਰਨਾਵੇਂ ਸੰਭਾਲੋ ਅਤੇ ਆਪਣੇ-ਆਪ ਭਰੋ ਸਿਰਨਾਵਿਆਂ ਨੂੰ ਸੰਭਾਲੋ ਅਤੇ ਭਰੋ - - ਨੰਬਰ, ਈਮੇਲ ਅਤੇ ਭੇਜਣ ਵਾਲੇ ਸਿਰਨਾਵਿਆਂ ਸਮੇਤ ਜਾਣਕਾਰੀ ਫ਼ੋਨ ਨੰਬਰਾਂ ਅਤੇ ਈਮੇਲ ਸਿਰਨਾਵਿਆਂ ਸਮੇਤ @@ -1972,8 +1957,6 @@ ਕਾਰਡ ਨੂੰ ਹਟਾਓ - ਕੀ ਤੁਸੀਂ ਇਹ ਕਰੈਡਿਟ ਕਾਰਡ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? - ਕਾਰਡ ਨੂੰ ਹਟਾਉਣਾ ਹੈ? ਹਟਾਓ @@ -1987,24 +1970,15 @@ ਸੰਭਾਲੇ ਹੋਏ ਕਾਰਡ - - ਵਾਜਬ ਕਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ ਦਿਓ ਜੀ - ਵਾਜਬ ਕਾਰਡ ਨੰਬਰ ਭਰੋ - - ਇਹ ਖੇਤਰ ਭਰੋ ਨਾਂ ਜੋੜੋ ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਕਾਰਡ ਵੇਖਣ ਲਈ ਅਣ-ਲਾਕ ਕਰੋ - ਆਪਣੇ ਕਰੈਡਿਟ ਕਾਰਡ ਸੁਰੱਖਿਅਤ ਕਰੋ - ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਭੁਗਤਾਨ ਢੰਗਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ - ਆਪਣੀ ਡਿਵਾਇਸ ਲਈ ਲਾਕ ਪੈਟਰਨ, ਪਿੰਨ, ਜਾਂ ਪਾਸਵਰਡ ਸੈੱਟ ਕਰੋ ਤਾਂ ਜੋ ਕਿਸੇ ਹੋਰ ਹੱਥ ਤੁਹਾਡਾ ਡਿਵਾਇਸ ਹੋਣ ਉੱਤੇ ਤੁਹਾਡੇ ਸੰਭਾਲੇ ਪਾਸਵਰਡ ਸੁਰੱਖਿਅਤ ਰਹਿਣ। - ਆਪਣੇ ਸੰਭਾਲੇ ਹੋਏ ਪਾਸਵਰਡ ਢੰਗਾਂ ਨੂੰ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਕਿਸੇ ਹੋਰ ਵੱਲੋਂ ਪਹੁੰਚ ਕਰਨ ਤੋਂ ਬਚਾਉਣ ਤੋਂ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਡਿਵਾਈਸ ਲਾਕ ਪੈਟਰਨ, ਪਿੰਨ ਜਾਂ ਪਾਸਵਰਡ ਸੈੱਟ ਕਰੋ। ਹੁਣੇ ਸੈਟ ਅੱਪ ਕਰੋ @@ -2012,8 +1986,6 @@ ਬਾਦ \'ਚ ਆਪਣਾ ਡਿਵਾਇਸ ਅਣ-ਲਾਕ ਕਰੋ - - ਸੰਭਾਲੀ ਕਰੈਡਿਟ ਕਾਰਡ ਜਾਣਕਾਰੀ ਵਰਤਣ ਲਈ ਅਣ-ਲਾਕ ਕਰੋ ਸੰਭਾਲੇ ਹੋਏ ਭੁਗਤਾਨ ਢੰਗਾਂ ਨੂੰ ਵਰਤਣ ਲਈ ਅਣ-ਲਾਕ ਕਰੋ @@ -2023,12 +1995,6 @@ ਸਿਰਨਾਵੇਂ ਨੂੰ ਸੋਧੋ ਸਿਰਨਾਵਿਆਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ - - ਨਾਂ ਦਾ ਪਹਿਲਾਂ ਹਿੱਸਾ - - ਮੱਧ ਨਾਂ - - ਨਾਂ ਦਾ ਆਖਰੀ ਹਿੱਸਾ ਨਾਂ @@ -2054,8 +2020,6 @@ ਸਿਰਨਾਵੇਂ ਨੂੰ ਹਟਾਓ - - ਕੀ ਤੁਸੀਂ ਇਹ ਸਿਰਨਾਵੇਂ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? ਇਹ ਸਿਰਨਾਵੇਂ ਨੂੰ ਹਟਾਉਣਾ ਹੈ? @@ -2154,8 +2118,6 @@ ਹਟਾਓ ਸੋਧੋ - - ਕੀ ਤੁਸੀਂ ਇਹ ਲਾਗਇਨ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? ਕੀ ਤੁਸੀਂ ਇਸ ਪਾਸਵਰਡ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? @@ -2163,41 +2125,23 @@ ਰੱਦ ਕਰੋ - - ਲਾਗਇਨ ਚੋਣਾਂ ਪਾਸਵਰਡ ਚੋਣਾਂ - - ਲਾਗਇਨ ਦੇ ਵੈੱਬ ਐਡਰੈਸ ਲਈ ਸੋਧਣਯੋਗ ਲਿਖਤ ਖੇਤਰ ਹੈ। ਵੈੱਬਸਾਈਟ ਸਿਰਨਾਵਿਆਂ ਲਈ ਸੋਧ ਕਰਨ ਯੋਗ ਲਿਖਤ ਖੇਤਰ। - - ਲਾਗਇਨ ਦੇ ਵਰਤੋਂਕਾਰ-ਨਾਂ ਲਈ ਸੋਧਣਯੋਗ ਲਿਖਤ ਖੇਤਰ ਹੈ। ਵਰਤੋਂਕਾਰ-ਨਾਂ ਲਈ ਸੋਧ ਕਰਨ ਯੋਗ ਲਿਖਤ ਖੇਤਰ। - ਲਾਗਇਨ ਦੇ ਪਾਸਵਰਡ ਲਈ ਸੋਧਣਯੋਗ ਲਿਖਤ ਖੇਤਰ ਹੈ। - ਪਾਸਵਰਡ ਲਈ ਸੋਧ ਕਰਨ ਯੋਗ ਲਿਖਤ ਖੇਤਰ। - - ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗਇਨ ਲਈ ਸੰਭਾਲੋ। ਤਬਦੀਲੀਆਂ ਨੂੰ ਸੰਭਾਲੋ। - - ਸੋਧੋ ਪਾਸਵਰਡ ਨੂੰ ਸੋਧੋ - - ਨਵਾਂ ਲਾਗਇਨ ਜੋੜੋ ਪਾਸਵਰਡ ਜੋੜੋ - - ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ ਪਾਸਵਰਡ ਦਿਓ - ਵਰਤੋਂਕਾਰ-ਨਾਂ ਚਾਹੀਦਾ ਹੈ - ਵਰਤੋਂਕਾਰ-ਨਾਂ ਦਿਓ ਹੋਸਟ-ਨਾਂ ਚਾਹੀਦਾ ਹੈ @@ -2545,6 +2489,8 @@ ਹਾਲੇ ਨਹੀਂ ਅਸਲ ਵੇਖਾਓ + + ਅਸਲ ਨਾ-ਅਨੁਵਾਦ ਕੀਤਾ ਸਫ਼ਾ ਲੋਡ ਕੀਤਾ ਮੁਕੰਮਲ @@ -2602,6 +2548,9 @@ ਉਲੱਥਾ ਸ਼ੀਟ ਬੰਦ ਕਰੋ + + ਕੁਝ ਸੈਟਿੰਗਾਂ ਆਰਜ਼ੀ ਤੌਰ ਉੱਤੇ ਮੌਜੂਦ ਨਹੀਂ ਹਨ। + ਉਲੱਥੇ @@ -2624,6 +2573,9 @@ ”ਹਮੇਸ਼ਾ ਟਰਾਂਸਲੇਟ ਕਰੋ“ ਅਤੇ "ਕਦੇ ਟਰਾਂਸਲੇਟ ਨਾ ਕਰੋ" ਪਸੰਦਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰਨ ਲਈ ਭਾਸ਼ਾ ਚੁਣੋ। + + ਭਾਸ਼ਾਵਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਬਾਅਦ ਵਿੱਚ ਮੁੜ ਜਾਂਚੋ। + ਟਰਾਂਸਲੇਸ਼ਨ ਲਈ ਪੇਸ਼ਕਸ਼ ਕਰੋ (ਮੂਲ) @@ -2646,6 +2598,8 @@ %1$s ਨੂੰ ਹਟਾਓ + + ਸਾਈਟਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਬਾਅਦ ਵਿੱਚ ਮੁੜ ਜਾਂਚੋ। %1$s ਨੂੰ ਹਟਾਉਣਾ ਹੈ? @@ -2725,13 +2679,18 @@ ਪਿੱਛੇ ਜਾਓ + + ਡੀਬੱਗ ਦਰਾਜ ਨੂੰ ਖੋਲ੍ਹੋ + ਟੈਬ ਟੂਲ ਟੈਬ ਗਿਣਤੀ - ਸਰਗਰਮ + ਸਰਗਰਮ + + ਸਰਗਰਮ ਨਾ-ਸਰਗਰਮ @@ -2742,6 +2701,16 @@ ਟੈਬ ਬਣਾਉਣ ਵਾਲਾ ਟੂਲ bਣਾਉਣ ਲਈ ਟੈਬਾਂ ਦੀ ਗਿਣਤੀ + + ਲਿਖਤ ਖੇਤਰ ਖਾਲੀ ਹੈ + + ਸਿਰਫ਼ ਸਕਰਾਤਮਕ ਮੁੱਲ ਹੀ ਭਰੋ + + ਸਿਫ਼ਰ ਤੋਂ ਵੱਡਾ ਨੰਬਰ ਭਰੋ + + ਇੱਕ ਕਾਰਵਾਈ ਰਾਹੀਂ ਵੱਧ ਤੋਂ ਵੱਧ ਤਿਆਰ ਹੋਣ ਵਾਲੀਆਂ ਟੈਬਾਂ (%1$s) ਦੀ ਗਿਣਤੀ ਅੱਪੜ ਗਈ ਹੈ ਸਰਗਰਮ ਟੈਬਾਂ ਵਿੱਚ ਜੋੜੋ @@ -2758,11 +2727,11 @@ ਪਰਦੇਦਾਰੀ ਸੂਚਨਾ - ਭੇਜੋ + ਭੇਜੋ - ਬੰਦ ਕਰੋ + ਬੰਦ ਕਰੋ - ਤੁਹਾਡੀ ਫ਼ੀਡਬੈਕ ਲਈ ਤੁਹਾਡਾ ਧੰਨਵਾਦ ਹੈ! + ਤੁਹਾਡੀ ਫ਼ੀਡਬੈਕ ਲਈ ਤੁਹਾਡਾ ਧੰਨਵਾਦ ਹੈ! ਬਹੁਤ ਸੰਤੁਸ਼ਟ @@ -2774,6 +2743,14 @@ ਬਹੁਤ ਅਸੰਤੁਸ਼ਟ + + + ਸਰਵੇਖਣ ਨੂੰ ਖੋਲ੍ਹੋ + + ਸਰਵੇਖਣ ਨੂੰ ਬੰਦ ਕਰੋ + + ਬੰਦ ਕਰੋ + ਲਾਗਇਨ diff --git a/mobile/android/fenix/app/src/main/res/values-pl/strings.xml b/mobile/android/fenix/app/src/main/res/values-pl/strings.xml index 0ba564a816..bbf9f2122d 100644 --- a/mobile/android/fenix/app/src/main/res/values-pl/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-pl/strings.xml @@ -49,12 +49,20 @@ - Ostatnio zachowane + Ostatnio zachowane - Wyświetl wszystkie dodane zakładki + Wyświetl wszystkie dodane zakładki - Usuń + Usuń + + + + Zakładki + + Wyświetl wszystkie zakładki + + Usuń %1$s jest tworzony przez Mozillę. @@ -143,8 +151,10 @@ Nowa karta prywatna - - Skrót do haseł + + Hasła + + Skrót do haseł @@ -213,6 +223,8 @@ Synchronizuj ponownie Znajdź na stronie + + Znajdź na stronie… Przetłumacz stronę @@ -248,9 +260,29 @@ Dostosuj stronę startową - Zaloguj się - - Synchronizuj hasła, karty i nie tylko + Zaloguj się + + Synchronizuj hasła, karty i nie tylko + + + Ponownie zaloguj się do synchronizacji + + Wstrzymano synchronizację + + Nowa karta prywatna + + Hasła + + + Nowości w przeglądarce %1$s + + Wersja na komputery + + Narzędzia + + Zachowaj @@ -558,6 +590,8 @@ Połącz ponownie, aby wznowić synchronizację Język + + Tłumaczenie Udostępniane dane @@ -626,6 +660,20 @@ Niedozwolone + + + Wymagane + + Opcjonalne + + Odczytywanie i zmienianie danych na witrynach + + Usuń witrynę + + Zezwalaj na wszystkich witrynach + + Jeśli ufasz temu rozszerzeniu, możesz udzielić mu pozwolenia na każdej witrynie. + Inna kolekcja dodatków @@ -650,7 +698,9 @@ Wskocz z powrotem - Ostatnio dodane zakładki + Ostatnio dodane zakładki + + Zakładki Ostatnio odwiedzone @@ -2317,10 +2367,14 @@ Język źródłowy: Język docelowy: + + Wypróbuj inny język źródłowy Nie teraz Wyświetl w oryginale + + Wczytano oryginalną nieprzetłumaczoną stronę OK @@ -2340,7 +2394,7 @@ Nie obsługujemy jeszcze tego języka (%1$s). - Więcej informacji + Więcej informacji @@ -2354,7 +2408,9 @@ - Opcje tłumaczenia + Opcje tłumaczenia + + Opcje tłumaczenia Zawsze proponuj tłumaczenie @@ -2372,6 +2428,9 @@ Informacje o tłumaczeniach w przeglądarce %1$s + + Zamknij funkcję tłumaczenia + Tłumaczenia @@ -2494,6 +2553,8 @@ Narzędzia do debugowania Przejdź wstecz + + Narzędzia kart @@ -2516,4 +2577,40 @@ Dodaj do nieaktywnych kart Dodaj do prywatnych kart + + + + + Kontynuuj + + Wypełnij tę ankietę + + Zasady ochrony prywatności + + Wyślij + + Zamknij + + Dziękujemy za opinię! + + + Bardzo zadowolony + + Zadowolony + + Obojętny + + Niezadowolony + + Bardzo niezadowolony + + + + Dane logowania + + Obecna domena: %s + + Dodaj fałszywe dane logowania do tej domeny + + Usuń dane logowania z nazwą użytkownika „%s” diff --git a/mobile/android/fenix/app/src/main/res/values-pt-rBR/strings.xml b/mobile/android/fenix/app/src/main/res/values-pt-rBR/strings.xml index ec824d3eaa..c8b4cdf923 100644 --- a/mobile/android/fenix/app/src/main/res/values-pt-rBR/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-pt-rBR/strings.xml @@ -201,6 +201,10 @@ Extensões Extensões + + Gerenciar extensões + + Descobrir mais extensões Informações da conta @@ -219,6 +223,8 @@ Abrir em aba normal Adicionar à tela do dispositivo + + Adicionar à tela do dispositivo… Instalar @@ -230,10 +236,14 @@ Traduzir página + Salvar em coleção… + Salvar em coleção Compartilhar + + Compartilhar… Abrir no %1$s @@ -287,6 +297,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Salvar + + Adicionar aos favoritos + + Editar favorito + + Salvar como PDF… + + Ativar modo de leitura + + Desativar modo de leitura + + Traduzir página… + + Traduzido para %1$s + + Imprimir… + Nenhuma extensão aqui @@ -384,8 +412,6 @@ Aviso de privacidade do Firefox - - Saiba mais em nosso aviso de privacidade Adoramos manter você seguro Idioma - Tradução + Tradução + + Tradução Escolha de dados @@ -667,10 +695,6 @@ Obrigatório Opcional - - Ler e alterar dados de sites - - Excluir site Permitir em todos os sites @@ -798,8 +822,6 @@ Histórico Favoritos - - Contas de acesso Senhas @@ -826,8 +848,6 @@ and the third is the device model. --> %1$s no %2$s %3$s - - Cartões de crédito Métodos de pagamento @@ -843,6 +863,14 @@ Aba de %s + + + Abas do %1$s fechadas: %2$d + + Ver abas fechadas recentemente + Exceções @@ -1772,13 +1800,9 @@ Você pode facilmente adicionar este site à tela inicial do dispositivo para ter acesso imediato e navegar mais rápido com uma experiência semelhante a um aplicativo. - - Contas e senhas Senhas - Salvar contas e senhas - Salvar senhas Perguntar se deve salvar @@ -1793,46 +1817,27 @@ Preencher nomes de usuário e senhas em outros aplicativos no dispositivo. - - Adicionar conta - Adicionar senha - - Sincronizar contas Sincronizar senhas - - Sincronizar contas entre dispositivos Sincronizar senhas entre dispositivos - - Contas salvas Senhas salvas - As contas que você salvar ou sincronizar no %s aparecem aqui. - As senhas que você salva ou sincroniza no %s aparecem aqui. Todas as senhas que você salva são criptografadas. - - Saiba mais sobre sincronização. Saiba mais sobre sincronização Exceções - - Contas e senhas que não são salvas são mostradas aqui. O %s não salva senhas dos sites desta lista. - - Contas e senhas desses sites não serão salvas. O %s não salva senhas desses sites. Excluir todas as exceções - - Pesquisar contas Procurar senhas @@ -1861,18 +1866,12 @@ Exibir senha Ocultar senha - - Desbloqueie para ver suas contas salvas Desbloqueie para ver as senhas salvas - - Proteja suas contas e senhas Proteja as senhas salvas - Configure um método de bloqueio do dispositivo (desenho, código PIN ou senha) para proteger o acesso a suas contas e senhas salvas, caso outras pessoas usem seu dispositivo. - Configure um método de bloqueio do dispositivo (desenho, código PIN ou senha) para proteger o acesso a suas senhas salvas, caso outras pessoas usem seu dispositivo. Mais tarde @@ -1889,8 +1888,6 @@ Nome (A-Z) Data de uso - - Menu de ordenação de contas Menu de ordenação de senhas @@ -1900,41 +1897,27 @@ Preenchimento automático Endereços - - Cartões de crédito Métodos de pagamento - Salvar e preencher cartões automaticamente - Salvar e preencher métodos de pagamento - - Os dados são criptografados O %s criptografa todos os métodos de pagamento que você salva Sincronizar cartões entre dispositivos Sincronizar cartões - - Adicionar cartão de crédito Adicionar cartão - - Gerenciar cartões salvos Gerenciar cartões Adicionar endereço Gerenciar endereços - - Salvar e preencher endereços automaticamente Salvar e preencher endereços - - Incluir informações como números, email e endereços de entrega Incluir números de telefone e endereços de email @@ -1958,8 +1941,6 @@ Excluir cartão - Tem certeza que quer excluir este cartão de crédito? - Excluir cartão? Excluir @@ -1973,24 +1954,15 @@ Cartões salvos - - Digite um número de cartão de crédito válido - Digite um número de cartão válido - - Preencha este campo Adicionar um nome Desbloqueie para ver seus cartões salvos - Proteja seus cartões de crédito - Proteja seus métodos de pagamento salvos - Configure um método de bloqueio do dispositivo (desenho, código PIN ou senha) para proteger o acesso a seus cartões de crédito salvos, caso outras pessoas usem seu dispositivo. - Configure um método de bloqueio do dispositivo (desenho, código PIN ou senha) para proteger o acesso a seus métodos de pagamento salvos, caso outras pessoas usem seu dispositivo. Configurar agora @@ -1999,9 +1971,6 @@ Desbloquear dispositivo - - Desbloqueie para usar informações armazenadas de cartões de crédito - Desbloqueie para usar formas de pagamento salvas @@ -2011,12 +1980,6 @@ Gerenciar endereços - - Primeiro nome - - Nome do meio - - Sobrenome Nome @@ -2042,8 +2005,6 @@ Excluir endereço - - Tem certeza que quer excluir este endereço? Excluir este endereço? @@ -2142,49 +2103,29 @@ Excluir Editar - - Tem certeza que quer excluir esta conta? Tem certeza que quer excluir esta senha? Excluir Cancelar - - Opções da conta Opções de senhas - - O campo de texto editável do endereço web da conta. O campo de texto editável do endereço do site. - - O campo de texto editável do nome de usuário da conta. O campo de texto editável do nome de usuário. - O campo de texto editável da senha da conta. - O campo de texto editável da senha. - - Salvar alterações na conta. Salvar alterações. - - Editar Editar senha - - Adicionar conta Adicionar senha - - Senha é obrigatória Digite uma senha - Nome de usuário é obrigatório - Digite um nome de usuário Nome de servidor é obrigatório @@ -2597,6 +2538,9 @@ Fechar painel de tradução + + Algumas configurações estão temporariamente indisponíveis. + Tradução @@ -2619,6 +2563,9 @@ Selecione um idioma para gerenciar as preferências de ”sempre traduzir“ e ”nunca traduzir“. + + Não foi possível carregar idiomas. Tente novamente mais tarde. + Oferecer tradução (padrão) @@ -2641,6 +2588,8 @@ Remover %1$s + + Não foi possível carregar sites. Tente novamente mais tarde. Excluir %1$s? @@ -2719,13 +2668,18 @@ Voltar à página anterior + + Abrir painel de depuração + Ferramentas de abas Número de abas - Ativas + Ativas + + Ativo Inativas @@ -2736,6 +2690,16 @@ Ferramenta de criação de abas Número de abas a criar + + O campo de texto está vazio + + Insira apenas números inteiros positivos + + Insira um número maior que zero + + Excedido o número máximo de abas (%1$s) que podem ser geradas em uma operação Adicionar às abas ativas @@ -2752,11 +2716,11 @@ Aviso de privacidade - Enviar + Enviar - Fechar + Fechar - Obrigado por sua opinião! + Obrigado por sua opinião! Muito satisfeito @@ -2768,6 +2732,14 @@ Muito insatisfeito + + + Abrir pesquisa de opinião + + Fechar pesquisa de opinião + + Fechar + Contas de acesso diff --git a/mobile/android/fenix/app/src/main/res/values-pt-rPT/strings.xml b/mobile/android/fenix/app/src/main/res/values-pt-rPT/strings.xml index 35a61211fc..3a9bbb4c9a 100644 --- a/mobile/android/fenix/app/src/main/res/values-pt-rPT/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-pt-rPT/strings.xml @@ -201,6 +201,10 @@ Extras Extensões + + Gerir extensões + + Descubra mais extensões Informações da conta @@ -219,6 +223,8 @@ Abrir num separador normal Adicionar ao ecrã Inicial + + Adicionar ao ecrã inicial… Instalar @@ -230,9 +236,13 @@ Traduzir página + Guardar na coleção… + Guardar na coleção Partilhar + + Partilhar… Abrir no %1$s @@ -285,6 +295,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Guardar + + Adicionar página aos marcadores + + Editar marcador + + Guardar como PDF… + + Ativar Vista de leitura + + Desativar a Vista de leitura + + Traduzir página… + + Traduzido para %1$s + + Imprimir… + Sem extensões aqui @@ -382,8 +410,6 @@ Política de privacidade do Firefox - - Saiba mais nas nossas informações de privacidade Adoramos mantê-lo(a) seguro(a) Idioma - Tradução + Tradução + + Traduções Opções de dados @@ -665,10 +693,6 @@ Necessário Opcional - - Ler e alterar dados de websites - - Eliminar site Permitir para todos os sites @@ -774,7 +798,7 @@ Experimente reiniciar as extensões - Tente reiniciar as extensões + Experimentar reiniciar as extensões Continuar com as extensões desativadas @@ -794,8 +818,6 @@ Histórico Marcadores - - Credenciais Palavras-passe @@ -822,8 +844,6 @@ and the third is the device model. --> %1$s no %2$s %3$s - - Cartões de crédito Métodos de pagamento @@ -839,6 +859,14 @@ Separador de %s + + + %1$s separadores fechados: %2$d + + Ver separadores fechados recentemente + Exceções @@ -1769,12 +1797,8 @@ Pode adicionar facilmente este site ao ecrã inicial do seu dispositivo para ter acesso instantâneo e navegar mais rápido, com uma experiência semelhante ao de uma aplicação. - - Credenciais e palavras-passe Palavras-passe - - Guardar credenciais e palavras-passe Guardar palavras-passe @@ -1791,47 +1815,28 @@ Preencher nomes de utilizador e palavras-passe noutras aplicações no seu dispositivo. - - Adicionar credenciais - Adicionar palavra-passe - - Sincronização de credenciais Sincronizar palavras-passe - - Sincronizar credenciais entre dispositivos Sincronize palavras-passe entre dispositivos - - Credenciais guardadas Palavras-passe guardadas - As credenciais que guardar ou sincronizar com o %s serão apresentadas aqui. - As palavras-passe que guardar ou sincronizar com o %s serão listadas aqui. Todas as palavras-passe que guarda são encriptadas. - - Saber mais sobre a sincronização. Saber mais sobre a sincronização Exceções - - As credenciais e palavras-passe que não estão guardadas serão mostradas aqui. O %s não irá guardar palavras-passe para sites listados aqui. - - As credenciais e palavras-passe não serão guardadas para estes sites. O %s não irá guardar as palavras-passe para estes sites. Eliminar todas as exceções - - Pesquisar credenciais Procurar palavras-passe @@ -1861,17 +1866,11 @@ Mostrar palavra-passe Ocultar palavra-passe - - Desbloqueie para ver as credenciais guardadas Desbloqueie para ver as palavras-passe guardadas - Proteja as suas credenciais e palavras-passe - Proteja as suas palavras-passe guardadas - Configure um padrão, PIN ou palavra-passe de bloqueio do dispositivo para impedir que as suas credenciais e palavras-passe guardadas sejam acedidas por outra pessoa que tenha acesso ao seu dispositivo. - Configure um padrão, PIN ou palavra-passe de bloqueio do dispositivo para impedir que as suas palavras-passe guardadas sejam acedidas por outra pessoa que tenha acesso ao seu dispositivo. Mais tarde @@ -1888,8 +1887,6 @@ Nome (A-Z) Última utilização - - Menu de ordenação de credenciais Menu de ordenação de palavras-passe @@ -1899,29 +1896,19 @@ Preenchimento automático Endereços - - Cartões de crédito Métodos de pagamento - Guardar e preencher automaticamente cartões - Guardar e preencher métodos de pagamento - - Os dados são encriptados O %s encripta todos os métodos de pagamento que guarda Sincronizar cartões entre dispositivos Sincronizar cartões - - Adicionar cartão de crédito Adicionar cartão - - Gerir cartões guardados Gerir cartões @@ -1929,12 +1916,8 @@ Gerir endereços - - Guardar e preencher automaticamente endereços Guardar e preencher endereços - - Incluir informações como números, e-mail e endereços de entrega Inclui números de telefone e endereços de e-mail @@ -1958,8 +1941,6 @@ Eliminar cartão - Tem a certeza de que quer apagar este cartão de crédito? - Eliminar cartão? Apagar @@ -1973,23 +1954,15 @@ Cartões guardados - - Por favor, introduza um número de cartão de crédito válido Insira um número de cartão válido - - Por favor preencha este campo Adicionar um nome Desbloquear para ver os cartões guardados - Proteja os seus cartões de crédito - Proteja os seus métodos de pagamento guardados - Configure um padrão, PIN ou palavra-passe de bloqueio do dispositivo para impedir que os seus cartões de crédito guardados sejam acedidos por outra pessoa que tenha acesso ao seu dispositivo. - Configure um padrão, PIN ou palavra-passe de bloqueio do dispositivo para impedir que os seus métodos de pagamento guardados sejam acedidos por outra pessoa que tenha acesso ao seu dispositivo. Configurar agora @@ -1997,8 +1970,6 @@ Mais tarde Desbloquear o seu dispositivo - - Desbloquear para utilizar as informações de cartão de crédito armazenadas Desbloquear para utilizar métodos de pagamento guardados @@ -2008,12 +1979,6 @@ Editar endereço Gerir endereços - - Primeiro nome - - Nome do meio - - Último nome Nome @@ -2039,8 +2004,6 @@ Apagar endereço - - Tem a certeza de que quer eliminar este endereço? Eliminar este endereço? @@ -2140,49 +2103,29 @@ Eliminar Editar - - Tem a certeza que deseja eliminar esta credencial? Tem a certeza que quer eliminar esta palavra-passe? Eliminar Cancelar - - Opções de credenciais Opções de palavra-passe - - O campo de texto editável para o endereço de Internet da credencial. O campo de texto editável para o endereço do site. - - O campo de texto editável para o nome de utilizador da credencial. O campo de texto editável para o nome de utilizador. - O campo de texto editável para a palavra-passe da credencial. - O campo de texto editável para a palavra-passe. - - Guardar alterações na credencial. Guardar alterações. - - Editar Editar palavra-passe - - Adicionar nova credencial Adicionar palavra-passe - - É necessária uma palavra-passe Introduza uma palavra-passe - É necessário um nome de utilizador - Introduza um nome de utilizador É necessário um nome de servidor @@ -2525,13 +2468,13 @@ Traduzir para - Tente outro idioma fonte + Tentar outro idioma fonte Agora não Mostrar original - Carregada página original não traduzida + Carregada a página original não traduzida Feito @@ -2588,6 +2531,9 @@ Fechar folha de traduções + + Algumas definições estão temporariamente indisponíveis. + Traduções @@ -2610,6 +2556,9 @@ Selecione um idioma para gerir as preferências “traduzir sempre” e “nunca traduzir”. + + Não foi possível carregar os idiomas. Por favor, volte mais tarde. + Oferecer para traduzir (predefinição) @@ -2632,6 +2581,8 @@ Remover %1$s + + Não foi possível carregar os sites. Por favor, volte mais tarde. Eliminar %1$s? @@ -2709,13 +2660,18 @@ Navegar de volta + + Abrir caixa de depuração + Ferramentas de separadores Contagem de separadores - Ativo + Ativo + + Ativo Inativo @@ -2726,6 +2682,16 @@ Ferramenta de criação de separadores Quantidade de separadores a criar + + O campo de texto está vazio + + Introduza apenas inteiros positivos + + Introduza um número maior que zero + + Excedido o número máximo de (%1$s) separadores que podem ser gerados numa operação Adicionar aos separadores ativos @@ -2740,24 +2706,32 @@ Concluir este questionário - Política de privacidade + Informação de privacidade - Submeter + Submeter - Fechar + Fechar - Obrigado pela sua opinião! + Obrigado pela sua opinião! Muito satisfeito(a) - Satisfeito + Satisfeito(a) - Neutro + Neutro(a) - Insatisfeito + Insatisfeito(a) Muito insatisfeito(a) + + + Abrir inquérito + + Fechar inquérito + + Fechar + Credenciais diff --git a/mobile/android/fenix/app/src/main/res/values-rm/strings.xml b/mobile/android/fenix/app/src/main/res/values-rm/strings.xml index 84687df723..da3bde562b 100644 --- a/mobile/android/fenix/app/src/main/res/values-rm/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-rm/strings.xml @@ -386,6 +386,8 @@ Dapli che 100 milliuns persunas protegian lur sfera privata cun tscherner in navigatur dad ina organisaziun senza finamira da profit. + Ils fastizaders enconuschents? Bloccads automaticamain. Extensiuns? Emprova tuttas 700. PDFs? Noss lectur integrà als administrescha senza problems. + Noss navigatur dad in\'organisaziun senza finamira da profit, gida ad evitar che interpresas ta persequiteschian a la zuppada en il web.\n \nLegia dapli dal tema en nossas infurmaziuns davart la protecziun da datas. Расширения + + Управление расширениями + + Откройте для себя больше расширений Ваш аккаунт @@ -224,6 +228,8 @@ Открыть в обычной вкладке На домашний экран + + Добавить на домашний экран… Установить @@ -235,9 +241,13 @@ Перевести страницу + Сохранить в коллекцию… + В сборник Поделиться + + Поделиться… Открыть в %1$s @@ -292,6 +302,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Сохранить + + Добавить страницу в закладки + + Изменить закладку + + Сохранить как PDF… + + Включить Вид для чтения + + Отключить Вид для чтения + + Перевести страницу… + + Переведено на %1$s + + Печать… + Здесь нет расширений @@ -389,8 +417,6 @@ Уведомление о конфиденциальности Firefox - - Узнайте больше в нашем уведомлении о конфиденциальности Нам нравится обеспечивать вашу безопасность Язык - Перевод + Перевод + + Переводы Выбор данных @@ -672,12 +700,8 @@ Обязательно - Необязательно - - Чтение и изменение данных веб-сайтов + Необязательные - - Удалить веб-сайт Разрешить для всех сайтов @@ -803,8 +827,6 @@ Историю Закладки - - Пароли Пароли @@ -831,8 +853,6 @@ and the third is the device model. --> %1$s на %2$s %3$s - - Банковские карты Способы оплаты @@ -848,6 +868,14 @@ Вкладка с %s + + + Закрыто %1$s вкладок: %2$d + + Просмотр недавно закрытых вкладок + Исключения @@ -1795,13 +1823,9 @@ Вы можете легко добавить этот сайт на домашний экран вашего устройства, чтобы иметь к нему мгновенный доступ. - - Пароли Пароли - Сохранение паролей - Сохранять пароли Предлагать сохранить @@ -1816,47 +1840,28 @@ Заполнять имена пользователей и пароли в других приложениях на вашем устройстве. - - Добавить логин - Добавить пароль - - Синхронизация логинов Синхронизировать пароли - - Синхронизировать логины между устройствами Синхронизировать пароли между устройствами - - Сохранённые пароли Сохранённые пароли - Пароли, которые вы сохраняете или синхронизируете в %s, появятся тут. - Пароли, которые вы сохраните или синхронизируете в %s, будут показаны здесь. Все сохраняемые вами пароли зашифрованы. - - Узнайте больше о синхронизации. Узнайте больше о синхронизации Исключения - - Здесь будут показаны не сохраняемые логины и пароли. %s не будет сохранять пароли для перечисленных здесь сайтов. - - Логины и пароли для этих сайтов сохраняться не будут. %s не будет сохранять пароли для этих сайтов. Удалить все исключения - - Поиск логинов Поиск паролей @@ -1885,17 +1890,11 @@ Показать пароль Скрыть пароль - - Разблокируйте, чтобы просмотреть сохранённые пароли Разблокируйте, чтобы просмотреть сохранённые пароли - Защитите свои логины и пароли - Защитите сохранённые пароли - Настройте графический ключ, Пин-код или пароль для блокировки устройства, чтобы защитить сохранённые пароли, если кто-либо ещё получит доступ к вашему устройству. - Настройте графический ключ, пин-код или пароль для разблокировки устройства, чтобы защитить сохранённые пароли на случай, если кто-либо ещё получит доступ к вашему устройству. Позже @@ -1912,8 +1911,6 @@ По имени (А-Я) По последнему использованию - - Меню сортировки логинов Меню сортировки паролей @@ -1923,41 +1920,27 @@ Автозаполнение Адреса - - Банковские карты Способы оплаты - Сохранять и автоматически заполнять данные карт - Сохранить и заполнять способы оплаты - - Данные зашифрованы %s шифрует все сохраняемые вами способы оплаты Синхронизировать карты между различными устройствами Синхронизировать карты - - Добавить банковскую карту Добавить карту - - Управление сохранёнными картами Управление картами Добавить адрес Управление адресами - - Сохранять и автоматически заполнять адреса Сохранять и заполнять адреса - - Включая такие сведения, как номера, адреса эл. почты и доставок Включает номера телефонов и адреса электронной почты @@ -1981,8 +1964,6 @@ Удалить карту - Вы уверены, что хотите удалить эту банковскую карту? - Удалить карту? Удалить @@ -1995,24 +1976,15 @@ Сохранённые карты - - Пожалуйста, введите правильный номер карты - Введите корректный номер карты - - Пожалуйста, заполните это поле Добавьте имя Разблокируйте, чтобы просмотреть сохранённые карты - Защитите свои банковские карты - Защитите сохранённые способы оплаты - Настройте графический ключ, Пин-код или пароль для блокировки устройства, чтобы защитить сохранённые банковские карты, если кто-либо ещё получит доступ к вашему устройству. - Настройте графический ключ, Пин-код или пароль для разблокировки устройства, чтобы защитить сохранённые способы оплаты, на случай, если кто-либо ещё получит доступ к вашему устройству. Настроить сейчас @@ -2020,8 +1992,6 @@ Позже Разблокируйте своё устройство - - Разблокируйте, чтобы использовать сохранённые данные банковской карты Разблокируйте, чтобы использовать сохранённые способы оплаты @@ -2031,12 +2001,6 @@ Изменить адрес Управление адресами - - Имя - - Отчество - - Фамилия Название @@ -2062,8 +2026,6 @@ Удалить адрес - Вы уверены, что хотите удалить этот адрес? - Удалить этот адрес? Удалить @@ -2161,49 +2123,29 @@ Удалить Править - - Вы уверены, что хотите удалить этот пароль? Вы уверены, что хотите удалить этот пароль? Удалить Отмена - - Настройки логина Настройки пароля - - Редактируемое текстовое поле для веб-адреса логина. Редактируемое текстовое поле для адреса веб-сайта. - - Редактируемое текстовое поле для имени пользователя логина. Редактируемое текстовое поле для имени пользователя. - Редактируемое текстовое поле для пароля логина. - Редактируемое текстовое поле для пароля. - - Сохранить изменения в логине. Сохранить изменения. - - Правка Изменить пароль - - Добавить новый логин Добавить пароль - - Требуется пароль Введите пароль - Требуется имя пользователя - Введите имя пользователя Введите имя сервера @@ -2611,6 +2553,9 @@ Закрыть область переводов + + Некоторые настройки временно недоступны. + Переводы @@ -2633,6 +2578,9 @@ Выберите язык для управления настройками «всегда переводить» и «никогда не переводить». + + Не удалось загрузить языки. Пожалуйста, проверьте позже. + Предлагать перевод (по умолчанию) @@ -2655,6 +2603,8 @@ Удалить %1$s + + Не удалось загрузить сайты. Пожалуйста, проверьте позже. Удалить %1$s? @@ -2732,13 +2682,18 @@ Перейти назад + + Открыть панель отладки + Инструменты вкладок Число вкладок - Активных + Активных + + Активные Неактивных @@ -2749,6 +2704,16 @@ Инструмент создания вкладок Количество вкладок для создания + + Текстовое поле пусто + + Пожалуйста, введите только положительные целые числа + + Пожалуйста, введите число больше нуля + + Превышено максимальное число вкладок (%1$s), которое может быть сгенерировано за одну операцию Добавить в активные вкладки @@ -2765,11 +2730,11 @@ Уведомление о конфиденциальности - Отправить + Отправить - Закрыть + Закрыть - Спасибо за ваш отзыв! + Спасибо за ваш отзыв! Полностью удовлетворяет @@ -2781,6 +2746,14 @@ Совсем не удовлетворяет + + + Открыть опрос + + Закрыть опрос + + Закрыть + Логины diff --git a/mobile/android/fenix/app/src/main/res/values-sat/strings.xml b/mobile/android/fenix/app/src/main/res/values-sat/strings.xml index 45ab837aed..4b085d93f8 100644 --- a/mobile/android/fenix/app/src/main/res/values-sat/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sat/strings.xml @@ -45,12 +45,21 @@ - ᱱᱤᱛ ᱥᱟᱺᱪᱟᱣᱟᱜ + ᱱᱤᱛ ᱥᱟᱺᱪᱟᱣᱟᱜ - ᱡᱷᱚᱛᱚ ᱥᱟᱺᱪᱟᱣ ᱠᱟᱱ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ ᱫᱮᱠᱷᱟᱣ ᱢᱮ + ᱡᱷᱚᱛᱚ ᱥᱟᱺᱪᱟᱣ ᱠᱟᱱ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ ᱫᱮᱠᱷᱟᱣ ᱢᱮ - ᱚᱪᱚᱜᱽ ᱢᱮ + ᱚᱪᱚᱜᱽ ᱢᱮ + + + + ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ + + + ᱡᱷᱚᱛᱚ ᱵᱩᱠᱢᱟᱨᱠᱠᱚ ᱫᱮᱠᱷᱟᱣ ᱢᱮ + + ᱚᱪᱚᱜᱽ ᱢᱮ %1$s ᱛᱮᱭᱟᱨ ᱦᱩᱭ ᱠᱟᱱᱟ ᱢᱳᱡᱤᱞᱟ ᱫᱟᱨᱟᱭᱛᱮ ᱾ @@ -144,8 +153,10 @@ ᱱᱟᱶᱟ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵᱽ - - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱥᱚᱴᱠᱚᱴ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱥᱚᱴᱠᱚᱴ @@ -190,6 +201,10 @@ ᱮᱰ-ᱟᱸᱱᱥ ᱮᱠᱥᱴᱮᱱᱥᱚᱱᱠᱚ + + ᱮᱠᱮᱴᱮᱱᱮᱚᱱ ᱠᱚ ᱢᱮᱱᱟᱡᱽ ᱢᱮ + + ᱟᱨᱦᱚᱸ ᱰᱷᱮᱨ ᱮᱠᱥᱴᱮᱱᱥᱚᱱ ᱯᱟᱱᱛᱮ ᱢᱮ ᱠᱷᱟᱛᱟ ᱵᱤᱵᱨᱚᱬ @@ -208,18 +223,26 @@ ᱱᱟᱶᱟ ᱴᱮᱵᱽ ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮ ᱚᱲᱟᱜ ᱥᱠᱨᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ + + ᱚᱲᱟᱜ ᱥᱠᱨᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ… ᱵᱚᱦᱟᱞ ᱢᱮ ᱨᱤᱥᱭᱝᱠ ᱥᱟᱦᱴᱟ ᱨᱮ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ + + ᱥᱟᱦᱴᱟ ᱨᱮ ᱯᱟᱱᱛᱮ ᱢᱮ… ᱥᱟᱦᱴᱟ ᱛᱚᱨᱡᱚᱢᱟᱭ ᱢᱮ + ᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱺᱪᱟᱣ ᱢᱮ… + ᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱺᱪᱟᱣ ᱢᱮ ᱦᱟᱹᱴᱤᱧ + + ᱦᱟᱹᱴᱤᱧ ᱢᱮ… %1$s ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮ @@ -247,9 +270,46 @@ ᱚᱲᱟᱜᱥᱟᱦᱴᱟ ᱠᱩᱥᱤᱛᱮ ᱫᱚᱦᱚᱭ ᱢᱮ - ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ - - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ, ᱴᱮᱵᱽ ᱟᱨ ᱟᱨᱦᱚᱸ ᱟᱭᱢᱟ ᱡᱤᱱᱤᱥᱠᱚ ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱢᱮ + ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ, ᱴᱮᱵᱽ ᱟᱨ ᱟᱨᱦᱚᱸ ᱟᱭᱢᱟ ᱡᱤᱱᱤᱥᱠᱚ ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱢᱮ + + + ᱥᱭᱝᱠ ᱞᱟᱹᱜᱤᱫ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮ + + ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱛᱷᱩᱢ ᱠᱟᱱᱟ + + ᱱᱟᱶᱟ ᱱᱤᱡᱮᱨᱟᱠ ᱴᱮᱵᱽ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ + + %1$s ᱨᱮ ᱱᱟᱶᱟ + + + ᱰᱮᱥᱠᱴᱚᱯ ᱥᱟᱭᱤᱴ ᱛᱮ ᱪᱟᱞᱟᱜ ᱢᱮ + + ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱚ + + ᱥᱟᱺᱪᱟᱣ ᱢᱮ + + ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟ ᱵᱩᱩᱠᱢᱟᱨᱠ ᱢᱮ + + ᱵᱩᱠᱢᱟᱨᱠ ᱥᱟᱯᱲᱟᱣ ᱢᱮ + + PDF ᱞᱮᱠᱷᱟ ᱥᱟᱺᱪᱟᱣ ᱢᱮ… + + ᱯᱟᱲᱦᱟᱣ ᱧᱮᱞᱡᱚᱝ ᱞᱮᱠᱷᱟ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ + + ᱯᱟᱲᱦᱟᱣ ᱧᱮᱞᱡᱚᱝ ᱞᱮᱠᱷᱟ ᱵᱚᱸᱫᱚᱭ ᱢᱮ + + ᱥᱟᱦᱴᱟ ᱛᱚᱨᱡᱚᱢᱟᱭ ᱢᱮ… + + %1$s ᱛᱮ ᱛᱚᱨᱡᱚᱢᱟ ᱠᱟᱱᱟ + + ᱪᱷᱟᱯᱟ… @@ -347,8 +407,6 @@ ᱯᱷᱚᱭᱟᱨᱯᱷᱚᱠᱥ ᱡᱟᱹᱯᱛᱤ ᱠᱷᱚᱵᱚᱨ ᱾ - - ᱟᱞᱮᱭᱟᱜ ᱱᱤᱥᱚᱱ ᱠᱷᱚᱵᱚᱨ ᱨᱮ ᱰᱷᱮᱨ ᱡᱤᱱᱤᱥ ᱥᱮᱬᱟᱭ ᱢᱮ ᱟᱢ ᱴᱷᱤᱠ ᱨᱮ ᱫᱚᱦᱚᱢᱮᱪᱷᱚ ᱞᱟᱹᱜᱤᱫ ᱠᱩᱥᱤᱭᱟᱜᱼᱟ ᱞᱮ ᱯᱟᱹᱨᱥᱤ + + ᱛᱚᱨᱡᱚᱢᱟ + + ᱛᱚᱨᱡᱚᱢᱟᱠᱚ ᱥᱟᱹᱠᱷᱤᱭᱟᱹᱛ ᱠᱩᱥᱤᱭᱟᱜ ᱠᱚ @@ -623,6 +685,16 @@ ᱢᱟᱱᱟ ᱜᱮᱭᱟ + + + ᱞᱟᱹᱠᱛᱤ + + ᱢᱚᱱᱮᱛᱮᱭᱟᱜ + + ᱡᱷᱚᱛᱚ ᱥᱟᱭᱤᱴ ᱞᱟᱹᱜᱤᱫ ᱮᱢᱪᱷᱚᱭ ᱢᱮ + + ᱟᱢ ᱡᱩᱫᱤ ᱱᱚᱶᱟ ᱮᱠᱥᱴᱮᱱᱥᱚᱱ ᱪᱮᱛᱟᱱ ᱨᱮ ᱵᱷᱚᱨᱥᱟ ᱮᱫ ᱠᱷᱟᱱ, ᱟᱢ ᱫᱚ ᱡᱷᱚᱛᱚ ᱣᱮᱵᱽᱥᱟᱭᱤᱴ ᱨᱮ ᱦᱚᱠᱮᱢ ᱮᱢ ᱫᱟᱲᱮ ᱟᱭᱟ ᱾ + ᱠᱟᱹᱥᱴᱚᱢ ᱮᱰᱰᱼᱚᱱ ᱛᱩᱢᱟᱹᱞ @@ -646,7 +718,9 @@ ᱦᱮᱡ ᱨᱩᱣᱟᱹᱲᱚᱜ ᱢᱮ - ᱱᱤᱛᱚᱜᱼᱟᱜ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ + ᱱᱤᱛᱚᱜᱼᱟᱜ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ + + ᱵᱩᱠᱢᱟᱨᱠᱠᱚ ᱱᱤᱛᱚᱜᱟᱜ ᱦᱤᱨᱤᱭᱟᱜ @@ -741,8 +815,6 @@ ᱱᱟᱜᱟᱢ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ - - ᱵᱚᱞᱚᱱ ᱠᱚ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ @@ -768,8 +840,6 @@ and the third is the device model. --> %1$s on %2$s %3$s - - ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱜᱚᱱᱚᱝ ᱦᱚᱨᱟᱠᱚ @@ -786,6 +856,14 @@ %s ᱠᱷᱚᱱ ᱴᱮᱵᱽ + + + %1$s ᱴᱮᱵᱽᱠᱚ ᱵᱚᱸᱫ ᱠᱟᱱᱟ : %2$d + + ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵᱽ ᱠᱚ ᱫᱮᱠᱷᱟᱣ ᱢᱮ + ᱪᱷᱟᱰᱟ ᱠᱚ @@ -1718,13 +1796,9 @@ ᱟᱢ ᱞᱚᱜᱚᱱ ᱵᱽᱨᱟᱣᱩᱡᱽ ᱟᱨ ᱮᱯ ᱞᱮᱠᱷᱟᱱ ᱢᱟᱨᱠᱷᱤ ᱤᱫᱤ ᱞᱟᱹᱜᱤᱫ ᱣᱮᱵᱥᱟᱭᱤᱴ ᱫᱚ ᱥᱟᱫᱷᱚᱱ ᱨᱮᱭᱟᱜ ᱚᱲᱟᱜ ᱥᱠᱨᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱫᱟᱲᱮᱟᱜᱼᱟᱢ ᱾ - - ᱵᱚᱞᱚᱱ ᱠᱚ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ - ᱞᱚᱜᱤᱱ ᱠᱚ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱢᱮ - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱢᱮ ᱥᱟᱺᱪᱟᱣ ᱞᱟᱹᱜᱤᱫᱛᱮ ᱠᱩᱠᱞᱤ @@ -1740,46 +1814,27 @@ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱨᱮ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱯᱩᱨᱟᱹᱣ ᱢᱮ ᱾ - - ᱵᱚᱞᱚ ᱥᱮᱞᱮᱫ ᱢᱮ - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱥᱮᱞᱮᱫ ᱢᱮ - - ᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱭᱝᱠ ᱢᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽᱠᱚ ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱢᱮ - - ᱡᱷᱚᱛᱚ ᱥᱟᱫᱷᱚᱱ ᱵᱷᱤᱛᱨᱤ ᱨᱮ ᱵᱚᱞᱚ ᱠᱚ ᱥᱭᱝᱠ ᱢᱮ ᱡᱷᱚᱛᱚ ᱥᱟᱫᱷᱚᱱ ᱵᱷᱤᱛᱨᱤ ᱨᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱢᱮ - - ᱥᱟᱺᱪᱟᱣᱟᱠᱟᱱ ᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱟᱧᱪᱟᱣ ᱠᱟᱱ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱠᱚ - ᱞᱚᱜᱤᱱ ᱚᱠᱟ %s ᱨᱮ ᱥᱟᱺᱪᱟᱣ ᱟᱨ ᱥᱭᱝᱠ ᱥᱟᱱᱟᱢ ᱠᱟᱱᱟ ᱚᱱᱟ ᱠᱚ ᱱᱚᱰᱮ ᱫᱮᱠᱷᱟᱣᱜᱼᱟ ᱾ - ᱟᱢ %s ᱨᱮ ᱚᱞ ᱟᱠᱟᱱ ᱥᱮ ᱟᱹᱭᱩᱨ ᱢᱤᱫ ᱟᱠᱟᱱ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱱᱚᱸᱰᱮ ᱞᱤᱥᱴᱤ ᱟ ᱾ ᱟᱢ ᱡᱟᱦᱟᱸᱱ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱮᱢ ᱥᱟᱧᱪᱟᱣ ᱟ ᱚᱱᱟ ᱠᱚᱫᱚ ᱫᱟᱱᱟᱝᱟᱜᱼᱟ ᱾ - - ᱥᱭᱝᱠ ᱵᱟᱵᱚᱛ ᱡᱟᱹᱥᱛᱤ ᱵᱟᱰᱟᱭ ᱢᱮ ᱾ ᱥᱭᱝᱠ ᱵᱟᱵᱚᱛ ᱰᱷᱮᱨ ᱵᱟᱰᱟᱭ ᱢᱮ ᱪᱷᱟᱰᱟ ᱠᱚ - - ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱡᱟ ᱵᱟᱝ ᱥᱟᱺᱪᱟᱣ ᱠᱟᱱᱟ ᱚᱱᱟᱠᱩ ᱱᱚᱰᱮ ᱩᱫᱩᱜᱚᱣᱟ ᱾ %s ᱫᱚ ᱱᱚᱰᱮ ᱞᱤᱥᱴᱤ ᱠᱟᱱ ᱥᱟᱭᱤᱴᱠᱚᱨᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱟᱭ ᱥᱟᱧᱪᱟᱣ ᱟ ᱾ - - ᱞᱚᱜᱤᱱᱥ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱫᱚ ᱱᱚᱶᱟ ᱥᱟᱭᱤᱴ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱝ ᱥᱟᱺᱪᱟᱣᱜᱼᱟ ᱾ %s ᱫᱚ ᱱᱚᱰᱮ ᱢᱮᱱᱟᱜ ᱞᱤᱥᱴᱤ ᱠᱟᱱ ᱥᱟᱭᱤᱴᱠᱚᱨᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱟᱭ ᱥᱟᱧᱪᱟᱣ ᱟ ᱾ ᱡᱷᱚᱛᱚ ᱪᱷᱟᱰᱟᱠᱚ ᱢᱮᱴᱟᱣ ᱢᱮ - - ᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱫᱮᱠᱷᱟᱣ ᱢᱮ @@ -1808,17 +1863,11 @@ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱫᱮᱠᱷᱟᱣ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱩᱠᱩ - - ᱥᱟᱺᱪᱟᱣᱠᱟᱱ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱠᱚ ᱧᱮᱞ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱚᱱᱞᱚᱠ ᱢᱮ ᱥᱟᱧᱪᱟᱣᱟᱠᱟᱱ ᱠᱟᱰ ᱠᱚ ᱧᱮᱞ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ - ᱟᱢᱟᱜ ᱞᱚᱜᱤᱱ ᱠᱚ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱡᱟᱯᱛᱤ ᱢᱮ - ᱟᱢᱟᱜ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱨᱩᱠᱷᱤᱭᱟᱹᱭ ᱢᱮ - ᱥᱟᱫᱷᱚᱱ ᱠᱩᱞᱩᱯ ᱪᱤᱱᱦᱟᱹ, ᱯᱤᱱ, ᱟᱨ ᱵᱟᱝ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱞᱟᱜᱟᱣᱢᱮ ᱟᱢᱟᱜ ᱥᱟᱺᱪᱟᱣ ᱞᱚᱜᱤᱱ ᱠᱚ ᱮᱢᱟᱱ ᱨᱩᱠᱷᱭᱟ ᱞᱟᱹᱜᱤᱫ ᱡᱩᱫᱤ ᱚᱞᱜᱟ ᱦᱚᱲ ᱴᱷᱮᱱ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱛᱟᱦᱮᱸᱱ ᱠᱷᱟᱱ ᱾ - ᱥᱟᱫᱷᱚᱱ ᱠᱩᱞᱩᱯ ᱪᱤᱱᱦᱟᱹ, ᱯᱤᱱ, ᱟᱨ ᱵᱟᱝ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱢᱟᱜ ᱥᱟᱧᱪᱟᱣ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱚ ᱮᱢᱟᱱ ᱨᱩᱠᱷᱭᱟ ᱞᱟᱹᱜᱤᱫ ᱡᱩᱫᱤ ᱚᱞᱜᱟ ᱦᱚᱲ ᱴᱷᱮᱱ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱛᱟᱦᱮᱸᱱ ᱠᱷᱟᱱ ᱾ ᱛᱟᱭᱚᱢ ᱛᱮ @@ -1835,8 +1884,6 @@ ᱢᱟᱲᱟᱝ ᱵᱮᱵᱷᱟᱨᱟᱜ - - ᱞᱚᱜᱤᱱ ᱢᱮᱱᱭᱩ ᱥᱟᱞᱟᱭ ᱢᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱢᱮᱱᱩ ᱥᱮᱴ ᱢᱮ @@ -1846,17 +1893,11 @@ ᱟᱡ ᱛᱮ ᱯᱩᱨᱟᱹᱣ ᱴᱷᱤᱠᱬᱟᱤᱭᱟᱹ - - ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱜᱚᱱᱚᱝ ᱦᱚᱨᱟᱠᱚ - ᱥᱟᱺᱪᱟᱣ ᱟᱨ ᱠᱟᱰ ᱠᱚ ᱟᱡ ᱛᱮ ᱯᱮᱨᱮᱡᱽ ᱢᱮ - ᱯᱮᱢᱮᱱᱴ ᱦᱚᱨᱟ ᱥᱟᱧᱪᱟᱣ ᱢᱮ ᱟᱨ ᱯᱮᱨᱮᱪ ᱢᱮ - - ᱰᱟᱴᱟ ᱫᱚ ᱮᱱᱠᱨᱤᱯᱴᱮᱰ ᱠᱟᱱᱟ %s ᱫᱚ ᱟᱢ ᱡᱟᱦᱟᱸ ᱥᱟᱧᱪᱟᱣ ᱮᱫ ᱡᱷᱚᱛᱚ ᱛᱚᱨᱠᱟᱠᱚ ᱨᱩᱠᱷᱤᱭᱟᱹ ᱟ @@ -1864,25 +1905,16 @@ ᱠᱟᱰ ᱥᱭᱝᱠ ᱢᱮ - - ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱥᱮᱞᱮᱫᱽ ᱢᱮ - ᱠᱟᱰ ᱥᱮᱞᱮᱫᱽ ᱢᱮ - - ᱥᱟᱺᱪᱟᱣ ᱠᱟᱰ ᱠᱚ ᱢᱮᱱᱮᱡᱽ ᱢᱮ ᱠᱟᱰ ᱢᱮᱱᱮᱡᱽ ᱢᱮ ᱴᱷᱤᱠᱬᱟᱹ ᱥᱮᱞᱮᱫ ᱢᱮ ᱴᱷᱤᱠᱬᱟᱹᱤᱭᱟᱹ ᱡᱚᱛᱚᱱ ᱮᱢ - - ᱴᱷᱤᱬᱟᱹᱤᱭᱟᱹ ᱥᱟᱺᱪᱟᱣ ᱟᱨ ᱟᱡ ᱛᱮ ᱯᱮᱨᱮᱡ ᱢᱮ ᱴᱷᱤᱬᱟᱹᱤᱭᱟᱹ ᱥᱟᱧᱪᱟᱣ ᱟᱨ ᱟᱡ ᱛᱮ ᱯᱮᱨᱮᱡ ᱢᱮ - - ᱮᱞ, ᱤᱢᱮᱞ ᱟᱨ ᱵᱷᱮᱡᱟ ᱴᱷᱤᱠᱬᱟᱹ ᱞᱮᱠᱷᱟ ᱵᱤᱵᱨᱚᱬ ᱢᱮᱥᱟᱭ ᱢᱮ ᱯᱷᱚᱱ ᱱᱚᱢᱵᱚᱨ ᱟᱨ ᱤᱼᱢᱮᱞ ᱴᱷᱤᱠᱬᱟᱹ ᱥᱮᱞᱮᱫ ᱢᱮᱱᱟᱜᱼᱟ @@ -1907,8 +1939,6 @@ ᱠᱟᱰ ᱢᱮᱴᱟᱣ ᱢᱮ - ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱛᱮ ᱢᱮᱱᱟᱢᱼᱟ ᱱᱚᱶᱟ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱢᱮᱴᱟᱣ ᱞᱟ.ᱜᱤᱛ ᱛᱮ? - ᱠᱟᱰ ᱢᱮᱴᱟᱣᱟᱢ ᱥᱮ ? ᱢᱮᱴᱟᱣ ᱢᱮ @@ -1920,44 +1950,34 @@ ᱵᱟᱹᱰᱨᱟᱹ ᱥᱟᱺᱪᱟᱣ ᱠᱟᱱ ᱠᱟᱰ - - ᱴᱷᱤᱠ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱮᱞ ᱟᱫᱮᱨ ᱢᱮ ᱢᱤᱫᱴᱟᱝ ᱴᱷᱤᱠ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱮᱞ ᱟᱫᱮᱨ ᱢᱮ - - ᱱᱚᱶᱟ ᱡᱟᱭᱜᱟ ᱯᱮᱨᱮᱡᱽ ᱢᱮ ᱧᱩᱛᱩᱢ ᱥᱮᱞᱮᱫ ᱢᱮ ᱥᱟᱺᱪᱟᱣᱟᱠᱟᱱ ᱠᱟᱰ ᱠᱚ ᱧᱮᱞ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ - ᱟᱢᱟᱜ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱨᱩᱠᱷᱤᱭᱟᱹᱭ ᱢᱮ - ᱟᱢᱟᱜ ᱥᱟᱧᱪᱟᱣ ᱠᱟᱱ ᱜᱚᱱᱚᱝ ᱛᱚᱨᱠᱟᱠᱚ ᱨᱩᱠᱷᱤᱭᱟᱹᱭ ᱢᱮ - ᱥᱟᱫᱷᱚᱱ ᱠᱩᱞᱩᱯ ᱪᱤᱱᱦᱟᱹ, ᱯᱤᱱ, ᱟᱨ ᱵᱟᱝ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱢᱟᱜ ᱥᱟᱺᱪᱟᱣ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱠᱚ ᱮᱢᱟᱱ ᱨᱩᱠᱷᱭᱟ ᱞᱟᱹᱜᱤᱫ ᱡᱩᱫᱤ ᱚᱞᱜᱟ ᱦᱚᱲ ᱴᱷᱮᱱ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱛᱟᱦᱮᱸᱱ ᱠᱷᱟᱱ ᱾ + ᱥᱟᱫᱷᱚᱱ ᱠᱩᱞᱩᱯ ᱪᱤᱱᱦᱟᱹ, ᱯᱤᱱ, ᱟᱨ ᱵᱟᱝ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱟᱢᱟᱜ ᱥᱟᱧᱪᱟᱣ ᱜᱚᱱᱚᱝ ᱛᱚᱨᱠᱟᱠᱚ ᱮᱢᱟᱱ ᱨᱩᱠᱷᱭᱟ ᱞᱟᱹᱜᱤᱫ ᱡᱩᱫᱤ ᱚᱞᱜᱟ ᱦᱚᱲ ᱴᱷᱮᱱ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱛᱟᱦᱮᱸᱱ ᱠᱷᱟᱱ ᱾ ᱱᱤᱛᱚᱜ ᱥᱟᱡᱟᱣ ᱢᱮ ᱛᱟᱭᱚᱢ ᱛᱮ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱚᱱᱞᱚᱠ ᱢᱮ - - ᱫᱚᱦᱚ ᱠᱟᱱ ᱠᱨᱮᱰᱤᱴ ᱠᱟᱰ ᱵᱤᱵᱨᱚᱬ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ + + ᱥᱟᱧᱪᱟᱣ ᱠᱟᱱ ᱜᱚᱱᱚᱝ ᱛᱚᱨᱠᱟ ᱵᱮᱵᱷᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱤᱩᱞᱟᱹᱭ ᱢᱮ ᱴᱷᱤᱠᱬᱟᱹ ᱥᱮᱞᱮᱫ ᱢᱮ ᱴᱷᱤᱠᱬᱟᱹ ᱥᱟᱯᱲᱟᱣ ᱢᱮ ᱴᱷᱤᱠᱬᱟᱹᱤᱭᱟᱹ ᱡᱚᱛᱚᱱ ᱮᱢ - - ᱯᱩᱭᱞᱩ ᱧᱩᱛᱩᱢ - - ᱛᱟᱞᱟ ᱧᱩᱛᱩᱢ - - ᱢᱩᱪᱟᱹᱫ ᱧᱩᱛᱩᱢ + + ᱧᱩᱛᱩᱢ ᱥᱚᱰᱚᱠ ᱴᱷᱤᱠᱬᱟᱹ @@ -1982,7 +2002,7 @@ ᱴᱷᱤᱠᱬᱟᱹ ᱢᱮᱴᱟᱣ ᱢᱮ - ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱛᱮ ᱢᱮᱱᱟᱢᱼᱟ ᱱᱚᱶᱟ ᱴᱷᱤᱠᱬᱟᱹ ᱢᱮᱴᱟᱣ ᱞᱟ.ᱜᱤᱛ ᱛᱮ? + ᱱᱚᱶᱟ ᱴᱷᱤᱠᱬᱟᱹ ᱢᱮᱴᱟᱣᱟ ᱥᱮ ? ᱢᱮᱴᱟᱣ ᱢᱮ @@ -2080,32 +2100,34 @@ ᱢᱮᱴᱟᱣ ᱢᱮ ᱥᱟᱯᱲᱟᱣ - - ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱢᱮᱱᱟᱢᱼᱟ ᱱᱚᱶᱟ ᱞᱚᱜᱤᱱ ᱢᱮᱴᱟᱣ ᱞᱟ.ᱜᱤᱫ ᱛᱮ? + + ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱛᱮ ᱢᱮᱱᱟᱢᱼᱟ ᱱᱚᱶᱟ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱢᱮᱴᱟᱣ ᱞᱟ.ᱜᱤᱛ ᱛᱮ? ᱢᱮᱴᱟᱣ ᱢᱮ ᱵᱟᱹᱰᱨᱟᱹ - - ᱮᱴᱟᱜᱟᱜ ᱠᱚ ᱞᱚᱜᱤᱱ - - ᱣᱤᱵ ᱴᱷᱤᱠᱬᱟᱹ ᱞᱚᱜᱤᱱ ᱵᱚᱞᱚ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ - - ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱞᱚᱜᱤᱱ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱨᱮᱭᱟᱜ ᱵᱟᱪᱷᱟᱣ ᱠᱚ + + ᱣᱤᱵᱽ ᱴᱷᱤᱠᱬᱟᱹ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ + + ᱵᱤᱵᱷᱟᱨᱤᱭᱟᱹᱧᱩᱛᱩᱢ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱞᱚᱜᱤᱱ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ - - ᱞᱚᱜᱤᱱ ᱵᱚᱫᱚᱞ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱢᱮ ᱾ - - ᱥᱟᱯᱲᱟᱣ - - ᱱᱟᱶᱟ ᱵᱚᱞᱚ ᱥᱮᱞᱮᱫ ᱢᱮ - - ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱨᱮᱭᱟᱜ ᱫᱚᱨᱠᱟᱨ ᱢᱮᱱᱟᱜ-ᱟ + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱯᱲᱟᱣ ᱚᱞ ᱡᱟᱭᱜᱟ ᱾ + + ᱵᱚᱫᱚᱞ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱢᱮ ᱾ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱥᱟᱯᱲᱟᱣ ᱢᱮ + + ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱥᱮᱞᱮᱫ ᱢᱮ + + ᱢᱤᱫᱴᱟᱹᱝ ᱥᱟᱵᱟᱫ ᱟᱫᱮᱨ ᱢᱮ - ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ ᱫᱚᱨᱠᱟᱨ + ᱢᱤᱫᱴᱟᱹᱝ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ ᱟᱫᱮᱨ ᱢᱮ ᱦᱚᱥᱴᱧᱩᱛᱩᱢ ᱫᱚᱨᱠᱟᱨ ᱠᱟᱱᱟ + + ᱢᱤᱫᱴᱟᱹᱝ ᱣᱮᱵᱽ ᱴᱷᱤᱠᱬᱟᱹ ᱟᱫᱮᱨ ᱢᱮ ᱨᱚᱲ ᱥᱮᱸᱫᱽᱨᱟ @@ -2200,6 +2222,9 @@ %s ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ + + + ᱟᱢᱟᱜ ᱢᱩᱞ ᱵᱽᱨᱟᱣᱡᱚᱨ ᱛᱮ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ Firefox ᱟᱡ ᱛᱮ ᱠᱷᱩᱞᱟᱹ ᱪᱷᱚ ᱞᱟᱹᱜᱤᱫ ᱣᱮᱵᱥᱟᱭᱤᱴ, ᱤᱢᱮᱞ, ᱟᱨ ᱢᱮᱥᱮᱡᱽ ᱨᱮᱭᱟᱜ ᱞᱤᱝᱠ ᱥᱮᱴ ᱢᱮ ᱾ @@ -2237,6 +2262,85 @@ ᱥᱟᱡᱟᱣ ᱴᱷᱮᱱ ᱪᱟᱞᱟᱜ ᱢᱮ + + + ᱫᱩᱦᱲᱟᱹ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣᱤᱭᱟᱹ + + ᱴᱷᱤᱠ ᱵᱟᱛᱟᱣᱟᱜᱠᱚ + + ᱴᱷᱤᱠ ᱵᱟᱛᱟᱣᱟᱜ ᱟᱨ ᱵᱤᱱᱥᱨᱚᱛ ᱢᱮᱥᱟᱢᱤᱥᱤ ᱧᱮᱞᱵᱤᱲᱟᱹᱣᱟᱜᱠᱚ + + ᱵᱤᱱᱥᱨᱚᱛ ᱧᱮᱞᱵᱤᱲᱟᱹᱣ + + ᱱᱚᱶᱟ ᱧᱮᱞᱵᱤᱲᱟᱹᱣᱠᱚ ᱛᱤᱱᱟᱹᱜ ᱵᱷᱚᱨᱥᱟ ᱫᱮᱲᱮᱭᱟᱜᱼᱟᱢ ? + + ᱨᱮᱴᱤᱝ ᱥᱚᱢᱚᱡᱚᱥ ᱮᱱᱟ + + ᱵᱷᱚᱨᱥᱟᱡᱚᱱᱚᱠ ᱧᱮᱞᱵᱤᱲᱟᱹᱣ ᱪᱮᱛᱟᱱ ᱨᱮ + + ᱱᱤᱛᱚᱜᱟᱜ ᱧᱮᱞᱵᱤᱲᱟᱹᱣ ᱠᱷᱚᱱ ᱛᱤᱱᱟᱹᱜ ᱜᱟᱱ ᱪᱤᱱᱦᱟᱹᱦᱟᱜ + + ᱟᱞᱮ ᱫᱚ ᱧᱮᱞᱵᱤᱲᱟᱹᱣᱟᱜ ᱜᱩᱱ ᱫᱚ ᱪᱮᱫ ᱞᱮᱠᱷᱟ ᱞᱮ ᱵᱟᱲᱟᱭ ᱮᱫᱟ + + Mozilla ᱯᱟᱦᱴᱟ ᱠᱷᱚᱱ %s ᱠᱷᱚᱱ AI ᱴᱮᱠᱱᱚᱞᱚᱡᱤ ᱵᱮᱵᱷᱟᱨ ᱠᱟᱛᱮᱜ ᱟᱞᱮ ᱯᱨᱚᱰᱚᱠᱴ ᱨᱤᱵᱷᱤᱭᱩ ᱨᱮᱱᱟᱜ ᱥᱟᱹᱨᱤᱜᱟᱱᱤ ᱧᱮᱞ ᱵᱤᱰᱟᱹᱣᱜ ᱮᱫᱟ ᱞᱮ ᱾ ᱱᱚᱶᱟ ᱫᱚ ᱧᱮᱞ ᱵᱤᱰᱟᱣᱜ ᱜᱩᱱ ᱵᱟᱲᱟᱭ ᱪᱷᱚ ᱨ ᱜᱚᱲᱚ ᱮᱢ ᱫᱟᱲᱮᱭᱟᱢᱟ, ᱯᱨᱚᱰᱚᱠᱴ ᱨᱮᱭᱟᱜ ᱢᱩᱞ ᱜᱩᱱ ᱵᱟᱵᱚᱛ ᱫᱚ ᱵᱟᱝᱟ ᱾ + + ᱪᱤᱠᱤ ᱜᱽᱨᱮᱰ A ᱠᱷᱚᱱ F ᱫᱷᱟᱹᱵᱤᱡ ᱞᱮ ᱮᱢ ᱮᱫᱟ ᱾]]> + + %s ᱵᱟᱵᱚᱛ ᱰᱷᱮᱨ ᱵᱟᱰᱟᱭ ᱢᱮ ᱾ + + %s ᱫᱚ ᱧᱮᱞᱵᱤᱲᱟᱹᱣᱟᱜ ᱜᱩᱱ ᱫᱚ ᱪᱮᱫ ᱞᱮᱠᱷᱟ ᱵᱟᱲᱟᱭ ᱮᱫᱟ + + ᱥᱟᱡᱟᱣᱠᱚ + + ᱫᱩᱦᱲᱟᱹ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣᱤᱭᱟᱹ ᱨᱮ ᱫᱷᱟᱶᱨᱟ ᱫᱮᱠᱷᱟᱣ ᱢᱮ + + ᱰᱷᱮᱨ ᱥᱮᱬᱟᱭ ᱢᱮ + + ᱫᱩᱦᱲᱟᱹ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣᱤᱭᱟᱹ ᱵᱚᱸᱫᱚᱭ ᱢᱮ + + ᱰᱷᱮᱨ ᱮᱢ ᱧᱮᱞᱟ + + ᱰᱷᱟᱶᱨᱟᱤᱭᱟᱹ ᱫᱚ %s + + ᱫᱩᱦᱲᱟᱹ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣᱤᱭᱟᱹ ᱫᱚ %s ᱜᱚᱲᱚ ᱥᱟᱦᱚᱫ ᱮᱢ ᱠᱟᱜᱼᱟᱭ + + %s Mozilla ᱛᱮ + + ᱧᱮᱞ ᱞᱟᱹᱜᱤᱫ ᱱᱚᱶᱟ ᱵᱤᱵᱨᱚᱬ + + ᱱᱤᱛᱚᱜ ᱧᱮᱞ ᱢᱮ + + ᱩᱱᱟᱹᱜ ᱧᱮᱞᱵᱤᱲᱟᱹᱣ ᱵᱟᱹᱱᱩᱜ ᱠᱟᱫᱟ + + ᱛᱤᱧ ᱡᱷᱚᱜ ᱡᱤᱱᱤᱥ ᱨᱮ ᱡᱟᱹᱥᱛᱤ ᱧᱮᱞᱵᱤᱲᱟᱹᱣ ᱛᱟᱦᱮᱸᱱᱟ, ᱟᱞᱮ ᱫᱚ ᱚᱱᱟ ᱨᱮᱭᱟᱜ ᱜᱩᱱ ᱵᱚᱵᱚᱛ ᱞᱮ ᱢᱮᱱ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾ + + ᱡᱤᱱᱤᱥ ᱫᱚ ᱵᱟᱹᱱᱩᱜ ᱠᱟᱫᱟ + + ᱡᱩᱫᱤ ᱥᱴᱚᱠ ᱛᱮ ᱡᱤᱱᱤᱥ ᱦᱮᱡ ᱞᱮᱠᱷᱟᱢ ᱵᱩᱡᱷᱟᱹᱣ ᱠᱮᱫ ᱠᱷᱟᱱ, ᱟᱞᱮ ᱴᱷᱮᱱ ᱠᱷᱚᱵᱚᱨ ᱮᱢ ᱠᱟᱜ ᱢᱮ ᱟᱞᱮ ᱫᱚ ᱧᱮᱞᱵᱤᱲᱟᱹᱣᱠᱚᱞᱮ ᱧᱮᱞ ᱟ ᱾ + + ᱠᱷᱚᱵᱚᱨ ᱢᱮ ᱡᱮ ᱡᱤᱱᱤᱥ ᱫᱚ ᱥᱴᱚᱠ ᱨᱮ ᱦᱮᱡ ᱦᱩᱭ ᱮᱱᱟ + + ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣ ᱜᱩᱱ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣ ᱮᱫᱟ (%s) + + ᱱᱚᱶᱟ ᱫᱚ 60 ᱴᱤᱡ ᱜᱟᱱ ᱚᱠᱛᱚ ᱤᱫᱤᱭᱟᱭ ᱾ + + ᱠᱷᱚᱵᱚᱨ ᱮᱢ ᱞᱟᱹᱜᱤᱫ ᱟᱹᱰᱤ ᱥᱟᱯᱲᱟᱣ! + + ᱟᱞᱮ ᱴᱷᱮᱱ ᱫᱚ ᱱᱚᱶᱟ ᱡᱤᱱᱤᱥ ᱵᱚᱵᱛ ᱧᱮᱞ ᱵᱤᱰᱟᱣᱜ 24 ᱴᱟᱲᱟᱝ ᱜᱟᱱ ᱞᱟᱣᱟ ᱞᱮᱭᱟ ᱾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣ ᱢᱮ ᱾ + + ᱟᱞᱮ ᱫᱚ ᱱᱚᱶᱟ ᱵᱟᱞᱮ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ + + ᱵᱤᱵᱨᱚᱬ ᱞᱚᱜᱚᱱ ᱜᱮ ᱦᱟᱹᱡᱩᱜ ᱠᱟᱱᱟ + + ᱟᱞᱮ ᱴᱷᱮᱱ ᱫᱚ ᱱᱚᱶᱟ ᱡᱤᱱᱤᱥ ᱵᱚᱵᱛ ᱧᱮᱞ ᱵᱤᱰᱟᱣᱜ 24 ᱴᱟᱲᱟᱝ ᱜᱟᱱ ᱞᱟᱣᱟ ᱞᱮᱭᱟ ᱾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱧᱮᱞ ᱵᱤᱲᱟᱹᱣ ᱢᱮ ᱾ + + ᱯᱚᱨᱚᱠ ᱨᱮᱭᱟᱜ ᱦᱟᱹᱞᱤᱭᱟᱹᱠ ᱴᱷᱤᱠ ᱜᱭᱟ + + ᱵᱩᱡᱷᱟᱹᱣ ᱠᱮᱫᱟ + + ᱱᱤᱛᱚᱜ ᱪᱮᱫ ᱦᱚᱸ ᱵᱤᱵᱨᱚᱬ ᱵᱟᱹᱱᱩᱜ ᱠᱟᱫᱼᱟ + + ᱱᱚᱶᱟ ᱮᱴᱠᱮᱴᱚᱬᱮ ᱥᱩᱫᱷᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱠᱟᱹᱢᱤ ᱪᱟᱹᱞᱩ ᱠᱟᱱᱟ ᱾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱛᱟᱭᱚᱢ ᱛᱮ ᱫᱩᱦᱲᱟᱹ ᱧᱮᱞ ᱦᱟᱹᱡᱩᱜ ᱢᱮ ᱾ ᱠᱚᱢ ᱫᱮᱠᱷᱟᱣ ᱢᱮ @@ -2265,6 +2369,20 @@ + + ᱛᱚᱨᱡᱚᱢᱟ + + ᱫᱩᱦᱲᱟᱹ ᱠᱩᱨᱩᱢᱩᱴᱩᱭ ᱢᱮ + + ᱛᱚᱨᱡᱚᱢᱟᱜ ᱠᱟᱱᱟ + + ᱛᱚᱨᱡᱚᱢᱟ ᱞᱟᱦᱟ ᱨᱮ ᱢᱮᱱᱟᱜ ᱠᱟᱫᱟ + + ᱢᱤᱫᱴᱟᱝ ᱯᱟᱹᱨᱥᱤ ᱵᱟᱪᱷᱟᱣ ᱢᱮ + + ᱛᱚᱨᱡᱚᱢᱟ ᱡᱷᱚᱜ ᱵᱷᱩᱞ ᱮᱴᱠᱮᱴᱚᱬᱮ ᱦᱩᱭᱮᱱᱟ ᱾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱫᱩᱦᱲᱟᱹ ᱪᱮᱥᱴᱟᱭ ᱢᱮ ᱾ + + ᱢᱳᱴ @@ -2277,4 +2395,7 @@ ᱵᱟᱝ ᱠᱟᱹᱢᱤ ᱴᱮᱵᱽᱠᱚᱨᱮ ᱥᱮᱞᱮᱫ ᱢᱮ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵᱽ ᱥᱮᱞᱮᱫᱽ ᱢᱮ - + + + + diff --git a/mobile/android/fenix/app/src/main/res/values-sc/strings.xml b/mobile/android/fenix/app/src/main/res/values-sc/strings.xml index 799f6bb624..b126355172 100644 --- a/mobile/android/fenix/app/src/main/res/values-sc/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sc/strings.xml @@ -36,6 +36,8 @@ Nòmine Seletziona una colletzione + + Essi de sa modalidade de seletzione mùltiple Sarva is ischedas seletzionadas a sa colletzione @@ -43,11 +45,19 @@ - Sarvadas de reghente + Sarvadas de reghente - Ammustra totu is sinnalibros sarvados + Ammustra totu is sinnalibros sarvados - Boga + Boga + + + + Sinnalibros + + Ammustra totu is sinnalibros + + Boga %1$s est prodotu dae Mozilla. @@ -101,6 +111,8 @@ Iscarta + + Cunfigura sa serrada in automàticu de is ischedas abertas chi non siant istadas visualizadas in s\'ùrtima die, chida o mese. Ammustra is optziones @@ -127,8 +139,10 @@ Ischeda privada noa - - Curtzadòrgiu pro is craes + + Craes + + Curtzadòrgiu pro is craes @@ -168,11 +182,17 @@ Firma - Cumplementos + Cumplementos + + Estensiones + + Gesti is estensiones + + Iscoberi àteras estensiones Informatziones de su contu - Nissunu cumplementu inoghe + Nissunu cumplementu inoghe Agiudu @@ -187,18 +207,26 @@ Aberi in un’ischeda normale Agiunghe a s’ischermu printzipale + + Agiunghe a s’ischermu printzipale… - Installa + Installa Torra a sincronizare Chirca in sa pàgina + + Chirca in sa pàgina… Tradue sa pàgina + Sarva in una colletzione… + Sarva in una colletzione Cumpartzi + + Cumpartzi… Aberi in %1$s @@ -224,6 +252,50 @@ Personaliza sa pàgina printzipale + + Identìfica·ti + + Sincroniza craes, ischedas e àteru + + Torra·ti a identificare pro sincronizare + + Sincronizatzione in pàusa + + Ischeda privada noa + + Craes + + Nou in %1$s + + Càmbia a situ de iscrivania + + Istrumentos + + Sarva + + Agiunghe custa pàgina a is sinnalibros + + Modìfica su sinnalibru + + Sarva comente PDF… + + Ativa sa visualizatzione de letura + + Disativa sa visualizatzione de letura + + Tradue sa pàgina… + + Traduidu in %1$s + + Imprenta… + + + + Nissuna estensione inoghe + Ischermu printzipale @@ -234,6 +306,11 @@ Tradue sa pàgina + + Pàgina traduida in %2$s dae: %1$s + Lìngua seletzionada @@ -297,7 +374,6 @@ Avisu de riservadesa de Firefox - Nos praghet a t’amparare @@ -467,6 +543,10 @@ Torra a connètere pro sighire cun sa sincronizatzione Lìngua + + Tradutzione + + Tradutziones Sèberu de is datos @@ -493,6 +573,9 @@ Cumpletamentu de URL in automàticu Cussìgios dae is ispònsors + + Sustene %1$s cun propostas isponsorizadas in manera ocasionale Cussìgios dae %1$s @@ -515,9 +598,13 @@ Serrende s’aplicatzione pro aplicare is modìficas… - Cumplementos + Cumplementos + + Estensiones - Installa unu cumplementu dae un’archìviu + Installa unu cumplementu dae un’archìviu + + Installa un\'estensione dae un’archìviu Notìficas @@ -526,9 +613,17 @@ Non permìtidu + + + Rechestu + + Optzionale + + Permite pro totu is sitos + - Colletzione de cumplementos personalizada + Colletzione de cumplementos personalizada AB @@ -541,10 +636,14 @@ Torra a s’ischeda - Sinnalibros reghentes + Sinnalibros reghentes + + Sinnalibros Bisitadas de reghente + + Istòrias chi faghent pensare Artìculos seletzionados dae %s @@ -571,6 +670,8 @@ Àteras informatziones %s clàssicu + + Sèrie de artista Sa colletzione Boghes indipendentes. %s @@ -586,19 +687,31 @@ - Cumplementos noos a disponimentu + Cumplementos noos a disponimentu + + Estensiones noas a disponimentu Controlla is prus de chentu estensiones noas chi ti permitint de personalizare Firefox. - Esplora cumplementos + Esplora cumplementos + + + Esplora is estensiones - + - Is cumplementos sunt istados disativados in manera temporànea + Is cumplementos sunt istados disativados in manera temporànea + + Is estensiones sunt disativadas in manera temporànea - Proa torrende a aviare is cumplementos + Proa torrende a aviare is cumplementos + + Proa torrende a aviare is estensiones - Sighi cun is cumplementos disativados + Sighi cun is cumplementos disativados + + + Sighi cun is estensiones disativadas @@ -613,10 +726,8 @@ Cronologia Sinnalibros - - Credentziales - Craes + Craes Ischedas abertas @@ -641,10 +752,8 @@ The first parameter is the application name, the second is the device manufacturer name and the third is the device model. --> %1$s in %2$s %3$s - - Cartas de crèditu - Mètodos de pagamentu + Mètodos de pagamentu Indiritzos @@ -658,11 +767,21 @@ Ischeda dae %s + + + Ischedas de %1$s serradas: %2$d + + Ammustra ischedas serradas de reghente + Etzetziones Ativa pro totu is sitos + + Is estensiones ti permitint de disativare s\'amparu contra a sa sighidura in tzertos sitos. Àteras informatziones @@ -1444,13 +1563,9 @@ Nòmine de su curtzadòrgiu - Credentziales e craes - - Craes - - Sarva credentziales e craes + Craes - Sarva is craes + Sarva is craes Pregunta·mi·ddu @@ -1463,42 +1578,24 @@ Cumpletamentu automàticu in àteras aplicatziones Cumpleta nòmines de utente e craes in àteras aplicatziones de su dispositivu tuo. - - Agiunghe credentziale - Agiunghe crae + Agiunghe crae - - Sincronizatzione de credentziales - Sincroniza is craes - - Sincronizatzione de credentziales intre dispositivos + Sincroniza is craes - Sincroniza is craes intre dispositivos - - Credentziales sarvadas + Sincroniza is craes intre dispositivos - Craes sarvadas - - Is credentziales chi as a sarvare o sincronizare cun %s ant a èssere ammustradas inoghe. - - Leghe àteru subra de Sync. + Craes sarvadas - Àteras informatziones in pitzus de sa sincronizatzione + Àteras informatziones in pitzus de sa sincronizatzione Etzetziones - - Is credentziales e is craes non sarvadas ant a èssere ammustradas inoghe. - - No ant a èssere sarvadas is credentziales e is craes de custos sitos. Cantzella totu is etzetziones - - Chirca credentziales - Chirca craes + Chirca craes Situ @@ -1523,12 +1620,6 @@ Ammustra sa crae Cua sa crae - - Isbloca pro bìdere is credentziales sarvadas tuas - - Ampara is credentziales e is craes tuas - - Cunfigura una secuèntzia de blocu, PIN o crae pro amparare is credentziales e is craes tuas de s’atzessu de àtera gente chi tèngiat su dispositivu tuo. A pustis @@ -1545,44 +1636,30 @@ Cumpletamentu automàticu Indiritzos - - Cartas de crèditu - Mètodos de pagamentu - - Sarva e cumpleta in automàticu is cartas + Mètodos de pagamentu - Sarva e cumpleta cun is mètodos de pagamentu - - Is datos sunt tzifrados + Sarva e cumpleta cun is mètodos de pagamentu - %s tzifrat totu is mètodos de pagamentu chi sarves + %s tzifrat totu is mètodos de pagamentu chi sarves Sincronizatzione de cartas intre dispositivos Sincronizatzione de cartas - - Agiunghe una carta de crèditu - Agiunghe una carta - - Gesti is cartas + Agiunghe una carta - Gesti is cartas + Gesti is cartas Agiunghe un’indiritzu Gesti is indiritzos - - Sarva e cumpleta in automàticu is indiritzos - Sarva e cumpleta cun is indiritzos - - Include datos comente nùmeros, indiritzos eletrònicos e indiritzos de ispeditzione + Sarva e cumpleta cun is indiritzos - Includet nùmeros de telèfonu e indiritzos eletrònicos + Includet nùmeros de telèfonu e indiritzos eletrònicos Agiunghe una carta @@ -1603,9 +1680,7 @@ Cantzella sa carta - Seguru chi boles cantzellare custa carta de crèditu? - - Boles cantzellare sa carta? + Boles cantzellare sa carta? Cantzella @@ -1616,46 +1691,30 @@ Annulla Cartas sarvadas - - Inserta unu nùmeru de carta vàlidu - Inserta unu nùmeru de carta vàlidu - - Cumpila custu campu + Inserta unu nùmeru de carta vàlidu - Agiunghe unu nòmine + Agiunghe unu nòmine Isbloca pro bìdere is cartas sarvadas tuas - Ampara is cartas de crèditu tuas - - Ampara is mètodos de pagamentu sarvados - - Cunfigura una secuèntzia de blocu, PIN o crae pro amparare is cartas de crèditu sarvadas tuas de s’atzessu de àtera gente chi tèngiat su dispositivu tuo. + Ampara is mètodos de pagamentu sarvados - Cunfigura una secuèntzia de blocu, PIN o crae pro amparare is mètodos de pagamentu sarvados de s’atzessu de àtera gente chi tèngiat su dispositivu tuo. + Cunfigura una secuèntzia de blocu, PIN o crae pro amparare is mètodos de pagamentu sarvados de s’atzessu de àtera gente chi tèngiat su dispositivu tuo. Cunfigura immoe A pustis Isbloca su disposivitu tuo - - Isbloca pro impreare informatzione sarvada de una carta de crèditu - Isbloca pro impreare is mètodos de pagamentu sarvados + Isbloca pro impreare is mètodos de pagamentu sarvados Agiunghe un’indiritzu Modìfica s’indiritzu Gesti is indiritzos - - Nòmine - - Segundu nòmine - - Sangunadu Indiritzu postale @@ -1679,9 +1738,7 @@ Cantzella s’indiritzu - Seguru chi boles cantzellare custu indiritzu? - - Boles cantzellare custu indiritzu? + Boles cantzellare custu indiritzu? Cantzella @@ -1776,38 +1833,24 @@ Cantzella Modìfica - - Seguru chi boles cantzellare custa credentziale? - Seguru chi boles cantzellare custa crae? + Seguru chi boles cantzellare custa crae? Cantzella Annulla - - Optziones de credentziales - Optziones de is craes - - Sarva is modìficas a sa credentziale. + Optziones de is craes - Sarva is modìficas. - - Modìfica + Sarva is modìficas. - Modìfica sa crae - - Agiunghe una credentziale noa + Modìfica sa crae - Agiunghe una crae - - Crae rechesta + Agiunghe una crae - Inserta una crae - - Nòmine de utente rechestu + Inserta una crae - Inserta unu nòmine de utente + Inserta unu nòmine de utente Inserta un’indiritzu web @@ -1885,7 +1928,7 @@ Chirca cun %s - + Aberi in Firefox in manera automàtica is ligòngios de sitos web, de posta eletrònica e de messàgios. @@ -1895,7 +1938,7 @@ Incarca inoghe pro àteros detàllios - Nàviga in artu + Nàviga in artu Serra @@ -2080,12 +2123,12 @@ Custa lìngua no est ancora atzetada: %1$s. - Àteras informatziones + Àteras informatziones - Optziones de tradutzione + Optziones de tradutzione Cunfiguratzione de sa tradutzione @@ -2169,4 +2212,6 @@ Annulla + + diff --git a/mobile/android/fenix/app/src/main/res/values-si/strings.xml b/mobile/android/fenix/app/src/main/res/values-si/strings.xml index a90119065d..1f5dd3908d 100644 --- a/mobile/android/fenix/app/src/main/res/values-si/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-si/strings.xml @@ -46,11 +46,19 @@ - මෑතදී සුරැකි + මෑතදී සුරැකි - සුරැකි සියළු පොත්යොමු පෙන්වන්න + සුරැකි සියළු පොත්යොමු පෙන්වන්න - ඉවත් කරන්න + ඉවත් කරන්න + + + + පොත්යොමු + + සියලුම පොත්යොමු පෙන්වන්න + + ඉවත් කරන්න මොසිල්ලා හරහා %1$s නිෂ්පාදනය කෙරේ. @@ -245,14 +253,28 @@ පිවිසෙන්න + + මුරපද, පටිති සහ තවත් දෑ සමමුහූර්ත කරන්න + + සමමුහූර්තයට පිවිසෙන්න + + සමමුහූර්ත විරාමයකි + + නව පෞද්. පටිත්ත මුරපද + + වැඩතල අඩවිය මෙවලම් සුරකින්න + + + මෙතැන දිගු නැත + මුල් තිරය @@ -264,6 +286,11 @@ පිටුව පරිවර්තනය + + %1$s වෙතින් %2$s වෙත පිටුව පරිවර්තනය වී ඇත. + තෝරාගත් භාෂාව @@ -342,6 +369,8 @@ ෆයර්ෆොක්ස් රහස්‍යතා දැන්වීම + + රහස්‍යතා දැන්වීම ගැන තව දැනගන්න ඔබට ආරක්‍ෂාව සලසන්නෙමු @@ -533,6 +562,8 @@ සමමුහූර්තය යළි ඇරඹීමට නැවත සබඳින්න භාෂාව + + පරිවර්තනය දත්ත තේරීම් @@ -584,8 +615,12 @@ එක්කහු + + දිගු ගොනුවකින් එක්කහුවක් ස්ථාපනය… + + ගොනුවකින් දිගුවක් ස්ථාපනය දැනුම්දීම් @@ -594,9 +629,22 @@ ඉඩ නොදේ + + + වුවමනාය + + විකල්පයකි + + අඩවියේ දත්ත කියවා වෙනස් කිරීම + + + අඩවිය මකන්න + අභිරුචි එක්කහු එකතුව + + අභිරුචි දිගු එකතුව හරි @@ -613,7 +661,9 @@ ආපසු පනින්න - මෑත පොත්යොමු + මෑත පොත්යොමු + + පොත්යොමු මෑතදී ගොඩවැදුණු @@ -664,6 +714,8 @@ දැන් නව එක්කහු තිබේ + + දැන් නව දිගු තිබේ ෆයර්ෆොක්ස් අභිරුචිකරණයට උපකාරී වන නව දිගු 100 කට වඩා බලන්න. diff --git a/mobile/android/fenix/app/src/main/res/values-sk/strings.xml b/mobile/android/fenix/app/src/main/res/values-sk/strings.xml index b5795659e3..75648fb199 100644 --- a/mobile/android/fenix/app/src/main/res/values-sk/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sk/strings.xml @@ -201,6 +201,10 @@ Doplnky Rozšírenia + + Spravovať rozšírenia + + Objavte ďalšie rozšírenia Informácie o účte @@ -219,6 +223,8 @@ Otvoriť na bežnej karte Pridať na úvodnú obrazovku + + Pridať na úvodnú obrazovku… Nainštalovať @@ -230,9 +236,13 @@ Preložiť stránku + Uložiť do kolekcie… + Uložiť do kolekcie Zdieľať + + Zdieľať… Otvoriť v aplikácii %1$s @@ -289,6 +299,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Uložiť + + Pridať stránku medzi záložky + + Upraviť záložku + + Uložiť ako PDF… + + Zapnúť zobrazenie Čítačka + + Ukončiť zobrazenie Čítačka + + Preložiť stránku… + + Preložená do jazyka %1$s + + Tlačiť… + Nie sú tu žiadne rozšírenia @@ -388,8 +416,6 @@ Vyhlásenie o ochrane osobných údajov Firefoxu - - Ďalšie informácie nájdete v našom Vyhlásení o ochrane osobných údajov Radi vás držíme v bezpečí Jazyk - Preklady + Preklady + + Preklady Odosielanie údajov @@ -674,10 +702,6 @@ Vyžadované Voliteľné - - Čítanie a zmena údajov webových stránok - - Odstrániť webovú stránku Povoliť pre všetky stránky @@ -804,8 +828,6 @@ História Záložky - - Prihlasovacie údaje Heslá @@ -832,8 +854,6 @@ and the third is the device model. --> %1$s na %2$s %3$s - - Platobné karty Spôsoby platby @@ -850,6 +870,14 @@ Karta z %s + + + Zatvorené karty %1$su: %2$d + + Zobraziť nedávno zatvorené karty + Výnimky @@ -1778,13 +1806,9 @@ Túto webovú stránku si môžete jednoducho pridať na svoju domovskú obrazovku a mať tak okamžitý prístup k prehliadaniu. - - Prihlasovacie údaje Heslá - Ukladanie prihlasovacích údajov - Ukladanie hesiel Pred uložením sa opýtať @@ -1799,46 +1823,27 @@ Vypĺňa používateľské mená a heslá v iných aplikáciách vo vašom zariadení. - - Pridať prihlasovacie údaje - Pridať heslo - - Synchronizácia prihlasovacích údajov Synchronizácia hesiel - - Synchronizovať prihlasovacie údaje medzi zariadeniami Synchronizovať heslá naprieč zariadeniami - - Uložené prihlasovacie údaje Uložené heslá - Uložené alebo synchronizované údaje v aplikácii %s sa zobrazia tu. - Heslá, ktoré uložíte alebo synchronizujete do %su budú zobrazené tu. Všetky heslá sú šifrované. - - Ďalšie informácie o službe Sync. Ďalšie informácie o synchronizácii Výnimky - - Tu sa zobrazia prihlasovacie údaje, ktoré sa nebudú ukladať. %s nebude ukladať heslá pre tu uvedené stránky. - - Pre nasledujúce stránky sa nebudú ukladať prihlasovacie údaje. %s nebude ukladať heslá pre tieto stránky. Odstrániť všetky výnimky - - Hľadať Hľadať v heslách @@ -1867,17 +1872,11 @@ Zobraziť heslo Skryť heslo - - Pre zobrazenie prihlasovacích údajov odomknite zariadenie Ak chcete zobraziť uložené heslá, odomknite zariadenie - Zabezpečte svoje prihlasovacie údaje - Zabezpečte svoje heslá - Nastavte si vzor, kód alebo heslo, ktorým ochránite svoje uložené prihlasovacie údaje v prípade, že vaše zariadenie bude používať niekto iný. - Nastavte si vzor, kód alebo heslo, ktorým ochránite svoje uložené heslá v prípade, že vaše zariadenie bude používať niekto iný. Neskôr @@ -1894,8 +1893,6 @@ názvu (A-Z) posledného použitia - - Zoradiť ponuku prihlasovacích údajov Ponuka pre zoradenie hesiel @@ -1905,41 +1902,27 @@ Automatické dopĺňanie Adresy - - Platobné karty Spôsoby platby - Ukladať a automaticky dopĺňať údaje o platobných kartách - Ukladať a dopĺňať spôsoby platby - - Údaje sú šifrované %s zašifruje všetky spôsoby platby, ktoré uložíte Synchronizovať platobné karty naprieč zariadeniami Synchronizovať platobné karty - - Pridať platobnú kartu Pridať kartu - - Spravovať uložené karty Spravovať karty Pridať adresu Spravovať adresy - - Ukladať a automaticky dopĺňať adresy Ukladať a dopĺňať adresy - - Zahŕňa informácie ako sú čísla, e‑mailové adresy a dodacie adresy Zahŕňa telefónne čísla a e‑mailové adresy @@ -1963,8 +1946,6 @@ Odstrániť kartu - Naozaj chcete odstrániť túto kreditnú kartu? - Odstrániť kartu? Odstrániť @@ -1977,24 +1958,15 @@ Uložené karty - - Prosím, zadajte platné číslo platobnej karty - Zadajte platné číslo karty - - Vyplňte toto pole Zadajte meno Odomknutím zobrazíte svoje uložené kreditné karty - Zabezpečte svoje kreditné karty - Zabezpečte svoje uložené spôsoby platby - Nastavte si vzor, kód alebo heslo, ktorým ochránite svoje uložené kreditné karty v prípade, že vaše zariadenie bude používať niekto iný. - Nastavte si vzor, kód alebo heslo, ktorým ochránite svoje uložené spôsoby platby v prípade, že vaše zariadenie bude používať niekto iný. Nastaviť teraz @@ -2002,8 +1974,6 @@ Neskôr Odomknite svoje zariadenie - - Odomknutím použijete uložené informácie o kreditnej karte Ak chcete použiť uložené spôsoby platby, odomknite zariadenie @@ -2013,12 +1983,6 @@ Úprava adresy Spravovať adresy - - Krstné meno - - Stredné meno - - Priezvisko Meno @@ -2044,8 +2008,6 @@ Odstrániť adresu - - Naozaj chcete odstrániť túto adresu? Chcete odstrániť túto adresu? @@ -2144,49 +2106,29 @@ Odstrániť Upraviť - - Naozaj chcete odstrániť tieto prihlasovacie údaje? Naozaj chcete odstrániť toto heslo? Odstrániť Zrušiť - - Možnosti prihlásenia Možnosti hesiel - - Upraviteľné textové pole pre webovú adresu. Upraviteľné textové pole pre webovú adresu. - - Upraviteľné textové pole pre používateľské meno. Upraviteľné textové pole pre používateľské meno. - Upraviteľné textové pole pre heslo. - Upraviteľné textové pole pre heslo. - - Uložiť zmeny prihlasovacích údajov. Uložiť zmeny - - Upraviť Upraviť heslo - - Pridať nové prihlasovacie údaje Pridať heslo - - Vyžaduje sa heslo Zadajte heslo - Vyžaduje sa používateľské meno - Zadajte používateľské meno Vyžaduje sa názov hostiteľa @@ -2594,6 +2536,9 @@ Zavrieť hárok Preklady + + Niektoré nastavenia sú dočasne nedostupné. + Preklady @@ -2617,6 +2562,9 @@ Vyberte jazyk, pre ktorý chcete spravovať predvoľby „Vždy prekladať“ a „Nikdy neprekladať“. + + Nepodarilo sa načítať jazyky. Skúste to neskôr. + Ponúkať preklad (predvolené) @@ -2640,6 +2588,8 @@ Odstrániť %1$s + + Stránky sa nepodarilo načítať. Skúste to neskôr. Odstrániť %1$s? @@ -2717,13 +2667,18 @@ Prejsť dozadu + + Otvoriť zásuvku na ladenie + Nástroje pre karty Počet kariet - Aktívne + Aktívne + + Aktívne Neaktívne @@ -2734,6 +2689,16 @@ Nástroj na vytváranie kariet Počet kariet, ktorý chcete vytvoriť + + Textové pole je prázdne + + Zadajte iba kladné celé čísla + + Zadajte číslo väčšie ako nula + + Prekročili ste maximálny počet kariet (%1$s), ktoré je možné vygenerovať v rámci jednej operácie Pridať medzi aktívne karty @@ -2750,11 +2715,11 @@ Vyhlásenie o ochrane osobných údajov - Odoslať + Odoslať - Zavrieť + Zavrieť - Ďakujeme za vašu spätnú väzbu. + Ďakujeme za vašu spätnú väzbu. veľmi spokojný @@ -2766,6 +2731,14 @@ veľmi nespokojný + + + Otvoriť prieskum + + Zavrieť prieskum + + Zavrieť + Prihlasovacie údaje diff --git a/mobile/android/fenix/app/src/main/res/values-skr/strings.xml b/mobile/android/fenix/app/src/main/res/values-skr/strings.xml index bec0a1a1c1..7461c7baa6 100644 --- a/mobile/android/fenix/app/src/main/res/values-skr/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-skr/strings.xml @@ -55,6 +55,9 @@ ہٹاؤ + + + نشانیاں ہٹاؤ @@ -371,8 +374,6 @@ فائرفوکس رازداری نوٹس - - ساݙے رازداری نوٹس وچ ٻیا سکھو اَساکوں تُہاکوں محفوظ رکّھݨ بھان٘دا ہِے - ترجمہ + ترجمہ ڈیٹا چوائس @@ -654,10 +655,6 @@ ضروری ہے آپشنل - - ویب سائٹ ڈیٹا پڑھو تے تبدیلی کرو - - ویب سائٹ مٹاؤ ساریاں سائٹاں کیتے اجازت ݙیوو @@ -789,8 +786,6 @@ نشانیاں - - لاگ ان آں پاس ورڈز @@ -815,8 +810,6 @@ The first parameter is the application name, the second is the device manufacturer name and the third is the device model. --> %3$s %2$s تے %1$s - - کریڈٹ کارڈز ادائیگی طریقے @@ -1772,13 +1765,9 @@ تساں ایں ویب سائٹ کوں آسانی نال آپݨی ڈیوائس دی ہوم سکرین وچ شامل کر سڳدے ہو تاں جو فوری رسائی حاصل تھیوے تے ایپ جیہے تجربے نال تیزی نال براؤز کرو۔ - - لاگ ان تے پاس ورڈ پاس ورڈز - لاگ ان تے پاس ورڈ محفوظ کرو - پاس ورڈ محفوظ کرو محفوظ کرݨ کیتے پُچھو @@ -1792,46 +1781,28 @@ ٻیاں ایپاں وچ آٹوفل کرو آپݨی ڈیوائس تے موجود ٻیاں ایپاں وچ ورتݨ ناں تے پاس ورڈ بھرو۔ - - لاگ ان شامل کرو پاس ورڈ شامل کرو - - لاگ اناں کوں ہم وقت کرو پاس ورڈ ہم وقت کرو - - ڈیوائساں دے مابین لاگ ان ہم وقت کرو ڈیوائساں دے وِچالے پاس ورڈز کوں ہم وقت کرو - - محفوظ تھئے لاگ ان محفوظ تھئے پاس ورڈ - تساں جہڑے لاگ ان محفوظ کریندے ہو یا %s نال ہم وقت کریندے ہو او اِتھاں ݙکھائے ویسن۔ - تُساں جہڑے پاس ورڈز محفوظ کرین٘دے ہِیوے یا %s نال ہم وقت کرین٘دے ہِیوے اُوہ اِتّھاں درج ہوسِن۔ تُہاݙے محفوظ کِیتا ڳئے سبّھے پاس ورڈز کوں انکرپٹ کِیتا ڳِیا ہِے۔ - - ہم وقت کرݨ بارے ٻیا سِکھو۔ ہم وقت کرݨ بارے ٻیا سِکھو۔ استثنیات - - جہڑے لاگ ان تے پاس ورڈ محفوظ کائنی کیتے ڳئے او اِتھ ݙکھائے ویسن۔ %s اِتّھاں درج سائٹاں دے پاس ورڈز محفوظ نہ کریسی۔ - - انہاں سائٹاں کیتے لاگ تے پاس ورڈ محفوظ کائناں تھیسن۔ %s اِنّھاں سائٹاں دے پاس ورڈز محفوظ نہ کریسی۔ سارے استثناء مٹاؤ - - لاگ ان ڳولو پاس ورڈز ڳولو @@ -1862,17 +1833,11 @@ پاس ورڈ لکاؤ - - آپݨے محفوظ کیتے لاگ اناں کوں ݙیکھݨ کیتے اݨ لاک کرو آپݨے محفوظ تھئے پاس ورڈاں کوں ݙیکھݨ کیتے اݨ لاک کرو - لاگ ان تے پاس ورڈ دی محفوظ بݨاؤ - آپݨے محفوظ تھئے ہوئے پاس ورڈز کوں محفوظ بݨاؤ - جے تہاݙے کول کوئی ٻئی ڈیوائس ہے تاں آپݨے محفوظ کیتے لاگ ان تے پاس ورڈ تائیں پہنچ کنوں بچݨ سانگے ڈیوائس لاگ ان پیٹرن، پن پاس ورڈ قائم کرو۔ - ڄیکر تُہاݙی ڈیوائس کہیں ٻئے کول ہِے تاں آپݨے محفوظ کِیتے ڳئے پاس ورڈز تئیں پہنچ کنوں بچاوݨ کِیتے ڈیوائس لاک پیٹرن، PIN، یا پاس ورڈ مُرتّب کرو۔ بعد وچ @@ -1889,8 +1854,6 @@ ناں(A-Z) چھیکڑی ورتاوا - - لاگ ان مینیو کوں ترتیب ݙیوو پاس ورڈز مینیو کوں مُرتّب کرو @@ -1900,40 +1863,26 @@ خودبخود بھرݨ پتے - - کریڈٹ کارڈز ادائیگی طریقے - کارڈ محفوظ کرو تے آپݨے آپ بھرو - ادائیگی دے طریقے محفوظ اَتے پُر کرو - - ڈیٹا مخفی ہے۔ %sتُہاݙے محفوظ کِیتے ڳئے ادائیگی دے سبّھے طریقیاں کوں انکرپٹ کرین٘دا ہِے ڈیوائساں دے مابین کارڈ ہم وقت کرو کارڈ ہم وقت کرو - - کریڈٹ کارڈ شامل کرو کارڈ شامل کرو - - محفوظ تھئے کارڈ منیج کرو کارڈز منیج کرو پتہ شامل کرو پتے منیج کرو - - محفوظ کرو تے پتے خودبخود بھرو محفوظ کرو تے پتے پُر کرو - - معلومات شامل کرو، مثلاً تعداد، ای میل تے شپنگ پتے فون لمبرز اَتے ای میل پتے شامل ہِن @@ -1957,8 +1906,6 @@ کارڈ مٹاؤ - بھلا تہاکوں پک ہے جو تساں ایہ کریڈٹ کارڈ مٹاوݨ چاہندے ہو؟ - کارڈ مٹاؤں؟ مٹاؤ @@ -1970,23 +1917,15 @@ منسوخ کرو محفوظ تھئے کارڈ - - سوہݨا، ٹھیک کریڈٹ کارڈ نمبر درج کرو ہک درست کارڈ نمبر درج کرو۔ - - سوہݨا، ایہ خانہ پُر کرو ناں شامل کرو آپݨے محفوظ تھئے کارڈاں کوں ݙیکھݨ کیتے اݨ لاک کرو - آپݨے کریڈٹ کارڈاں کوں محفوظ بݨاؤ - آپݨے محفوظ کِیتے ڳئے ادائیگی دے طریقیاں کوں محفوظ بݨاؤ - جے تہاݙے کول کوئی ٻئی ڈیوائس ہے تاں آپݨے محفوظ کیتے کریڈٹ کارڈ تائیں پہنچ کنوں بچݨ سانگے ڈیوائس لاگ ان پیٹرن، پن پاس ورڈ قائم کرو۔ - ڄیکر تُہاݙی ڈیوائس کہیں ٻئے کول ہِے تاں آپݨے محفوظ کِیتے ڳئے ادائیگی دے طریقیاں تئیں پہنچ کنوں بچاوݨ کِیتے ڈیوائس لاک پیٹرن، PIN، یا پاس ورڈ مُرتّب کرو۔ بݨ قائم کرو @@ -1994,8 +1933,6 @@ بعد وچ آپݨی ڈیوائس اݨ لاک کرو - - ذخیرہ تھئی کریڈٹ کارڈ ڄاݨکاری کوں ورتݨ کیتے اݨ لاک کرو محفوظ کِیتے ڳئے ادائیگی دے طریقے استعمال کرݨ کِیتے غیر مقفّل کرو @@ -2004,12 +1941,6 @@ پتے وچ تبدیلی کرو پتے منیج کرو - - پہلا ناں - - وچلا ناں - - چھیکڑی ناں ناں @@ -2037,8 +1968,6 @@ پتہ مٹاؤ - - بھلا تہاکوں پک ہے جو تساں ایہ پتہ مٹاوݨ چاہندے ہو؟ ایہ پتہ مٹاؤں؟ @@ -2141,49 +2070,29 @@ مٹاؤ تبدیلی کرو - - بھلا تہاکوں پک ہے جو تساں ایہ لاگ ان مٹاوݨ چاہندے ہو؟ بَھلا تُہاکُوں پَک ہِے جو تُساں اِیں پاس ورڈ کوں مٹاوݨ چاہن٘دے ہِیوے؟ مٹاؤ منسوخ کرو - - لاگ ان آپشناں پاس ورڈ آپشناں - - لاگ ان دے ویب پتے کیتے عبارت تبدیل کرݨ دے قابل خانہ۔ ویب سائٹ ایڈریس کِیتے قابلِ تدوین ٹیکسٹ فیلڈ۔ - - لاگ ان دے ورتݨ ناں کیتے عبارت تبدیل کرݨ دے قابل خانہ۔ صارف ناں کیتے عبارت تبدیل کرݨ دے قابل خانہ۔ - لاگ ان دے پاس ورڈ کیتے عبارت تبدیل کرݨ دے قابل خانہ۔ - پاس ورڈ کیتے عبارت تبدیل کرݨ دے قابل خانہ۔ - - لاگ ان وچ تبدیلیاں محفوظ کرو۔ تبدیلیاں محفوظ کرو۔ - - تبدیلی کرو پاس ورڈ وچ تبدیلی کرو - - نواں لاگ ان شامل کرو پاس ورڈ شامل کرو - - پاس ورڈ ضروری ہے پاس ورڈ درج کرو - ورتݨ ناں ضروری ہے - ورتݨ ناں درج کرو ہوسٹ ناں ضروری ہے @@ -2718,7 +2627,7 @@ ٹیب شمار - فعال + فعال غیر فعال @@ -2745,11 +2654,11 @@ رازداری نوٹس - جمع کرواؤ + جمع کرواؤ - بند کرو + بند کرو - تہاݙے فیڈبیک تے تھورائت ہیں! + تہاݙے فیڈبیک تے تھورائت ہیں! ݙاڈھا تسلی بخش diff --git a/mobile/android/fenix/app/src/main/res/values-sl/strings.xml b/mobile/android/fenix/app/src/main/res/values-sl/strings.xml index 9207394e10..96cc2722ca 100644 --- a/mobile/android/fenix/app/src/main/res/values-sl/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sl/strings.xml @@ -49,12 +49,20 @@ - Nedavno shranjeno + Nedavno shranjeno - Prikaži vse shranjene zaznamke + Prikaži vse shranjene zaznamke - Odstrani + Odstrani + + + + Zaznamki + + Prikaži vse zaznamke + + Odstrani %1$s izdeluje Mozilla. @@ -145,8 +153,10 @@ Nov zasebni zavihek - - Bližnjica do gesel + + Gesla + + Bližnjica do gesel @@ -191,6 +201,10 @@ Dodatki Razširitve + + Upravljanje razširitev + + Odkrijte več razširitev Podatki o računu @@ -209,18 +223,26 @@ Odpri v navadnem zavihku Dodaj na domač zaslon + + Dodaj na domači zaslon … Namesti Znova sinhroniziraj Najdi na strani + + Najdi na strani … Prevedi stran + Shrani v zbirko … + Shrani v zbirko Deli + + Deli … Odpri v %1$su @@ -248,9 +270,47 @@ Prilagodi domačo stran - Prijava - - Sinhronizirajte gesla, zavihke in druge podatke + Prijava + + Sinhronizirajte gesla, zavihke in druge podatke + + + Znova se prijavite za sinhronizacijo + + Sinhroniziranje ustavljeno + + Nov zasebni zavihek + + + Gesla + + Novo v %1$su + + Preklop na stran za računalnike + + Orodja + + Shrani + + + Dodaj stran med zaznamke + + Uredi zaznamek + + Shrani kot PDF … + + Vklopi bralni pogled + + Izklopi bralni pogled + + Prevedi stran … + + Prevedeno v jezik %1$s + + Natisni … @@ -348,8 +408,6 @@ Obvestilo o zasebnosti za Firefox - - Preberite več v obvestilu o zasebnosti Z veseljem skrbimo za vašo varnost Jezik + + Prevajanje + + Prevajanje Podatkovne možnosti @@ -624,6 +686,16 @@ Ni dovoljeno + + + Zahtevano + + Izbirno + + Dovoli za vsa spletna mesta + + Če tej razširitvi zaupate, ji lahko dodelite dovoljenje za vsako spletno mesto. + Zbirka dodatkov po meri @@ -648,7 +720,9 @@ Skoči nazaj - Nedavni zaznamki + Nedavni zaznamki + + Zaznamki Nedavno obiskano @@ -743,8 +817,6 @@ Zgodovina Zaznamki - - Prijave Gesla @@ -772,8 +844,6 @@ and the third is the device model. --> %1$s na %2$s %3$s - - Kreditne kartice Plačilna sredstva @@ -790,6 +860,14 @@ Zavihek iz naprave %s + + + Zaprtih %1$s zavihkov: %2$d + + Prikaži nedavno zaprte zavihke + Izjeme @@ -1365,6 +1443,8 @@ Zapri zasebne zavihke + + Zaprem zasebne zavihke? Tapnite to obvestilo ali ga povlecite vstran, da zaprete zasebne zavihke. @@ -1724,13 +1804,9 @@ To stran lahko preprosto dodate na svoj domači zaslon naprave za lažji dostop in hitrejše brskanje v načinu, podobnem aplikaciji. - - Prijave in gesla Gesla - Shranjevanje prijav in gesel - Shranjuj gesla Vprašaj pred shranjevanjem @@ -1747,46 +1823,27 @@ Izpolnjuj uporabniška imena in gesla v drugih aplikacijah na napravi. - - Dodaj prijavo - Dodaj geslo - - Sinhronizacija prijav Sinhroniziraj gesla - - Sinhroniziraj prijave med napravami Sinhroniziraj gesla med napravami - - Shranjene prijave Shranjena gesla - Tu se prikažejo prijave, ki jih shranite ali sinhronizirate v %s. - Gesla, ki jih boste shranili ali sinhronizirali v %su, bodo našteta tukaj. Vsa gesla so shranjena v šifrirani obliki. - - Več o Sync. Več o sinhronizaciji Izjeme - - Tu bodo prikazane prijave in gesla, ki niso shranjena. Za tukaj navedena mesta %s ne bo shranjeval gesel. - - Prijave in gesla za te strani ne bodo shranjene. Za ta spletna mesta %s ne bo shranjeval gesel. Izbriši vse izjeme - - Iskanje prijav Iskanje gesel @@ -1815,17 +1872,11 @@ Prikaži geslo Skrij geslo - - Odklenite za ogled shranjenih prijav Odklenite za ogled shranjenih gesel - Zavarujte svoje prijave in gesla - Zavarujte shranjena gesla - Nastavite vzorec za zaklepanje naprave, PIN ali geslo za zaščito pred dostopom do shranjenih prijav in gesel, če vašo napravo uporablja še kdo. - Nastavite vzorec za zaklepanje naprave, PIN ali geslo, da preprečite dostop do shranjenih gesel, če vašo napravo uporabi kdo drug. Pozneje @@ -1845,9 +1896,6 @@ Času zadnje uporabe - - Meni razvrščanja prijav - Meni za razvrščanje gesel @@ -1856,41 +1904,27 @@ Samodejno izpolnjevanje Naslovi - - Kreditne kartice Plačilna sredstva - Shrani in samodejno izpolni kartice - Shranjuj in izpolnjuj plačilna sredstva - - Podatki so šifrirani %s šifrira vsa plačilna sredstva, ki jih shranite Sinhroniziraj kartice med napravami Sinhroniziraj kartice - - Dodaj kreditno kartico Dodaj kartico - - Upravljanje shranjenih kartic Upravljanje kartic Dodaj naslov Upravljanje naslovov - - Shranjuj in samodejno izpolnjuj naslove Shranjuj in izpolnjuj naslove - - Vključi podatke, kot so številke, e-poštni naslovi in naslovi za dostavo Vključuje telefonske številke in e-poštne naslove @@ -1914,7 +1948,7 @@ Izbriši kartico - Ali ste prepričani, da želite izbrisati to kreditno kartico? + Izbrišem kartico? Izbriši @@ -1927,24 +1961,15 @@ Shranjene kartice - - Vnesite veljavno številko kreditne kartice - Vnesite veljavno številko kartice - - Izpolnite to polje Dodajte ime Odklenite za ogled shranjenih kartic - Zavarujte svoje kreditne kartice - Zavarujte shranjena plačilna sredstva - Nastavite vzorec za zaklepanje naprave, PIN ali geslo za zaščito pred dostopom do kreditnih kartic, če vašo napravo uporablja še kdo. - Nastavite vzorec za zaklepanje naprave, PIN ali geslo, da preprečite dostop do plačilnih sredstev, če vašo napravo uporabi kdo drug. Nastavi zdaj @@ -1953,9 +1978,6 @@ Odklenite svojo napravo - - Odklenite za uporabo shranjenih podatkov o kreditnih karticah - Odklenite za ogled shranjenih plačilnih sredstev @@ -1964,12 +1986,6 @@ Uredi naslov Upravljanje naslovov - - Ime - - Drugo ime - - Priimek Ime @@ -1996,7 +2012,7 @@ Izbriši naslov - Res želite izbrisati ta naslov? + Izbrišem ta naslov? Izbriši @@ -2095,49 +2111,29 @@ Izbriši Uredi - - Ali ste prepričani, da želite izbrisati to prijavo? Ali ste prepričani, da želite izbrisati to geslo? Izbriši Prekliči - - Možnosti prijave Možnosti gesel - - Besedilno polje za urejanje spletnega naslova prijave. Besedilno polje za urejanje spletnega naslova. - - Besedilno polje za urejanje uporabniškega imena prijave. Besedilno polje za urejanje uporabniškega imena. - Besedilno polje za urejanje gesla prijave. - Besedilno polje za urejanje gesla. - - Shrani spremembe v prijavo. Shrani spremembe. - - Urejanje Uredi geslo - - Dodaj novo prijavo Dodaj geslo - - Zahtevano je geslo Vnesite geslo - Zahtevano je uporabniško ime - Vnesite uporabniško ime Zahtevano je ime domene @@ -2463,6 +2459,10 @@ Prevedem to stran? + + Stran prevedena iz jezika %1$s v jezik %2$s Preskusite zasebno prevajanje v %1$su @@ -2473,10 +2473,14 @@ Izvorni jezik Ciljni jezik + + Poskusi z drugim izvornim jezikom Ne zdaj Prikaži izvirnik + + Naložena izvirna neprevedena stran Končano @@ -2497,16 +2501,25 @@ %1$s žal še ni podprt jezik. - Več o tem + Več o tem Prevajanje … + + Prenesem jezik ob varčevanju s podatki (%1$s)? + + - Možnosti prevajanja + Možnosti prevajanja + + Možnosti prevajanja Vedno ponudi prevod @@ -2518,11 +2531,16 @@ Nikoli ne prevajaj tega spletnega mesta Preglasi vse druge nastavitve + + Preglasi ponudbe za prevajanje Nastavitve prevajanja O prevodih v %1$su + + Nekatere nastavitve začasno niso na voljo. + Prevajanje @@ -2544,6 +2562,9 @@ Samodejno prevajanje + + Jezikov ni bilo mogoče naložiti. Poskusite znova pozneje. + Ponudi prevajanje (privzeto) @@ -2562,6 +2583,14 @@ Nikoli ne prevajaj teh spletnih mest + + Odstrani %1$s + + Spletnih mest ni bilo mogoče naložiti. Poskusite znova pozneje. + + Izbrišem %1$s? Izbriši @@ -2591,6 +2620,14 @@ Izbrano + + Izbrišem jezik %1$s (%2$s)? + + Izbrišem vse jezike (%1$s)? Če izbrišete vse jezike, bo %1$s pri prevajanju v predpomnilnik naložil delne jezike. @@ -2598,6 +2635,10 @@ Prekliči + + Dele jezikov prenesemo v vaš predpomnilnik, da prevodi ostanejo zasebni. + + Dele jezikov prenesemo, da prevodi ostanejo zasebni. Vedno prenesi tudi ob varčevanju s podatki @@ -2612,12 +2653,19 @@ Orodja za razhroščevanje Pojdi nazaj + + + Odpri predal za razhroščevanje + + Orodja za zavihke Št. zavihkov - Dejavni + Dejavni + + Dejaven Nedejavni @@ -2628,10 +2676,58 @@ Orodje za ustvarjanje zavihkov Količina zavihkov, ki jih želite ustvariti + + Besedilno polje je prazno + + Vnesite pozitivno celo število + + Vnesite število, večje od nič Dodaj med dejavne zavihke Dodaj med nedejavne zavihke Dodaj med zasebne zavihke + + + + + Nadaljuj + + Izpolnite anketo + + Obvestilo o zasebnosti + + Pošlji + + Zapri + + Hvala za vaše mnenje! + + + zelo zadovoljen/-na + + zadovoljen/-na + + neodločen/-a + + nezadovoljen/-na + + zelo nezadovoljen/-na + + + + Odpri anketo + + Zapri anketo + + Zapri + + + + Prijave + + Trenutna domena: %s + + Izbriši prijavo z uporabniškim imenom %s diff --git a/mobile/android/fenix/app/src/main/res/values-sq/strings.xml b/mobile/android/fenix/app/src/main/res/values-sq/strings.xml index e7f4179835..acd961e78c 100644 --- a/mobile/android/fenix/app/src/main/res/values-sq/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sq/strings.xml @@ -47,12 +47,20 @@ - Ruajtur së fundi + Ruajtur së fundi - Shfaqi krejt faqerojtësit e ruajtur + Shfaqi krejt faqerojtësit e ruajtur - Hiqe + Hiqe + + + + Faqerojtës + + Shfaqni krejt faqerojtësit + + Hiqe %1$s prodhohet nga Mozilla. @@ -190,6 +198,10 @@ Shtesa Zgjerime + + Administroni zgjerime + + Zbuloni më tepër zgjerime Hollësi llogarie @@ -208,6 +220,8 @@ Hape në skedë të rregullt Shtoje te skena e Kreut + + Shtoje te skena e Kreut… Instaloje @@ -219,9 +233,13 @@ Përktheje faqen + Ruajeni në koleksion… + Ruaje në koleksion Ndajeni me të tjerët + + Ndajeni me të tjerët… Hape në %1$s @@ -272,6 +290,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Ruaje + + Faqeruajeni këtë faqe + + Përpunoni faqerojtësin + + Ruajeni si PDF… + + Aktivizo Pamjen Lexues + + Çaktivizo Pamjen Lexues + + Përktheni faqen… + + Përkthyer në %1$s + + Shtypeni… + S’ka zgjerime këtu @@ -578,7 +614,9 @@ Gjuhë - Përkthim + Përkthim + + Përkthime Zgjedhje për të dhënat @@ -685,7 +723,9 @@ Hidhu mbrapa - Faqerojtës së fundi + Faqerojtës së fundi + + Faqerojtës Vizituar së fundi @@ -825,6 +865,14 @@ Skedë nga %s + + + Skeda %1$s të mbyllura: %2$d + + Shihni skeda të mbyllura së fundi + Përjashtime @@ -2509,6 +2557,8 @@ Jo tani Shfaq origjinalin + + U ngarkua faqe origjinale e papërkthyer U bë @@ -2689,13 +2739,18 @@ Shko mbrapsht + + Hap sirtar diagnostikimi + Mjete Skedash Numër skedash - Aktive + Aktive + + Aktive Joaktive diff --git a/mobile/android/fenix/app/src/main/res/values-su/strings.xml b/mobile/android/fenix/app/src/main/res/values-su/strings.xml index ca7114a3cb..47f4e76ee8 100644 --- a/mobile/android/fenix/app/src/main/res/values-su/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-su/strings.xml @@ -198,6 +198,10 @@ Émboh Éksténsi + + Atur émbohan + + Singraykeun émbohan leuwih loba Iber akun @@ -216,6 +220,8 @@ Buka dina tab biasa Tambahkeun ka layar Tepas + + Tambahkeun ka layar Tepas… Pasang @@ -228,9 +234,13 @@ Tarjamahkeun kaca + Simpen koléksi… + Simpen kana koléksi Bagikeun + + Bagikeun… Buka di %1$s @@ -281,6 +291,25 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Teundeun + + Markahkeun ieu kaca + + Édit markah + + Simpen minangka PDF… + + Hurungkeun Pamintonan Pamaca + + Pareuman Pamintonan Pamaca + + Tarjamahkeun kaca… + + + Ditarjamahkeun kana %1$s + + Citak… + Euweuh éksténsi di dieu @@ -379,8 +408,6 @@ Iber pripasi Firefox - - Ilikan deui iber privasi urang Urang resep ngajaga anjeun sangkan aman Trackers dipikawanoh? Diblokir otomatis. Ekstensi? Coba sakabéhna 700. PDFs? Pamaca anu diwangun ku urang ngajadikeun éta gampang diurus. + + Browser anu dirojong kalawan nirlaba urang nulungan pikeun mahing pausahaan ngukuntit anjeun dina wéb. \n \n Diajar leuwih lengkep dina iber privasi urang. wawar pripasi @@ -405,10 +434,19 @@ Moal waka Tetep énkripsi nalika anjeun luncat antara alat + + Sabot anjeun asup jeung nyingkronkeun, anjeun téh leuwih aman. Firefox ngaénkripsi sandi anjeun, tetengger, jeung réa-réa deui. Asup Moal waka + + Iber mantuan anjeun tetep aman jeung Firefox + + Kirim tab kalawan aman antara parabot anjeun jeung téangan fitur privasi séjénna dina Firefox. Hurungkeun iber @@ -495,6 +533,8 @@ Pameungpeuk Spanduk Réréméh + + Cookie Banner Blocker dina panyungsian pribadi Pareum jang ieu loka @@ -512,9 +552,23 @@ Rekés dukungan dikirim Kiwari loka teu didukung + + Hurungkeun Pameungpeuk Spanduk Réréméh pikeun %1$s? + + Pareuman Pameungpeuk Spanduk Réréméh pikeun %1$s? %1$s teu bisa otomatis nolak rekés réréméh dina ieu loka. Anjeun bisa ngirim rekés pikeun ngadukung ieu loka jaga. + + Pareuman jeung %1$s bakal mupus cookies sarta ngamuat deui situs ieu. Ieu bisa ngaluarkeun anjeun atawa ngosongkeun carangka balanja. + + Hurungkeun jeung %1$s bakal nyobaan nampik sakabéh spanduk cookie dina situs ieu kalawan otomatis. + + %1$s cikénéh nampik réréméh pikeun anjeun + + + Saeutik gangguan, saeutik cookies ngukuntit anjeun dina situs ieu. + Otomatis nyoba nyambung ka loka maké protokol énkripsi HTTPS pikeun ngaronjatkeun kaamanan. @@ -532,13 +586,17 @@ Situs aman teu sayaga - Sigana, raramatlokana teu ngarojong HTTPs. + Sigana, raramatlokana teu ngarojong HTTPS. Nanging, bisa waé aya panyerang kalibet. Upama diteruskeun asup kana raramatlokana, anjeun ulah ngasupkeun émbaran sénsitip. Upama diteruskeun, mode Ngan-HTTPS bakal dipareuman samentawis pikeun éta loka. Aksésibilitas + + Serper akun Mozilal kustom Serper Sync kustom + + Akun Mozilla/Server Sync dirobah. Ngaluarkeun aplikasi pikeun nerapkeun parobahan… Akun @@ -559,6 +617,10 @@ Sambungkeun deui pikeun neruskeun nyingkronkeun Basa + + Tarjamah + + Tarjamah Pilihan data @@ -586,9 +648,16 @@ Otokumplit URLs + + Saran ti sponsor + + Pangrojong %1$s kalawan saran anu disponsoran Saran ti %1$s + + Pundut saran tina wéb patali jeung panyungsian anjeun Buka tutumbu dina aplikasi @@ -608,6 +677,12 @@ Émbohan + + Éksténsi + + Pasang panambah tina berkas + + Pasang pangémboh tina berkas Iber @@ -616,9 +691,22 @@ Teu diidinan + + + Dibutuhkeun + + Pilihan + + Idinan pikeun sakabéh situs + + + Lamun anjeun percaya pangémboh ieu, anjeun bisa méré idin dina unggal situs. + Koléksi Émboh sakahayang + + Koléksi Émboh sakahayang Heug @@ -631,11 +719,16 @@ Koléksi Émboh geus dirobah. Kaluar ti aplikasi pikeun nerapkeun parobahan… + + Koléksi Émboh geus dirobah. Kaluar ti aplikasi pikeun nerapkeun parobahan… + Asup deui Markah anyar + + Markah Anyar dianjangan @@ -684,19 +777,41 @@ Langlang latar lianna + + + Aya add-on anyar + + + Aya add-on anyar yeuh + + Pariksa 100+ pangémboh anyar anu ngamungkinkeun anjeun ngajadikeun Firefox sorangan. + + Paluruh add-ons + + Paluruh pangémboh + Add-on tumpur saheulaanan + + Éksténsi saheulanaan dipareuman Aya add-on anu mugen, matak teu stabil kana sistem. %1$s gagal nyobaan malikan deui add-onna.\n\nAdd-on moal dimimitian ulang dina rintakan anu ayeuna.\n\nMiceun atawa numpurkeun add-on bisa jadi menerkeun ieu masalah. Coba balikan deui add-on + + Jalankeun deui éksténsi Tuluykeun bari add-on tumpur + + Tuluykeun bari add-on tumpur + Kokolakeun akun + + Robah kecap aksés anjeun, atur pangumpulan data, atawa pupus akun anjeun Singkronkeun ayeuna @@ -706,8 +821,8 @@ Markah - - Login + + Kecap sandi Buka tab @@ -732,8 +847,8 @@ and the third is the device model. --> %1$s dina %2$s %3$s - - Kartu kiridit + + Cara mayar Alamat @@ -747,6 +862,14 @@ Tab ti %s + + + %1$s tab ditutup: %2$d + + Témbongkeun tab nu anyar ditutup + Iwal @@ -1319,6 +1442,11 @@ Tutup tab nyamuni + + Tutup tab pribadi? + + Toél atawa golosorkeun bewara ieu pikeun nutup tab pribadi. + Pamasaran @@ -1371,6 +1499,8 @@ Tab nyamuni ditutup Tab nyamuni ditutup + + Data panyungsi dipupus BEDO @@ -1561,6 +1691,8 @@ Sakabéh réréméh (bakal ngabalukarkeun raramatloka jadi teu bener) Kerem réréméh meuntas-loka + + Béjakeun situs wéb sangkan henteu ngabagi & ngajual data Kontén palacak @@ -1590,7 +1722,7 @@ Meungpeuk réréméh anu dipaké ku maskapé jaringan iklan jeung analitika pikeun ngumpulkeun data langlangan anjeun di loka anu kaanjangan. - Total Cookie Protection ngerem réréméh dina loka anu keur dibuka sangkan teu bisa dipaké ku palacak kayaning jaringan iklan pikeun nunutur anjeun meuntas-loa. + Total Cookie Protection ngerem réréméh dina loka anu keur dibuka ku anjeun sangkan teu bisa dipaké ku palacak kayaning jaringan iklan pikeun nunutur anjeun meuntas-loa. Gurandil kripto @@ -1680,9 +1812,9 @@ Anjeun bisa kalawan gampang nambahkeun ieu raramatloka kana layar Tepas ponsél pikeun aksés instan sarta maluruh leuwih gancang kawas muka aplikasi. - Login jeung kecap sandi + Kecap sandi - Teundeun login jeung kecap sandi + Simpen kecap sandi Naros keur neundeun @@ -1697,30 +1829,30 @@ Eusian sandiasma jeung kecap sandi dina séjén aplikasi dina parabot anjeun. - - Tambahkeun login + + Tambahan sandi - Singkronkeun login - - Singkronkeun login di sakabéh parabot - - Login nu disimpen + Singkronkeun sandi + + Singkronkeun sandi dina sakabéh parabot + + Sandi nu disimpen - Login anu diteundeun atawa disingkronkeun ka %s bakal némbongan di dieu. - - Leuwih jéntré ngeunaan Sync. + Sandi anu anjeun simpen atawa disingkronkeun kana %s bakal didaptarkeun di dieu. Sakumna kecap sandi anu disimpen ku anjeun téh diénkripsi. + + Lenyepan ngeunaan singkronisasi Iwal - - Login jeung kecap sandi anu teu diteundeun bakal ditémbongkeun di dieu. - - Login jeung kecap sandi moal diteundeun pikeun ieu loka. + + %s moal nyimpen sandi pikeun situs anu didaptarkeun di dieu. + + %s moal nyimpen sandi pikeun situs-situs ieu. Hapus sadaya pengecualian - - Paluruh login + + Téangan kecap sandi Loka @@ -1747,12 +1879,10 @@ Témbongkeun sandi Sumputkeun sandi - - Buka konci pikeun nempo login anu diteundeun + + Buka konci pikeun nempo sandi anu diteundeun - Amankeun login jeung kecap sandi anjeun - - Jieun pola konci paranti, PIN, atawa kecap sandi pikeun ngajaga login jeung kecap sandi anu diteundeun bisi paranti anjeun dipaké batur. + Amankeun kecap sandi anjeun nu disimpen Engké @@ -1768,37 +1898,39 @@ Panungtun dipaké - - Runtuykeun menu login + + + Nyortir ménu kecap sandi Eusi otomatis Alamat - - Kartu kiridit + + Cara mayar - Simpen jeung otongeusi kartu - - Data diénkrip + Simpen jeung eusian cara mayar + + %s énkripsi kabéh cara mayar anu disimpen ku anjeun Singkronkeun kartu di sakabeh parabot Singkronkeun kartu - - Tambah kartu kiridit - - Kokolakeun kartu anu diteundeun + + Tambahan kartu + + Atur kartu Tambah alamat Kokolakeun alamat - - Simpen jeung otongeusi alamat - - Kaasup émbaran sarupaning nomer, surélék, jeung alamat kirim + + Simpen jeung eusian alamat + + + Sartakeun nomer telepon jeung alamat surélék Tambah kartu @@ -1819,7 +1951,7 @@ Hapus kartu - Yakin anjeun rék mupus ieu kartu kiridit? + Pupus kartu? Hapus @@ -1831,37 +1963,31 @@ Kartu disimpen - - Mangga lebetkeun nomer kartu kiridit anu sah - - Mangga eusian kolom ieu + + Asupkeun nomer kartu anu sah + + Tambahan ngaran Buka konci pikeun nempo kartu anu diteundeun - Amankeun kartu kiridit anjeun - - Jieun pola konci paranti, PIN, atawa kecap sandi pikeun ngajaga kartu kiridit anu diteundeun bisi paranti anjeun dipaké batur. + Amankeun metode mayar anjeun anu disimpen Setél ayeuna Engké deui Buka konci paranti anjeun - - Buka konci pikeun nganggo inpormasi kartu kiridit anu disimpen + + Buka konci pikeun maké cara mayar anu disimpen Tambah alamat Ropéa alamat Kokolakeun alamat - - Ngaran Hareup - - Ngaran Tengah - - Ngaran Tukang + + Ngaran Alamat Jalan @@ -1886,7 +2012,7 @@ Hapus alamat - Yakin rék mupus ieu alamat? + Pupus alamat ieu? Hapus @@ -1984,32 +2110,34 @@ Pupus Édit - - Yakin rék mupus ieu login? + + Yakin anjeun rék mupus ieu sandi? Pupus Bolay - - Pilihan asup log - - Widang téks éditeun pikeun alamat raramat login. - - Widang téks éditeun pikeun sandiasma login. + + Pilihan sandi + + Widang téks éditeun pikeun alamat raramat login. + + Widang téks éditeun pikeun sandiasma login. - Widang téks éditeun pikeun kecap sandi login. - - Teundeun parobahan login. - - Édit - - Tambahkeun login anyar - - Butuh kecap sandi + Widang téks éditeun pikeun sandi kecap. + + Simpen parobahan. + + Ropéa sandi + + Tambahan sandi + + Asupkeun sandi - Kudu ngeusian sandiasma + Asupkeun ngaran pamaké Kudu ngeusian ngaran host + + Asupkeun kaca raramat Sungsi sora @@ -2104,6 +2232,9 @@ Pamaluruhan %s + + + Alihkeun panyungsi baku anjeun Setél tutumbu ti raramatloka, surélék, jeung surat pikeun muka otomatis dina Firefox. @@ -2141,17 +2272,90 @@ Buka setélan + + + Pamariksa repiu + + Repiu andelan + + Campuran bahasan nu dipercaya jeung nu teu dipercaya + + Repiu lain andelan + + Kumaha kadar andelan ulasan ieu? + + Peunteun disaluyukeun + + Dumasarkana ulasan andelan + + Sorot tina ulasan cikénéh + + Kumaha urang nangtukeun kualitas ulasan + + Leuwih jéntré ngeunaan %s. + + kumaha %s nangtukeun kualitas ulasan + + Setélan + + Témbongkeun iklan dina pamariksa ténjoan + + Lenyepan + + Pareuman pamariksaan ténjoan + + Leuwih tinimbangan Iklan ku %s + + Pamariksaan ulasan drojong ku %s %s ku Mozilla + + Katerangan anyar pikeun dipariksa + + Pariksa ayeuna + + Ulasan tacan cukup + + Lamun produk ieu leuwih loba ulasanana, urang bisa mariksa kualitasna. + + Produk teu sadia + + Laporan produk aya dina stok + + Mariksa kualitas ulasan (%s) + + Lilana ieu kira-kira 60 detik. + + Hatur nuhun geus ngalaporkeun! + + Urang henteu bisa mariksa ulasan ieu + + béja nu arék datang + + Analisis geus luyu nu jeung panganyarna Ngarti + + Euweuh inpo nu sadia kiwari + + Urang keur digawé pikeun ngabéréskeun masalah éta. Mangga pariksa engké deui. Euweuh sambungan jaringan + + Pariksa sambungan jaringan anjeun terus cobaan muatkeun deui kacana. + + Euweuh inpormasi ngeunaan ulasan ieu + + Pariksa kualitas review + + Cobaan pituduh ulasan produk urang nu dipercaya Leuwih teleb + + wawar pripasi katangtuan maké @@ -2166,11 +2370,38 @@ Béta + + Buka pamariksa ténjoan + + Tutup pamariksa ténjoan + + %1$s tina 5 béntang + + Témbongkeun leuwih saeutik + + Pidangkeu leuwih réa + + Kualitas + + Harga + + Pangiriman + + Bungkusan jeung dedegan + + Daya saing + + "%s" + tilep + + tilep batek + + batek buka tutumbu pikeun leuwih teleb ngeunaan ieu koléksi @@ -2178,9 +2409,245 @@ buka tutumbu pikeun ngalenyepan + + %s, Judul + + Tumbu + + Tumbu sadia + + + + Tarjamahkeun kaca ieu? + + Kaca ditarjamahkeun tina %1$s ka %2$s + + Coba tarjamahan pribadi dina %1$s + + + + Pikeun privasi anjeun, tarjamahan moal kungsi ninggalkeun alat anjeun. Basa anyar jeung panghadéan sakeudeung deui datang! %1$s + + Lenyepan + + Tarjamahkeun tina + + Tarjamahkeun kana + + Cobaan basa sumber séjén + + Moal waka + + Témbongkeun aslina + + Kaca asli teu ditarjamahkeun dimuat + + Anggeus + + Tarjamahkeun + + Pecakan deui + + Narjamahkeun + + Narjamahkeun keur Dipigawé + + Pilih basa + + Aya masalah narjamahkeun. Pék cobaan deui. + + Teu bisa ngamuat basa. Pariksa sambungan internét anjeun sarta cobaan deui. + + Hampura, urang acan ngadukung %1$s. + + Lenyepan + + Narjamahkeun… + + Unduh basa dina modeu irit data (%1$s)? + + + + + Pilihan Tarjamahan + + Pilihan tarjamahan + + Salawasna tawarkeun narjamahkeun + + Salawasna tarjamahkeun %1$s + + Ulah narjamahkeun %1$s deui + + Ulah narjamahkeun deui situs ieu + + Timpah sakabéh setelan séjén + + Timpah tawaran pikeun narjamahkeun + + Setélan tarjamahan + + Ngeunaan tarjamahan dina %1$s + + Tutup lambaran Tarjamahan + + Sababaraha setélan samentara teu nyampak. + + + + Tarjamah + + Tawarkeun pikeun narjamahkeun sabot mungkin + + Salawasna unduh basa dina modeu irit data + + Préférénsi tarjamahan + + Tarjamahan otomatis + + Ulah narjamahkeun situs-situs ieu + + Undeur basa-basa + + + + Tarjamahan otomatis + + + Pilih basa pikeun ngatur préférénsi "salawasna tarjamahkeun" jeung "ulah pernah narjamahkeun". + + Teu bisa ngamuat basa. Mangga pariksa engké deui. + + + Salawasna tarjamahkeun + + Ulah narjamahkeun deui + + + Piceun %1$s + + Pupus %1$s? + + Pupus + + Bolay + + + + Undeur Basa-basa + + Lenyepan + + Basa nu sadia + + diperlukeun + + %1$s (%2$s) + + + Pupus + + Bolay + + + Unduh sabot dina modeu irit data (%1$s)? + + Salawasna unduh dina modeu irit data + + Undeur + + Undeur jeung tarjamahkeun + + Bolay + + + + Parabot Debug + + Mundur + + + + Pakakas Tab + + Jumlah tab + + Aktip + + Aktip + + Teu aktif + + Salindungan + + Jumlah + + Alat nyieun tab + + Tab kuantitas pikeun nyieun + + Tambahkeun kana tab aktip + + Tambahkeun kana tab teu aktip + + Tambahkeun kana tab nyamuni + - + + Tuluykeun + + Lengkepan survéy ieu + + Wawar Privasi + + Kirimkeun + + Tutup + + Hatur nuhun pikeun jawaban anjeun! + + Puas pisan + + + Sugema + + Nétral + + Teu sugema + + Kacida teu sugema + + + + Buka survéy + + Tutup Survéy + + Tutup + + + + Asup log + + Domain ayeuna: %s + + Tambahan asup log palsu pikeun domain ieu + + Pupus asup log maké ngaran pamaké %s + diff --git a/mobile/android/fenix/app/src/main/res/values-sv-rSE/strings.xml b/mobile/android/fenix/app/src/main/res/values-sv-rSE/strings.xml index d8659fb4d9..d8339db68e 100644 --- a/mobile/android/fenix/app/src/main/res/values-sv-rSE/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sv-rSE/strings.xml @@ -202,6 +202,10 @@ Tillägg Tillägg + + Hantera tillägg + + Upptäck fler tillägg Kontoinformation @@ -220,6 +224,8 @@ Öppna i vanlig flik Lägg till på startsidan + + Lägg till på startskärmen… Installera @@ -231,9 +237,13 @@ Översätt sida + Spara i samling… + Spara i samling Dela + + Dela… Öppna i %1$s @@ -289,6 +299,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Spara + + Bokmärk denna sida + + Redigera bokmärke + + Spara som PDF… + + Aktivera Läsvyn + + Stäng av Läsvyn + + Översätt sida… + + Översatt till %1$s + + Skriv ut… + Inga tillägg här @@ -386,8 +414,6 @@ Firefox sekretessmeddelande - - Läs mer i vårt sekretessmeddelande Vi älskar att hålla dig säker Språk - Översättning + Översättning + + Översättningar Dataalternativ @@ -670,10 +698,6 @@ Krävs Valfri - - Läsa och ändra webbplatsdata - - Ta bort webbplats Tillåt för alla webbplatser @@ -800,8 +824,6 @@ Historik Bokmärken - - Inloggningar Lösenord @@ -829,8 +851,6 @@ and the third is the device model. --> %1$s på %2$s %3$s - - Kreditkort Betalningsmetoder @@ -846,6 +866,14 @@ Flik från %s + + + %1$s stängde %2$d flikar + + Visa nyligen stängda flikar + Undantag @@ -1780,13 +1808,9 @@ Du kan enkelt lägga till den här webbplatsen på enhetens startskärm för att få direktåtkomst och surfa snabbare med en appliknande upplevelse. - - Inloggningar och lösenord Lösenord - Spara inloggningar och lösenord - Spara lösenord Fråga för att spara @@ -1801,47 +1825,28 @@ Fyll i användarnamn och lösenord i andra appar på din enhet. - - Lägg till inloggning - Lägg till lösenord - - Synkronisera inloggningar Synkronisera lösenord - - Synkronisera inloggningar mellan enheter Synkronisera lösenord mellan enheter - - Sparade inloggningar Sparade lösenord - De inloggningar som du sparar eller synkroniserar till %s kommer att dyka upp här. - Lösenorden du sparar eller synkroniserar till %s kommer att listas här. Alla lösenord du sparar är krypterade. - - Läs mer om Sync. Läs mer om synkronisering Undantag - - Inloggningar och lösenord som inte sparas visas här. %s kommer inte att spara lösenord för webbplatser som listas här. - - Inloggningar och lösenord sparas inte för dessa webbplatser. %s kommer inte att spara lösenord för dessa webbplatser. Ta bort alla undantag - - Sök inloggningar Sök efter lösenord @@ -1870,17 +1875,11 @@ Visa lösenord Dölj lösenord - - Lås upp för att se dina sparade inloggningar Lås upp för att se dina sparade lösenord - Säkra dina inloggningar och lösenord - Säkra dina sparade lösenord - Konfigurera enhetens låsmönster, PIN eller lösenord för att skydda dina sparade inloggningar och lösenord från åtkomst om någon annan har din enhet. - Konfigurera ett enhetslåsmönster, PIN-kod eller lösenord för att skydda dina sparade lösenord från åtkomst om någon annan använder din enhet. Senare @@ -1897,8 +1896,6 @@ Namn (A-Ö) Senast använd - - Sortera inloggningsmenyn Menyn sortera lösenord @@ -1908,41 +1905,27 @@ Autofyll Adresser - - Kreditkort Betalningsmetoder - Spara och fyll i kreditkort automatiskt - Spara och fyll i betalningsmetoder - - Data är krypterad %s krypterar alla betalningsmetoder som du sparar Synkronisera kort mellan enheter Synkronisera kort - - Lägg till kreditkort Lägg till kort - - Hantera sparade kreditkort Hantera kort Lägg till adress Hantera adresser - - Spara och fyll i adresser automatiskt Spara och fyll i adresser - - Inkludera information som nummer, e-post och leveransadresser Inkluderar telefonnummer och e-postadresser @@ -1966,8 +1949,6 @@ Ta bort kort - Är du säker på att du vill ta bort det här kreditkortet? - Ta bort kort? Ta bort @@ -1981,24 +1962,15 @@ Sparade kreditkort - - Ange ett giltigt kreditkortsnummer - Ange ett giltigt kortnummer - - Fyll i det här fältet Lägg till ett namn Lås upp för att se dina sparade kreditkort - Säkra dina kreditkort - Säkra dina sparade betalningsmetoder - Konfigurera enhetens låsmönster, PIN eller lösenord för att skydda dina sparade kreditkort från åtkomst om någon annan har din enhet. - Konfigurera ett enhetslåsmönster, PIN-kod eller lösenord för att skydda dina sparade betalningsmetoder från att användas om någon annan har din enhet. Konfigurera nu @@ -2007,9 +1979,6 @@ Lås upp din enhet - - Lås upp för att använda lagrad kreditkortsinformation - Lås upp för att använda sparade betalningsmetoder @@ -2018,12 +1987,6 @@ Redigera adress Hantera adresser - - Förnamn - - Mellannamn - - Efternamn Namn @@ -2049,8 +2012,6 @@ Ta bort adress - - Är du säker på att du vill ta bort den här adressen? Ta bort den här adressen? @@ -2150,49 +2111,29 @@ Ta bort Redigera - - Är du säker på att du vill ta bort den här inloggningen? Är du säker på att du vill ta bort det här lösenordet? Ta bort Avbryt - - Inloggningsalternativ Lösenordsalternativ - - Det redigerbara textfältet för inloggningens webbadress. Det redigerbara textfältet för webbplatsadressen. - - Det redigerbara textfältet för inloggningens användarnamn. Det redigerbara textfältet för användarnamnet. - Det redigerbara textfältet för inloggningens lösenord. - Det redigerbara textfältet för lösenordet. - - Spara ändringar för inloggning. Spara ändringar. - - Redigera Redigera lösenord - - Lägg till ny inloggning Lägg till lösenord - - Lösenord krävs Ange ett lösenord - Användarnamn krävs - Ange ett användarnamn Värdnamn krävs @@ -2600,6 +2541,9 @@ Stäng översättningsarket + + Vissa inställningar är inte tillgängliga för tillfället. + Översättningar @@ -2622,6 +2566,9 @@ Välj ett språk för att hantera inställningarna för "översätt alltid" och "översätt aldrig". + + Det gick inte att ladda språk. Kom tillbaka senare. + Erbjuda att översätta (standard) @@ -2644,6 +2591,8 @@ Ta bort %1$s + + Det gick inte att ladda webbplatser. Kom tillbaka senare. Tog bort %1$s? @@ -2721,13 +2670,18 @@ Navigera bakåt + + Öppna felsökningslådan + Flikverktyg Antal flikar - Aktiv + Aktiv + + Aktiv Inaktiv @@ -2738,6 +2692,16 @@ Verktyg för att skapa flikar Antal flikar att skapa + + Textfältet är tomt + + Ange endast positiva heltal + + Ange ett nummer större än noll + + Översteg det maximala antalet flikar (%1$s) som kan genereras i en operation Lägg till i aktiva flikar @@ -2754,11 +2718,11 @@ Sekretessmeddelande - Skicka in + Skicka in - Stäng + Stäng - Tack för din feedback! + Tack för din feedback! Väldigt nöjd @@ -2770,6 +2734,14 @@ Väldigt missnöjd + + + Öppna undersökning + + Stäng undersökning + + Stäng + Inloggningar diff --git a/mobile/android/fenix/app/src/main/res/values-tg/strings.xml b/mobile/android/fenix/app/src/main/res/values-tg/strings.xml index 35aaf65dd9..8e3c6c45a9 100644 --- a/mobile/android/fenix/app/src/main/res/values-tg/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-tg/strings.xml @@ -1,7 +1,7 @@ - %s-и хусусӣ + Хусусӣ %s %s (Хусусӣ) @@ -202,6 +202,10 @@ Ҷузъҳои иловагӣ Васеъшавиҳо + + Идоракунии васеъшавиҳо + + Кашф кардани васеъшавиҳои бештар Маълумот дар бораи ҳисоби корбар @@ -221,6 +225,8 @@ Кушодан дар варақаи одӣ Илова кардан ба экрани асосӣ + + Илова кардан ба экрани асосӣ… Насб кардан @@ -232,9 +238,13 @@ Тарҷума кардани саҳифа + Нигоҳ доштан дар маҷмуа… + Нигоҳ доштан дар маҷмуа Мубодила кардан + + Мубодила… Кушодан дар %1$s @@ -288,6 +298,25 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Нигоҳ доштан + + Гузоштани хатбарак барои ин саҳифа + + Таҳрир кардани хатбарак + + Нигоҳ доштан ҳамчун PDF… + + Фаъол кардани намоиши хонанда + + Хомӯш кардани намоиши хонанда + + Тарҷума кардани саҳифа… + + + Ба забони %1$s тарҷума шуд + + Чоп кардан… + Дар ин ҷой ягон васеъшавӣ нест @@ -384,8 +413,6 @@ Огоҳномаи махфияти «Firefox» - - Маълумоти бештар дар «Огоҳномаи махфияти мо» Мо нигоҳдории бехатарии шуморо дӯст медорем Забон - Тарҷума + Тарҷума + + Тарҷумаҳо Интихоби маълумот @@ -665,10 +694,6 @@ Ҳатмӣ аст Интихобӣ аст - - Хониш ва тағйироти маълумоти сомона - - Нест кардани сомона Иҷозат барои ҳамаи сомонаҳо @@ -797,8 +822,6 @@ Таърих Хатбаракҳо - - Воридшавиҳо Ниҳонвожаҳо @@ -826,8 +849,6 @@ and the third is the device model. --> %1$s дар %2$s %3$s - - Кортҳои кредитӣ Тарзҳои пардохт @@ -843,6 +864,14 @@ Варақа аз %s + + + %2$d варақаи пӯшидашудаи «%1$s» + + Дидани варақаҳои ба наздикӣ пӯшидашуда + Истисноҳо @@ -1778,13 +1807,9 @@ Шумо метавонед ин сомонаро ба экрани асосии дастгоҳи худ ба осонӣ илова кунед, то ки ба он дастрасии фаврӣ дошта бошед ва бо таҷрибаи ба барнома монанд зудтар паймоиш кунед. - - Воридшавиҳо ва ниҳонвожаҳо Ниҳонвожаҳо - Нигоҳ доштани воридшавиҳо ва ниҳонвожаҳо - Нигоҳ доштани ниҳонвожаҳо Бо пешниҳоди нигоҳдорӣ @@ -1799,46 +1824,27 @@ Номи корбарон ва ниҳонвожаҳоро дар барномаҳои дигари дастгоҳи худ пур кунед. - - Илова кардани воридшавӣ - Илова кардани ниҳонвожа - - Воридшавиҳои ҳамоҳангшуда Ҳамоҳанг кардани ниҳонвожаҳо - - Ҳамоҳанг кардани воридшавиҳо байни дастгоҳҳо Ҳамоҳанг кардани ниҳонвожаҳо байни дастгоҳҳо - - Воридшавиҳои нигоҳдошташуда Ниҳонвожаҳои нигоҳдошташуда - Воридшавиҳое, ки шумо дар %s нигоҳ медоред ё ҳамоҳанг мекунед, дар ин ҷо нишон дода мешаванд. - Ниҳонвожаҳоеро, ки шумо нигоҳ медоред ё бо «%s» ҳамоҳанг месозед, дар ин рӯйхат нишон дода мешаванд. Ҳамаи ниҳонвожаҳое, ки шумо нигоҳ медоред, рамзгузорӣ карда мешаванд. - - Маълумоти бештар дар бораи ҳамоҳангсозӣ Маълумоти бештар дар бораи ҳамоҳангсозӣ Истисноҳо - - Воридшавиҳо ва ниҳонвожаҳое, ки нигоҳ дошта нашудаанд, дар ин ҷо нишон дошта мешаванд. «%s» барои сомонаҳое, ки дар ин рӯйхат нишон дода шудаанд, ниҳонвожаҳоро нигоҳ намедорад. - - Воридшавиҳо ва ниҳонвожаҳо барои сомонаҳои зерин нигоҳ дошта намешаванд. «%s» барои сомонаҳои зерин ниҳонвожаҳоро нигоҳ намедорад. Нест кардани ҳамаи истисноҳо - - Ҷустуҷӯи воридшавиҳо Ҷустуҷӯи ниҳонвожаҳо @@ -1867,17 +1873,11 @@ Нишон додани ниҳонвожа Пинҳон кардани ниҳонвожа - - Барои дидани воридшавиҳои нигоҳдошташуда, қулфро кушоед Барои дидани ниҳонвожаҳои нигоҳдошташуда, қулфро кушоед - Воридшавиҳо ва ниҳонвожаҳои худро муҳофизат намоед - Ниҳонвожаҳои нигоҳдоштаро муҳофизат намоед - Барои муҳофизат кардани воридшавиҳо ва ниҳонвожаҳои худ аз дастрасии озод, агар касе дигар аз дастгоҳи шумо истифода барад, шаклвораи қулфи экран, рамзи PIN ё ниҳонвожаеро барои дастгоҳи худ танзим намоед. - Барои муҳофизат кардани ниҳонвожаҳои нигоҳдошташудаи худ аз дастрасии озод, агар касе дигар аз дастгоҳи шумо истифода барад, шаклвораи қулфи экран, рамзи PIN ё ниҳонвожаеро барои дастгоҳи худ танзим намоед. Дертар @@ -1895,9 +1895,6 @@ Истифодашудаи охирин - - Мураттаб кардани менюи воридшавиҳо - Менюи мураттабсозии ниҳонвожаҳо @@ -1906,41 +1903,27 @@ Пуркунии худкор Нишониҳо - - Кортҳои кредитӣ Тарзҳои пардохт - Нигоҳ доштан ва ба таври худкор пур кардани кортҳо - Нигоҳ доштан ва пур кардани тарзҳои пардохт - - Маълумот рамзгузорӣ карда шуд «%s» ҳамаи тарзҳои пардохтеро, ки шумо нигоҳ медоред, рамзгузорӣ мекунад Ҳамоҳанг кардани кортҳо байни дастгоҳҳо Ҳамоҳанг кардани кортҳо - - Илова кардани корти кредитӣ Илова кардани корт - - Идора кардани кортҳои нигоҳдошташуда Идоракунии кортҳо Илова кардани нишонӣ Идоракунии нишониҳо - - Нигоҳ доштан ва ба таври худкор пур кардани нишониҳо Нигоҳ доштан ва пур кардани нишониҳо - - Илова кардани маълумот монанди рақамҳо, нишониҳои почтаи электронӣ ва бурдарасонӣ Рақамҳои телефон ва нишониҳои почтаи электрониро дар бар мегирад @@ -1965,8 +1948,6 @@ Нест кардани корт - Шумо мутмаин ҳастед, ки мехоҳед ин корти кредитиро нест намоед? - Кортро нест мекунед? Нест кардан @@ -1980,24 +1961,15 @@ Кортҳои нигоҳдошташуда - - Лутфан, рақами корти кредитии дурустро ворид намоед - Рақами корти дурустро ворид намоед - - Лутфан, ин майдонро пур кунед Номеро ворид намоед Барои дидани кортҳои нигоҳдошташуда, қулфро кушоед - Кортҳои кредитии худро муҳофизат кунед - Тарзҳои пардохти нигоҳдоштаи худро муҳофизат намоед - Барои муҳофизат кардани кортҳои кредитии нигоҳдошташудаи худ аз дастрасии озод, агар касе дигар аз дастгоҳи шумо истифода барад, шаклвораи қулфи экран, рамзи PIN ё ниҳонвожаеро барои дастгоҳи худ танзим намоед. - Барои муҳофизат кардани тарзҳои пардохти нигоҳдошташудаи худ аз дастрасии озод, агар касе дигар аз дастгоҳи шумо истифода барад, шаклвораи қулфи экран, рамзи PIN ё ниҳонвожаеро барои дастгоҳи худ танзим намоед. Ҳозир насб кунед @@ -2005,8 +1977,6 @@ Дертар Қулфи дастгоҳи худро кушоед - - Барои истифодаи маълумоти кортҳои кредитии нигоҳдошташуда, қулфро кушоед Барои истифодаи тарзҳои пардохти нигоҳдоштаи худ, қулфро кушоед @@ -2016,12 +1986,6 @@ Таҳрир кардани нишонӣ Идоракунии нишониҳо - - Ном - - Номи падар - - Насаб Ном @@ -2047,8 +2011,6 @@ Нест кардани нишонӣ - - Шумо мутмаин ҳастед, ки мехоҳед ин нишониро нест намоед? Ин нишониро нест мекунед? @@ -2146,50 +2108,30 @@ Нест кардан Таҳрир кардан - - Шумо мутмаин ҳастед, ки мехоҳед ин воридшавиро нест намоед? Шумо мутмаин ҳастед, ки мехоҳед ин ниҳонвожаро нест намоед? Нест кардан Бекор кардан - - Имконоти воридшавӣ Инконоти ниҳонвожа - - Майдони матни таҳриршаванда барои нишонии сомонаи воридшавӣ. Майдони матни таҳриршаванда барои нишонии сомона. - - Майдони матни таҳриршаванда барои номи корбарии воридшавӣ. Майдони матни таҳриршаванда барои номи корбар. - Майдони матни таҳриршаванда барои ниҳонвожаи воридшавӣ. - Майдони матни таҳриршаванда барои ниҳонвожа. - - Нигоҳ доштани тағйирот барои воридшавӣ Тағийротро нигоҳ медорад. - - Таҳрир кардан Таҳрир кардани ниҳонвожа - - Илова кардани воридшавии нав Илова кардани ниҳонвожа - - Ниҳонвожа лозим аст Ниҳонвожаеро ворид намоед - Номи корбар лозим аст - Номи корбареро ворид намоед Номи сервер лозим аст @@ -2600,6 +2542,9 @@ Пӯшидани саҳифаи тарҷума + + Баъзе танзимот муваққатан дастнорасанд. + Тарҷумаҳо @@ -2623,6 +2568,9 @@ Барои идоракунии хусусиятҳои «Ҳамеша тарҷума карда шавад» ва «Ҳеҷ вақт тарҷума карда нашавад» забонеро интихоб намоед. + + Забонҳоро бор карда натавонист. Лутфан, баъдтар такрор кунед. + Тарҷума пешниҳод карда шавад (Ба таври пешфарз) @@ -2645,6 +2593,8 @@ Тоза кардани «%1$s» + + Сомонаҳоро бор карда натавонист. Лутфан, баъдтар такрор кунед. «%1$s»-ро нест мекунед? @@ -2722,13 +2672,18 @@ Ба қафо гузаштан + + Кушодани менюи таҳиякунандаи ислоҳи хатоҳо + Абзорҳои варақаҳо Шумораи варақаҳо - Фаъол + Фаъол + + Фаъол Ғайрифаъол @@ -2739,6 +2694,16 @@ Абзори эҷоди варақаҳо Шумораи варақаҳое, ки эҷод карда мешаванд + + Майдони матн холӣ аст + + Лутфан, танҳо ададҳои бутуни мусбатро ворид кунед + + Лутфан, рақамеро зиёда аз сифр ворид намоед + + Шумораи ҳадди аксари варақаҳое (%1$s), ки дар як амалиёт эҷод карда мешаванд, зиёд шуд Илова кардан ба варақаҳои фаъол @@ -2755,12 +2720,12 @@ Огоҳномаи махфият - Пешниҳод кардан + Пешниҳод кардан - Пӯшидан + Пӯшидан - Ташаккур барои изҳори назари шумо! + Ташаккур барои изҳори назари шумо! Хеле қаноатманд @@ -2773,6 +2738,14 @@ Хеле дарғазаб + + + Кушодани саволнома + + Пӯшидани саволнома + + Пӯшидан + Воридшавиҳо diff --git a/mobile/android/fenix/app/src/main/res/values-tr/strings.xml b/mobile/android/fenix/app/src/main/res/values-tr/strings.xml index 45d931495b..48587d9d04 100644 --- a/mobile/android/fenix/app/src/main/res/values-tr/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-tr/strings.xml @@ -200,6 +200,10 @@ Eklentiler Uzantılar + + Uzantıları yönet + + Daha fazla uzantı keşfet Hesap bilgileri @@ -218,6 +222,8 @@ Normal sekmede aç Ana ekrana ekle + + Ana ekrana ekle… Yükle @@ -229,9 +235,13 @@ Sayfayı çevir + Koleksiyona kaydet… + Koleksiyona kaydet Paylaş + + Paylaş… %1$s ile aç @@ -285,6 +295,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Kaydet + + Sayfayı yer imlerine ekle + + Yer imini düzenle + + PDF olarak kaydet… + + Okuyucu görünümünü aç + + Okuyucu görünümünü kapat + + Sayfayı çevir… + + %1$s diline çevrildi + + Yazdır… + Şu anda uzantı yok @@ -382,8 +410,6 @@ Firefox gizlilik bildirimi - - Ayrıntıları gizlilik bildirimimizde bulabilirsiniz Sizi güvende tutmayı seviyoruz Dil - Çeviri + Çeviri + + Çeviriler Veri seçenekleri @@ -667,10 +695,6 @@ Gerekli İsteğe bağlı - - Web sitesi verilerini okuma ve değiştirme - - Siteyi sil Tüm siteler için izin ver @@ -795,8 +819,6 @@ Geçmiş Yer imleri - - Hesaplar Parolalar @@ -823,8 +845,6 @@ and the third is the device model. --> %1$s - %2$s %3$s - - Kredi kartları Ödeme yöntemleri @@ -840,6 +860,14 @@ %s üzerinden gelen sekme + + + %2$d %1$s sekmesi kapatıldı + + Son kapatılan sekmeleri göster + İstisnalar @@ -1769,13 +1797,9 @@ Bu siteyi cihazınızın ana ekranına ekleyerek ona hızlıca erişebilir, site bir uygulamaymış gibi daha hızlı gezinti yapabilirsiniz. - - Hesaplar ve parolalar Parolalar - Kullanıcı adı ve parolaları kaydet - Parolaları kaydet Kaydetmeyi sor @@ -1790,46 +1814,27 @@ Cihazınızdaki diğer uygulamalarda kullanıcı adı ve parolalar doldurulsun. - - Hesap ekle - Parola ekle - - Hesapları eşitle Parolaları eşitle - - Hesapları cihazlar arasında eşitle Parolaları cihazlar arasında eşitle - - Kayıtlı hesaplar Kayıtlı parolalar - Kaydettiğiniz veya %s ile eşitlediğiniz hesaplar burada görünecektir. - %s tarayıcısına kaydettiğiniz veya eşitlediğiniz parolalar burada listelenecektir. Kaydettiğiniz tüm parolalar şifrelenir. - - Sync hakkında bilgi alın. Eşitlemeyle ilgili bilgi al İstisnalar - - Kaydedilmeyen hesaplar ve parolalar burada görünecektir. %s, burada listelenen sitelerin parolalarını kaydetmeyecektir. - - Bu sitelere ait hesaplar ve parolalar kaydedilmeyecektir. %s bu sitelerin parolalarını kaydetmeyecektir. Tüm istisnaları sil - - Hesaplarda ara Parolalarda ara @@ -1858,17 +1863,11 @@ Parolayı göster Parolayı gizle - - Kayıtlı hesaplarınızı görmek için kilidi açın Kayıtlı parolalarınızı görmek için kilidi açın - Hesaplarınızı güvence altına alın - Kayıtlı parolalarınızı güvence altına alın - Cihazınız başka birinin eline geçerse kayıtlı hesaplarına erişilmesini önlemek için cihaz kilidi deseni, PIN veya parola ayarlayın. - Cihazınız başka birinin eline geçerse kayıtlı parolalarınıza erişilmesini önlemek için cihaz kilidi deseni, PIN veya parola ayarlayın. Daha sonra @@ -1886,8 +1885,6 @@ Ad (A-Z) Son kullanım - - Hesapları sırala menüsü Parolaları sırala menüsü @@ -1897,41 +1894,27 @@ Otomatik doldurma Adresler - - Kredi kartları Ödeme yöntemleri - Kartları kaydedip otomatik doldur - Ödeme yöntemlerini kaydet ve doldur - - Veriler şifrelenir %s kaydettiğiniz tüm ödeme yöntemlerini şifreler Kartları cihazlar arasında eşitle Kartları eşitle - - Kredi kartı ekle Kart ekle - - Kayıtlı kartları yönet Kartları yönet Adres ekle Adresleri yönet - - Adresleri kaydedip otomatik doldur Adresleri kaydet ve doldur - - Numaralar, e-posta ve gönderim adresleri gibi bilgileri dahil et Telefon numaraları ve e-posta adresleri de dahildir @@ -1955,8 +1938,6 @@ Kartı sil - Bu kredi kartını silmek istediğinizden emin misiniz? - Kart silinsin mi? Sil @@ -1970,24 +1951,15 @@ Kayıtlı kartlar - - Lütfen geçerli bir kredi kartı numarası girin - Geçerli bir kart numarası girin - - Lütfen bu alanı doldurun Adınızı yazın Kayıtlı kartlarınızı görmek için kilidi açın - Kredi kartlarını güvence altına al - Kayıtlı ödeme yöntemlerinizi güvence altına alın - Cihazınız başka birinin eline geçerse kayıtlı kartlarınıza erişilmesini önlemek için cihaz kilidi deseni, PIN veya parola ayarlayın. - Cihazınız başka birinin eline geçerse kayıtlı ödeme yöntemlerinize erişilmesini önlemek için cihaz kilidi deseni, PIN veya parola ayarlayın. Hemen ayarla @@ -1996,8 +1968,6 @@ Cihazınızın kilidini açın - - Depolanan kredi kartı bilgilerini kullanmak için kilidi açın Kayıtlı ödeme yöntemlerinizi kullanmak için kilidi açın @@ -2007,12 +1977,6 @@ Adresi düzenle Adresleri yönet - - Ad - - İkinci ad - - Soyadı Ad @@ -2038,8 +2002,6 @@ Adresi sil - - Bu adresi silmek istediğinizden emin misiniz? Bu adres silinsin mi? @@ -2138,49 +2100,29 @@ Sil Düzenle - - Bu hesabı silmek istediğinizden emin misiniz? Bu parolayı silmek istediğinizden emin misiniz? Sil Vazgeç - - Hesap seçenekleri Parola seçenekleri - - Hesabın web adresi için düzenlenebilir metin alanı. Web sitesi adresi için düzenlenebilir metin alanı. - - Hesabın kullanıcı adı için düzenlenebilir metin alanı. Kullanıcı adı için düzenlenebilir metin alanı. - Hesabın parolası için düzenlenebilir metin alanı. - Parola için düzenlenebilir metin alanı. - - Değişiklikleri hesaba kaydet. Değişiklikleri kaydet. - - Düzenle Parola düzenle - - Yeni hesap ekle Parola ekle - - Parola gerekli Parolayı girin - Kullanıcı adı gereklidir - Kullanıcı adını girin Sunucu gereklidir @@ -2590,6 +2532,9 @@ Çeviriler sayfasını kapat + + Bazı ayarlar geçici olarak kullanılamıyor. + Çeviriler @@ -2614,6 +2559,9 @@ ”Her zaman çevir“ ve ”asla çevirme“ tercihlerini yönetmek için bir dil seçin. + + Diller yüklenemedi. Lütfen daha sonra yeniden deneyin. + Çevirmeyi öner (varsayılan) @@ -2637,6 +2585,8 @@ %1$s sitesini kaldır + + Siteler yüklenemedi. Lütfen daha sonra yeniden deneyin. %1$s silinsin mi? @@ -2715,13 +2665,18 @@ Geri git + + Hata ayıklama bölmesini aç + Sekme araçları Sekme sayısı - Aktif + Aktif + + Aktif Pasif @@ -2732,6 +2687,16 @@ Sekme oluşturma aracı Oluşturulacak sekme sayısı + + Metin alanı boş + + Lütfen yalnızca pozitif tamsayı girin + + Lütfen sıfırdan büyük bir sayı girin + + Tek bir işlemde oluşturulabilecek maksimum sekme sayısı (%1$s) aşıldı Aktif sekmelere ekle @@ -2748,11 +2713,11 @@ Gizlilik Bildirimi - Gönder + Gönder - Kapat + Kapat - Geri bildiriminiz için teşekkürler! + Geri bildiriminiz için teşekkürler! Çok memnunum @@ -2764,6 +2729,14 @@ Hiç memnun değilim + + + Anketi aç + + Anketi kapat + + Kapat + Hesaplar diff --git a/mobile/android/fenix/app/src/main/res/values-ug/strings.xml b/mobile/android/fenix/app/src/main/res/values-ug/strings.xml index 08c92ff266..2c820c15c1 100644 --- a/mobile/android/fenix/app/src/main/res/values-ug/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ug/strings.xml @@ -199,6 +199,10 @@ قوشۇلما كېڭەيتمە + + كېڭەيتىلمە باشقۇرۇش + + تېخىمۇ كۆپ كېڭەيتمە بايقا ھېسابات ئۇچۇرى @@ -217,6 +221,8 @@ ئادەتتىكى بەتكۈچتە ئاچ باش ئېكرانغا قوش + + باش ئېكرانغا قوش… قاچىلاش @@ -228,9 +234,13 @@ بەت تەرجىمىسى + يىغقۇچقا ساقلا… + يىغقۇچقا ساقلا ھەمبەھىرلەش + + ھەمبەھىر… %1$s دا ئاچ @@ -281,6 +291,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> ساقلا + + بۇ بەتنى خەتكۈچكە قوش + + خەتكۈچ تەھرىر + + PDF كە ساقلا… + + ئوقۇرمەن كۆرۈنۈشىنى ئاچ + + ئوقۇرمەن كۆرۈنۈشىنى تاقا + + بەت تەرجىمىسى… + + %1$s غا تەرجىمە قىلىندى + + باس… + بۇ يەردە كېڭەيتمە يوق @@ -374,8 +402,6 @@ Firefox شەخسىيەت ئۇقتۇرۇشى - - شەخسىيەت ئۇقتۇرۇشىنىڭ تەپسىلاتىنى ئوقۇڭ بىخەتەرلىكىڭىزنى ساقلاشنى ياخشى كۆرىمىز - تەرجىمە + تەرجىمە + + تەرجىمە سانلىق مەلۇمات تاللاش @@ -656,10 +684,6 @@ زۆرۈر تاللاشچان - - تور بېكەت سانلىق مەلۇماتىنى ئوقۇيدۇ ياكى ئۆزگەرتىدۇ - - تور بېكەت ئۆچۈر ھەممە تور بېكەتكە يول قويىدۇ @@ -786,8 +810,6 @@ تارىخ خەتكۈچ - - كىرىش ئىم @@ -812,8 +834,6 @@ The first parameter is the application name, the second is the device manufacturer name and the third is the device model. --> %2$s %3$s دىكى %1$s - - ئىناۋەتلىك كارتا چىقىم قىلىش ئۇسۇلى @@ -829,6 +849,14 @@ %s بەتكۈچى + + + %1$s بەتكۈچ تاقالدى: %2$d + + يېقىندا تاقالغان بەتكۈچلەرنى كۆرسەت + مۇستەسنا @@ -1754,13 +1782,9 @@ تېز سۈرئەتتە زىيارەت قىلىش ۋە ئەپكە ئوخشاش زىيارەت تۇيغۇسىغا ئېرىشىش ئۈچۈن، مەزكۇر تور بېكەتنى باش ئېكرانغا ئاسانلا قوشالايسىز. - - كىرىش ۋە ئىم ئىم - كىرىش ۋە ئىم ساقلا - ئىم ساقلا ساقلاشنى سورا @@ -1774,46 +1798,28 @@ باشقا ئەپلەردە ئاپتوماتىك تولدۇرىدۇ ئۈسكۈنىڭىزدىكى باشقا ئەپلەردە ئىشلەتكۈچى ئىسمى ۋە ئىمنى تولدۇرىدۇ. - - كىرىشنى قوش ئىم قوش - - كىرىشنى قەدەمداشلا ئىم قەدەمداشلا - - ئۈسكۈنىلەر ئارا قەدەمداشلا ئىمنى ئۈسكۈنىلەر ئارا قەدەمداشلايدۇ - - ساقلانغان كىرىش ساقلانغان ئىم - سىز ساقلىغان كىرىش ياكى %s بىلەن بولغان قەدەمداش بۇ جايدا كۆرۈنىدۇ. - سىز ساقلىغان ياكى %s غا قەدەمداشلىغان ئىم بۇ يەردە كۆرسىتىلىدۇ. سىز ساقلىغان بارلىق ئىم مەخپىيلەشتۈرۈلگەن. - - قەدەمداش ھەققىدىكى تەپسىلاتلار. قەدەمداش ھەققىدىكى تەپسىلاتلار مۇستەسنا - - ساقلانمىغان كىرىش ۋە ئىم بۇ يەردە كۆرسىتىلىدۇ. بۇ جايدا كۆرسىتىلگەن تور بېكەتلەر ئۈچۈن %s ئىم ساقلىمايدۇ. - - بۇ تور بېكەتلەرنىڭ كىرىش ۋە ئىم ئۇچۇرلىرى ساقلانمايدۇ. بۇ تور بېكەتلەر ئۈچۈن %s ئىم ساقلىمايدۇ. ھەممە مۇستەسنالارنى ئۆچۈرىدۇ - - كىرىش خاتىرىسىنى ئىزدە پارول ئىزدەش @@ -1843,17 +1849,11 @@ ئىم يوشۇر - - ساقلانغان كىرىشلىرىڭىزنى كۆرۈش ئۈچۈن قۇلۇپ ئېچىڭ ساقلانغان پارولىڭىزنى كۆرۈش ئۈچۈن قۇلۇپ ئېچىڭ - كىرىش ۋە ئىمنى قوغدايدۇ - ساقلانغان ئىملىرىڭىز شىفىرلىنىدۇ - ئۈسكۈنە قۇلۇپلاش ئەندىزىسى، PIN ياكى ئىم ئورنىتىشنى تەڭشىسىڭىز، ئۈسكۈنىڭىز باشقىلارنىڭ قولىدا بولسىمۇ ساقلانغان تىزىمغا كىرىش ئۇچۇرى ۋە ئىمنى زىيارەت قىلالمايدۇ. - ئۈسكۈنە قۇلۇپلاش ئەندىزىسى، PIN ياكى ئىم ئورنىتىشنى تەڭشىسىڭىز، ئۈسكۈنىڭىز باشقىلارنىڭ قولىدا بولسىمۇ ساقلانغان ئىملىرىڭىزنى زىيارەت قىلالمايدۇ. كېيىنچە @@ -1871,8 +1871,6 @@ ئىسمى (A-Z) ئاخىرقى قېتىم ئىشلىتىلگەن - - كىرىش تىزىملىكىنى تەرتىپلەيدۇ ئىم تىزىملىكىنى تەرتىپلە @@ -1882,40 +1880,26 @@ ئاپتوماتىك تولدۇر ئادرېس - - ئىناۋەتلىك كارتا چىقىم قىلىش ئۇسۇلى - كارتا ساقلاش ۋە ئاپتوماتىك تولدۇرۇش - چىقىم قىلىش ئۇسۇلىنى ساقلاپ ۋە تولدۇرىدۇ - - سانلىق مەلۇمات شىفىرلانغان سىز ساقلىغان بارلىق چىقىم قىلىش ئۇسۇللىرىنى %s شىفىرلايدۇ كارتىنى ئۈسكۈنىلەر ئارا قەدەمداشلايدۇ كارتا قەدەمداشلا - - ئىناۋەتلىك كارتا قوش كارتا قوش - - ساقلانغان كارتا باشقۇرۇش كارتا باشقۇرۇش ئادرېس قوش ئادرېس باشقۇرۇش - - ئادرېسلارنى ساقلاش ۋە ئاپتوماتىك تولدۇرۇش ئادرېس ساقلاپ تولدۇرىدۇ - - سان، ئېلخەت ۋە توشۇش ئادرېسى قاتارلىق ئۇچۇرلارنى ئۆز ئىچىگە ئالىدۇ تېلېفون نومۇرى ۋە ئېلخەت ئادرېسىنى ئۆز ئىچىگە ئالىدۇ @@ -1939,8 +1923,6 @@ كارتىنى ئۆچۈر - بۇ ئىناۋەتلىك كارتىنى راستىنلا ئۆچۈرەمسىز؟ - كارتىنى ئۆچۈرەمدۇ؟ ئۆچۈر @@ -1952,24 +1934,16 @@ ۋاز كەچ ساقلانغان كارتا - - ئىناۋەتلىك كارتا نومۇرىنى كىرگۈزۈڭ ئىناۋەتلىك كارتا نومۇرى كىرگۈزۈلىدۇ - - بۇ بۆلەكنى تولدۇرۇڭ ئات قوش ساقلانغان كارتىلىرىڭىزنى كۆرۈش ئۈچۈن قۇلۇپ ئېچىڭ - - ئىناۋەتلىك كارتىڭىزنى شىفىرلايدۇ ساقلانغان چىقىم قىلىش ئۇسۇللىرىڭىز شىفىرلىنىدۇ - ئۈسكۈنە قۇلۇپلاش ئەندىزىسى، PIN ياكى ئىم ئورنىتىشنى تەڭشىسىڭىز، ئۈسكۈنىڭىز باشقىلارنىڭ قولىدا بولسىمۇ ساقلانغان ئىناۋەتلىك كارتىڭىزنى زىيارەت قىلالمايدۇ. - ئۈسكۈنە قۇلۇپلاش ئەندىزىسى، PIN ياكى ئىم ئورنىتىشنى تەڭشىسىڭىز، ئۈسكۈنىڭىز باشقىلارنىڭ قولىدا بولسىمۇ ساقلانغان چىقىم قىلىش ئۇسۇلىڭىزنى زىيارەت قىلالمايدۇ. ھازىر تەڭشە @@ -1977,8 +1951,6 @@ كېيىنچە ئۈسكۈنىڭىزنىڭ قۇلۇپىنى ئېچىڭ - - ساقلانغان ئىناۋەتلىك كارتا ئۇچۇرلىرىنى ئىشلىتىش ئۈچۈن قۇلۇپ ئېچىڭ ساقلانغان چىقىم قىلىش ئۇسۇلىنى ئىشلىتىش ئۈچۈن قۇلۇپ ئېچىڭ @@ -1987,12 +1959,6 @@ ئادرېس تەھرىر ئادرېس باشقۇرۇش - - ئىسمى - - ئوتتۇرا ئىسمى - - تەگئات ئىسمى @@ -2018,8 +1984,6 @@ ئادرېس ئۆچۈر - بۇ ئادرېسنى راستىنلا ئۆچۈرەمسىز؟ - بۇ ئادرېسنى ئۆچۈرەمدۇ؟ ئۆچۈر @@ -2117,50 +2081,30 @@ ئۆچۈر تەھرىر - - بۇ كىرىشنى راستىنلا ئۆچۈرەمسىز؟ بۇ ئىمنى راستىنلا ئۆچۈرەمسىز؟ ئۆچۈر ۋاز كەچ - - كىرىش تاللانمىلىرى ئىم تاللانمىسى - - كىرىشنىڭ تور ئادرېسىنى تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. تور بېكەت ئادرېسىنىڭ تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. - - كىرىشنىڭ ئىشلەتكۈچى ئىسمىنى تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. ئىشلەتكۈچى ئاتىنىڭ تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. - كىرىشنىڭ ئىمىنى تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. - ئىمنىڭ تەھرىرلىگىلى بولىدىغان تېكىست بۆلىكى. - - كىرىش ئۆزگەرتىشلىرىنى ساقلايدۇ. ئۆزگەرتىشلەرنى ساقلايدۇ. - - تەھرىر ئىم تەھرىر - - يېڭى كىرىش قوش ئىم قوش - - ئىم زۆرۈر ئىم كىرگۈزۈلىدۇ - ئىشلەتكۈچى ئىسمى زۆرۈر - ئىشلەتكۈچى ئاتى كىرگۈزۈلىدۇ مۇلازىم ئىسمى زۆرۈر @@ -2571,6 +2515,9 @@ تەرجىمە جەدۋىلىنى تاقا + + بەزى تەڭشەكلەرنى ۋاقىتلىق ئىشلەتكىلى بولمايدۇ. + تەرجىمە @@ -2594,6 +2541,9 @@ «ھەمىشە تەرجىمە قىل» ۋە «ھەرگىز تەرجىمە قىلما» مايىللىقىدىن باشقۇرىدىغان تىل تاللىنىدۇ. + + تىلنى يۈكلىيەلمەيدۇ. كېيىن قايتا تەكشۈرۈڭ. + تەرجىمە قىلىش (كۆڭۈلدىكى) @@ -2617,6 +2567,8 @@ %1$s نى چىقىرىۋەت + + تور بېكەتنى يۈكلىيەلمەيدۇ. كېيىن قايتا تەكشۈرۈڭ. %1$s نى ئۆچۈرەمدۇ؟ @@ -2694,13 +2646,18 @@ كەينىگە قايت + + سازلاش تارتمىسىنى ئاچ + بەتكۈچ قوراللىرى بەتكۈچ سانى - ئاكتىپ + ئاكتىپ + + ئاكتىپ ئىشلەتمىگەن @@ -2711,6 +2668,16 @@ بەتكۈچ قۇرۇش قورالى قۇرىدىغان بەتكۈچ سانى + + تېكىست بۆلىكى بوش + + پەقەت مۇسپەت پۈتۈن سان كىرگۈزۈڭ + + نۆلدىن چوڭ سان كىرگۈزۈڭ + + بىر مەشغۇلاتتا ھاسىللايدىغان ئەڭ كۆپ بەتكۈچ (%1$s) سانىدىن ئېشىپ كەتتى ئاكتىپ بەتكۈچكە قوش @@ -2727,11 +2694,11 @@ شەخسىيەت ئۇقتۇرۇشى - يوللا + يوللا - تاقا + تاقا - قايتۇرما ئىنكاسىڭىزغا رەھمەت! + قايتۇرما ئىنكاسىڭىزغا رەھمەت! بەك رازى @@ -2744,6 +2711,14 @@ بەك نارازى + + + راي سىناشنى ئاچ + + راي سىناشنى تاقا + + تاقا + كىرىش diff --git a/mobile/android/fenix/app/src/main/res/values-uk/strings.xml b/mobile/android/fenix/app/src/main/res/values-uk/strings.xml index a03bc42b8b..0800dff8c6 100644 --- a/mobile/android/fenix/app/src/main/res/values-uk/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-uk/strings.xml @@ -198,6 +198,10 @@ Додатки Розширення + + Керувати розширеннями + + Знайти більше розширень Ваш обліковий запис @@ -217,6 +221,8 @@ Відкрити у звичайній вкладці Додати на головний екран + + Додати на головний екран… Встановити @@ -228,9 +234,13 @@ Перекласти сторінку + Зберегти до збірки… + Зберегти до збірки Поділитися + + Поділитися… Відкрити в %1$s @@ -284,6 +294,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Зберегти + + Додати сторінку до закладок + + Редагувати закладку + + Зберегти як PDF… + + Увімкнути режим читання + + Вимкнути режим читання + + Перекласти сторінку… + + Перекладено на %1$s + + Друкувати… + Тут немає розширень @@ -381,8 +409,6 @@ Положення про приватність Firefox - - Докладніше в нашому положенні про приватність Ми дбаємо про вашу безпеку Мова - Переклад + Переклад + + Переклади Вибір даних @@ -668,10 +696,6 @@ Обов\'язково Необов\'язково - - Читати та змінювати дані вебсайту - - Видалити вебсайт Дозволити для всіх сайтів @@ -798,8 +822,6 @@ Історію Закладки - - Паролі Паролі @@ -826,8 +848,6 @@ and the third is the device model. --> %1$s на %2$s %3$s - - Кредитні картки Способи оплати @@ -844,6 +864,14 @@ Вкладка з %s + + + Закрито вкладок %1$s: %2$d + + Переглянути недавно закриті вкладки + Винятки @@ -1771,13 +1799,9 @@ Ви можете легко додати цей вебсайт на головний екран вашого пристрою, щоб мати миттєвий доступ до нього і працювати швидше. - - Паролі Паролі - Зберігати паролі - Зберігати паролі Питати про збереження @@ -1792,48 +1816,29 @@ Заповнювати імена користувачів і паролі в інших програмах на цьому пристрої. - - Додати пароль - Додати пароль - - Синхронізація паролів Синхронізувати паролі - - Синхронізувати паролі між пристроями Синхронізація паролів між пристроями - - Збережені паролі Збережені паролі - Тут з’являтимуться ваші збережені та синхронізовані з %s паролі. - Тут з\'являтимуться паролі, які ви зберігаєте чи синхронізуєте в %s. Усі паролі зашифровано. - - Докладніше про синхронізацію. Докладніше про синхронізацію Винятки - - Не збережені паролі з’являтимуться тут. %s не зберігатиме паролі для вказаних тут сайтів. - - Паролі для цих сайтів не зберігатимуться. %s не зберігатиме паролі для цих сайтів. Видалити всі винятки - - Шукати паролі Пошук паролів @@ -1863,17 +1868,11 @@ Показати пароль Приховати пароль - - Розблокуйте для перегляду збережених паролів Розблокуйте, щоб переглянути збережені паролі - Захистіть свої паролі - Захистіть збережені паролі - Встановіть графічний ключ, PIN-код чи пароль для захисту збережених паролів від інших, хто може отримати доступ до вашого пристрою. - Встановіть графічний ключ, PIN-код чи пароль для захисту збережених паролів від інших, хто може отримати доступ до вашого пристрою. Пізніше @@ -1891,9 +1890,6 @@ Востаннє використано - - Меню впорядкування паролів - Меню сортування паролів @@ -1902,41 +1898,27 @@ Автозаповнення Адреси - - Кредитні картки Способи оплати - Зберігати та автоматично заповнювати дані карток - Зберігати й заповнювати способи оплати - - Дані зашифровано %s шифрує всі збережені способи оплати Синхронізувати картки між пристроями Синхронізувати картки - - Додати кредитну картку Додати картку - - Керувати збереженими картками Керувати картками Додати адресу Керувати адресами - - Зберігати та автоматично заповнювати адреси Зберігати й заповнювати адреси - - Включити дані, як-от номери, електронні адреси та адреси доставлення Містить номери телефонів і адреси електронної пошти @@ -1960,8 +1942,6 @@ Видалити картку - Ви впевнені, що хочете видалити цю кредитну картку? - Видалити картку? Видалити @@ -1975,24 +1955,15 @@ Збережені картки - - Введіть дійсний номер кредитної картки - Введіть дійсний номер картки - - Будь ласка, заповніть це поле Додайте назву Розблокуйте для перегляду збережених карток - Захистіть свої банківські картки - Захистіть свої збережені способи оплати - Встановіть графічний ключ, PIN-код чи пароль для захисту збережених банківських карток від інших, хто може отримати доступ до вашого пристрою. - Встановіть графічний ключ, PIN-код чи пароль для захисту збережених способів оплати від інших, хто може отримати доступ до вашого пристрою. Встановити @@ -2001,9 +1972,6 @@ Розблокуйте свій пристрій - - Розблокуйте, щоб використовувати збережену інформацію про кредитну картку - Розблокуйте, щоб використати збережені способи оплати @@ -2012,12 +1980,6 @@ Редагувати адресу Керувати адресами - - Ім’я - - По батькові - - Прізвище Ім’я @@ -2043,8 +2005,6 @@ Видалити адресу - - Ви дійсно хочете видалити цю адресу? Видалити цю адресу? @@ -2144,49 +2104,29 @@ Видалити Змінити - - Ви дійсно хочете видалити цей запис? Ви дійсно хочете видалити цей пароль? Видалити Скасувати - - Опції запису Параметри пароля - - Текстове поле для редагування вебадреси запису. Текстове поле для редагування адреси вебсайту. - - Текстове поле для редагування імені користувача запису. Текстове поле для редагування імені користувача. - Текстове поле для редагування пароля запису. - Текстове поле для редагування пароля. - - Зберегти зміни. Зберегти зміни. - - Змінити Редагувати пароль - - Додати новий пароль Додати пароль - - Потрібен пароль Введіть пароль - Необхідно вказати ім’я користувача - Введіть ім’я користувача Необхідно вказати назву вузла @@ -2594,6 +2534,9 @@ Закрити блок перекладів + + Деякі налаштування тимчасово недоступні. + Переклади @@ -2617,6 +2560,9 @@ Виберіть мову, щоб керувати налаштуваннями ”завжди перекладати“ та ”ніколи не перекладати“. + + Не вдалося завантажити мови. Повторіть спробу пізніше. + Пропонувати переклад (типово) @@ -2641,6 +2587,8 @@ Вилучити %1$s + + Не вдалося завантажити сайти. Повторіть спробу пізніше. Видалити %1$s? @@ -2718,13 +2666,18 @@ Перейти назад + + Відкрити панель налагодження + Інструменти вкладки Кількість вкладок - Активні + Активні + + Активні Неактивні @@ -2735,6 +2688,16 @@ Інструмент створення вкладок Кількість вкладок для створення + + Текстове поле порожнє + + Будь ласка, введіть лише додатні цілі числа + + Будь ласка, введіть число більше нуля + + Перевищено максимальну кількість вкладок (%1$s), яку можна створити за одну операцію Додати до активних вкладок @@ -2751,11 +2714,11 @@ Повідомлення про приватність - Надіслати + Надіслати - Закрити + Закрити - Дякуємо за ваш відгук! + Дякуємо за ваш відгук! Дуже задоволені @@ -2767,6 +2730,14 @@ Дуже незадоволені + + + Відкрити опитування + + Закрити опитування + + Закрити + Паролі diff --git a/mobile/android/fenix/app/src/main/res/values-vi/strings.xml b/mobile/android/fenix/app/src/main/res/values-vi/strings.xml index 4b8cd97821..0428dce517 100644 --- a/mobile/android/fenix/app/src/main/res/values-vi/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-vi/strings.xml @@ -199,6 +199,10 @@ Tiện ích Tiện ích + + Quản lý tiện ích + + Khám phá tiện tích khác Thông tin tài khoản @@ -217,6 +221,8 @@ Mở trong thẻ thông thường Thêm vào màn hình chính + + Thêm vào màn hình chính… Cài đặt @@ -228,9 +234,13 @@ Dịch trang + Lưu vào bộ sưu tập… + Lưu vào bộ sưu tập Chia sẻ + + Chia sẻ… Mở bằng %1$s @@ -283,6 +293,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> Lưu + + Đánh dấu trang này + + Chỉnh sửa dấu trang + + Lưu dưới dạng PDF… + + Bật chế độ đọc sách + + Tắt chế độ đọc sách + + Dịch trang… + + Đã dịch sang %1$s + + In… + Không có tiện ích ở đây @@ -379,8 +407,6 @@ Thông báo về quyền riêng tư Firefox - - Tìm hiểu thêm trong thông báo bảo mật của chúng tôi Chúng tôi thích giữ an toàn cho bạn Ngôn ngữ - Dịch + Dịch + + Dịch Lựa chọn dữ liệu @@ -662,10 +690,6 @@ Bắt buộc Không bắt buộc - - Đọc và thay đổi dữ liệu trang web - - Xóa trang web Cho phép tất cả các trang web @@ -790,8 +814,6 @@ Lịch sử Dấu trang - - Đăng nhập Mật khẩu @@ -818,8 +840,6 @@ and the third is the device model. --> %1$s trên %2$s %3$s - - Thẻ tín dụng Phương thức thanh toán @@ -836,6 +856,14 @@ Thẻ từ %s + + + %2$d thẻ %1$s đã đóng + + Xem thẻ đã đóng gần đây + Ngoại lệ @@ -1754,13 +1782,9 @@ Bạn có thể dễ dàng thêm trang web vào màn hình chính thiết bị của bạn để có thể truy cập và duyệt web nhanh hơn với trải nghiệm giống như trên ứng dụng. - - Thông tin đăng nhập và mật khẩu Mật khẩu - Lưu thông tin đăng nhập và mật khẩu - Lưu mật khẩu Yêu cầu để lưu @@ -1775,47 +1799,28 @@ Điền tên người dùng và mật khẩu vào các ứng dụng khác trên thiết bị của bạn. - - Thêm thông tin đăng nhập - Thêm mật khẩu - - Đồng bộ hóa thông tin đăng nhập Đồng bộ mật khẩu - - Đồng bộ hóa thông tin đăng nhập trên các thiết bị Đồng bộ hóa mật khẩu giữa các thiết bị - - Thông tin đăng nhập đã lưu Mật khẩu đã lưu - Thông tin đăng nhập bạn lưu hoặc đồng bộ hóa với %s sẽ hiển thị tại đây. - Mật khẩu bạn lưu hoặc đồng bộ hóa với %s sẽ được liệt kê ở đây. Tất cả mật khẩu bạn lưu đều được mã hóa. - - Tìm hiểu thêm về đồng bộ hóa. Tìm hiểu thêm về đồng bộ hoá Ngoại lệ - - Thông tin đăng nhập và mật khẩu không được lưu sẽ được hiển thị ở đây. %s sẽ không lưu mật khẩu cho các trang được liệt kê ở đây. - - Thông tin đăng nhập và mật khẩu sẽ không được lưu cho các trang web này. %s sẽ không lưu mật khẩu cho các trang web này. Xóa tất cả ngoại lệ - - Tìm thông tin đăng nhập Tìm kiếm mật khẩu @@ -1844,17 +1849,11 @@ Hiện mật khẩu Ẩn mật khẩu - - Mở khóa để xem thông tin đăng nhập đã lưu của bạn Mở khóa để xem mật khẩu đã lưu của bạn - Bảo mật thông tin đăng nhập và mật khẩu của bạn - Giữ mật khẩu đã lưu của bạn một cách an toàn - Thiết lập mẫu khóa thiết bị, mã PIN hoặc mật khẩu để bảo vệ thông tin đăng nhập và mật khẩu đã lưu của bạn khỏi bị truy cập nếu người khác lấy được thiết bị của bạn. - Đặt mật khẩu thiết bị dạng mẫu hình, mã PIN hoặc mật khẩu để bảo vệ mật khẩu đã lưu của bạn không bị truy cập nếu người khác lấy được thiết bị của bạn. Để sau @@ -1871,8 +1870,6 @@ Tên (A-Z) Sử dụng lần cuối - - Sắp xếp menu đăng nhập Menu sắp xếp mật khẩu @@ -1882,41 +1879,27 @@ Tự động điền Địa chỉ - - Thẻ tín dụng Phương thức thanh toán - Lưu và tự động điền thẻ tín dụng - Lưu và điền phương thức thanh toán - - Dữ liệu được mã hóa %s mã hóa tất cả các phương thức thanh toán bạn lưu Đồng bộ thông tin thẻ tín dụng trên các thiết bị Đồng bộ hóa thẻ tín dụng - - Thêm thẻ tín dụng Thêm thẻ - - Quản lý thẻ tín dụng đã lưu Quản lý thẻ tín dụng Thêm địa chỉ Quản lý địa chỉ - - Lưu và tự động điền địa chỉ Lưu và điền địa chỉ - - Bao gồm thông tin như số, email và địa chỉ giao hàng Bao gồm số điện thoại và địa chỉ email @@ -1940,8 +1923,6 @@ Xóa thẻ - Bạn có chắc chắn muốn xóa thẻ tín dụng này không? - Xoá thẻ? Xóa @@ -1955,24 +1936,15 @@ Thẻ tín dụng đã lưu - - Vui lòng nhập số thẻ tín dụng hợp lệ - Nhập số thẻ hợp lệ - - Vui lòng điền vào trường này Thêm tên Mở khóa để xem các thẻ tín dụng đã lưu của bạn - Bảo mật thẻ tín dụng của bạn - Giữ các phương thức thanh toán đã lưu của bạn một cách an toàn - Thiết lập màn hình khóa thiết bị với mẫu hình, mã PIN hoặc mật khẩu để bảo vệ thẻ tín dụng đã lưu của bạn không bị truy cập nếu người khác có thiết bị của bạn. - Đặt mật khẩu thiết bị dạng mẫu hình, mã PIN hoặc mật khẩu để bảo vệ phương thức thanh toán đã lưu của bạn không bị truy cập nếu người khác lấy đuọc thiết bị của bạn. Thiết lập ngay @@ -1980,8 +1952,6 @@ Để sau Mở khóa thiết bị của bạn - - Mở khóa để sử dụng thông tin thẻ tín dụng được lưu trữ Mở khóa để sử dụng các phương thức thanh toán đã lưu @@ -1991,12 +1961,6 @@ Sửa địa chỉ Quản lý địa chỉ - - Tên - - Tên đệm - - Họ Tên @@ -2022,8 +1986,6 @@ Xóa địa chỉ - - Bạn có chắc chắn muốn xóa địa chỉ này không? Xoá địa chỉ này? @@ -2122,49 +2084,29 @@ Xóa Chỉnh sửa - - Bạn có chắc chắn muốn xóa thông tin đăng nhập này không? Bạn có chắc chắn muốn xóa mật khẩu này? Xóa Hủy bỏ - - Tùy chọn thông tin đăng nhập Cài đặt mật khẩu - - Trường văn bản có thể chỉnh sửa cho địa chỉ web của thông tin đăng nhập. Trường văn bản có thể chỉnh sửa cho địa chỉ trang web. - - Trường văn bản có thể chỉnh sửa cho tên người dùng của thông tin đăng nhập. Trường văn bản có thể chỉnh sửa cho tên người dùng. - Trường văn bản có thể chỉnh sửa cho mật khẩu của thông tin đăng nhập. - Trường văn bản có thể chỉnh sửa cho mật khẩu. - - Lưu các thay đổi vào thông tin đăng nhập. Lưu thay đổi. - - Chỉnh sửa Chỉnh sửa mật khẩu - - Thêm thông tin đăng nhập mới Thêm mật khẩu - - Yêu cầu mật khẩu Nhập mật khẩu - Yêu cầu tên đăng nhập - Nhập tên người dùng Yêu cầu tên máy chủ @@ -2512,6 +2454,8 @@ Không phải bây giờ Hiển thị bản gốc + + Trang nguyên bản chưa dịch đã được tải Xong @@ -2569,6 +2513,9 @@ Đóng bảng dịch + + Một số cài đặt tạm thời không khả dụng. + Dịch @@ -2592,6 +2539,9 @@ Hãy chọn một ngôn ngữ để quản lí các tùy chọn ”Luôn dịch“ và ”Không bao giờ dịch“. + + Không thể tải ngôn ngữ. Vui lòng kiểm tra lại sau. + Đề xuất dịch (mặc định) @@ -2615,6 +2565,8 @@ Xóa %1$s + + Không thể tải trang web. Vui lòng kiểm tra lại sau. Xóa %1$s? @@ -2692,13 +2644,18 @@ Điều hướng quay lại + + Mở ngăn gỡ lỗi + Công cụ thẻ Số lượng thẻ - Hoạt động + Hoạt động + + Đang hoạt động Không hoạt động @@ -2709,6 +2666,16 @@ Công cụ tạo thẻ Số lượng thẻ cần tạo + + Trường văn bản đang trống + + Vui lòng chỉ nhập số nguyên dương + + Vui lòng nhập số lớn hơn 0 + + Đã vượt quá số lượng thẻ tối đa (%1$s) có thể được tạo ra trong một thao tác Thêm vào thẻ đang hoạt động @@ -2725,11 +2692,11 @@ Thông báo bảo mật - Gửi + Gửi - Đóng + Đóng - Cảm ơn phản hồi của bạn! + Cảm ơn phản hồi của bạn! Rất hài lòng @@ -2741,6 +2708,14 @@ Rất không hài lòng + + + Mở khảo sát + + Đóng khảo sát + + Đóng + Thông tin đăng nhập diff --git a/mobile/android/fenix/app/src/main/res/values-zh-rCN/strings.xml b/mobile/android/fenix/app/src/main/res/values-zh-rCN/strings.xml index 9d9182dcd4..9d92f3c32d 100644 --- a/mobile/android/fenix/app/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-zh-rCN/strings.xml @@ -204,6 +204,10 @@ 附加组件 扩展 + + 管理扩展 + + 探索更多扩展 账户信息 @@ -222,6 +226,8 @@ 在常规标签页中打开 添加到主屏幕 + + 添加到主屏幕… 安装 @@ -233,10 +239,14 @@ 翻译页面 + 保存到收藏集… + 保存到收藏集 分享 + + 分享… 用 %1$s 打开 @@ -291,6 +301,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> 保存 + + 将此页面加入书签 + + 编辑书签 + + 另存为 PDF… + + 开启阅读模式 + + 关闭阅读视图 + + 翻译页面… + + 已翻译成%1$s + + 打印… + 暂无扩展 @@ -390,8 +418,6 @@ Firefox 隐私声明 - - 阅读我们的隐私声明详细了解 我们乐于为您护航 语言 - 翻译 + 翻译 + + 翻译 数据反馈 @@ -677,10 +705,6 @@ 必需 可选 - - 读取和更改网站数据 - - 删除网站 允许在所有网站上运行 @@ -808,8 +832,6 @@ 历史记录 书签 - - 登录信息 密码 @@ -836,9 +858,6 @@ and the third is the device model. --> %2$s %3$s 上的 %1$s - - 信用卡 - 付款方式 @@ -855,6 +874,14 @@ 来自 %s 的标签页 + + + 已关闭 %1$s 中的 %2$d 个标签页 + + 查看最近关闭的标签页 + 例外 @@ -1812,13 +1839,9 @@ 您可以轻松将此网站添加到设备主屏幕,以便迅捷访问并以类似应用的体验畅享浏览。 - - 密码 密码 - 保存登录名和密码 - 保存密码 询问是否保存 @@ -1835,47 +1858,28 @@ 在您设备上的其他应用程序中填充用户名和密码。 - - 添加登录信息 - 添加密码 - - 同步登录信息 同步密码 - - 跨设备同步登录信息 跨设备同步密码 - - 保存的登录信息 保存的密码 - 您保存或同步到 %s 的登录信息将显示于此处。 - 保存和同步到 %s 的密码会显示在这里,所有密码均已加密保存。 - - 详细了解“同步”。 详细了解同步功能 例外 - - 不保存登录名和密码的网站将显示于此处。 %s 将不会保存此处所列网站的密码。 - - 将不保存这些网站的登录名和密码。 %s 将不会保存这些网站的密码。 删除所有例外 - - 搜索登录信息 搜索密码 @@ -1904,17 +1908,11 @@ 显示密码 隐藏密码 - - 解锁以查看您保存的登录信息 解锁以查看保存的密码 - 保护您的登录名和密码 - 保护您保存的密码 - 设置设备锁定图案、PIN 或密码以保护您保存的登录名与密码,避免他人盗用。 - 设置设备锁定图案、PIN 或密码以保护您保存的密码,避免他人盗用。 稍后 @@ -1931,8 +1929,6 @@ 名称(A-Z) 上次使用 - - 排序登录信息菜单 密码排序菜单 @@ -1942,41 +1938,27 @@ 自动填充 地址 - - 信用卡 付款方式 - 保存并自动填充信用卡信息 - 保存和填写付款方式 - - 数据已加密 %s 会加密您保存的所有付款方式 跨设备同步信用卡信息 同步卡片信息 - - 添加信用卡 添加信用卡 - - 管理保存的卡片 管理信用卡 添加地址 管理地址 - - 保存并自动填充地址 保存和填充地址 - - 包含号码、邮箱和收货地址等信息 包括电话号码和邮箱地址 @@ -2000,8 +1982,6 @@ 删除卡片 - 您确定要删除此信用卡吗? - 确定删除信用卡吗? 删除 @@ -2015,25 +1995,16 @@ 保存的卡片 - - 请输入有效的信用卡卡号 - 请输入有效卡号 - - 请填写此栏 请输入持卡人姓名 解锁后即可查看保存的卡片信息 - - 保护您的卡片信息 保护您保存的付款方式 - 设置设备锁定图案、PIN 或密码以保护您保存的卡片信息,避免他人盗用。 - 设置设备锁定图案、PIN 或密码以保护您保存的付款方式,避免他人盗用。 立即设置 @@ -2042,9 +2013,6 @@ 解锁设备 - - 解锁以使用存储的卡片信息 - 解锁以使用保存的付款方式 @@ -2053,12 +2021,6 @@ 编辑地址 管理地址 - - - - 中间名 - - 姓名 @@ -2084,8 +2046,6 @@ 删除地址 - - 您确定要删除此地址吗? 确定删除此地址吗? @@ -2184,49 +2144,29 @@ 删除 编辑 - - 您确定要删除此登录信息吗? 您确定要删除此密码吗? 删除 取消 - - 登录选项 密码选项 - - 登录信息中的网址输入框。 网址输入框。 - - 登录信息中的用户名输入框。 用户名输入框。 - 登录信息中的密码输入框。 - 密码输入框。 - - 保存编辑过的登录信息。 保存更改。 - - 编辑 编辑密码 - - 新建登录信息 添加密码 - - 需要密码 请输入密码 - 用户名不能为空 - 请输入用户名 主机名不能为空 @@ -2643,6 +2583,9 @@ 关闭翻译表单 + + 部分设置暂时无法使用。 + 翻译 @@ -2665,6 +2608,9 @@ 请选择语言来管理“总是翻译”和“永不翻译”首选项。 + + 无法加载语言包,请稍后再试。 + 询问是否翻译(默认) @@ -2688,6 +2634,8 @@ 移除 %1$s + + 无法加载网站,请稍后再试。 确定删除 %1$s 吗? @@ -2765,13 +2713,18 @@ 向前导航 + + 打开调试抽屉 + 标签页工具 标签页数量 - 活跃 + 活跃 + + 活跃 休眠 @@ -2782,6 +2735,16 @@ 标签页创建工具 要创建的标签页数量 + + 文本框为空 + + 只能输入正整数 + + 只能输入大于 0 的数字 + + 超出单次操作所能生成的标签页数量上限(%1$s 个) 添加到活跃标签页 @@ -2798,11 +2761,11 @@ 隐私声明 - 提交 + 提交 - 关闭 + 关闭 - 感谢反馈! + 感谢反馈! 非常满意 @@ -2815,6 +2778,14 @@ 非常不满意 + + + 打开问卷 + + 关闭问卷 + + 关闭 + 登录信息 diff --git a/mobile/android/fenix/app/src/main/res/values-zh-rTW/strings.xml b/mobile/android/fenix/app/src/main/res/values-zh-rTW/strings.xml index fee537aaf9..1bec6b339b 100644 --- a/mobile/android/fenix/app/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-zh-rTW/strings.xml @@ -202,6 +202,10 @@ 附加元件 擴充套件 + + 管理擴充套件 + + 探索更多擴充套件 帳號資訊 @@ -220,6 +224,8 @@ 用一般分頁開啟 新增至裝置主畫面 + + 新增至裝置主畫面… 安裝 @@ -231,9 +237,13 @@ 翻譯頁面 + 儲存至收藏集… + 儲存至收藏集 分享 + + 分享… 使用 %1$s 開啟 @@ -289,6 +299,24 @@ bookmarking a page, saving to collection, shortcut or as a PDF, and adding to home screen --> 儲存 + + 將此頁加入書籤 + + 編輯書籤 + + 儲存為 PDF… + + 開啟閱讀模式 + + 關閉閱讀模式 + + 翻譯此頁… + + 已翻譯為%1$s + + 列印… + 這裡沒有擴充套件 @@ -386,8 +414,6 @@ Firefox 隱私權公告 - - 可以到我們的隱私權公告了解更多資訊 我們希望確保您上網安全 語言 - 翻譯 + 翻譯 + + 翻譯 回報資料 @@ -671,10 +699,6 @@ 必需 非必要 - - 讀取或變更網站資料 - - 刪除網站 允許所有網站 @@ -800,8 +824,6 @@ 瀏覽紀錄 書籤 - - 登入資訊 密碼 @@ -828,8 +850,6 @@ and the third is the device model. --> 在 %2$s %3$s 上的 %1$s - - 信用卡 付款方式 @@ -846,6 +866,14 @@ 來自 %s 的分頁 + + + 已關閉 %1$s 分頁:%2$d + + 檢視最近關閉的分頁 + 例外網站 @@ -1797,13 +1825,9 @@ 您可將此網站加到裝置主畫面,方便快速開啟,或是以類似 App 的方式使用。 - - 登入資訊與密碼 密碼 - 儲存登入資訊與密碼 - 已存密碼 詢問我是否儲存 @@ -1818,46 +1842,27 @@ 使用其他裝置中的其他應用程式時也自動填寫使用者名稱與密碼。 - - 新增登入資訊 - 新增密碼 - - 同步登入資訊 同步密碼 - - 在不同裝置間同步登入資訊 在不同裝置間同步密碼 - - 儲存的登入資訊 已存密碼 - 您儲存或同步到 %s 的登入資訊將顯示於此處。 - 您儲存或同步到 %s 的密碼將顯示於此處,所有儲存的密碼資訊都會被加密。 - - 了解 Sync 的更多資訊。 瞭解同步功能的更多資訊 例外網站 - - 不儲存登入資訊與密碼的網站將顯示於此處。 %s 不會儲存下列網站的密碼。 - - 將不儲存這些網站的登入資訊與密碼。 %s 不會儲存下列網站的密碼。 刪除所有例外 - - 搜尋登入資訊 搜尋密碼 @@ -1886,17 +1891,11 @@ 顯示密碼 隱藏密碼 - - 解鎖後即可檢視儲存的登入資訊 解鎖後即可檢視儲存的密碼 - 保護您的登入資訊與密碼 - 保護您儲存的密碼 - 設定裝置解鎖圖形、PIN 碼或密碼來保護您儲存下來的登入資訊與密碼,避免別人盜用。 - 設定裝置解鎖圖形、PIN 碼或密碼來保護您儲存的密碼,避免別人盜用。 稍後處理 @@ -1913,8 +1912,6 @@ 名稱(A-Z 排序) 上次使用 - - 排序登入資訊選單 排序密碼選單 @@ -1924,42 +1921,28 @@ 自動填寫 地址 - - 信用卡 付款方式 - 儲存並自動填寫卡片 - 儲存並自動填寫付款方式 - - 資料有加密 %s 會加密您儲存的所有付款方式資料 在不同裝置間同步卡片資料 同步信用卡資訊 - - 新增信用卡 新增付款卡片 - - 管理已儲存的卡片 管理卡片 新增地址 管理已存地址 - - 儲存並自動填寫地址 儲存並自動填寫地址 - - 包含電話號碼、E-Mail、收件地址等資訊 包含電話號碼與電子郵件地址 @@ -1983,8 +1966,6 @@ 刪除卡片 - 你確定要刪除這張信用卡嗎? - 要刪除卡片嗎? 刪除 @@ -1998,24 +1979,15 @@ 已儲存的卡片 - - 請輸入有效的信用卡號 - 請輸入有效卡號 - - 請填寫此欄位 請輸入持卡人姓名 解鎖後即可檢視儲存的卡片資訊 - 保護您的信用卡資訊 - 保護您儲存的付款方式 - 設定裝置解鎖圖形、PIN 碼或密碼來保護您儲存的信用卡資訊,避免別人盜用。 - 設定裝置解鎖圖形、PIN 碼或密碼來保護您儲存的付款方式資訊,避免別人盜用。 立即設定 @@ -2024,9 +1996,6 @@ 裝置解鎖 - - 解鎖後,即可使用儲存的信用卡資訊 - 解鎖後即可使用儲存的付款方式 @@ -2035,12 +2004,6 @@ 編輯地址 管理已存地址 - - 名字 - - 中間名 - - 姓氏 姓名 @@ -2066,8 +2029,6 @@ 刪除地址 - - 您確定要刪除這筆地址嗎? 要刪除這個地址嗎? @@ -2166,49 +2127,29 @@ 刪除 編輯 - - 您確定要刪除這筆登入資訊嗎? 你確定要刪除這筆密碼嗎? 刪除 取消 - - 登入選項 密碼選項 - - 登入資訊當中,網址的輸入欄位。 網址的輸入欄位。 - - 登入資訊當中,使用者名稱的輸入欄位。 使用者名稱的輸入欄位。 - 登入資訊當中,密碼的輸入欄位。 - 密碼的輸入欄位。 - - 儲存編輯過的登入資訊。 儲存變更。 - - 編輯 編輯密碼 - - 新增登入資訊 新增密碼 - - 需要密碼 輸入密碼 - 必須輸入使用者名稱 - 輸入使用者名稱 必須輸入主機名稱 @@ -2424,7 +2365,7 @@ 感謝您回報! - 我們將在 24 小時內提供更新過的評論分析資訊,請稍候再回來。 + 我們將在 24 小時內提供更新過的評論分析資訊,請稍後再回來。 我們無法檢查這些評論 @@ -2614,6 +2555,9 @@ 關閉翻譯表單 + + 暫時無法使用某些設定。 + 翻譯 @@ -2636,6 +2580,9 @@ 請選擇一種語言,來管理該語言的「總是翻譯」與「永不翻譯」偏好設定。 + + 無法載入語言,請稍後再回來。 + 提供翻譯(預設) @@ -2659,6 +2606,8 @@ 移除 %1$s + + 無法載入網站清單,請稍後再回來。 要刪除 %1$s 嗎? @@ -2736,13 +2685,18 @@ 瀏覽上一頁 + + 開啟除錯抽屜 + 分頁工具 分頁數量 - 使用中 + 使用中 + + 使用中 未使用 @@ -2753,6 +2707,16 @@ 分頁產生工具 要產生的分頁數量 + + 文字欄位空白 + + 僅可輸入正整數 + + 僅可輸入大於 0 的數字 + + 超過單一操作中可產生的最大分頁數量(%1$s) 新增至使用中的分頁 @@ -2769,11 +2733,11 @@ 隱私權公告 - 送出 + 送出 - 關閉 + 關閉 - 感謝您的意見回饋! + 感謝您的意見回饋! 非常滿意 @@ -2785,6 +2749,14 @@ 非常不滿意 + + + 開啟問卷 + + 關閉問卷 + + 關閉 + 登入資訊 diff --git a/mobile/android/fenix/app/src/main/res/values/attrs.xml b/mobile/android/fenix/app/src/main/res/values/attrs.xml index edd57c6d88..fef61285a5 100644 --- a/mobile/android/fenix/app/src/main/res/values/attrs.xml +++ b/mobile/android/fenix/app/src/main/res/values/attrs.xml @@ -29,7 +29,7 @@ - + diff --git a/mobile/android/fenix/app/src/main/res/values/colors.xml b/mobile/android/fenix/app/src/main/res/values/colors.xml index 9abba577a2..d5d58df47e 100644 --- a/mobile/android/fenix/app/src/main/res/values/colors.xml +++ b/mobile/android/fenix/app/src/main/res/values/colors.xml @@ -27,11 +27,11 @@ @color/photonYellow20 - @color/photonGreen20 + @color/photonGreen20 - @color/photonRed10 + @color/photonRed10 - @color/photonBlue50A44 + @color/photonBlue50A44 @@ -47,11 +47,11 @@ @color/photonYellow60A40 - @color/photonGreen60 + @color/photonGreen60 - @color/photonRed30 + @color/photonRed30 - @color/photonBlue50 + @color/photonBlue50 @color/photonDarkGrey90 @@ -77,9 +77,9 @@ @color/photonDarkGrey90A40 - @color/photonRed70 + @color/photonRed70 - @color/photonRed70 + @color/photonRed70 @color/photonViolet70 @@ -116,10 +116,10 @@ @color/photonBlue30 @color/photonInk20 - @color/photonRed70 + @color/photonRed70 - @color/photonRed70 - @color/photonViolet60 + @color/photonRed70 + @color/photonViolet70 @color/photonBlue60 @color/photonPink60 @color/photonGreen60 @@ -150,15 +150,15 @@ @color/photonDarkGrey90A40 - @color/photonRed70 + @color/photonRed70 - @color/photonInk50 + @color/photonViolet90 - @color/photonInk50 + @color/photonViolet90 @color/photonInk90 @color/photonInk90 @@ -184,7 +184,7 @@ @color/photonViolet60 - @color/photonLightGrey30 + @color/photonDarkGrey05 @color/photonDarkGrey10 @@ -214,9 +214,9 @@ @color/photonLightGrey05A40 - @color/photonRed20 + @color/photonRed20 - @color/photonRed70 + @color/photonRed20 @color/photonViolet20 @@ -228,7 +228,7 @@ @color/photonLightGrey05 - @color/photonDarkGrey90 + @color/photonLightGrey05 @color/photonLightGrey05 @@ -251,9 +251,9 @@ @color/photonBlue30 @color/photonLightGrey05 - @color/photonRed20 + @color/photonRed20 - @color/photonRed70 + @color/photonRed20 @color/photonViolet20 @color/photonBlue20 @color/photonPink20 @@ -262,7 +262,7 @@ @color/photonLightGrey05 - @color/photonDarkGrey90 + @color/photonLightGrey05 @color/photonLightGrey05 @@ -272,7 +272,7 @@ - @color/photonDarkGrey05 + @color/photonInk05 @color/photonInk10 @color/photonLightGrey30 @@ -283,7 +283,7 @@ @color/photonLightGrey05A40 - @color/photonRed40 + @color/photonRed20 @color/photonViolet80 diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml index 8e94d3251f..678602bd9b 100644 --- a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml +++ b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml @@ -17,6 +17,7 @@ pref_key_accessibility_force_enable_zoom pref_key_advanced pref_key_language + pref_key_translation pref_key_data_choices pref_key_delete_browsing_data pref_key_delete_browsing_data_on_quit_preference @@ -53,6 +54,9 @@ pref_key_override_sync_tokenserver pref_key_override_push_server pref_key_sync_debug_quit + pref_key_sync_debug_network_error + pref_key_sync_debug_temporary_auth_error + pref_key_sync_debug_permanent_auth_error pref_key_customize pref_key_private_browsing pref_key_leakcanary @@ -164,7 +168,7 @@ pref_key_swipe_toolbar_switch_tabs pref_key_swipe_toolbar_show_tabs pref_key_recent_tabs - pref_key_recent_bookmarks + pref_key_recent_bookmarks pref_key_customization_category_toolbar diff --git a/mobile/android/fenix/app/src/main/res/values/static_strings.xml b/mobile/android/fenix/app/src/main/res/values/static_strings.xml index 66ec907040..6ed8c8901c 100644 --- a/mobile/android/fenix/app/src/main/res/values/static_strings.xml +++ b/mobile/android/fenix/app/src/main/res/values/static_strings.xml @@ -66,6 +66,12 @@ Stop Firefox Custom server changes will take effect on the next Firefox run. + + Simulate account network error + + Simulate temporary account auth error + + Simulate permanent account auth error Enable Tabs Tray to Compose rewrite diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml index a2a5d53866..9cfeb301e5 100644 --- a/mobile/android/fenix/app/src/main/res/values/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values/strings.xml @@ -47,11 +47,19 @@ - Recently saved + Recently saved - Show all saved bookmarks + Show all saved bookmarks - Remove + Remove + + + + Bookmarks + + Show all bookmarks + + Remove %1$s is produced by Mozilla. @@ -184,6 +192,10 @@ Add-ons Extensions + + Manage extensions + + Discover more extensions Account info @@ -202,18 +214,26 @@ Open in regular tab Add to Home screen + + Add to Home screen… Install Resync Find in page + + Find in page… Translate page + Save to collection… + Save to collection Share + + Share… Open in %1$s @@ -249,6 +269,33 @@ New private tab Passwords + + New in %1$s + + Switch to desktop site + + Tools + + Save + + Bookmark this page + + Edit bookmark + + Save as PDF… + + Turn on Reader View + + Turn off Reader View + + Translate page… + + Translated to %1$s + + Print… @@ -541,6 +588,10 @@ Reconnect to resume syncing Language + + Translation + + Translations Data choices @@ -641,7 +692,9 @@ Jump back in - Recent bookmarks + Recent bookmarks + + Bookmarks Recently visited @@ -774,6 +827,14 @@ Tab from %s + + + %1$s tabs closed: %2$d + + View recently closed tabs + Exceptions @@ -2403,6 +2464,8 @@ Not now Show original + + Original untranslated page loaded Done @@ -2576,6 +2639,8 @@ Debug Tools Navigate back + + Open debug drawer @@ -2583,7 +2648,10 @@ Tab count - Active + Active + + Active Inactive @@ -2601,6 +2669,31 @@ Add to private tabs + + + + Continue + + Complete this survey + + Privacy Notice + + Submit + + Close + + Thanks for your feedback! + + Very Satisfied + + Satisfied + + Neutral + + Dissatisfied + + Very Dissatisfied + Logins diff --git a/mobile/android/fenix/app/src/main/res/values/styles.xml b/mobile/android/fenix/app/src/main/res/values/styles.xml index c7a7932b8a..420511a5e2 100644 --- a/mobile/android/fenix/app/src/main/res/values/styles.xml +++ b/mobile/android/fenix/app/src/main/res/values/styles.xml @@ -34,9 +34,9 @@ @color/fx_mobile_text_color_accent @color/fx_mobile_text_color_warning + tools:ignore="UnusedResources">@color/fx_mobile_text_color_critical @color/fx_mobile_text_color_warning + tools:ignore="UnusedResources">@color/fx_mobile_text_color_critical @style/SelectPromptHeaderTextStyle @style/SelectPromptHeaderTextStyle @style/SelectPromptHeaderTextStyle @@ -67,7 +67,7 @@ @color/fx_mobile_text_color_disabled - @color/fx_mobile_text_color_warning + @color/fx_mobile_text_color_critical @color/fx_mobile_text_color_accent @@ -230,9 +230,9 @@ @style/BottomSheetPrivate @color/fx_mobile_private_text_color_warning + tools:ignore="UnusedResources">@color/fx_mobile_private_text_color_critical @color/fx_mobile_private_text_color_warning + tools:ignore="UnusedResources">@color/fx_mobile_private_text_color_critical @style/SelectPromptHeaderTextStyle @style/SelectPromptHeaderTextStyle @style/SelectPromptHeaderTextStyle @@ -263,7 +263,7 @@ @color/fx_mobile_private_text_color_disabled - @color/fx_mobile_private_text_color_warning + @color/fx_mobile_private_text_color_critical @color/fx_mobile_private_text_color_accent @@ -378,8 +378,8 @@ + - - -

    %about-version%

    -%about-content% - + + +

    %about-version%

    + %about-content% + diff --git a/mobile/android/focus-android/app/src/main/res/raw/gpl.html b/mobile/android/focus-android/app/src/main/res/raw/gpl.html index 1af9930b33..5b891f8ce7 100644 --- a/mobile/android/focus-android/app/src/main/res/raw/gpl.html +++ b/mobile/android/focus-android/app/src/main/res/raw/gpl.html @@ -1,14 +1,14 @@ - + - - + + GNU General Public License 3.0 - + - + - + +

    GNU General Public License 3.0

    -

    GNU General Public License 3.0

    - -
    Version 3, 29 June 2007
    +    
    Version 3, 29 June 2007
     
     Copyright © 2007 Free Software Foundation, Inc.
     <http://fsf.org/>
    @@ -33,639 +36,820 @@ Copyright © 2007 Free Software Foundation, Inc.
     Everyone is permitted to copy and distribute verbatim copies
     of this license document, but changing it is not allowed.
    -

    Preamble

    - -

    The GNU General Public License is a free, copyleft license for -software and other kinds of works.

    - -

    The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too.

    - -

    When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things.

    - -

    To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others.

    - -

    For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights.

    - -

    Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it.

    - -

    For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions.

    - -

    Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users.

    - -

    Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free.

    - -

    The precise terms and conditions for copying, distribution and -modification follow.

    - -

    TERMS AND CONDITIONS

    - -

    0. Definitions.

    - -

    “This License” refers to version 3 of the GNU General Public License.

    - -

    “Copyright” also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks.

    - -

    “The Program” refers to any copyrightable work licensed under this -License. Each licensee is addressed as “you”. “Licensees” and -“recipients” may be individuals or organizations.

    - -

    To “modify” a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a “modified version” of the -earlier work or a work “based on” the earlier work.

    - -

    A “covered work” means either the unmodified Program or a work based -on the Program.

    - -

    To “propagate” a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well.

    - -

    To “convey” a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying.

    - -

    An interactive user interface displays “Appropriate Legal Notices” -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion.

    - -

    1. Source Code.

    - -

    The “source code” for a work means the preferred form of the work -for making modifications to it. “Object code” means any non-source -form of a work.

    - -

    A “Standard Interface” means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language.

    - -

    The “System Libraries” of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -“Major Component”, in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it.

    - -

    The “Corresponding Source” for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work.

    - -

    The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source.

    - -

    The Corresponding Source for a work in source code form is that -same work.

    - -

    2. Basic Permissions.

    - -

    All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law.

    - -

    You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you.

    - -

    Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary.

    - -

    3. Protecting Users' Legal Rights From Anti-Circumvention Law.

    - -

    No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures.

    - -

    When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures.

    - -

    4. Conveying Verbatim Copies.

    - -

    You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program.

    - -

    You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee.

    - -

    5. Conveying Modified Source Versions.

    - -

    You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions:

    - -
      -
    • a) The work must carry prominent notices stating that you modified - it, and giving a relevant date.
    • - -
    • b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - “keep intact all notices”.
    • - -
    • c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it.
    • - -
    • d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so.
    • -
    - -

    A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -“aggregate” if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate.

    - -

    6. Conveying Non-Source Forms.

    - -

    You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways:

    - -
      -
    • a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange.
    • - -
    • b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge.
    • - -
    • c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b.
    • - -
    • d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements.
    • - -
    • e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d.
    • -
    - -

    A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work.

    - -

    A “User Product” is either (1) a “consumer product”, which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, “normally used” refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product.

    - -

    “Installation Information” for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made.

    - -

    If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM).

    - -

    The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network.

    - -

    Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying.

    - -

    7. Additional Terms.

    - -

    “Additional permissions” are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions.

    - -

    When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission.

    - -

    Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms:

    - -
      -
    • a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or
    • - -
    • b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or
    • - -
    • c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or
    • - -
    • d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or
    • - -
    • e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or
    • - -
    • f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors.
    • -
    - -

    All other non-permissive additional terms are considered “further -restrictions” within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying.

    - -

    If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms.

    - -

    Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way.

    - -

    8. Termination.

    - -

    You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11).

    - -

    However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation.

    - -

    Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice.

    - -

    Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10.

    - -

    9. Acceptance Not Required for Having Copies.

    - -

    You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so.

    - -

    10. Automatic Licensing of Downstream Recipients.

    - -

    Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License.

    - -

    An “entity transaction” is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts.

    - -

    You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it.

    - -

    11. Patents.

    - -

    A “contributor” is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's “contributor version”.

    - -

    A contributor's “essential patent claims” are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, “control” includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License.

    - -

    Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version.

    - -

    In the following three paragraphs, a “patent license” is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To “grant” such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party.

    - -

    If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. “Knowingly relying” means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid.

    - -

    If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it.

    - -

    A patent license is “discriminatory” if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007.

    - -

    Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law.

    - -

    12. No Surrender of Others' Freedom.

    - -

    If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program.

    - -

    13. Use with the GNU Affero General Public License.

    - -

    Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such.

    - -

    14. Revised Versions of this License.

    - -

    The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns.

    - -

    Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License “or any later version” applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation.

    - -

    If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program.

    - -

    Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version.

    - -

    15. Disclaimer of Warranty.

    - -

    THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

    - -

    16. Limitation of Liability.

    - -

    IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES.

    - -

    17. Interpretation of Sections 15 and 16.

    - -

    If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee.

    - -

    END OF TERMS AND CONDITIONS

    - -

    How to Apply These Terms to Your New Programs

    - -

    If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms.

    - -

    To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the “copyright” line and a pointer to where the full notice is found.

    - -
        <one line to give the program's name and a brief idea of what it does.>
    +    

    Preamble

    + +

    + The GNU General Public License is a free, copyleft license for software + and other kinds of works. +

    + +

    + The licenses for most software and other practical works are designed to + take away your freedom to share and change the works. By contrast, the GNU + General Public License is intended to guarantee your freedom to share and + change all versions of a program--to make sure it remains free software + for all its users. We, the Free Software Foundation, use the GNU General + Public License for most of our software; it applies also to any other work + released this way by its authors. You can apply it to your programs, too. +

    + +

    + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for them if you + wish), that you receive source code or can get it if you want it, that you + can change the software or use pieces of it in new free programs, and that + you know you can do these things. +

    + +

    + To protect your rights, we need to prevent others from denying you these + rights or asking you to surrender the rights. Therefore, you have certain + responsibilities if you distribute copies of the software, or if you + modify it: responsibilities to respect the freedom of others. +

    + +

    + For example, if you distribute copies of such a program, whether gratis or + for a fee, you must pass on to the recipients the same freedoms that you + received. You must make sure that they, too, receive or can get the source + code. And you must show them these terms so they know their rights. +

    + +

    + Developers that use the GNU GPL protect your rights with two steps: (1) + assert copyright on the software, and (2) offer you this License giving + you legal permission to copy, distribute and/or modify it. +

    + +

    + For the developers' and authors' protection, the GPL clearly explains that + there is no warranty for this free software. For both users' and authors' + sake, the GPL requires that modified versions be marked as changed, so + that their problems will not be attributed erroneously to authors of + previous versions. +

    + +

    + Some devices are designed to deny users access to install or run modified + versions of the software inside them, although the manufacturer can do so. + This is fundamentally incompatible with the aim of protecting users' + freedom to change the software. The systematic pattern of such abuse + occurs in the area of products for individuals to use, which is precisely + where it is most unacceptable. Therefore, we have designed this version of + the GPL to prohibit the practice for those products. If such problems + arise substantially in other domains, we stand ready to extend this + provision to those domains in future versions of the GPL, as needed to + protect the freedom of users. +

    + +

    + Finally, every program is threatened constantly by software patents. + States should not allow patents to restrict development and use of + software on general-purpose computers, but in those that do, we wish to + avoid the special danger that patents applied to a free program could make + it effectively proprietary. To prevent this, the GPL assures that patents + cannot be used to render the program non-free. +

    + +

    + The precise terms and conditions for copying, distribution and + modification follow. +

    + +

    TERMS AND CONDITIONS

    + +

    0. Definitions.

    + +

    + “This License” refers to version 3 of the GNU General Public + License. +

    + +

    + “Copyright” also means copyright-like laws that apply to other + kinds of works, such as semiconductor masks. +

    + +

    + “The Program” refers to any copyrightable work licensed under + this License. Each licensee is addressed as “you”. + “Licensees” and “recipients” may be individuals or + organizations. +

    + +

    + To “modify” a work means to copy from or adapt all or part of + the work in a fashion requiring copyright permission, other than the + making of an exact copy. The resulting work is called a “modified + version” of the earlier work or a work “based on” the + earlier work. +

    + +

    + A “covered work” means either the unmodified Program or a work + based on the Program. +

    + +

    + To “propagate” a work means to do anything with it that, + without permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. +

    + +

    + To “convey” a work means any kind of propagation that enables + other parties to make or receive copies. Mere interaction with a user + through a computer network, with no transfer of a copy, is not conveying. +

    + +

    + An interactive user interface displays “Appropriate Legal + Notices” to the extent that it includes a convenient and prominently + visible feature that (1) displays an appropriate copyright notice, and (2) + tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the work + under this License, and how to view a copy of this License. If the + interface presents a list of user commands or options, such as a menu, a + prominent item in the list meets this criterion. +

    + +

    1. Source Code.

    + +

    + The “source code” for a work means the preferred form of the + work for making modifications to it. “Object code” means any + non-source form of a work. +

    + +

    + A “Standard Interface” means an interface that either is an + official standard defined by a recognized standards body, or, in the case + of interfaces specified for a particular programming language, one that is + widely used among developers working in that language. +

    + +

    + The “System Libraries” of an executable work include anything, + other than the work as a whole, that (a) is included in the normal form of + packaging a Major Component, but which is not part of that Major + Component, and (b) serves only to enable use of the work with that Major + Component, or to implement a Standard Interface for which an + implementation is available to the public in source code form. A + “Major Component”, in this context, means a major essential + component (kernel, window system, and so on) of the specific operating + system (if any) on which the executable work runs, or a compiler used to + produce the work, or an object code interpreter used to run it. +

    + +

    + The “Corresponding Source” for a work in object code form + means all the source code needed to generate, install, and (for an + executable work) run the object code and to modify the work, including + scripts to control those activities. However, it does not include the + work's System Libraries, or general-purpose tools or generally available + free programs which are used unmodified in performing those activities but + which are not part of the work. For example, Corresponding Source includes + interface definition files associated with source files for the work, and + the source code for shared libraries and dynamically linked subprograms + that the work is specifically designed to require, such as by intimate + data communication or control flow between those subprograms and other + parts of the work. +

    + +

    + The Corresponding Source need not include anything that users can + regenerate automatically from other parts of the Corresponding Source. +

    + +

    + The Corresponding Source for a work in source code form is that same work. +

    + +

    2. Basic Permissions.

    + +

    + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program. The output from running a + covered work is covered by this License only if the output, given its + content, constitutes a covered work. This License acknowledges your rights + of fair use or other equivalent, as provided by copyright law. +

    + +

    + You may make, run and propagate covered works that you do not convey, + without conditions so long as your license otherwise remains in force. You + may convey covered works to others for the sole purpose of having them + make modifications exclusively for you, or provide you with facilities for + running those works, provided that you comply with the terms of this + License in conveying all material for which you do not control copyright. + Those thus making or running the covered works for you must do so + exclusively on your behalf, under your direction and control, on terms + that prohibit them from making any copies of your copyrighted material + outside their relationship with you. +

    + +

    + Conveying under any other circumstances is permitted solely under the + conditions stated below. Sublicensing is not allowed; section 10 makes it + unnecessary. +

    + +

    + 3. Protecting Users' Legal Rights From + Anti-Circumvention Law. +

    + +

    + No covered work shall be deemed part of an effective technological measure + under any applicable law fulfilling obligations under article 11 of the + WIPO copyright treaty adopted on 20 December 1996, or similar laws + prohibiting or restricting circumvention of such measures. +

    + +

    + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention + is effected by exercising rights under this License with respect to the + covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's + users, your or third parties' legal rights to forbid circumvention of + technological measures. +

    + +

    4. Conveying Verbatim Copies.

    + +

    + You may convey verbatim copies of the Program's source code as you receive + it, in any medium, provided that you conspicuously and appropriately + publish on each copy an appropriate copyright notice; keep intact all + notices stating that this License and any non-permissive terms added in + accord with section 7 apply to the code; keep intact all notices of the + absence of any warranty; and give all recipients a copy of this License + along with the Program. +

    + +

    + You may charge any price or no price for each copy that you convey, and + you may offer support or warranty protection for a fee. +

    + +

    5. Conveying Modified Source Versions.

    + +

    + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the terms of + section 4, provided that you also meet all of these conditions: +

    + +
      +
    • + a) The work must carry prominent notices stating that you modified it, + and giving a relevant date. +
    • + +
    • + b) The work must carry prominent notices stating that it is released + under this License and any conditions added under section 7. This + requirement modifies the requirement in section 4 to “keep intact + all notices”. +
    • + +
    • + c) You must license the entire work, as a whole, under this License to + anyone who comes into possession of a copy. This License will therefore + apply, along with any applicable section 7 additional terms, to the + whole of the work, and all its parts, regardless of how they are + packaged. This License gives no permission to license the work in any + other way, but it does not invalidate such permission if you have + separately received it. +
    • + +
    • + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your work need + not make them do so. +
    • +
    + +

    + A compilation of a covered work with other separate and independent works, + which are not by their nature extensions of the covered work, and which + are not combined with it such as to form a larger program, in or on a + volume of a storage or distribution medium, is called an + “aggregate” if the compilation and its resulting copyright are + not used to limit the access or legal rights of the compilation's users + beyond what the individual works permit. Inclusion of a covered work in an + aggregate does not cause this License to apply to the other parts of the + aggregate. +

    + +

    6. Conveying Non-Source Forms.

    + +

    + You may convey a covered work in object code form under the terms of + sections 4 and 5, provided that you also convey the machine-readable + Corresponding Source under the terms of this License, in one of these + ways: +

    + +
      +
    • + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium customarily used + for software interchange. +
    • + +
    • + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a written + offer, valid for at least three years and valid for as long as you offer + spare parts or customer support for that product model, to give anyone + who possesses the object code either (1) a copy of the Corresponding + Source for all the software in the product that is covered by this + License, on a durable physical medium customarily used for software + interchange, for a price no more than your reasonable cost of physically + performing this conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. +
    • + +
    • + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This alternative is + allowed only occasionally and noncommercially, and only if you received + the object code with such an offer, in accord with subsection 6b. +
    • + +
    • + d) Convey the object code by offering access from a designated place + (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to copy + the object code is a network server, the Corresponding Source may be on + a different server (operated by you or a third party) that supports + equivalent copying facilities, provided you maintain clear directions + next to the object code saying where to find the Corresponding Source. + Regardless of what server hosts the Corresponding Source, you remain + obligated to ensure that it is available for as long as needed to + satisfy these requirements. +
    • + +
    • + e) Convey the object code using peer-to-peer transmission, provided you + inform other peers where the object code and Corresponding Source of the + work are being offered to the general public at no charge under + subsection 6d. +
    • +
    + +

    + A separable portion of the object code, whose source code is excluded from + the Corresponding Source as a System Library, need not be included in + conveying the object code work. +

    + +

    + A “User Product” is either (1) a “consumer + product”, which means any tangible personal property which is + normally used for personal, family, or household purposes, or (2) anything + designed or sold for incorporation into a dwelling. In determining whether + a product is a consumer product, doubtful cases shall be resolved in favor + of coverage. For a particular product received by a particular user, + “normally used” refers to a typical or common use of that + class of product, regardless of the status of the particular user or of + the way in which the particular user actually uses, or expects or is + expected to use, the product. A product is a consumer product regardless + of whether the product has substantial commercial, industrial or + non-consumer uses, unless such uses represent the only significant mode of + use of the product. +

    + +

    + “Installation Information” for a User Product means any + methods, procedures, authorization keys, or other information required to + install and execute modified versions of a covered work in that User + Product from a modified version of its Corresponding Source. The + information must suffice to ensure that the continued functioning of the + modified object code is in no case prevented or interfered with solely + because modification has been made. +

    + +

    + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as part + of a transaction in which the right of possession and use of the User + Product is transferred to the recipient in perpetuity or for a fixed term + (regardless of how the transaction is characterized), the Corresponding + Source conveyed under this section must be accompanied by the Installation + Information. But this requirement does not apply if neither you nor any + third party retains the ability to install modified object code on the + User Product (for example, the work has been installed in ROM). +

    + +

    + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access to a + network may be denied when the modification itself materially and + adversely affects the operation of the network or violates the rules and + protocols for communication across the network. +

    + +

    + Corresponding Source conveyed, and Installation Information provided, in + accord with this section must be in a format that is publicly documented + (and with an implementation available to the public in source code form), + and must require no special password or key for unpacking, reading or + copying. +

    + +

    7. Additional Terms.

    + +

    + “Additional permissions” are terms that supplement the terms + of this License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall be + treated as though they were included in this License, to the extent that + they are valid under applicable law. If additional permissions apply only + to part of the Program, that part may be used separately under those + permissions, but the entire Program remains governed by this License + without regard to the additional permissions. +

    + +

    + When you convey a copy of a covered work, you may at your option remove + any additional permissions from that copy, or from any part of it. + (Additional permissions may be written to require their own removal in + certain cases when you modify the work.) You may place additional + permissions on material, added by you to a covered work, for which you + have or can give appropriate copyright permission. +

    + +

    + Notwithstanding any other provision of this License, for material you add + to a covered work, you may (if authorized by the copyright holders of that + material) supplement the terms of this License with terms: +

    + +
      +
    • + a) Disclaiming warranty or limiting liability differently from the terms + of sections 15 and 16 of this License; or +
    • + +
    • + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal Notices + displayed by works containing it; or +
    • + +
    • + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +
    • + +
    • + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or +
    • + +
    • + e) Declining to grant rights under trademark law for use of some trade + names, trademarks, or service marks; or +
    • + +
    • + f) Requiring indemnification of licensors and authors of that material + by anyone who conveys the material (or modified versions of it) with + contractual assumptions of liability to the recipient, for any liability + that these contractual assumptions directly impose on those licensors + and authors. +
    • +
    + +

    + All other non-permissive additional terms are considered “further + restrictions” within the meaning of section 10. If the Program as + you received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further restriction, + you may remove that term. If a license document contains a further + restriction but permits relicensing or conveying under this License, you + may add to a covered work material governed by the terms of that license + document, provided that the further restriction does not survive such + relicensing or conveying. +

    + +

    + If you add terms to a covered work in accord with this section, you must + place, in the relevant source files, a statement of the additional terms + that apply to those files, or a notice indicating where to find the + applicable terms. +

    + +

    + Additional terms, permissive or non-permissive, may be stated in the form + of a separately written license, or stated as exceptions; the above + requirements apply either way. +

    + +

    8. Termination.

    + +

    + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or modify + it is void, and will automatically terminate your rights under this + License (including any patent licenses granted under the third paragraph + of section 11). +

    + +

    + However, if you cease all violation of this License, then your license + from a particular copyright holder is reinstated (a) provisionally, unless + and until the copyright holder explicitly and finally terminates your + license, and (b) permanently, if the copyright holder fails to notify you + of the violation by some reasonable means prior to 60 days after the + cessation. +

    + +

    + Moreover, your license from a particular copyright holder is reinstated + permanently if the copyright holder notifies you of the violation by some + reasonable means, this is the first time you have received notice of + violation of this License (for any work) from that copyright holder, and + you cure the violation prior to 30 days after your receipt of the notice. +

    + +

    + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under this + License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. +

    + +

    + 9. Acceptance Not Required for Having Copies. +

    + +

    + You are not required to accept this License in order to receive or run a + copy of the Program. Ancillary propagation of a covered work occurring + solely as a consequence of using peer-to-peer transmission to receive a + copy likewise does not require acceptance. However, nothing other than + this License grants you permission to propagate or modify any covered + work. These actions infringe copyright if you do not accept this License. + Therefore, by modifying or propagating a covered work, you indicate your + acceptance of this License to do so. +

    + +

    + 10. Automatic Licensing of Downstream Recipients. +

    + +

    + Each time you convey a covered work, the recipient automatically receives + a license from the original licensors, to run, modify and propagate that + work, subject to this License. You are not responsible for enforcing + compliance by third parties with this License. +

    + +

    + An “entity transaction” is a transaction transferring control + of an organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered work + results from an entity transaction, each party to that transaction who + receives a copy of the work also receives whatever licenses to the work + the party's predecessor in interest had or could give under the previous + paragraph, plus a right to possession of the Corresponding Source of the + work from the predecessor in interest, if the predecessor has it or can + get it with reasonable efforts. +

    + +

    + You may not impose any further restrictions on the exercise of the rights + granted or affirmed under this License. For example, you may not impose a + license fee, royalty, or other charge for exercise of rights granted under + this License, and you may not initiate litigation (including a cross-claim + or counterclaim in a lawsuit) alleging that any patent claim is infringed + by making, using, selling, offering for sale, or importing the Program or + any portion of it. +

    + +

    11. Patents.

    + +

    + A “contributor” is a copyright holder who authorizes use under + this License of the Program or a work on which the Program is based. The + work thus licensed is called the contributor's “contributor + version”. +

    + +

    + A contributor's “essential patent claims” are all patent + claims owned or controlled by the contributor, whether already acquired or + hereafter acquired, that would be infringed by some manner, permitted by + this License, of making, using, or selling its contributor version, but do + not include claims that would be infringed only as a consequence of + further modification of the contributor version. For purposes of this + definition, “control” includes the right to grant patent + sublicenses in a manner consistent with the requirements of this License. +

    + +

    + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to make, + use, sell, offer for sale, import and otherwise run, modify and propagate + the contents of its contributor version. +

    + +

    + In the following three paragraphs, a “patent license” is any + express agreement or commitment, however denominated, not to enforce a + patent (such as an express permission to practice a patent or covenant not + to sue for patent infringement). To “grant” such a patent + license to a party means to make such an agreement or commitment not to + enforce a patent against the party. +

    + +

    + If you convey a covered work, knowingly relying on a patent license, and + the Corresponding Source of the work is not available for anyone to copy, + free of charge and under the terms of this License, through a publicly + available network server or other readily accessible means, then you must + either (1) cause the Corresponding Source to be so available, or (2) + arrange to deprive yourself of the benefit of the patent license for this + particular work, or (3) arrange, in a manner consistent with the + requirements of this License, to extend the patent license to downstream + recipients. “Knowingly relying” means you have actual + knowledge that, but for the patent license, your conveying the covered + work in a country, or your recipient's use of the covered work in a + country, would infringe one or more identifiable patents in that country + that you have reason to believe are valid. +

    + +

    + If, pursuant to or in connection with a single transaction or arrangement, + you convey, or propagate by procuring conveyance of, a covered work, and + grant a patent license to some of the parties receiving the covered work + authorizing them to use, propagate, modify or convey a specific copy of + the covered work, then the patent license you grant is automatically + extended to all recipients of the covered work and works based on it. +

    + +

    + A patent license is “discriminatory” if it does not include + within the scope of its coverage, prohibits the exercise of, or is + conditioned on the non-exercise of one or more of the rights that are + specifically granted under this License. You may not convey a covered work + if you are a party to an arrangement with a third party that is in the + business of distributing software, under which you make payment to the + third party based on the extent of your activity of conveying the work, + and under which the third party grants, to any of the parties who would + receive the covered work from you, a discriminatory patent license (a) in + connection with copies of the covered work conveyed by you (or copies made + from those copies), or (b) primarily for and in connection with specific + products or compilations that contain the covered work, unless you entered + into that arrangement, or that patent license was granted, prior to 28 + March 2007. +

    + +

    + Nothing in this License shall be construed as excluding or limiting any + implied license or other defenses to infringement that may otherwise be + available to you under applicable patent law. +

    + +

    12. No Surrender of Others' Freedom.

    + +

    + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey + the Program, the only way you could satisfy both those terms and this + License would be to refrain entirely from conveying the Program. +

    + +

    + 13. Use with the GNU Affero General Public + License. +

    + +

    + Notwithstanding any other provision of this License, you have permission + to link or combine any covered work with a work licensed under version 3 + of the GNU Affero General Public License into a single combined work, and + to convey the resulting work. The terms of this License will continue to + apply to the part which is the covered work, but the special requirements + of the GNU Affero General Public License, section 13, concerning + interaction through a network will apply to the combination as such. +

    + +

    14. Revised Versions of this License.

    + +

    + The Free Software Foundation may publish revised and/or new versions of + the GNU General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. +

    + +

    + Each version is given a distinguishing version number. If the Program + specifies that a certain numbered version of the GNU General Public + License “or any later version” applies to it, you have the + option of following the terms and conditions either of that numbered + version or of any later version published by the Free Software Foundation. + If the Program does not specify a version number of the GNU General Public + License, you may choose any version ever published by the Free Software + Foundation. +

    + +

    + If the Program specifies that a proxy can decide which future versions of + the GNU General Public License can be used, that proxy's public statement + of acceptance of a version permanently authorizes you to choose that + version for the Program. +

    + +

    + Later license versions may give you additional or different permissions. + However, no additional obligations are imposed on any author or copyright + holder as a result of your choosing to follow a later version. +

    + +

    15. Disclaimer of Warranty.

    + +

    + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF + THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +

    + +

    16. Limitation of Liability.

    + +

    + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL + ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE + PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. +

    + +

    17. Interpretation of Sections 15 and 16.

    + +

    + If the disclaimer of warranty and limitation of liability provided above + cannot be given local legal effect according to their terms, reviewing + courts shall apply local law that most closely approximates an absolute + waiver of all civil liability in connection with the Program, unless a + warranty or assumption of liability accompanies a copy of the Program in + return for a fee. +

    + +

    END OF TERMS AND CONDITIONS

    + +

    How to Apply These Terms to Your New Programs

    + +

    + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these + terms. +

    + +

    + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively state the + exclusion of warranty; and each file should have at least the + “copyright” line and a pointer to where the full notice is + found. +

    + +
    +    <one line to give the program's name and a brief idea of what it does.>
         Copyright (C) <year>  <name of author>
     
         This program is free software: you can redistribute it and/or modify
    @@ -680,34 +864,51 @@ the “copyright” line and a pointer to where the full notice is found
     
         You should have received a copy of the GNU General Public License
         along with this program.  If not, see <http://www.gnu.org/licenses/>.
    -
    +
    -

    Also add information on how to contact you by electronic and paper mail.

    +

    + Also add information on how to contact you by electronic and paper mail. +

    -

    If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode:

    +

    + If the program does terminal interaction, make it output a short notice + like this when it starts in an interactive mode: +

    -
        <program>  Copyright (C) <year>  <name of author>
    +    
    +    <program>  Copyright (C) <year>  <name of author>
         This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
         This is free software, and you are welcome to redistribute it
         under certain conditions; type `show c' for details.
    -
    - -

    The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an “about box”.

    - -

    You should also get your employer (if you work as a programmer) or school, -if any, to sign a “copyright disclaimer” for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -<http://www.gnu.org/licenses/>.

    - -

    The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -<http://www.gnu.org/philosophy/why-not-lgpl.html>.

    - +
    + +

    + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, your program's + commands might be different; for a GUI interface, you would use an + “about box”. +

    + +

    + You should also get your employer (if you work as a programmer) or school, + if any, to sign a “copyright disclaimer” for the program, if + necessary. For more information on this, and how to apply and follow the + GNU GPL, see <http://www.gnu.org/licenses/>. +

    + +

    + The GNU General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Lesser + General Public License instead of this License. But first, please read + <http://www.gnu.org/philosophy/why-not-lgpl.html>. +

    diff --git a/mobile/android/focus-android/app/src/main/res/raw/licenses.html b/mobile/android/focus-android/app/src/main/res/raw/licenses.html index 3f90063160..d6d71c6c67 100644 --- a/mobile/android/focus-android/app/src/main/res/raw/licenses.html +++ b/mobile/android/focus-android/app/src/main/res/raw/licenses.html @@ -1,948 +1,1299 @@ - + - - - - - - - -

    Licenses

    - -

    - Binaries of this product are made available to you by Mozilla under the Mozilla Public License 2.0 (MPL). -

    - -

    - More specifically, most of the source code for this product is available under the Mozilla Public License 2.0 (MPL) - and Apache License 2.0. Additional software components for this product are available under one of a variety of - other free and open source licenses. Those that require reproduction of the license text in the distribution are - given below. (Note: your copy of this product may not contain code covered by one or more of the licenses listed - here, depending on the exact product and version you choose.) -

    - - - - - - - - -

    Mozilla Public License 2.0

    - -
      -
    • Focus - https://github.com/mozilla-mobile/focus-android
    • -
    • Telemetry - https://github.com/mozilla-mobile/telemetry-android
    • -
    - -

    1. Definitions

    -
    -
    1.1. “Contributor”
    -

    means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.

    -
    -
    1.2. “Contributor Version”
    -

    means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.

    -
    -
    1.3. “Contribution”
    -

    means Covered Software of a particular Contributor.

    -
    -
    1.4. “Covered Software”
    -

    means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.

    -
    -
    1.5. “Incompatible With Secondary Licenses”
    -

    means

    -
      -
    1. that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or

    2. -
    3. that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.

    4. -
    -
    -
    1.6. “Executable Form”
    -

    means any form of the work other than Source Code Form.

    -
    -
    1.7. “Larger Work”
    -

    means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.

    -
    -
    1.8. “License”
    -

    means this document.

    -
    -
    1.9. “Licensable”
    -

    means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.

    -
    -
    1.10. “Modifications”
    -

    means any of the following:

    -
      -
    1. any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or

    2. -
    3. any new file in Source Code Form that contains any Covered Software.

    4. -
    -
    -
    1.11. “Patent Claims” of a Contributor
    -

    means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.

    -
    -
    1.12. “Secondary License”
    -

    means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.

    -
    -
    1.13. “Source Code Form”
    -

    means the form of the work preferred for making modifications.

    -
    -
    1.14. “You” (or “Your”)
    -

    means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.

    -
    -
    -

    2. License Grants and Conditions

    -

    2.1. Grants

    -

    Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:

    -
      -
    1. under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and

    2. -
    3. under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.

    4. -
    -

    2.2. Effective Date

    -

    The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.

    -

    2.3. Limitations on Grant Scope

    -

    The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:

    -
      -
    1. for any code that a Contributor has removed from Covered Software; or

    2. -
    3. for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or

    4. -
    5. under Patent Claims infringed by Covered Software in the absence of its Contributions.

    6. -
    -

    This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).

    -

    2.4. Subsequent Licenses

    -

    No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).

    -

    2.5. Representation

    -

    Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.

    -

    2.6. Fair Use

    -

    This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.

    -

    2.7. Conditions

    -

    Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.

    -

    3. Responsibilities

    -

    3.1. Distribution of Source Form

    -

    All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.

    -

    3.2. Distribution of Executable Form

    -

    If You distribute Covered Software in Executable Form then:

    -
      -
    1. such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and

    2. -
    3. You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.

    4. -
    -

    3.3. Distribution of a Larger Work

    -

    You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).

    -

    3.4. Notices

    -

    You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.

    -

    3.5. Application of Additional Terms

    -

    You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.

    -

    4. Inability to Comply Due to Statute or Regulation

    -

    If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.

    -

    5. Termination

    -

    5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.

    -

    5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.

    -

    5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.

    -

    6. Disclaimer of Warranty

    -

    Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.

    -

    7. Limitation of Liability

    -

    Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.

    -

    8. Litigation

    -

    Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.

    -

    9. Miscellaneous

    -

    This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.

    -

    10. Versions of the License

    -

    10.1. New Versions

    -

    Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.

    -

    10.2. Effect of New Versions

    -

    You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.

    -

    10.3. Modified Versions

    -

    If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).

    -

    10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

    -

    If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.

    -

    Exhibit A - Source Code Form License Notice

    -
    -

    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/.

    -
    -

    If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.

    -

    You may add additional accurate notices of copyright ownership.

    -

    Exhibit B - “Incompatible With Secondary Licenses” Notice

    -
    -

    This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.

    -
    - - - - - -

    Apache License 2.0

    - -
      -
    • com.android.support : animated-vector-drawable
    • -
    • org.jetbrains : annotations
    • -
    • com.android.support : appcompat-v7
    • -
    • com.android.support : cardview-v7
    • -
    • android.arch.lifecycle : common
    • -
    • android.arch.core : common
    • -
    • com.android.support : customtabs
    • -
    • com.android.support : design
    • -
    • android.arch.lifecycle : extensions
    • -
    • org.jetbrains.kotlin : kotlin-stdlib-jre7
    • -
    • org.jetbrains.kotlin : kotlin-stdlib
    • -
    • com.android.support : recyclerview-v7
    • -
    • android.arch.core : runtime
    • -
    • android.arch.lifecycle : runtime
    • -
    • com.android.support : support-annotations
    • -
    • com.android.support : support-compat
    • -
    • com.android.support : support-core-ui
    • -
    • com.android.support : support-core-utils
    • -
    • com.android.support : support-fragment
    • -
    • com.android.support : support-media-compat
    • -
    • com.android.support : support-v4
    • -
    • com.android.support :support-vector-drawable
    • -
    • com.android.support :transition
    • -
    - -

    1. Definitions.

    -

    "License" shall mean the terms and conditions for use, reproduction, and - distribution as defined by Sections 1 through 9 of this document.

    -

    "Licensor" shall mean the copyright owner or entity authorized by the - copyright owner that is granting the License.

    -

    "Legal Entity" shall mean the union of the acting entity and all other - entities that control, are controlled by, or are under common control with - that entity. For the purposes of this definition, "control" means (i) the - power, direct or indirect, to cause the direction or management of such - entity, whether by contract or otherwise, or (ii) ownership of fifty - percent (50%) or more of the outstanding shares, or (iii) beneficial - ownership of such entity.

    -

    "You" (or "Your") shall mean an individual or Legal Entity exercising - permissions granted by this License.

    -

    "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation source, - and configuration files.

    -

    "Object" form shall mean any form resulting from mechanical transformation - or translation of a Source form, including but not limited to compiled - object code, generated documentation, and conversions to other media types.

    -

    "Work" shall mean the work of authorship, whether in Source or Object form, - made available under the License, as indicated by a copyright notice that - is included in or attached to the work (an example is provided in the - Appendix below).

    -

    "Derivative Works" shall mean any work, whether in Source or Object form, - that is based on (or derived from) the Work and for which the editorial - revisions, annotations, elaborations, or other modifications represent, as - a whole, an original work of authorship. For the purposes of this License, - Derivative Works shall not include works that remain separable from, or - merely link (or bind by name) to the interfaces of, the Work and Derivative - Works thereof.

    -

    "Contribution" shall mean any work of authorship, including the original - version of the Work and any modifications or additions to that Work or - Derivative Works thereof, that is intentionally submitted to Licensor for - inclusion in the Work by the copyright owner or by an individual or Legal - Entity authorized to submit on behalf of the copyright owner. For the - purposes of this definition, "submitted" means any form of electronic, - verbal, or written communication sent to the Licensor or its - representatives, including but not limited to communication on electronic - mailing lists, source code control systems, and issue tracking systems that - are managed by, or on behalf of, the Licensor for the purpose of discussing - and improving the Work, but excluding communication that is conspicuously - marked or otherwise designated in writing by the copyright owner as "Not a - Contribution."

    -

    "Contributor" shall mean Licensor and any individual or Legal Entity on - behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work.

    -

    2. Grant of Copyright License. Subject to the - terms and conditions of this License, each Contributor hereby grants to You - a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, publicly - display, publicly perform, sublicense, and distribute the Work and such - Derivative Works in Source or Object form.

    -

    3. Grant of Patent License. Subject to the terms - and conditions of this License, each Contributor hereby grants to You a - perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, use, - offer to sell, sell, import, and otherwise transfer the Work, where such - license applies only to those patent claims licensable by such Contributor - that are necessarily infringed by their Contribution(s) alone or by - combination of their Contribution(s) with the Work to which such - Contribution(s) was submitted. If You institute patent litigation against - any entity (including a cross-claim or counterclaim in a lawsuit) alleging - that the Work or a Contribution incorporated within the Work constitutes - direct or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate as of the - date such litigation is filed.

    -

    4. Redistribution. You may reproduce and - distribute copies of the Work or Derivative Works thereof in any medium, - with or without modifications, and in Source or Object form, provided that - You meet the following conditions:

    -
      -
    1. You must give any other recipients of the Work or Derivative Works a - copy of this License; and
    2. - -
    3. You must cause any modified files to carry prominent notices stating - that You changed the files; and
    4. - -
    5. You must retain, in the Source form of any Derivative Works that You - distribute, all copyright, patent, trademark, and attribution notices from - the Source form of the Work, excluding those notices that do not pertain to - any part of the Derivative Works; and
    6. - -
    7. If the Work includes a "NOTICE" text file as part of its distribution, - then any Derivative Works that You distribute must include a readable copy - of the attribution notices contained within such NOTICE file, excluding - those notices that do not pertain to any part of the Derivative Works, in - at least one of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or documentation, - if provided along with the Derivative Works; or, within a display generated - by the Derivative Works, if and wherever such third-party notices normally - appear. The contents of the NOTICE file are for informational purposes only - and do not modify the License. You may add Your own attribution notices - within Derivative Works that You distribute, alongside or as an addendum to - the NOTICE text from the Work, provided that such additional attribution - notices cannot be construed as modifying the License. -
      -
      - You may add Your own copyright statement to Your modifications and may - provide additional or different license terms and conditions for use, - reproduction, or distribution of Your modifications, or for any such - Derivative Works as a whole, provided Your use, reproduction, and - distribution of the Work otherwise complies with the conditions stated in - this License. -
    8. - -
    - -

    5. Submission of Contributions. Unless You - explicitly state otherwise, any Contribution intentionally submitted for - inclusion in the Work by You to the Licensor shall be under the terms and - conditions of this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify the - terms of any separate license agreement you may have executed with Licensor - regarding such Contributions.

    -

    6. Trademarks. This License does not grant - permission to use the trade names, trademarks, service marks, or product - names of the Licensor, except as required for reasonable and customary use - in describing the origin of the Work and reproducing the content of the - NOTICE file.

    -

    7. Disclaimer of Warranty. Unless required by - applicable law or agreed to in writing, Licensor provides the Work (and - each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, - without limitation, any warranties or conditions of TITLE, - NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You - are solely responsible for determining the appropriateness of using or - redistributing the Work and assume any risks associated with Your exercise - of permissions under this License.

    -

    8. Limitation of Liability. In no event and - under no legal theory, whether in tort (including negligence), contract, or - otherwise, unless required by applicable law (such as deliberate and - grossly negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a result - of this License or out of the use or inability to use the Work (including - but not limited to damages for loss of goodwill, work stoppage, computer - failure or malfunction, or any and all other commercial damages or losses), - even if such Contributor has been advised of the possibility of such - damages.

    -

    9. Accepting Warranty or Additional Liability. - While redistributing the Work or Derivative Works thereof, You may choose - to offer, and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this License. - However, in accepting such obligations, You may act only on Your own behalf - and on Your sole responsibility, not on behalf of any other Contributor, - and only if You agree to indemnify, defend, and hold each Contributor - harmless for any liability incurred by, or claims asserted against, such - Contributor by reason of your accepting any such warranty or additional - liability.

    -

    END OF TERMS AND CONDITIONS

    - - - - - -

    GNU Lesser General Public License 2.1

    - -
      -
    • findbugs annotations (com.google.code.findbugs:annotations)
    • -
    - -

    GNU LESSER GENERAL PUBLIC LICENSE

    -

    -Version 2.1, February 1999 -

    - -

    -Copyright (C) 1991, 1999 Free Software Foundation, Inc.
    -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
    -Everyone is permitted to copy and distribute verbatim copies
    -of this license document, but changing it is not allowed. -

    - -

    -[This is the first released version of the Lesser GPL. It also counts
    - as the successor of the GNU Library Public License, version 2, hence
    - the version number 2.1.] -

    - -

    Preamble

    - -

    - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. -

    -

    - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. -

    -

    - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. -

    -

    - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. -

    -

    - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. -

    -

    - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. -

    -

    - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. -

    -

    - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. -

    -

    - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. -

    -

    - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. -

    -

    - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. -

    -

    - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. -

    -

    - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. -

    -

    - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. -

    -

    - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. -

    - -

    TERMS AND CONDITIONS FOR COPYING, -DISTRIBUTION AND MODIFICATION

    - - -

    -0. -This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". -

    -

    - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. -

    -

    - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) -

    -

    - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. -

    -

    - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. -

    -

    -1. -You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. -

    -

    - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. -

    -

    -2. -You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: -

    - -
      -
    • a) - The modified work must itself be a software library.
    • -
    • b) - You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change.
    • - -
    • c) - You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License.
    • - -
    • d) - If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. -

      - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.)

    • -
    - -

    -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Library, and can be -reasonably considered independent and separate works in themselves, then -this License, and its terms, do not apply to those sections when you -distribute them as separate works. But when you distribute the same -sections as part of a whole which is a work based on the Library, the -distribution of the whole must be on the terms of this License, whose -permissions for other licensees extend to the entire whole, and thus to -each and every part regardless of who wrote it. -

    -

    -Thus, it is not the intent of this section to claim rights or contest your -rights to work written entirely by you; rather, the intent is to exercise -the right to control the distribution of derivative or collective works -based on the Library. -

    -

    -In addition, mere aggregation of another work not based on the Library with -the Library (or with a work based on the Library) on a volume of a storage -or distribution medium does not bring the other work under the scope of -this License. -

    -

    -3. -You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. -

    -

    - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. -

    -

    - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. -

    -

    -4. -You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. -

    -

    - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. -

    -

    -5. -A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. -

    -

    - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. -

    -

    - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. -

    -

    - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) -

    -

    - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. -

    -

    -6. -As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. -

    -

    - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: -

    - -
      -
    • a) Accompany the work with the complete - corresponding machine-readable source code for the Library - including whatever changes were used in the work (which must be - distributed under Sections 1 and 2 above); and, if the work is an - executable linked with the Library, with the complete - machine-readable "work that uses the Library", as object code - and/or source code, so that the user can modify the Library and - then relink to produce a modified executable containing the - modified Library. (It is understood that the user who changes the - contents of definitions files in the Library will not necessarily - be able to recompile the application to use the modified - definitions.)
    • - -
    • b) Use a suitable shared library mechanism - for linking with the Library. A suitable mechanism is one that - (1) uses at run time a copy of the library already present on the - user's computer system, rather than copying library functions into - the executable, and (2) will operate properly with a modified - version of the library, if the user installs one, as long as the - modified version is interface-compatible with the version that the - work was made with.
    • - -
    • c) Accompany the work with a written offer, - valid for at least three years, to give the same user the - materials specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution.
    • - -
    • d) If distribution of the work is made by - offering access to copy from a designated place, offer equivalent - access to copy the above specified materials from the same - place.
    • - -
    • e) Verify that the user has already received - a copy of these materials or that you have already sent this user - a copy.
    • -
    - -

    - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. -

    -

    - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. -

    -

    -7. You may place library facilities that are a work -based on the Library side-by-side in a single library together with -other library facilities not covered by this License, and distribute -such a combined library, provided that the separate distribution of -the work based on the Library and of the other library facilities is -otherwise permitted, and provided that you do these two things: -

    - -
      -
    • a) Accompany the combined library with a copy - of the same work based on the Library, uncombined with any other - library facilities. This must be distributed under the terms of - the Sections above.
    • - -
    • b) Give prominent notice with the combined - library of the fact that part of it is a work based on the - Library, and explaining where to find the accompanying uncombined - form of the same work.
    • -
    - -

    -8. You may not copy, modify, sublicense, link with, -or distribute the Library except as expressly provided under this -License. Any attempt otherwise to copy, modify, sublicense, link -with, or distribute the Library is void, and will automatically -terminate your rights under this License. However, parties who have -received copies, or rights, from you under this License will not have -their licenses terminated so long as such parties remain in full -compliance. -

    -

    -9. -You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. -

    -

    -10. -Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. -

    -

    -11. -If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. -

    -

    -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. -

    -

    -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. -

    -

    -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. -

    -

    -12. -If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. -

    -

    -13. -The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. -

    -

    -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. -

    -

    -14. -If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. -

    -

    -NO WARRANTY -

    -

    -15. -BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. -

    -

    -16. -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. -

    - -

    END OF TERMS AND CONDITIONS

    - - - - - -

    adjust-android License

    - -

    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.

    - - + + + + + + +

    Licenses

    + +

    + Binaries of this product are made available to you by Mozilla under the + Mozilla Public License 2.0 (MPL). +

    + +

    + More specifically, most of the source code for this product is available + under the Mozilla Public License 2.0 (MPL) and Apache License 2.0. + Additional software components for this product are available under one of + a variety of other free and open source licenses. Those that require + reproduction of the license text in the distribution are given below. + (Note: your copy of this product may not contain code covered by one or + more of the licenses listed here, depending on the exact product and + version you choose.) +

    + + + + + + + +

    Mozilla Public License 2.0

    + +
      +
    • Focus - https://github.com/mozilla-mobile/focus-android
    • +
    • Telemetry - https://github.com/mozilla-mobile/telemetry-android
    • +
    + +

    1. Definitions

    +
    +
    1.1. “Contributor”
    +
    +

    + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. +

    +
    +
    1.2. “Contributor Version”
    +
    +

    + means the combination of the Contributions of others (if any) used by + a Contributor and that particular Contributor’s Contribution. +

    +
    +
    1.3. “Contribution”
    +

    means Covered Software of a particular Contributor.

    +
    1.4. “Covered Software”
    +
    +

    + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code Form, + and Modifications of such Source Code Form, in each case including + portions thereof. +

    +
    +
    1.5. “Incompatible With Secondary Licenses”
    +
    +

    means

    +
      +
    1. +

      + that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or +

      +
    2. +
    3. +

      + that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. +

      +
    4. +
    +
    +
    1.6. “Executable Form”
    +

    means any form of the work other than Source Code Form.

    +
    1.7. “Larger Work”
    +
    +

    + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. +

    +
    +
    1.8. “License”
    +

    means this document.

    +
    1.9. “Licensable”
    +
    +

    + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and all + of the rights conveyed by this License. +

    +
    +
    1.10. “Modifications”
    +
    +

    means any of the following:

    +
      +
    1. +

      + any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +

      +
    2. +
    3. +

      + any new file in Source Code Form that contains any Covered + Software. +

      +
    4. +
    +
    +
    1.11. “Patent Claims” of a Contributor
    +
    +

    + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. +

    +
    +
    1.12. “Secondary License”
    +
    +

    + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those licenses. +

    +
    +
    1.13. “Source Code Form”
    +
    +

    means the form of the work preferred for making modifications.

    +
    +
    1.14. “You” (or “Your”)
    +
    +

    + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, + is controlled by, or is under common control with You. For purposes of + this definition, “control” means (a) the power, direct or indirect, to + cause the direction or management of such entity, whether by contract + or otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. +

    +
    +
    +

    2. License Grants and Conditions

    +

    2.1. Grants

    +

    + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: +

    +
      +
    1. +

      + under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +

      +
    2. +
    3. +

      + under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. +

      +
    4. +
    +

    2.2. Effective Date

    +

    + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. +

    +

    2.3. Limitations on Grant Scope

    +

    + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: +

    +
      +
    1. +

      + for any code that a Contributor has removed from Covered Software; or +

      +
    2. +
    3. +

      + for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or +

      +
    4. +
    5. +

      + under Patent Claims infringed by Covered Software in the absence of + its Contributions. +

      +
    6. +
    +

    + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). +

    +

    2.4. Subsequent Licenses

    +

    + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). +

    +

    2.5. Representation

    +

    + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. +

    +

    2.6. Fair Use

    +

    + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. +

    +

    2.7. Conditions

    +

    + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. +

    +

    3. Responsibilities

    +

    3.1. Distribution of Source Form

    +

    + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source Code + Form of the Covered Software is governed by the terms of this License, and + how they can obtain a copy of this License. You may not attempt to alter + or restrict the recipients’ rights in the Source Code Form. +

    +

    3.2. Distribution of Executable Form

    +

    If You distribute Covered Software in Executable Form then:

    +
      +
    1. +

      + such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and +

      +
    2. +
    3. +

      + You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients’ rights in the Source Code Form under this License. +

      +
    4. +
    +

    3.3. Distribution of a Larger Work

    +

    + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this License + permits You to additionally distribute such Covered Software under the + terms of such Secondary License(s), so that the recipient of the Larger + Work may, at their option, further distribute the Covered Software under + the terms of either this License or such Secondary License(s). +

    +

    3.4. Notices

    +

    + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. +

    +

    3.5. Application of Additional Terms

    +

    + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. +

    +

    4. Inability to Comply Due to Statute or Regulation

    +

    + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply with the + terms of this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. +

    +

    5. Termination

    +

    + 5.1. The rights granted under this License will terminate automatically if + You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, this is the + first time You have received notice of non-compliance with this License + from such Contributor, and You become compliant prior to 30 days after + Your receipt of the notice. +

    +

    + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section 2.1 + of this License shall terminate. +

    +

    + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end + user license agreements (excluding distributors and resellers) which have + been validly granted by You or Your distributors under this License prior + to termination shall survive termination. +

    +

    6. Disclaimer of Warranty

    +

    + Covered Software is provided under this License on an “as is” basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is + free of defects, merchantable, fit for a particular purpose or + non-infringing. The entire risk as to the quality and performance of the + Covered Software is with You. Should any Covered Software prove + defective in any respect, You (not any Contributor) assume the cost of + any necessary servicing, repair, or correction. This disclaimer of + warranty constitutes an essential part of this License. No use of any + Covered Software is authorized under this License except under this + disclaimer. +

    +

    7. Limitation of Liability

    +

    + Under no circumstances and under no legal theory, whether tort + (including negligence), contract, or otherwise, shall any Contributor, + or anyone who distributes Covered Software as permitted above, be liable + to You for any direct, indirect, special, incidental, or consequential + damages of any character including, without limitation, damages for lost + profits, loss of goodwill, work stoppage, computer failure or + malfunction, or any and all other commercial damages or losses, even if + such party shall have been informed of the possibility of such damages. + This limitation of liability shall not apply to liability for death or + personal injury resulting from such party’s negligence to the extent + applicable law prohibits such limitation. Some jurisdictions do not + allow the exclusion or limitation of incidental or consequential + damages, so this exclusion and limitation may not apply to You. +

    +

    8. Litigation

    +

    + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party’s ability to bring cross-claims or + counter-claims. +

    +

    9. Miscellaneous

    +

    + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides + that the language of a contract shall be construed against the drafter + shall not be used to construe this License against a Contributor. +

    +

    10. Versions of the License

    +

    10.1. New Versions

    +

    + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. +

    +

    10.2. Effect of New Versions

    +

    + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. +

    +

    10.3. Modified Versions

    +

    + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). +

    +

    + 10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses +

    +

    + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. +

    +

    Exhibit A - Source Code Form License Notice

    +
    +

    + 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/. +

    +
    +

    + If it is not possible or desirable to put the notice in a particular file, + then You may include the notice in a location (such as a LICENSE file in a + relevant directory) where a recipient would be likely to look for such a + notice. +

    +

    You may add additional accurate notices of copyright ownership.

    +

    Exhibit B - “Incompatible With Secondary Licenses” Notice

    +
    +

    + This Source Code Form is “Incompatible With Secondary Licenses”, as + defined by the Mozilla Public License, v. 2.0. +

    +
    + + + + + +

    Apache License 2.0

    + +
      +
    • com.android.support : animated-vector-drawable
    • +
    • org.jetbrains : annotations
    • +
    • com.android.support : appcompat-v7
    • +
    • com.android.support : cardview-v7
    • +
    • android.arch.lifecycle : common
    • +
    • android.arch.core : common
    • +
    • com.android.support : customtabs
    • +
    • com.android.support : design
    • +
    • android.arch.lifecycle : extensions
    • +
    • org.jetbrains.kotlin : kotlin-stdlib-jre7
    • +
    • org.jetbrains.kotlin : kotlin-stdlib
    • +
    • com.android.support : recyclerview-v7
    • +
    • android.arch.core : runtime
    • +
    • android.arch.lifecycle : runtime
    • +
    • com.android.support : support-annotations
    • +
    • com.android.support : support-compat
    • +
    • com.android.support : support-core-ui
    • +
    • com.android.support : support-core-utils
    • +
    • com.android.support : support-fragment
    • +
    • com.android.support : support-media-compat
    • +
    • com.android.support : support-v4
    • +
    • com.android.support :support-vector-drawable
    • +
    • com.android.support :transition
    • +
    + +

    + 1. Definitions. +

    +

    + "License" shall mean the terms and conditions for use, reproduction, and + distribution as defined by Sections 1 through 9 of this document. +

    +

    + "Licensor" shall mean the copyright owner or entity authorized by the + copyright owner that is granting the License. +

    +

    + "Legal Entity" shall mean the union of the acting entity and all other + entities that control, are controlled by, or are under common control with + that entity. For the purposes of this definition, "control" means (i) the + power, direct or indirect, to cause the direction or management of such + entity, whether by contract or otherwise, or (ii) ownership of fifty + percent (50%) or more of the outstanding shares, or (iii) beneficial + ownership of such entity. +

    +

    + "You" (or "Your") shall mean an individual or Legal Entity exercising + permissions granted by this License. +

    +

    + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation source, + and configuration files. +

    +

    + "Object" form shall mean any form resulting from mechanical transformation + or translation of a Source form, including but not limited to compiled + object code, generated documentation, and conversions to other media + types. +

    +

    + "Work" shall mean the work of authorship, whether in Source or Object + form, made available under the License, as indicated by a copyright notice + that is included in or attached to the work (an example is provided in the + Appendix below). +

    +

    + "Derivative Works" shall mean any work, whether in Source or Object form, + that is based on (or derived from) the Work and for which the editorial + revisions, annotations, elaborations, or other modifications represent, as + a whole, an original work of authorship. For the purposes of this License, + Derivative Works shall not include works that remain separable from, or + merely link (or bind by name) to the interfaces of, the Work and + Derivative Works thereof. +

    +

    + "Contribution" shall mean any work of authorship, including the original + version of the Work and any modifications or additions to that Work or + Derivative Works thereof, that is intentionally submitted to Licensor for + inclusion in the Work by the copyright owner or by an individual or Legal + Entity authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submitted" means any form of electronic, + verbal, or written communication sent to the Licensor or its + representatives, including but not limited to communication on electronic + mailing lists, source code control systems, and issue tracking systems + that are managed by, or on behalf of, the Licensor for the purpose of + discussing and improving the Work, but excluding communication that is + conspicuously marked or otherwise designated in writing by the copyright + owner as "Not a Contribution." +

    +

    + "Contributor" shall mean Licensor and any individual or Legal Entity on + behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. +

    +

    + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor + hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable copyright license to reproduce, prepare + Derivative Works of, publicly display, publicly perform, sublicense, and + distribute the Work and such Derivative Works in Source or Object form. +

    +

    + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor + hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable (except as stated in this section) patent + license to make, have made, use, offer to sell, sell, import, and + otherwise transfer the Work, where such license applies only to those + patent claims licensable by such Contributor that are necessarily + infringed by their Contribution(s) alone or by combination of their + Contribution(s) with the Work to which such Contribution(s) was submitted. + If You institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work or a + Contribution incorporated within the Work constitutes direct or + contributory patent infringement, then any patent licenses granted to You + under this License for that Work shall terminate as of the date such + litigation is filed. +

    +

    + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works + thereof in any medium, with or without modifications, and in Source or + Object form, provided that You meet the following conditions: +

    +
      +
    1. + You must give any other recipients of the Work or Derivative Works a + copy of this License; and +
    2. + +
    3. + You must cause any modified files to carry prominent notices stating + that You changed the files; and +
    4. + +
    5. + You must retain, in the Source form of any Derivative Works that You + distribute, all copyright, patent, trademark, and attribution notices + from the Source form of the Work, excluding those notices that do not + pertain to any part of the Derivative Works; and +
    6. + +
    7. + If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of the + Derivative Works, in at least one of the following places: within a + NOTICE text file distributed as part of the Derivative Works; within the + Source form or documentation, if provided along with the Derivative + Works; or, within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents of the + NOTICE file are for informational purposes only and do not modify the + License. You may add Your own attribution notices within Derivative + Works that You distribute, alongside or as an addendum to the NOTICE + text from the Work, provided that such additional attribution notices + cannot be construed as modifying the License. +
      +
      + You may add Your own copyright statement to Your modifications and may + provide additional or different license terms and conditions for use, + reproduction, or distribution of Your modifications, or for any such + Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions stated + in this License. +
    8. +
    + +

    + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally + submitted for inclusion in the Work by You to the Licensor shall be under + the terms and conditions of this License, without any additional terms or + conditions. Notwithstanding the above, nothing herein shall supersede or + modify the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. +

    +

    + 6. Trademarks. This License does not grant permission to use the trade names, + trademarks, service marks, or product names of the Licensor, except as + required for reasonable and customary use in describing the origin of the + Work and reproducing the content of the NOTICE file. +

    +

    + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor + provides the Work (and each Contributor provides its Contributions) on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied, including, without limitation, any warranties or + conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any risks + associated with Your exercise of permissions under this License. +

    +

    + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including + negligence), contract, or otherwise, unless required by applicable law + (such as deliberate and grossly negligent acts) or agreed to in writing, + shall any Contributor be liable to You for damages, including any direct, + indirect, special, incidental, or consequential damages of any character + arising as a result of this License or out of the use or inability to use + the Work (including but not limited to damages for loss of goodwill, work + stoppage, computer failure or malfunction, or any and all other commercial + damages or losses), even if such Contributor has been advised of the + possibility of such damages. +

    +

    + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may + choose to offer, and charge a fee for, acceptance of support, warranty, + indemnity, or other liability obligations and/or rights consistent with + this License. However, in accepting such obligations, You may act only on + Your own behalf and on Your sole responsibility, not on behalf of any + other Contributor, and only if You agree to indemnify, defend, and hold + each Contributor harmless for any liability incurred by, or claims + asserted against, such Contributor by reason of your accepting any such + warranty or additional liability. +

    +

    END OF TERMS AND CONDITIONS

    + + + + + +

    GNU Lesser General Public License 2.1

    + +
      +
    • findbugs annotations (com.google.code.findbugs:annotations)
    • +
    + +

    GNU LESSER GENERAL PUBLIC LICENSE

    +

    Version 2.1, February 1999

    + +

    + Copyright (C) 1991, 1999 Free Software Foundation, Inc.
    + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed. +

    + +

    + [This is the first released version of the Lesser GPL. It also counts
    + as the successor of the GNU Library Public License, version 2, hence
    + the version number 2.1.] +

    + +

    Preamble

    + +

    + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public Licenses are + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. +

    +

    + This license, the Lesser General Public License, applies to some specially + designated software packages--typically libraries--of the Free Software + Foundation and other authors who decide to use it. You can use it too, but + we suggest you first think carefully about whether this license or the + ordinary General Public License is the better strategy to use in any + particular case, based on the explanations below. +

    +

    + When we speak of free software, we are referring to freedom of use, not + price. Our General Public Licenses are designed to make sure that you have + the freedom to distribute copies of free software (and charge for this + service if you wish); that you receive source code or can get it if you + want it; that you can change the software and use pieces of it in new free + programs; and that you are informed that you can do these things. +

    +

    + To protect your rights, we need to make restrictions that forbid + distributors to deny you these rights or to ask you to surrender these + rights. These restrictions translate to certain responsibilities for you + if you distribute copies of the library or if you modify it. +

    +

    + For example, if you distribute copies of the library, whether gratis or + for a fee, you must give the recipients all the rights that we gave you. + You must make sure that they, too, receive or can get the source code. If + you link other code with the library, you must provide complete object + files to the recipients, so that they can relink them with the library + after making changes to the library and recompiling it. And you must show + them these terms so they know their rights. +

    +

    + We protect your rights with a two-step method: (1) we copyright the + library, and (2) we offer you this license, which gives you legal + permission to copy, distribute and/or modify the library. +

    +

    + To protect each distributor, we want to make it very clear that there is + no warranty for the free library. Also, if the library is modified by + someone else and passed on, the recipients should know that what they have + is not the original version, so that the original author's reputation will + not be affected by problems that might be introduced by others. +

    +

    + Finally, software patents pose a constant threat to the existence of any + free program. We wish to make sure that a company cannot effectively + restrict the users of a free program by obtaining a restrictive license + from a patent holder. Therefore, we insist that any patent license + obtained for a version of the library must be consistent with the full + freedom of use specified in this license. +

    +

    + Most GNU software, including some libraries, is covered by the ordinary + GNU General Public License. This license, the GNU Lesser General Public + License, applies to certain designated libraries, and is quite different + from the ordinary General Public License. We use this license for certain + libraries in order to permit linking those libraries into non-free + programs. +

    +

    + When a program is linked with a library, whether statically or using a + shared library, the combination of the two is legally speaking a combined + work, a derivative of the original library. The ordinary General Public + License therefore permits such linking only if the entire combination fits + its criteria of freedom. The Lesser General Public License permits more + lax criteria for linking other code with the library. +

    +

    + We call this license the "Lesser" General Public License because it does + Less to protect the user's freedom than the ordinary General Public + License. It also provides other free software developers Less of an + advantage over competing non-free programs. These disadvantages are the + reason we use the ordinary General Public License for many libraries. + However, the Lesser license provides advantages in certain special + circumstances. +

    +

    + For example, on rare occasions, there may be a special need to encourage + the widest possible use of a certain library, so that it becomes a + de-facto standard. To achieve this, non-free programs must be allowed to + use the library. A more frequent case is that a free library does the same + job as widely used non-free libraries. In this case, there is little to + gain by limiting the free library to free software only, so we use the + Lesser General Public License. +

    +

    + In other cases, permission to use a particular library in non-free + programs enables a greater number of people to use a large body of free + software. For example, permission to use the GNU C Library in non-free + programs enables many more people to use the whole GNU operating system, + as well as its variant, the GNU/Linux operating system. +

    +

    + Although the Lesser General Public License is Less protective of the + users' freedom, it does ensure that the user of a program that is linked + with the Library has the freedom and the wherewithal to run that program + using a modified version of the Library. +

    +

    + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a "work + based on the library" and a "work that uses the library". The former + contains code derived from the library, whereas the latter must be + combined with the library in order to run. +

    + +

    + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +

    + +

    + 0. + This License Agreement applies to any software library or other program + which contains a notice placed by the copyright holder or other authorized + party saying it may be distributed under the terms of this Lesser General + Public License (also called "this License"). Each licensee is addressed as + "you". +

    +

    + A "library" means a collection of software functions and/or data prepared + so as to be conveniently linked with application programs (which use some + of those functions and data) to form executables. +

    +

    + The "Library", below, refers to any such software library or work which + has been distributed under these terms. A "work based on the Library" + means either the Library or any derivative work under copyright law: that + is to say, a work containing the Library or a portion of it, either + verbatim or with modifications and/or translated straightforwardly into + another language. (Hereinafter, translation is included without limitation + in the term "modification".) +

    +

    + "Source code" for a work means the preferred form of the work for making + modifications to it. For a library, complete source code means all the + source code for all modules it contains, plus any associated interface + definition files, plus the scripts used to control compilation and + installation of the library. +

    +

    + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running a + program using the Library is not restricted, and output from such a + program is covered only if its contents constitute a work based on the + Library (independent of the use of the Library in a tool for writing it). + Whether that is true depends on what the Library does and what the program + that uses the Library does. +

    +

    + 1. + You may copy and distribute verbatim copies of the Library's complete + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the notices + that refer to this License and to the absence of any warranty; and + distribute a copy of this License along with the Library. +

    +

    + You may charge a fee for the physical act of transferring a copy, and you + may at your option offer warranty protection in exchange for a fee. +

    +

    + 2. + You may modify your copy or copies of the Library or any portion of it, + thus forming a work based on the Library, and copy and distribute such + modifications or work under the terms of Section 1 above, provided that + you also meet all of these conditions: +

    + +
      +
    • + a) The modified work must itself be a software library. +
    • +
    • + b) You must cause the files modified to carry prominent + notices stating that you changed the files and the date of any change. +
    • + +
    • + c) You must cause the whole of the work to be licensed + at no charge to all third parties under the terms of this License. +
    • + +
    • + d) + If a facility in the modified Library refers to a function or a table of + data to be supplied by an application program that uses the facility, + other than as an argument passed when the facility is invoked, then you + must make a good faith effort to ensure that, in the event an + application does not supply such function or table, the facility still + operates, and performs whatever part of its purpose remains meaningful. +

      + (For example, a function in a library to compute square roots has a + purpose that is entirely well-defined independent of the application. + Therefore, Subsection 2d requires that any application-supplied + function or table used by this function must be optional: if the + application does not supply it, the square root function must still + compute square roots.) +

      +
    • +
    + +

    + These requirements apply to the modified work as a whole. If identifiable + sections of that work are not derived from the Library, and can be + reasonably considered independent and separate works in themselves, then + this License, and its terms, do not apply to those sections when you + distribute them as separate works. But when you distribute the same + sections as part of a whole which is a work based on the Library, the + distribution of the whole must be on the terms of this License, whose + permissions for other licensees extend to the entire whole, and thus to + each and every part regardless of who wrote it. +

    +

    + Thus, it is not the intent of this section to claim rights or contest your + rights to work written entirely by you; rather, the intent is to exercise + the right to control the distribution of derivative or collective works + based on the Library. +

    +

    + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. +

    +

    + 3. + You may opt to apply the terms of the ordinary GNU General Public License + instead of this License to a given copy of the Library. To do this, you + must alter all the notices that refer to this License, so that they refer + to the ordinary GNU General Public License, version 2, instead of to this + License. (If a newer version than version 2 of the ordinary GNU General + Public License has appeared, then you can specify that version instead if + you wish.) Do not make any other change in these notices. +

    +

    + Once this change is made in a given copy, it is irreversible for that + copy, so the ordinary GNU General Public License applies to all subsequent + copies and derivative works made from that copy. +

    +

    + This option is useful when you wish to copy part of the code of the + Library into a program that is not a library. +

    +

    + 4. + You may copy and distribute the Library (or a portion or derivative of it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you accompany it with the complete + corresponding machine-readable source code, which must be distributed + under the terms of Sections 1 and 2 above on a medium customarily used for + software interchange. +

    +

    + If distribution of object code is made by offering access to copy from a + designated place, then offering equivalent access to copy the source code + from the same place satisfies the requirement to distribute the source + code, even though third parties are not compelled to copy the source along + with the object code. +

    +

    + 5. + A program that contains no derivative of any portion of the Library, but + is designed to work with the Library by being compiled or linked with it, + is called a "work that uses the Library". Such a work, in isolation, is + not a derivative work of the Library, and therefore falls outside the + scope of this License. +

    +

    + However, linking a "work that uses the Library" with the Library creates + an executable that is a derivative of the Library (because it contains + portions of the Library), rather than a "work that uses the library". The + executable is therefore covered by this License. Section 6 states terms + for distribution of such executables. +

    +

    + When a "work that uses the Library" uses material from a header file that + is part of the Library, the object code for the work may be a derivative + work of the Library even though the source code is not. Whether this is + true is especially significant if the work can be linked without the + Library, or if the work is itself a library. The threshold for this to be + true is not precisely defined by law. +

    +

    + If such an object file uses only numerical parameters, data structure + layouts and accessors, and small macros and small inline functions (ten + lines or less in length), then the use of the object file is unrestricted, + regardless of whether it is legally a derivative work. (Executables + containing this object code plus portions of the Library will still fall + under Section 6.) +

    +

    + Otherwise, if the work is a derivative of the Library, you may distribute + the object code for the work under the terms of Section 6. Any executables + containing that work also fall under Section 6, whether or not they are + linked directly with the Library itself. +

    +

    + 6. + As an exception to the Sections above, you may also combine or link a + "work that uses the Library" with the Library to produce a work containing + portions of the Library, and distribute that work under terms of your + choice, provided that the terms permit modification of the work for the + customer's own use and reverse engineering for debugging such + modifications. +

    +

    + You must give prominent notice with each copy of the work that the Library + is used in it and that the Library and its use are covered by this + License. You must supply a copy of this License. If the work during + execution displays copyright notices, you must include the copyright + notice for the Library among them, as well as a reference directing the + user to the copy of this License. Also, you must do one of these things: +

    + +
      +
    • + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever changes + were used in the work (which must be distributed under Sections 1 and 2 + above); and, if the work is an executable linked with the Library, with + the complete machine-readable "work that uses the Library", as object + code and/or source code, so that the user can modify the Library and + then relink to produce a modified executable containing the modified + Library. (It is understood that the user who changes the contents of + definitions files in the Library will not necessarily be able to + recompile the application to use the modified definitions.) +
    • + +
    • + b) Use a suitable shared library mechanism for linking + with the Library. A suitable mechanism is one that (1) uses at run time + a copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) will + operate properly with a modified version of the library, if the user + installs one, as long as the modified version is interface-compatible + with the version that the work was made with. +
    • + +
    • + c) Accompany the work with a written offer, valid for + at least three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of performing + this distribution. +
    • + +
    • + d) If distribution of the work is made by offering + access to copy from a designated place, offer equivalent access to copy + the above specified materials from the same place. +
    • + +
    • + e) Verify that the user has already received a copy of + these materials or that you have already sent this user a copy. +
    • +
    + +

    + For an executable, the required form of the "work that uses the Library" + must include any data and utility programs needed for reproducing the + executable from it. However, as a special exception, the materials to be + distributed need not include anything that is normally distributed (in + either source or binary form) with the major components (compiler, kernel, + and so on) of the operating system on which the executable runs, unless + that component itself accompanies the executable. +

    +

    + It may happen that this requirement contradicts the license restrictions + of other proprietary libraries that do not normally accompany the + operating system. Such a contradiction means you cannot use both them and + the Library together in an executable that you distribute. +

    +

    + 7. You may place library facilities that are a work based + on the Library side-by-side in a single library together with other + library facilities not covered by this License, and distribute such a + combined library, provided that the separate distribution of the work + based on the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: +

    + +
      +
    • + a) Accompany the combined library with a copy of the + same work based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the Sections + above. +
    • + +
    • + b) Give prominent notice with the combined library of + the fact that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. +
    • +
    + +

    + 8. You may not copy, modify, sublicense, link with, or + distribute the Library except as expressly provided under this License. + Any attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, or + rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. +

    +

    + 9. + You are not required to accept this License, since you have not signed it. + However, nothing else grants you permission to modify or distribute the + Library or its derivative works. These actions are prohibited by law if + you do not accept this License. Therefore, by modifying or distributing + the Library (or any work based on the Library), you indicate your + acceptance of this License to do so, and all its terms and conditions for + copying, distributing or modifying the Library or works based on it. +

    +

    + 10. + Each time you redistribute the Library (or any work based on the Library), + the recipient automatically receives a license from the original licensor + to copy, distribute, link with or modify the Library subject to these + terms and conditions. You may not impose any further restrictions on the + recipients' exercise of the rights granted herein. You are not responsible + for enforcing compliance by third parties with this License. +

    +

    + 11. + If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Library at all. For example, if a patent license would not + permit royalty-free redistribution of the Library by all those who receive + copies directly or indirectly through you, then the only way you could + satisfy both it and this License would be to refrain entirely from + distribution of the Library. +

    +

    + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. +

    +

    + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any such + claims; this section has the sole purpose of protecting the integrity of + the free software distribution system which is implemented by public + license practices. Many people have made generous contributions to the + wide range of software distributed through that system in reliance on + consistent application of that system; it is up to the author/donor to + decide if he or she is willing to distribute software through any other + system and a licensee cannot impose that choice. +

    +

    + This section is intended to make thoroughly clear what is believed to be a + consequence of the rest of this License. +

    +

    + 12. + If the distribution and/or use of the Library is restricted in certain + countries either by patents or by copyrighted interfaces, the original + copyright holder who places the Library under this License may add an + explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. +

    +

    + 13. + The Free Software Foundation may publish revised and/or new versions of + the Lesser General Public License from time to time. Such new versions + will be similar in spirit to the present version, but may differ in detail + to address new problems or concerns. +

    +

    + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and conditions + either of that version or of any later version published by the Free + Software Foundation. If the Library does not specify a license version + number, you may choose any version ever published by the Free Software + Foundation. +

    +

    + 14. + If you wish to incorporate parts of the Library into other free programs + whose distribution conditions are incompatible with these, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the two + goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. +

    +

    + NO WARRANTY +

    +

    + 15. + BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR + THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE + LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. +

    +

    + 16. + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL + ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES + ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT + LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES + SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE + WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +

    + +

    END OF TERMS AND CONDITIONS

    + + + + + +

    adjust-android License

    + +

    + 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. +

    + diff --git a/mobile/android/focus-android/app/src/main/res/raw/rights.html b/mobile/android/focus-android/app/src/main/res/raw/rights.html index c34c1c5d11..73028929ea 100644 --- a/mobile/android/focus-android/app/src/main/res/raw/rights.html +++ b/mobile/android/focus-android/app/src/main/res/raw/rights.html @@ -1,56 +1,62 @@ - + - + - +

    %your-rights-content1%

    %your-rights-content2%

    %your-rights-content3%

    %your-rights-content4%

    %your-rights-content5%

    - + diff --git a/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml b/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml index 9d72cf1542..b9f2b886ac 100644 --- a/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml +++ b/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml @@ -84,10 +84,13 @@ sharing an URL. --> Ukomone\'xik pa - + Kayuj ucholajil b\'anoj rech nik\'onel? + Chachapa\' on chachupu\' we ub\'ixikil are chi man k\'o ta uk\'axk\'olil chi rij uyujik ucholajil b\'anoj. + + + Chachapa\' on chajililej we ub\'ixikil are chi man k\'o ta uk\'axk\'olil chi rij uyujik ucholajil b\'anoj. + Uchupik nik\'ob\'al rech b\'antal kanoq @@ -365,6 +368,9 @@ Jeqeb\'am taq ch\'ich\' rech tzukunem + + Chacha\' ch\'ich\' rech tzukub\'al + Utzalijisaxik taq ch\'ich\' rech tzukunem ya\'om chi uloq\n @@ -691,6 +697,48 @@ Taq ya‘b‘al b‘e rech k‘olib‘al + + Unitz\'arisaxik ub\'ixikil rech kuki + + + Utzijik + + + Uchupik + + + Unitz\'arisaxik ub\'ixikil rech kuki + + + Are chi man k\'i ta ub\'ixikil kawilo ruk\' retzelaxik le taq tz\'onoj rech taq kuki. + + --> + Unitz\'arisaxik ub\'ixikil rech kuki + + + ON pa we uk\'olib\'al web\' + + + Man toq\'am ta chi le uk\'olib\'al web\' + + + CHUPUM chech we uk\'olib\'al web\' + + + Unitz\'arisaxik ub\'ixikil rech kuki + + + CHUPUM chech we uk\'olib\'al web\' + + + TZIJOM pa we uk\'olib\'al web\' + + + Utzijik unitz\'arisaxik ub\'ixikil rech taq kuki chech %1$s? + + + Uchupik unitz\'arisaxik ub\'ixikil rech taq kuki chech %1$s? + Ja\'i\' diff --git a/mobile/android/focus-android/app/src/main/res/values-su/strings.xml b/mobile/android/focus-android/app/src/main/res/values-su/strings.xml index dad5f7fdd4..3a5d546d6d 100644 --- a/mobile/android/focus-android/app/src/main/res/values-su/strings.xml +++ b/mobile/android/focus-android/app/src/main/res/values-su/strings.xml @@ -713,7 +713,7 @@ Kurangan spanduk ku otomatis nolak rekés réréméh, lamun bisa. --> - Kurangan Spanduk Réréméh + Reduksi Spanduk Réréméh Hurungkeun jang ieu loka @@ -725,7 +725,7 @@ Pareum jang ieu loka - Kurangan Spanduk Réréméh + Reduksi Spanduk Réréméh Pareum jang ieu loka diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt index 7892c347b6..94f2410913 100644 --- a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt +++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt @@ -32,6 +32,10 @@ class TestFocusApplication : FocusApplication() { } override fun initializeNimbus() = Unit + override fun initializeTelemetry() = Unit + override fun finishSetupMegazord() = Unit + + override fun initializeWebExtensionSupport() = Unit } /** @@ -50,6 +54,13 @@ class FakeEngine : Engine { override val version: EngineVersion get() = throw NotImplementedError("Not needed for test") + override fun isTranslationsEngineSupported( + onSuccess: (Boolean) -> Unit, + onError: (Throwable) -> Unit, + ) { + // do nothing + } + override fun createView(context: Context, attrs: AttributeSet?): EngineView = throw UnsupportedOperationException() diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt index 3f9d9270c4..69b7a031b4 100644 --- a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt +++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt @@ -8,7 +8,9 @@ import android.app.Activity import android.content.res.Resources import android.view.View import android.view.Window +import android.view.WindowInsetsController import android.view.WindowManager +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import mozilla.components.browser.engine.gecko.GeckoEngineView import mozilla.components.browser.toolbar.BrowserToolbar @@ -33,6 +35,7 @@ import org.mozilla.focus.ext.showAsFixed import org.mozilla.focus.utils.Settings import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) internal class FullScreenIntegrationTest { @@ -128,7 +131,8 @@ internal class FullScreenIntegrationTest { @Test @Suppress("DEPRECATION") - fun `WHEN entering immersive mode THEN hide all system bars`() { + @Config(sdk = [28]) + fun `WHEN entering immersive mode THEN hide all system bars in SDK 28`() { val decorView: View = mock() val activityWindow: Window = mock() val activity: Activity = mock() @@ -158,9 +162,51 @@ internal class FullScreenIntegrationTest { verify(decorView).setOnApplyWindowInsetsListener(any()) } + @Test + fun `WHEN entering immersive mode THEN hide all system bars`() { + val decorView: View = mock() + val activityWindow: Window = mock() + val activity: Activity = mock() + val layoutParams = WindowManager.LayoutParams() + val insetsController: WindowInsetsController = mock() + + doReturn(activityWindow).`when`(activity).window + doReturn(decorView).`when`(activityWindow).decorView + doReturn(layoutParams).`when`(activityWindow).attributes + doReturn(insetsController).`when`(activityWindow).insetsController + + val integration = FullScreenIntegration( + activity, + mock(), + null, + mock(), + mock(), + mock(), + mock(), + mock(), + mock(), + ) + + integration.switchToImmersiveMode() + + // verify hiding system bars + verify(insetsController).hide(WindowInsetsCompat.Type.systemBars()) + + verify(activityWindow).setFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + ) + + assertEquals( + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, + layoutParams.layoutInDisplayCutoutMode, + ) + } + @Test @Suppress("DEPRECATION") - fun `GIVEN immersive mode WHEN exitImmersiveModeIfNeeded is called THEN show the system bars`() { + @Config(sdk = [28]) + fun `GIVEN immersive mode WHEN exitImmersiveModeIfNeeded is called THEN show the system bars on SDK 28`() { val decorView: View = mock() val activityWindow: Window = mock() val activity: Activity = mock() @@ -188,6 +234,43 @@ internal class FullScreenIntegrationTest { verify(decorView).setOnApplyWindowInsetsListener(null) } + @Test + fun `GIVEN immersive mode WHEN exitImmersiveModeIfNeeded is called THEN show the system bars`() { + val decorView: View = mock() + val activityWindow: Window = mock() + val activity: Activity = mock() + val layoutParams = WindowManager.LayoutParams() + val insetsController: WindowInsetsController = mock() + + doReturn(activityWindow).`when`(activity).window + doReturn(decorView).`when`(activityWindow).decorView + doReturn(layoutParams).`when`(activityWindow).attributes + doReturn(insetsController).`when`(activityWindow).insetsController + + val integration = FullScreenIntegration( + activity, + mock(), + null, + mock(), + mock(), + mock(), + mock(), + mock(), + mock(), + ) + + integration.exitImmersiveMode() + + verify(insetsController).show(WindowInsetsCompat.Type.systemBars()) + verify(decorView).setOnApplyWindowInsetsListener(null) + verify(activityWindow).clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) + + assertEquals( + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, + layoutParams.layoutInDisplayCutoutMode, + ) + } + @Test fun `GIVEN a11y is enabled WHEN enterBrowserFullscreen THEN hide the toolbar`() { val toolbar: BrowserToolbar = mock() @@ -335,7 +418,8 @@ internal class FullScreenIntegrationTest { } @Test - fun `WHEN exiting fullscreen THEN put browser in fullscreen, hide system bars and enter immersive mode`() { + @Config(sdk = [28]) + fun `WHEN exiting fullscreen THEN put browser in fullscreen, hide system bars and enter immersive mode in SDK 28`() { val toolbar: BrowserToolbar = mock() val engineView: GeckoEngineView = mock() doReturn(mock()).`when`(engineView).asView() @@ -371,4 +455,48 @@ internal class FullScreenIntegrationTest { verify(integration).exitImmersiveMode() verify(statusBar).isVisible = true } + + @Test + fun `WHEN exiting fullscreen THEN put browser in fullscreen, hide system bars and enter immersive mode in`() { + val toolbar: BrowserToolbar = mock() + val engineView: GeckoEngineView = mock() + doReturn(mock()).`when`(engineView).asView() + + val settings: Settings = mock() + doReturn(false).`when`(settings).isAccessibilityEnabled() + + val resources: Resources = mock() + val activityWindow: Window = mock() + val decorView: View = mock() + val windowAttributes = WindowManager.LayoutParams() + val activity: Activity = mock() + val insetsController: WindowInsetsController = mock() + + doReturn(activityWindow).`when`(activity).window + doReturn(decorView).`when`(activityWindow).decorView + doReturn(windowAttributes).`when`(activityWindow).attributes + doReturn(resources).`when`(activity).resources + doReturn(insetsController).`when`(activityWindow).insetsController + + val statusBar: View = mock() + val integration = spy( + FullScreenIntegration( + activity, + mock(), + null, + mock(), + settings, + toolbar, + statusBar, + engineView, + mock(), + ), + ) + + integration.fullScreenChanged(false) + + verify(integration).exitBrowserFullscreen() + verify(integration).exitImmersiveMode() + verify(statusBar).isVisible = true + } } diff --git a/mobile/android/focus-android/app/src/test/resources/robolectric.properties b/mobile/android/focus-android/app/src/test/resources/robolectric.properties index 4359826c57..05f43c0edf 100644 --- a/mobile/android/focus-android/app/src/test/resources/robolectric.properties +++ b/mobile/android/focus-android/app/src/test/resources/robolectric.properties @@ -1,3 +1 @@ -# Needed until Robolectric supports SDK 29+ -sdk=28 application=org.mozilla.focus.EmptyFocusApplication diff --git a/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties index b82aa23a4f..23e87f9b0b 100644 --- a/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,7 @@ +# 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/. + distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt b/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt index ecc69fe4f1..4e7483969f 100644 --- a/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt +++ b/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt @@ -21,7 +21,7 @@ object FocusVersions { object AndroidX { const val constraint_layout_compose = "1.0.1" const val splashscreen = "1.0.1" - const val transition = "1.4.1" + const val transition = "1.5.0" } object Google { diff --git a/mobile/android/focus-android/settings.gradle b/mobile/android/focus-android/settings.gradle index 0f5151cfe6..9db906b61f 100644 --- a/mobile/android/focus-android/settings.gradle +++ b/mobile/android/focus-android/settings.gradle @@ -31,6 +31,7 @@ plugins { ext.topsrcdir = rootProject.projectDir.absolutePath.minus("mobile/android/focus-android") apply from: file('../shared-settings.gradle') +apply from: file('../autopublish-settings.gradle') include ':app' @@ -47,90 +48,16 @@ gradle.projectsLoaded { -> } } } - - def appServicesSrcDir = null - if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { - appServicesSrcDir = gradle.getProperty('localProperties.autoPublish.application-services.dir') - } else if (gradle.hasProperty('localProperties.branchBuild.application-services.dir')) { - appServicesSrcDir = gradle.getProperty('localProperties.branchBuild.application-services.dir') - } - if (appServicesSrcDir) { - if (appServicesSrcDir.startsWith("/")) { - apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle" - } else { - apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle" - } - } } } -def log(message) { - logger.lifecycle("[settings] ${message}") -} - -def runCmd(cmd, workingDir, successMessage, captureStdout=true) { - def proc = cmd.execute(null, new File(workingDir)) - def standardOutput = captureStdout ? new ByteArrayOutputStream() : System.out - proc.consumeProcessOutput(standardOutput, System.err) - proc.waitFor() - - if (proc.exitValue() != 0) { - throw new GradleException("Process '${cmd}' finished with non-zero exit value ${proc.exitValue()}"); - } else { - log(successMessage) +def projectLocalProperties = file("local.properties").with { localPropertiesFile -> + def localProperties = new Properties() + if (localPropertiesFile.canRead()) { + localPropertiesFile.withInputStream { localProperties.load(it) } } - return captureStdout ? standardOutput : null -} - -////////////////////////////////////////////////////////////////////////// -// Local development enhancements -////////////////////////////////////////////////////////////////////////// - -Properties localProperties = null -String settingAppServicesPath = "autoPublish.application-services.dir" -String settingGleanPath = "autoPublish.glean.dir" - -if (file('local.properties').canRead()) { - localProperties = new Properties() - localProperties.load(file('local.properties').newDataInputStream()) - log('Loaded local.properties') -} else { - log('Missing local.properties; see https://github.com/mozilla-mobile/focus-android#localproperties-helpers for instructions.') + localProperties } - -if (localProperties != null) { - localProperties.each { prop -> - gradle.ext.set("localProperties.${prop.key}", prop.value) - } - - String appServicesLocalPath = localProperties.getProperty(settingAppServicesPath) - - if (appServicesLocalPath != null) { - log("Enabling automatic publication of application-services from: $appServicesLocalPath") - // Windows can't execute .py files directly, so we assume a "manually installed" python, - // which comes with a "py" launcher and respects the shebang line to specify the version. - def publishAppServicesCmd = []; - if (System.properties['os.name'].toLowerCase().contains('windows')) { - publishAppServicesCmd << "py"; - } - publishAppServicesCmd << "./automation/publish_to_maven_local_if_modified.py"; - runCmd(publishAppServicesCmd, appServicesLocalPath, "Published application-services for local development.", false) - } else { - log("Disabled auto-publication of application-services. Enable it by settings '$settingAppServicesPath' in local.properties") - } - - String gleanLocalPath = localProperties.getProperty(settingGleanPath) - - if (gleanLocalPath != null) { - log("Enabling automatic publication of Glean from: $gleanLocalPath") - // As above, hacks to execute .py files on Windows. - def publishGleanCmd = []; - if (System.properties['os.name'].toLowerCase().contains('windows')) { - publishGleanCmd << "py"; - } - publishGleanCmd << "./build-scripts/publish_to_maven_local_if_modified.py"; - runCmd(publishGleanCmd, gleanLocalPath, "Published Glean for local development.", false) - } else { - log("Disabled auto-publication of Glean. Enable it by settings '$settingGleanPath' in local.properties") - } +projectLocalProperties.each { prop -> + gradle.ext."localProperties.${prop.key}" = prop.value } diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 658677a509..57c4d18d56 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -102,7 +102,6 @@ import org.mozilla.geckoview.OverscrollEdgeEffect; import org.mozilla.geckoview.PanZoomController; import org.mozilla.geckoview.ProfilerController; import org.mozilla.geckoview.RuntimeSettings; -import org.mozilla.geckoview.RuntimeTelemetry; import org.mozilla.geckoview.ScreenLength; import org.mozilla.geckoview.SessionAccessibility; import org.mozilla.geckoview.SessionFinder; @@ -886,7 +885,6 @@ package org.mozilla.geckoview { method public boolean getRemoteDebuggingEnabled(); method @Nullable public GeckoRuntime getRuntime(); method @Nullable public Rect getScreenSizeOverride(); - method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate(); method public boolean getTranslationsOfferPopup(); method @NonNull public String getTrustedRecursiveResolverUri(); method public int getTrustedRecusiveResolverMode(); @@ -969,7 +967,6 @@ package org.mozilla.geckoview { method @NonNull public GeckoRuntimeSettings.Builder preferredColorScheme(int); method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int); - method @Deprecated @DeprecationSchedule(id="geckoview-gvst",version=127) @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate); method @NonNull public GeckoRuntimeSettings.Builder translationsOfferPopup(boolean); method @NonNull public GeckoRuntimeSettings.Builder trustedRecursiveResolverMode(int); method @NonNull public GeckoRuntimeSettings.Builder trustedRecursiveResolverUri(@NonNull String); @@ -1081,6 +1078,7 @@ package org.mozilla.geckoview { field public static final int FINDER_DISPLAY_DRAW_LINK_OUTLINE = 4; field public static final int FINDER_DISPLAY_HIGHLIGHT_ALL = 1; field public static final int FINDER_FIND_BACKWARDS = 1; + field public static final int FINDER_FIND_FORWARD = 0; field public static final int FINDER_FIND_LINKS_ONLY = 8; field public static final int FINDER_FIND_MATCH_CASE = 2; field public static final int FINDER_FIND_WHOLE_WORD = 4; @@ -2168,28 +2166,6 @@ package org.mozilla.geckoview { method @AnyThread @NonNull protected abstract Settings newSettings(@Nullable Settings); } - public final class RuntimeTelemetry { - ctor protected RuntimeTelemetry(); - } - - public static interface RuntimeTelemetry.Delegate { - method @AnyThread default public void onBooleanScalar(@NonNull RuntimeTelemetry.Metric); - method @AnyThread default public void onHistogram(@NonNull RuntimeTelemetry.Histogram); - method @AnyThread default public void onLongScalar(@NonNull RuntimeTelemetry.Metric); - method @AnyThread default public void onStringScalar(@NonNull RuntimeTelemetry.Metric); - } - - public static class RuntimeTelemetry.Histogram extends RuntimeTelemetry.Metric { - ctor protected Histogram(); - field public final boolean isCategorical; - } - - public static class RuntimeTelemetry.Metric { - ctor protected Metric(); - field @NonNull public final String name; - field @NonNull public final T value; - } - public class ScreenLength { method @AnyThread @NonNull public static ScreenLength bottom(); method @AnyThread @NonNull public static ScreenLength fromPixels(double); @@ -2396,9 +2372,11 @@ package org.mozilla.geckoview { } public static class TranslationsController.SessionTranslation.TranslationState { - ctor public TranslationState(@Nullable TranslationsController.SessionTranslation.TranslationPair, @Nullable String, @Nullable TranslationsController.SessionTranslation.DetectedLanguages, @NonNull Boolean); + ctor @Deprecated @DeprecationSchedule(version=130,id="translation-state-deprecated-constructor") public TranslationState(@Nullable TranslationsController.SessionTranslation.TranslationPair, @Nullable String, @Nullable TranslationsController.SessionTranslation.DetectedLanguages, @NonNull Boolean); + ctor public TranslationState(@Nullable TranslationsController.SessionTranslation.TranslationPair, @Nullable String, @Nullable TranslationsController.SessionTranslation.DetectedLanguages, @NonNull Boolean, @NonNull Boolean); field @Nullable public final TranslationsController.SessionTranslation.DetectedLanguages detectedLanguages; field @Nullable public final String error; + field @NonNull public final Boolean hasVisibleChange; field @NonNull public final Boolean isEngineReady; field @Nullable public final TranslationsController.SessionTranslation.TranslationPair requestedTranslationPair; } @@ -2792,6 +2770,7 @@ package org.mozilla.geckoview { method @UiThread default public void onInstallationFailed(@Nullable WebExtension, @NonNull WebExtension.InstallException); method @UiThread default public void onInstalled(@NonNull WebExtension); method @UiThread default public void onInstalling(@NonNull WebExtension); + method @UiThread default public void onOptionalPermissionsChanged(@NonNull WebExtension); method @UiThread default public void onReady(@NonNull WebExtension); method @UiThread default public void onUninstalled(@NonNull WebExtension); method @UiThread default public void onUninstalling(@NonNull WebExtension); diff --git a/mobile/android/geckoview/build.gradle b/mobile/android/geckoview/build.gradle index 040cd2fea8..8a8ba698ec 100644 --- a/mobile/android/geckoview/build.gradle +++ b/mobile/android/geckoview/build.gradle @@ -229,6 +229,8 @@ configurations { dependencies { implementation ComponentsDependencies.androidx_annotation + implementation ComponentsDependencies.androidx_collection + implementation ComponentsDependencies.androidx_core implementation "com.google.android.gms:play-services-fido:20.0.1" implementation "org.yaml:snakeyaml:2.2" @@ -384,20 +386,6 @@ android.libraryVariants.all { variant -> } } -android.libraryVariants.all { variant -> - // At this point, the Android-Gradle plugin has created all the Android - // tasks and configurations. This is the time for us to declare additional - // Glean files to package into AAR files. This packs `metrics.yaml` in the - // root of the AAR, sibling to `AndroidManifest.xml` and `classes.jar`. By - // default, consumers of the AAR will ignore this file, but consumers that - // look for it can find it (provided GeckoView is a `module()` dependency - // and not a `project()` dependency.) Under the hood this uses that the - // task provided by `packageLibraryProvider` task is a Maven `Zip` task, - // and we can simply extend its inputs. See - // https://android.googlesource.com/platform/tools/base/+/0cbe8846f7d02c0bb6f07156b9f4fde16d96d329/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/BundleAar.kt#94. - variant.packageLibraryProvider.get().from("${topsrcdir}/toolkit/components/telemetry/geckoview/streaming/metrics.yaml") -} - apply plugin: 'maven-publish' version = getVersionNumber() diff --git a/mobile/android/geckoview/src/androidTest/assets/web_extensions/optional-permission-all-urls/manifest.json b/mobile/android/geckoview/src/androidTest/assets/web_extensions/optional-permission-all-urls/manifest.json new file mode 100644 index 0000000000..c1c8fd9caa --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/optional-permission-all-urls/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 3, + "name": "optional-permission-all-urls", + "browser_specific_settings": { + "gecko": { + "id": "optional-permission-all-urls@example.com" + } + }, + "version": "1.0", + "description": "Request optional extension origins for all urls.", + "optional_permissions": ["geolocation", "activeTab"], + "host_permissions": ["http://*/", "https://*/", "file://*/*"] +} diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt index 2ec305f913..06b946ba3d 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt @@ -2029,9 +2029,6 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) { @IgnoreCrash @Test fun contentCrashIgnored() { - // TODO: Bug 1673953 - assumeThat(sessionRule.env.isFission, equalTo(false)) - // TODO: bug 1710940 assumeThat(sessionRule.env.isIsolatedProcess, equalTo(false)) diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoViewTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoViewTest.kt index 82af2c6475..c2ecb652a2 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoViewTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoViewTest.kt @@ -11,7 +11,6 @@ import android.view.View import android.view.ViewStructure import android.view.autofill.AutofillId import android.view.autofill.AutofillValue -import androidx.core.view.ViewCompat import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest @@ -79,7 +78,7 @@ class GeckoViewTest : BaseSessionTest() { activityRule.scenario.onActivity { assertThat( "View should be attached", - ViewCompat.isAttachedToWindow(it.view), + it.view.isAttachedToWindow(), equalTo(true), ) it.view.session!!.acquireDisplay() diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt index f3141c661c..301995c95c 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt @@ -12,7 +12,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports -import org.junit.Assert +import org.junit.Assert.* import org.junit.Assume.assumeThat import org.junit.Test import org.junit.runner.RunWith @@ -25,6 +25,7 @@ import org.mozilla.geckoview.GeckoSession.ContentDelegate import org.mozilla.geckoview.GeckoSession.ProgressDelegate import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay +import org.mozilla.geckoview.test.util.UiThreadUtils import java.lang.IllegalStateException import kotlin.math.absoluteValue import kotlin.math.max @@ -142,6 +143,27 @@ class ScreenshotTest : BaseSessionTest() { } } + @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH) + @Test + fun capturePixelsFailsWhenCompositorNotReady() { + sessionRule.display?.let { display -> + mainSession.close() + var exceptionListenerCalled = false + val result = display.capturePixels() + result.exceptionally { error: Throwable -> + assertTrue(error is IllegalStateException) + exceptionListenerCalled = true + result + }.accept { + fail("screenshot shouldn't complete successfully after session is closed") + } + UiThreadUtils.waitForCondition( + { exceptionListenerCalled }, + sessionRule.env.defaultTimeoutMillis, + ) + } ?: run { fail("no display found") } + } + // This tests tries to catch problems like Bug 1644561. @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH) @Test @@ -430,7 +452,7 @@ class ScreenshotTest : BaseSessionTest() { .capture() .exceptionally( OnExceptionListener { error: Throwable -> - Assert.assertTrue(error is OutOfMemoryError) + assertTrue(error is OutOfMemoryError) fromException(error) }, ) diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt deleted file mode 100644 index 42286c47a7..0000000000 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- - * Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -package org.mozilla.geckoview.test - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.MediumTest -import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.geckoview.GeckoResult -import org.mozilla.geckoview.RuntimeTelemetry -import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled - -@RunWith(AndroidJUnit4::class) -@MediumTest -class TelemetryTest : BaseSessionTest() { - @Test - fun testOnTelemetryReceived() { - // Let's make sure we batch the telemetry calls. - sessionRule.setPrefsUntilTestEnd( - mapOf("toolkit.telemetry.geckoview.batchDurationMS" to 100000), - ) - - val expectedHistograms = listOf(401, 12, 1, 109, 2000) - val receivedHistograms = mutableListOf() - val histogram = GeckoResult() - val stringScalar = GeckoResult() - val booleanScalar = GeckoResult() - val longScalar = GeckoResult() - - sessionRule.addExternalDelegateUntilTestEnd( - RuntimeTelemetry.Delegate::class, - sessionRule::setTelemetryDelegate, - { sessionRule.setTelemetryDelegate(null) }, - object : RuntimeTelemetry.Delegate { - @AssertCalled - override fun onHistogram(metric: RuntimeTelemetry.Histogram) { - if (metric.name != "TELEMETRY_TEST_STREAMING") { - return - } - - assertThat( - "The histogram should not be categorical", - metric.isCategorical, - equalTo(false), - ) - - receivedHistograms.addAll(metric.value.toList()) - - if (receivedHistograms.size == expectedHistograms.size) { - histogram.complete(null) - } - } - - @AssertCalled - override fun onStringScalar(metric: RuntimeTelemetry.Metric) { - if (metric.name != "telemetry.test.string_kind") { - return - } - - assertThat( - "Metric value should match", - metric.value, - equalTo("test scalar"), - ) - - stringScalar.complete(null) - } - - @AssertCalled - override fun onBooleanScalar(metric: RuntimeTelemetry.Metric) { - if (metric.name != "telemetry.test.boolean_kind") { - return - } - - assertThat( - "Metric value should match", - metric.value, - equalTo(true), - ) - - booleanScalar.complete(null) - } - - @AssertCalled - override fun onLongScalar(metric: RuntimeTelemetry.Metric) { - if (metric.name != "telemetry.test.unsigned_int_kind") { - return - } - - assertThat( - "Metric value should match", - metric.value, - equalTo(1234L), - ) - - longScalar.complete(null) - } - }, - ) - - sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", expectedHistograms[0]) - sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", expectedHistograms[1]) - sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", expectedHistograms[2]) - sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", expectedHistograms[3]) - - sessionRule.setScalar("telemetry.test.boolean_kind", true) - sessionRule.setScalar("telemetry.test.unsigned_int_kind", 1234) - sessionRule.setScalar("telemetry.test.string_kind", "test scalar") - - // Forces flushing telemetry data at next histogram. - sessionRule.setPrefsUntilTestEnd( - mapOf("toolkit.telemetry.geckoview.batchDurationMS" to 0), - ) - sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", expectedHistograms[4]) - - sessionRule.waitForResult(histogram) - sessionRule.waitForResult(stringScalar) - sessionRule.waitForResult(booleanScalar) - sessionRule.waitForResult(longScalar) - - assertThat( - "Metric values should match", - receivedHistograms, - equalTo(expectedHistograms), - ) - } -} diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TranslationsTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TranslationsTest.kt index a2c4eede62..966eba73b6 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TranslationsTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TranslationsTest.kt @@ -6,6 +6,7 @@ package org.mozilla.geckoview.test import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import org.json.JSONObject import org.junit.After @@ -78,7 +79,7 @@ class TranslationsTest : BaseSessionTest() { handled.complete(null) } }) - var expectedTranslateEvent = JSONObject( + val expectedTranslateEvent = JSONObject( """ { "actor":{ @@ -89,6 +90,7 @@ class TranslationsTest : BaseSessionTest() { "docLangTag": "es" }, "requestedTranslationPair": null, + "hasVisibleChange": false, "error": null, "isEngineReady": false } @@ -153,28 +155,43 @@ class TranslationsTest : BaseSessionTest() { val translate = sessionRule.session.sessionTranslation!!.translate("en", "es", null) try { sessionRule.waitForResult(translate) - assertTrue("Translate should complete.", true) + // When testing from AS, this path is possible. + if (!sessionRule.env.isAutomation) { + assertTrue("Translate should complete.", true) + } } catch (e: Exception) { - assertTrue("Should not have an exception while translating.", false) + if (sessionRule.env.isAutomation) { + assertTrue("Expect an exception while translating in automation.", true) + } } // Options should work as expected - var options = TranslationOptions.Builder().downloadModel(true).build() + val options = TranslationOptions.Builder().downloadModel(true).build() val translateOptions = sessionRule.session.sessionTranslation!!.translate("en", "es", options) try { sessionRule.waitForResult(translateOptions) - assertTrue("Translate should complete with options.", true) + // When testing from AS, this path is possible. + if (!sessionRule.env.isAutomation) { + assertTrue("Translate should complete with options.", true) + } } catch (e: Exception) { - assertTrue("Should not have an exception while translating with options.", false) + if (sessionRule.env.isAutomation) { + assertTrue("Expect an exception while translating in automation.", true) + } } // Language tags should be fault tolerant of minor variations val longLanguageTag = sessionRule.session.sessionTranslation!!.translate("EN", "ES", null) try { sessionRule.waitForResult(longLanguageTag) - assertTrue("Translate should complete with longer language tag.", true) + // When testing from AS, this path is possible. + if (!sessionRule.env.isAutomation) { + assertTrue("Translate should complete with longer language tag.", true) + } } catch (e: Exception) { - assertTrue("Should not have an exception while translating with a longer language tag.", false) + if (sessionRule.env.isAutomation) { + assertTrue("Expect an exception while translating in automation.", true) + } } } @@ -244,9 +261,14 @@ class TranslationsTest : BaseSessionTest() { val translate = sessionRule.session.sessionTranslation!!.translate("es", "en", null) try { sessionRule.waitForResult(translate) - assertTrue("Should be able to translate.", true) + // When testing from AS, this path is possible. + if (!sessionRule.env.isAutomation) { + assertTrue("Should be able to translate.", true) + } } catch (e: Exception) { - assertTrue("Should not have an exception.", false) + if (sessionRule.env.isAutomation) { + assertTrue("Expect an exception while translating in automation.", true) + } } } @@ -443,10 +465,10 @@ class TranslationsTest : BaseSessionTest() { @Test fun testGetLanguageSettings() { // Note: Test endpoint is using a mocked response and doesn't reflect actual prefs - var languageSettings: Map = + val languageSettings: Map = sessionRule.waitForResult(TranslationsController.RuntimeTranslation.getLanguageSettings()) - var frLanguageSetting = sessionRule.waitForResult(TranslationsController.RuntimeTranslation.getLanguageSetting("fr")) + val frLanguageSetting = sessionRule.waitForResult(TranslationsController.RuntimeTranslation.getLanguageSetting("fr")) if (sessionRule.env.isAutomation) { assertTrue("FR was correctly set to ALWAYS via full query.", languageSettings["fr"] == ALWAYS) @@ -621,4 +643,73 @@ class TranslationsTest : BaseSessionTest() { } } } + + @Test + fun hasVisibleChangeTest() { + mainSession.loadTestPath(TRANSLATIONS_ES) + mainSession.waitForPageStop() + + val handled = GeckoResult() + var delegateCalled = 0 + sessionRule.delegateUntilTestEnd(object : Delegate { + @AssertCalled(count = 2) + override fun onTranslationStateChange( + session: GeckoSession, + translationState: TranslationState?, + ) { + delegateCalled++ + + if (delegateCalled == 1) { + assertFalse("Initially not visibly changed.", translationState!!.hasVisibleChange) + } + + if (delegateCalled == 2) { + assertTrue("After a translation, the DOM should be visibly changed.", translationState!!.hasVisibleChange) + handled.complete(null) + } + } + }) + val notTranslated = JSONObject( + """ + { + "actor":{ + "languageState":{ + "detectedLanguages": { + "userLangTag": "en", + "isDocLangTagSupported": true, + "docLangTag": "es" + }, + "requestedTranslationPair": null, + "hasVisibleChange": false, + "error": null, + "isEngineReady": false + } + } + } + """.trimIndent(), + ) + mainSession.triggerLanguageStateChange(notTranslated) + + val translated = JSONObject( + """ + { + "actor":{ + "languageState":{ + "detectedLanguages": { + "userLangTag": "en", + "isDocLangTagSupported": true, + "docLangTag": "es" + }, + "requestedTranslationPair": {"fromLanguage" : "es" , "toLanguage" : "en"}, + "hasVisibleChange": true, + "error": null, + "isEngineReady": true + } + } + } + """.trimIndent(), + ) + mainSession.triggerLanguageStateChange(translated) + sessionRule.waitForResult(handled) + } } diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt index fa5caa8693..413ca7e2f6 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt @@ -482,6 +482,101 @@ class WebExtensionTest : BaseSessionTest() { sessionRule.waitForResult(controller.uninstall(extension)) } + @Test + fun optionalOriginsNormalized() { + // For mv3 extensions the host_permissions are being granted automatically at install time + // but this test needs them to not be granted yet and so we explicitly opt-out in this test. + sessionRule.setPrefsUntilTestEnd( + mapOf( + "extensions.originControls.grantByDefault" to false, + ), + ) + + var extension = sessionRule.waitForResult( + controller.ensureBuiltIn( + "resource://android/assets/web_extensions/optional-permission-all-urls/", + "optional-permission-all-urls@example.com", + ), + ) + + assertEquals("optional-permission-all-urls@example.com", extension.id) + + var grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + assertArrayEquals( + "grantedOptionalPermissions must be initially empty", + arrayOf(), + grantedOptionalOrigins, + ) + + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf(), + arrayOf("http://*/", "https://*/", "file://*/*"), + ), + ) + + grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + assertArrayEquals( + "grantedOptionalPermissions must be [http://*/*, https://*/*, file://*/*]", + arrayOf("http://*/*", "https://*/*", "file://*/*"), + grantedOptionalOrigins, + ) + + sessionRule.waitForResult(controller.uninstall(extension)) + } + + @Test + fun onOptionalPermissionsChanged() { + var extension = sessionRule.waitForResult( + controller.ensureBuiltIn( + "resource://android/assets/web_extensions/optional-permission-request/", + "optional-permission-request@example.com", + ), + ) + + assertEquals("optional-permission-request@example.com", extension.id) + + var grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + var grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + + sessionRule.delegateDuringNextWait(object : WebExtensionController.AddonManagerDelegate { + @AssertCalled(count = 1) + override fun onOptionalPermissionsChanged(updatedExtension: WebExtension) { + grantedOptionalPermissions = updatedExtension.metaData.grantedOptionalPermissions + grantedOptionalOrigins = updatedExtension.metaData.grantedOptionalOrigins + assertNull(updatedExtension) + assertArrayEquals( + "grantedOptionalPermissions must be [activeTab, geolocation].", + arrayOf("activeTab", "geolocation"), + grantedOptionalPermissions, + ) + assertArrayEquals( + "grantedOptionalPermissions must be [*://example.com/*].", + arrayOf("*://example.com/*"), + grantedOptionalOrigins, + ) + } + }) + + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf("activeTab", "geolocation"), + arrayOf("*://example.com/*"), + ), + ) + sessionRule.waitForResult(controller.uninstall(extension)) + } + private fun assertBodyBorderEqualTo(expected: String) { val color = mainSession.evaluateJS("document.body.style.borderColor") assertThat( @@ -2763,7 +2858,7 @@ class WebExtensionTest : BaseSessionTest() { "extensions.install.requireBuiltInCerts" to false, "extensions.update.requireBuiltInCerts" to false, "extensions.getAddons.cache.enabled" to true, - "extensions.getAddons.cache.lastUpdate" to 0, + "extensions.getAddons.cache.lastUpdate" to 1, ), ) mainSession.loadUri("https://example.com") @@ -3721,11 +3816,6 @@ class WebExtensionTest : BaseSessionTest() { mainSession.evaluateJS("typeof navigator.mozAddonManager") as String, equalTo("object"), ) - assertThat( - "mozAddonManager.abuseReportPanelEnabled should be false", - mainSession.evaluateJS("navigator.mozAddonManager.abuseReportPanelEnabled") as Boolean, - equalTo(false), - ) // Install an add-on, then assert results got from `mozAddonManager.getAddonByID()`. var addonId = "" diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java index 727f403931..7c95233552 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java @@ -89,7 +89,6 @@ import org.mozilla.geckoview.GeckoSession.TextInputDelegate; import org.mozilla.geckoview.GeckoSessionSettings; import org.mozilla.geckoview.MediaSession; import org.mozilla.geckoview.OrientationController; -import org.mozilla.geckoview.RuntimeTelemetry; import org.mozilla.geckoview.SessionTextInput; import org.mozilla.geckoview.TranslationsController; import org.mozilla.geckoview.WebExtension; @@ -988,10 +987,6 @@ public class GeckoSessionTestRule implements TestRule { return RuntimeCreator.getRuntime(); } - public void setTelemetryDelegate(final RuntimeTelemetry.Delegate delegate) { - RuntimeCreator.setTelemetryDelegate(delegate); - } - /** Sets an experiment delegate on the runtime creator. */ public void setExperimentDelegate(final ExperimentDelegate delegate) { RuntimeCreator.setExperimentDelegate(delegate); @@ -1463,7 +1458,6 @@ public class GeckoSessionTestRule implements TestRule { mLastWaitStart = 0; mLastWaitEnd = 0; mTimeoutMillis = 0; - RuntimeCreator.setTelemetryDelegate(null); RuntimeCreator.setExperimentDelegate(null); } diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java index 7eda360459..db18c06a9d 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java @@ -18,7 +18,6 @@ import org.mozilla.geckoview.ExperimentDelegate; import org.mozilla.geckoview.GeckoResult; import org.mozilla.geckoview.GeckoRuntime; import org.mozilla.geckoview.GeckoRuntimeSettings; -import org.mozilla.geckoview.RuntimeTelemetry; import org.mozilla.geckoview.WebExtension; import org.mozilla.geckoview.test.TestCrashHandler; @@ -34,40 +33,6 @@ public class RuntimeCreator { public static AtomicInteger sTestSupport = new AtomicInteger(0); public static WebExtension sTestSupportExtension; - // The RuntimeTelemetry.Delegate can only be set when creating the RuntimeCreator, to - // let tests set their own Delegate we need to create a proxy here. - public static class RuntimeTelemetryDelegate implements RuntimeTelemetry.Delegate { - public RuntimeTelemetry.Delegate delegate = null; - - @Override - public void onHistogram(@NonNull final RuntimeTelemetry.Histogram metric) { - if (delegate != null) { - delegate.onHistogram(metric); - } - } - - @Override - public void onBooleanScalar(@NonNull final RuntimeTelemetry.Metric metric) { - if (delegate != null) { - delegate.onBooleanScalar(metric); - } - } - - @Override - public void onStringScalar(@NonNull final RuntimeTelemetry.Metric metric) { - if (delegate != null) { - delegate.onStringScalar(metric); - } - } - - @Override - public void onLongScalar(@NonNull final RuntimeTelemetry.Metric metric) { - if (delegate != null) { - delegate.onLongScalar(metric); - } - } - } - /** * The ExperimentDelegate can only be set when starting the RuntimeCreator, so for testing we are * setting up a proxy here @@ -110,9 +75,6 @@ public class RuntimeCreator { } } - public static final RuntimeTelemetryDelegate sRuntimeTelemetryProxy = - new RuntimeTelemetryDelegate(); - public static RuntimeExperimentDelegate sRuntimeExperimentDelegateProxy = new RuntimeExperimentDelegate(); private static WebExtension.Port sBackgroundPort; @@ -162,17 +124,6 @@ public class RuntimeCreator { }); } - /** - * Set the {@link RuntimeTelemetry.Delegate} instance for this test. Application code can only - * register this delegate when the {@link GeckoRuntime} is created, so we need to proxy it for - * test code. - * - * @param delegate the {@link RuntimeTelemetry.Delegate} for this test run. - */ - public static void setTelemetryDelegate(final RuntimeTelemetry.Delegate delegate) { - sRuntimeTelemetryProxy.delegate = delegate; - } - /** * Set the {@link ExperimentDelegate} instance for this test. Application code can only register * this delegate when the {@link GeckoRuntime} is created, so we need to proxy it for test code. @@ -216,7 +167,6 @@ public class RuntimeCreator { .remoteDebuggingEnabled(true) .consoleOutput(true) .crashHandler(TestCrashHandler.class) - .telemetryDelegate(sRuntimeTelemetryProxy) .experimentDelegate(sRuntimeExperimentDelegateProxy) .build(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java index 1fc34cb8bb..e0c16d66cc 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java @@ -453,13 +453,15 @@ public class GeckoDisplay { *

    This function must be called on the UI thread. * * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing the pixels and - * size information of the requested portion of the visible web page. + * size information of the requested portion of the visible web page, or returns a failure + * {@link GeckoResult} including the reason why in an {@link Exception} */ @UiThread public @NonNull GeckoResult capture() { ThreadUtils.assertOnUiThread(); if (!mSession.isCompositorReady()) { - throw new IllegalStateException("Compositor must be ready before pixels can be captured"); + return GeckoResult.fromException( + new IllegalStateException("Compositor must be ready before pixels can be captured")); } final GeckoResult result = new GeckoResult<>(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java index 8750f344a8..ccd513f6bd 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java @@ -1893,7 +1893,7 @@ import org.mozilla.geckoview.SessionTextInput.EditableListener.IMEState; outAttrs.imeOptions = EditorInfo.IME_ACTION_GO; } else if (actionHint.equals("done")) { outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; - } else if (actionHint.equals("next") || actionHint.equals("maybenext")) { + } else if (actionHint.equals("next")) { outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT; } else if (actionHint.equals("previous")) { outAttrs.imeOptions = EditorInfo.IME_ACTION_PREVIOUS; @@ -1901,6 +1901,9 @@ import org.mozilla.geckoview.SessionTextInput.EditableListener.IMEState; outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH; } else if (actionHint.equals("send")) { outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND; + } else if (actionHint.equals("maybenext")) { + // this should be low priority as "maybenext" is internal type + outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT; } else if (actionHint.length() > 0) { if (DEBUG) Log.w(LOGTAG, "Unexpected actionHint=\"" + actionHint + "\""); outAttrs.actionLabel = actionHint; @@ -2568,6 +2571,7 @@ import org.mozilla.geckoview.SessionTextInput.EditableListener.IMEState; switch (keyCode) { case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_FORWARD: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_SEARCH: diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index 0a80b02b04..482fa32f39 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -456,21 +456,6 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return this; } - /** - * Add a {@link RuntimeTelemetry.Delegate} instance to this GeckoRuntime. This delegate can be - * used by the app to receive streaming telemetry data from GeckoView. - * - * @param delegate the delegate that will handle telemetry - * @return The builder instance. - */ - @Deprecated - @DeprecationSchedule(id = "geckoview-gvst", version = 127) - public @NonNull Builder telemetryDelegate(final @NonNull RuntimeTelemetry.Delegate delegate) { - getSettings().mTelemetryProxy = new RuntimeTelemetry.Proxy(delegate); - getSettings().mTelemetryEnabled.set(true); - return this; - } - /** * Set the {@link ExperimentDelegate} instance on this runtime, if any. This delegate is used to * send and receive experiment information from Nimbus. @@ -663,7 +648,6 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { /* package */ int mScreenHeightOverride; /* package */ Class mCrashHandler; /* package */ String[] mRequestedLocales; - /* package */ RuntimeTelemetry.Proxy mTelemetryProxy; /* package */ ExperimentDelegate mExperimentDelegate; /** @@ -674,10 +658,6 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { /* package */ void attachTo(final @NonNull GeckoRuntime runtime) { mRuntime = runtime; commit(); - - if (mTelemetryProxy != null) { - mTelemetryProxy.attach(); - } } @Override // RuntimeSettings @@ -719,7 +699,6 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { mCrashHandler = settings.mCrashHandler; mRequestedLocales = settings.mRequestedLocales; mConfigFilePath = settings.mConfigFilePath; - mTelemetryProxy = settings.mTelemetryProxy; mExperimentDelegate = settings.mExperimentDelegate; } @@ -1368,11 +1347,6 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return this; } - @SuppressWarnings("checkstyle:javadocmethod") - public @Nullable RuntimeTelemetry.Delegate getTelemetryDelegate() { - return mTelemetryProxy.getDelegate(); - } - /** * Get the {@link ExperimentDelegate} instance set on this runtime, if any, * diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 85b3abf9a9..ccf1e8520e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -2448,6 +2448,7 @@ public class GeckoSession { @IntDef( flag = true, value = { + FINDER_FIND_FORWARD, FINDER_FIND_BACKWARDS, FINDER_FIND_LINKS_ONLY, FINDER_FIND_MATCH_CASE, @@ -2455,6 +2456,9 @@ public class GeckoSession { }) public @interface FinderFindFlags {} + /** Go forward when finding the next match. */ + public static final int FINDER_FIND_FORWARD = 0; + /** Go backwards when finding the next match. */ public static final int FINDER_FIND_BACKWARDS = 1; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java index 14f6b14c47..4dc23bc519 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java @@ -331,11 +331,11 @@ public final class GeckoSessionSettings implements Parcelable { new Key("fullAccessibilityTree", /* initOnly */ false, /* values */ null); /** - * Key to specify if this GeckoSession is a Popup or not. Popup sessions can paint over other - * sessions and are not exposed to the tabs WebExtension API. + * Key to specify if this GeckoSession is a Extension Popup or not. Popup sessions can paint over + * other sessions and are not exposed to the tabs WebExtension API. */ - private static final Key IS_POPUP = - new Key("isPopup", /* initOnly */ false, /* values */ null); + private static final Key IS_EXTENSION_POPUP = + new Key("isExtensionPopup", /* initOnly */ false, /* values */ null); /** Internal Gecko key to specify the session context ID. Derived from `UNSAFE_CONTEXT_ID`. */ private static final Key CONTEXT_ID = @@ -375,7 +375,7 @@ public final class GeckoSessionSettings implements Parcelable { mBundle.putBoolean(SUSPEND_MEDIA_WHEN_INACTIVE.name, false); mBundle.putBoolean(ALLOW_JAVASCRIPT.name, true); mBundle.putBoolean(FULL_ACCESSIBILITY_TREE.name, false); - mBundle.putBoolean(IS_POPUP.name, false); + mBundle.putBoolean(IS_EXTENSION_POPUP.name, false); mBundle.putInt(USER_AGENT_MODE.name, USER_AGENT_MODE_MOBILE); mBundle.putString(USER_AGENT_OVERRIDE.name, null); mBundle.putInt(VIEWPORT_MODE.name, VIEWPORT_MODE_MOBILE); @@ -430,8 +430,8 @@ public final class GeckoSessionSettings implements Parcelable { setBoolean(FULL_ACCESSIBILITY_TREE, value); } - /* package */ void setIsPopup(final boolean value) { - setBoolean(IS_POPUP, value); + /* package */ void setIsExtensionPopup(final boolean value) { + setBoolean(IS_EXTENSION_POPUP, value); } private void setBoolean(final Key key, final boolean value) { @@ -498,8 +498,8 @@ public final class GeckoSessionSettings implements Parcelable { return getBoolean(FULL_ACCESSIBILITY_TREE); } - /* package */ boolean getIsPopup() { - return getBoolean(IS_POPUP); + /* package */ boolean getIsExtensionPopup() { + return getBoolean(IS_EXTENSION_POPUP); } private boolean getBoolean(final Key key) { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java index 2271ff71f7..8b31862524 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java @@ -22,6 +22,8 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.Handler; import android.print.PrintDocumentAdapter; @@ -269,6 +271,14 @@ public class GeckoView extends FrameLayout implements GeckoDisplay.NewSurfacePro // descendants to affect the way LayerView retains its focus. setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); + // When GeckoView.requestFocus() is called with hardware keyboard, the focused state color + // might be applied on this view. But we don't want to apply it as default. + final StateListDrawable drawable = new StateListDrawable(); + drawable.addState( + new int[] {android.R.attr.state_focused, -android.R.attr.state_focused}, + new ColorDrawable(Color.WHITE)); + setBackground(drawable); + // This will stop PropertyAnimator from creating a drawing cache (i.e. a // bitmap) from a SurfaceView, which is just not possible (the bitmap will be // transparent). diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java deleted file mode 100644 index 1fad0cb17e..0000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java +++ /dev/null @@ -1,171 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * vim: ts=4 sw=4 expandtab: - * 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 org.mozilla.geckoview; - -import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; -import org.mozilla.gecko.GeckoThread; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.mozglue.JNIObject; - -/** The telemetry API gives access to telemetry data of the Gecko runtime. */ -public final class RuntimeTelemetry { - protected RuntimeTelemetry() {} - - /** - * The runtime telemetry metric object. - * - * @param type of the underlying metric sample - */ - public static class Metric { - /** The runtime metric name. */ - public final @NonNull String name; - - /** The metric values. */ - public final @NonNull T value; - - /* package */ Metric(final String name, final T value) { - this.name = name; - this.value = value; - } - - @Override - public String toString() { - return "name: " + name + ", value: " + value; - } - - // For testing - protected Metric() { - name = null; - value = null; - } - } - - /** The Histogram telemetry metric object. */ - public static class Histogram extends Metric { - /** Whether or not this is a Categorical Histogram. */ - public final boolean isCategorical; - - /* package */ Histogram(final boolean isCategorical, final String name, final long[] value) { - super(name, value); - this.isCategorical = isCategorical; - } - - // For testing - protected Histogram() { - super(null, null); - isCategorical = false; - } - } - - /** - * The runtime telemetry delegate. Implement this if you want to receive runtime (Gecko) telemetry - * and attach it via {@link GeckoRuntimeSettings.Builder#telemetryDelegate}. - */ - public interface Delegate { - /** - * A runtime telemetry histogram metric has been received. - * - * @param metric The runtime metric details. - */ - @AnyThread - default void onHistogram(final @NonNull Histogram metric) {} - - /** - * A runtime telemetry boolean scalar has been received. - * - * @param metric The runtime metric details. - */ - @AnyThread - default void onBooleanScalar(final @NonNull Metric metric) {} - - /** - * A runtime telemetry long scalar has been received. - * - * @param metric The runtime metric details. - */ - @AnyThread - default void onLongScalar(final @NonNull Metric metric) {} - - /** - * A runtime telemetry string scalar has been received. - * - * @param metric The runtime metric details. - */ - @AnyThread - default void onStringScalar(final @NonNull Metric metric) {} - } - - // The proxy connects to telemetry core and forwards telemetry events - // to the attached delegate. - /* package */ static final class Proxy extends JNIObject { - private final Delegate mDelegate; - - public Proxy(final @NonNull Delegate delegate) { - mDelegate = delegate; - } - - // Attach to current runtime. - // We might have different mechanics of attaching to specific runtimes - // in future, for which case we should split the delegate assignment in - // the setup phase from the attaching. - public void attach() { - if (GeckoThread.isRunning()) { - registerDelegateProxy(this); - } else { - GeckoThread.queueNativeCall(Proxy.class, "registerDelegateProxy", Proxy.class, this); - } - } - - public @NonNull Delegate getDelegate() { - return mDelegate; - } - - @WrapForJNI(dispatchTo = "gecko") - private static native void registerDelegateProxy(Proxy proxy); - - @WrapForJNI(calledFrom = "gecko") - /* package */ void dispatchHistogram( - final boolean isCategorical, final String name, final long[] values) { - if (mDelegate == null) { - // TODO throw? - return; - } - mDelegate.onHistogram(new Histogram(isCategorical, name, values)); - } - - @WrapForJNI(calledFrom = "gecko") - /* package */ void dispatchStringScalar(final String name, final String value) { - if (mDelegate == null) { - return; - } - mDelegate.onStringScalar(new Metric<>(name, value)); - } - - @WrapForJNI(calledFrom = "gecko") - /* package */ void dispatchBooleanScalar(final String name, final boolean value) { - if (mDelegate == null) { - return; - } - mDelegate.onBooleanScalar(new Metric<>(name, value)); - } - - @WrapForJNI(calledFrom = "gecko") - /* package */ void dispatchLongScalar(final String name, final long value) { - if (mDelegate == null) { - return; - } - mDelegate.onLongScalar(new Metric<>(name, value)); - } - - @Override // JNIObject - protected void disposeNative() { - // We don't hold native references. - throw new UnsupportedOperationException(); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ScreenLength.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ScreenLength.java index 1ce4b41659..e88976a447 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ScreenLength.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ScreenLength.java @@ -1,4 +1,6 @@ -/* License, v. 2.0. If a copy of the MPL was not distributed with this +/* + * 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 org.mozilla.geckoview; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionFinder.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionFinder.java index 2ed0b1a6c3..b245a39f1e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionFinder.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionFinder.java @@ -27,6 +27,7 @@ public final class SessionFinder { private static final List> sFlagNames = Arrays.asList( + new Pair<>(GeckoSession.FINDER_FIND_FORWARD, "forward"), new Pair<>(GeckoSession.FINDER_FIND_BACKWARDS, "backwards"), new Pair<>(GeckoSession.FINDER_FIND_LINKS_ONLY, "linksOnly"), new Pair<>(GeckoSession.FINDER_FIND_MATCH_CASE, "matchCase"), @@ -70,7 +71,8 @@ public final class SessionFinder { * previous search string. * * @param searchString String to search, or null to find again using the previous string. - * @param flags Flags for performing the search; either 0 or a combination of {@link + * @param flags Flags for performing the search; either FINDER_FIND_FORWARD {@link + * GeckoSession#FINDER_FIND_FORWARD} or a combination of {@link * GeckoSession#FINDER_FIND_BACKWARDS FINDER_FIND_*} constants. * @return Result of the search operation as a {@link GeckoResult} object. * @see #clear diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TranslationsController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TranslationsController.java index 37e5e7139a..256877532b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TranslationsController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TranslationsController.java @@ -1081,14 +1081,20 @@ public class TranslationsController { /** If the translation engine is ready for use or will need to be loaded. */ public final @NonNull Boolean isEngineReady; + /** If the DOM has began visibly changing to the translated text. */ + public final @NonNull Boolean hasVisibleChange; + /** - * Translation State constructor. + * This constructor is deprecated, please use the [TranslationState] with [hasVisibleChange] + * parameter. This constructor will be removed in bug 1895275. Translation State constructor. * * @param requestedTranslationPair the language pair to translate * @param error if an error occurred * @param detectedLanguages detected language * @param isEngineReady if the engine is ready for translations */ + @Deprecated + @DeprecationSchedule(version = 130, id = "translation-state-deprecated-constructor") public TranslationState( final @Nullable TranslationPair requestedTranslationPair, final @Nullable String error, @@ -1098,6 +1104,29 @@ public class TranslationsController { this.error = error; this.detectedLanguages = detectedLanguages; this.isEngineReady = isEngineReady; + this.hasVisibleChange = false; + } + + /** + * Translation State constructor. + * + * @param requestedTranslationPair the language pair to translate + * @param error if an error occurred + * @param detectedLanguages detected language + * @param isEngineReady if the engine is ready for translations + * @param hasVisibleChange if the DOM has began to visibly change to translated text + */ + public TranslationState( + final @Nullable TranslationPair requestedTranslationPair, + final @Nullable String error, + final @Nullable DetectedLanguages detectedLanguages, + final @NonNull Boolean isEngineReady, + final @NonNull Boolean hasVisibleChange) { + this.requestedTranslationPair = requestedTranslationPair; + this.error = error; + this.detectedLanguages = detectedLanguages; + this.isEngineReady = isEngineReady; + this.hasVisibleChange = hasVisibleChange; } @Override @@ -1112,6 +1141,8 @@ public class TranslationsController { + detectedLanguages + ", isEngineReady=" + isEngineReady + + ", hasVisibleChange=" + + hasVisibleChange + '}'; } @@ -1130,7 +1161,8 @@ public class TranslationsController { TranslationPair.fromBundle(bundle.getBundle("requestedTranslationPair")), bundle.getString("error"), DetectedLanguages.fromBundle(bundle.getBundle("detectedLanguages")), - bundle.getBoolean("isEngineReady", false)); + bundle.getBoolean("isEngineReady", false), + bundle.getBoolean("hasVisibleChange", false)); } } @@ -1168,7 +1200,7 @@ public class TranslationsController { final GeckoBundle data = message.getBundle("data"); final TranslationState translationState = TranslationState.fromBundle(data); if (DEBUG) { - Log.d(LOGTAG, "received translation state: " + translationState); + Log.d(LOGTAG, "Received translation state: " + translationState); } delegate.onTranslationStateChange(mSession, translationState); if (translationState != null diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java index bf5d431cf1..53d57b126a 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java @@ -1442,7 +1442,7 @@ public class WebExtension { return; } - session.getSettings().setIsPopup(true); + session.getSettings().setIsExtensionPopup(true); session.loadUri(popupUri); }); } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java index 07e848b079..889cd91895 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java @@ -335,6 +335,14 @@ public class WebExtensionController { @UiThread default void onDisabling(@NonNull WebExtension extension) {} + /** + * Called whenever optional permissions of an extension have changed. + * + * @param extension The {@link WebExtension} that has optional permissions changed. + */ + @UiThread + default void onOptionalPermissionsChanged(@NonNull WebExtension extension) {} + /** * Called whenever an extension has been disabled. * @@ -489,6 +497,7 @@ public class WebExtensionController { EventDispatcher.getInstance() .unregisterUiThreadListener( mInternals, + "GeckoView:WebExtension:OnOptionalPermissionsChanged", "GeckoView:WebExtension:OnDisabling", "GeckoView:WebExtension:OnDisabled", "GeckoView:WebExtension:OnEnabling", @@ -503,6 +512,7 @@ public class WebExtensionController { EventDispatcher.getInstance() .registerUiThreadListener( mInternals, + "GeckoView:WebExtension:OnOptionalPermissionsChanged", "GeckoView:WebExtension:OnDisabling", "GeckoView:WebExtension:OnDisabled", "GeckoView:WebExtension:OnEnabling", @@ -1021,6 +1031,9 @@ public class WebExtensionController { } else if ("GeckoView:WebExtension:OnDisabling".equals(event)) { onDisabling(bundle); return; + } else if ("GeckoView:WebExtension:OnOptionalPermissionsChanged".equals(event)) { + onOptionalPermissionsChanged(bundle); + return; } else if ("GeckoView:WebExtension:OnDisabled".equals(event)) { onDisabled(bundle); return; @@ -1265,6 +1278,17 @@ public class WebExtensionController { mAddonManagerDelegate.onDisabling(extension); } + private void onOptionalPermissionsChanged(final GeckoBundle bundle) { + if (mAddonManagerDelegate == null) { + Log.e(LOGTAG, "no AddonManager delegate registered"); + return; + } + + final GeckoBundle extensionBundle = bundle.getBundle("extension"); + final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle); + mAddonManagerDelegate.onOptionalPermissionsChanged(extension); + } + private void onDisabled(final GeckoBundle bundle) { if (mAddonManagerDelegate == null) { Log.e(LOGTAG, "no AddonManager delegate registered"); @@ -1525,7 +1549,7 @@ public class WebExtensionController { if (delegate != null) { result = delegate.onCloseTab(extension, message.session); } else { - result = GeckoResult.fromValue(AllowOrDeny.DENY); + result = GeckoResult.deny(); } message.callback.resolveTo( diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md index e2df6df21b..a2d30d25ef 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -13,6 +13,17 @@ exclude: true ⚠️ breaking change and deprecation notices +## v127 +- ⚠️ Removed deprecated [`RuntimeTelemetry`][125.5], [`GeckoRuntimeSettings.getTelemetryDelegate`][125.6] and [`GeckoRuntimeSettings.telemetryDelegate`][125.7]. +- Added [FINDER_FIND_FORWARD][127.1] +- Added [`WebExtensionController.AddonManagerDelegate.onOptionalPermissionsChanged`][127.2] ([bug 1892302]({{bugzilla}}1892302). +- Added a new [`TranslationState`][127.3] constructor to add `hasVisibleChange` and deprecated the prior [`TranslationsState`][127.4] constructor to be removed in v130. + +[127.1]: {{javadoc_uri}}/GeckoSession.html#FINDER_FIND_FORWARD +[127.2]: {{javadoc_uri}}/WebExtensionController.AddonManagerDelegate.html#onOptionalPermissionsChanged +[127.3]: {{javadoc_uri}}/TranslationsController.SessionTranslation.TranslationState.html#%3Cinit%3E(org.mozilla.geckoview.TranslationsController.SessionTranslation.TranslationPair,java.lang.String,org.mozilla.geckoview.TranslationsController.SessionTranslation.DetectedLanguages,java.lang.Boolean,java.lang.Boolean) +[127.4]: {{javadoc_uri}}/TranslationsController.SessionTranslation.TranslationState.html#(org.mozilla.geckoview.TranslationsController.SessionTranslation.TranslationPair,java.lang.String,org.mozilla.geckoview.TranslationsController.SessionTranslation.DetectedLanguages,java.lang.Boolean) + ## v125 - ⚠️ Deprecated [`GeckoSession.NavigationDelegate.onLocationChange`][125.1], to be removed in v127. ([bug 1837601]({{bugzilla}}1837601)) @@ -1547,4 +1558,4 @@ to allow adding gecko profiler markers. [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String) [65.25]: {{javadoc_uri}}/GeckoResult.html -[api-version]: 2c319e9f18adb4178ce09d71088a173b56d1a694 +[api-version]: 0b9d0f241805fab7e71d3d745170a237f6ac113d diff --git a/mobile/android/geckoview_example/build.gradle b/mobile/android/geckoview_example/build.gradle index 4c8473c047..56363cecfc 100644 --- a/mobile/android/geckoview_example/build.gradle +++ b/mobile/android/geckoview_example/build.gradle @@ -53,6 +53,7 @@ android { dependencies { implementation ComponentsDependencies.androidx_annotation implementation ComponentsDependencies.androidx_appcompat + implementation ComponentsDependencies.androidx_core implementation ComponentsDependencies.androidx_preferences implementation project(path: ':geckoview') diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java index 30ae778b8b..48036f7bf5 100644 --- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java @@ -91,7 +91,6 @@ import org.mozilla.geckoview.GeckoWebExecutor; import org.mozilla.geckoview.Image; import org.mozilla.geckoview.MediaSession; import org.mozilla.geckoview.OrientationController; -import org.mozilla.geckoview.RuntimeTelemetry; import org.mozilla.geckoview.SlowScriptResponse; import org.mozilla.geckoview.TranslationsController; import org.mozilla.geckoview.WebExtension; @@ -874,7 +873,6 @@ public class GeckoViewActivity extends AppCompatActivity .build()) .crashHandler(ExampleCrashHandler.class) .preferredColorScheme(mPreferredColorScheme.value()) - .telemetryDelegate(new ExampleTelemetryDelegate()) .javaScriptEnabled(mJavascriptEnabled.value()) .extensionsProcessEnabled(mExtensionsProcessEnabled.value()) .globalPrivacyControlEnabled(mGlobalPrivacyControlEnabled.value()) @@ -3039,28 +3037,6 @@ public class GeckoViewActivity extends AppCompatActivity } } - private final class ExampleTelemetryDelegate implements RuntimeTelemetry.Delegate { - @Override - public void onHistogram(final @NonNull RuntimeTelemetry.Histogram histogram) { - Log.d(LOGTAG, "onHistogram " + histogram); - } - - @Override - public void onBooleanScalar(final @NonNull RuntimeTelemetry.Metric scalar) { - Log.d(LOGTAG, "onBooleanScalar " + scalar); - } - - @Override - public void onLongScalar(final @NonNull RuntimeTelemetry.Metric scalar) { - Log.d(LOGTAG, "onLongScalar " + scalar); - } - - @Override - public void onStringScalar(final @NonNull RuntimeTelemetry.Metric scalar) { - Log.d(LOGTAG, "onStringScalar " + scalar); - } - } - private class ExampleActivityDelegate implements GeckoView.ActivityContextDelegate { public Context getActivityContext() { return GeckoViewActivity.this; diff --git a/mobile/android/gradle.py b/mobile/android/gradle.py index 9f310ddb7b..51b9313940 100644 --- a/mobile/android/gradle.py +++ b/mobile/android/gradle.py @@ -5,10 +5,12 @@ import os import subprocess import sys +import time from contextlib import contextmanager import mozpack.path as mozpath -from mozbuild.util import ensureParentDir, lock_file +from mozbuild.dirutils import ensureParentDir +from mozbuild.lock import lock_file @contextmanager @@ -27,6 +29,10 @@ def gradle_lock(topobjdir, max_wait_seconds=600): def android(verb, *args): + env = dict(os.environ) + should_print_status = env.get("MACH") and not env.get("NO_BUILDSTATUS_MESSAGES") + if should_print_status: + print("BUILDSTATUS " + str(time.time()) + " START_Gradle " + verb) import buildconfig with gradle_lock(buildconfig.topobjdir): @@ -37,7 +43,6 @@ def android(verb, *args): verb, ] cmd.extend(args) - env = dict(os.environ) # Confusingly, `MACH` is set only within `mach build`. if env.get("MACH"): env["GRADLE_INVOKED_WITHIN_MACH_BUILD"] = "1" @@ -45,7 +50,9 @@ def android(verb, *args): del env["LD_LIBRARY_PATH"] subprocess.check_call(cmd, env=env) - return 0 + if should_print_status: + print("BUILDSTATUS " + str(time.time()) + " END_Gradle " + verb) + return 0 def assemble_app(dummy_output_file, *inputs): diff --git a/mobile/android/gradle/dotgradle-offline/gradle.properties b/mobile/android/gradle/dotgradle-offline/gradle.properties index 3f77ec9a2f..bc2b01e1e5 100644 --- a/mobile/android/gradle/dotgradle-offline/gradle.properties +++ b/mobile/android/gradle/dotgradle-offline/gradle.properties @@ -1,3 +1,7 @@ +# 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/. + // Per https://docs.gradle.org/current/userguide/build_environment.html, this // overrides the gradle.properties in topsrcdir. org.gradle.daemon=false diff --git a/mobile/android/gradle/dotgradle-online/gradle.properties b/mobile/android/gradle/dotgradle-online/gradle.properties index 3f77ec9a2f..bc2b01e1e5 100644 --- a/mobile/android/gradle/dotgradle-online/gradle.properties +++ b/mobile/android/gradle/dotgradle-online/gradle.properties @@ -1,3 +1,7 @@ +# 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/. + // Per https://docs.gradle.org/current/userguide/build_environment.html, this // overrides the gradle.properties in topsrcdir. org.gradle.daemon=false diff --git a/mobile/android/mach_commands.py b/mobile/android/mach_commands.py index fac483902c..9674650be4 100644 --- a/mobile/android/mach_commands.py +++ b/mobile/android/mach_commands.py @@ -9,6 +9,7 @@ import re import subprocess import sys import tarfile +import time import mozpack.path as mozpath from mach.decorators import Command, CommandArgument, SubCommand @@ -551,13 +552,19 @@ def gradle(command_context, args, verbose=False): if android_sdk_root: env["ANDROID_SDK_ROOT"] = android_sdk_root - return command_context.run_process( + should_print_status = env.get("MACH") and not env.get("NO_BUILDSTATUS_MESSAGES") + if should_print_status: + print("BUILDSTATUS " + str(time.time()) + " START_Gradle " + args[0]) + rv = command_context.run_process( [command_context.substs["GRADLE"]] + gradle_flags + args, explicit_env=env, pass_thru=True, # Allow user to run gradle interactively. ensure_exit_code=False, # Don't throw on non-zero exit code. cwd=mozpath.join(command_context.topsrcdir), ) + if should_print_status: + print("BUILDSTATUS " + str(time.time()) + " END_Gradle " + args[0]) + return rv @Command("gradle-install", category="devenv", conditions=[REMOVED]) diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs b/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs index 483f0b01f2..911b08a70e 100644 --- a/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs @@ -291,10 +291,10 @@ export class GeckoViewNavigation extends GeckoViewModule { waitAndSetupWindow(aSessionId, aOpenWindowInfo, aName) { if (!aSessionId) { - return Promise.resolve(null); + return Promise.reject(); } - return new Promise(resolve => { + return new Promise((resolve, reject) => { const handler = { observe(aSubject, aTopic) { if ( @@ -318,6 +318,10 @@ export class GeckoViewNavigation extends GeckoViewModule { aSubject.browser.removeAttribute("remoteType"); } Services.obs.removeObserver(handler, "geckoview-window-created"); + if (!aSubject) { + reject(); + return; + } resolve(aSubject); } }, @@ -332,8 +336,45 @@ export class GeckoViewNavigation extends GeckoViewModule { debug`handleNewSession: uri=${aUri && aUri.spec} where=${aWhere} flags=${aFlags}`; + const setupPromise = this.#handleNewSessionAsync( + aUri, + aOpenWindowInfo, + aFlags, + aName + ); + + let browser = undefined; + setupPromise.then( + window => { + browser = window.browser; + }, + () => { + browser = null; + } + ); + + // Wait indefinitely for app to respond with a browser or null + Services.tm.spinEventLoopUntil( + "GeckoViewNavigation.jsm:handleNewSession", + () => this.window.closed || browser !== undefined + ); + return browser || null; + } + + #isNewTab(aWhere) { + return [ + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND, + ].includes(aWhere); + } + + /** + * Similar to handleNewSession. But this returns a promise to wait for new + * browser. + */ + #handleNewSessionAsync(aUri, aOpenWindowInfo, aFlags, aName) { if (!this.enabled) { - return null; + return Promise.reject(); } const newSessionId = Services.uuid @@ -356,30 +397,15 @@ export class GeckoViewNavigation extends GeckoViewModule { aName ); - let browser = undefined; - this.eventDispatcher + return this.eventDispatcher .sendRequestForResult(message) .then(didOpenSession => { if (!didOpenSession) { + // New session cannot be opened, so we should throw NS_ERROR_ABORT. return Promise.reject(); } return setupPromise; - }) - .then( - window => { - browser = window.browser; - }, - () => { - browser = null; - } - ); - - // Wait indefinitely for app to respond with a browser or null - Services.tm.spinEventLoopUntil( - "GeckoViewNavigation.sys.mjs:handleNewSession", - () => this.window.closed || browser !== undefined - ); - return browser || null; + }); } // nsIBrowserDOMWindow. @@ -393,6 +419,11 @@ export class GeckoViewNavigation extends GeckoViewModule { debug`createContentWindow: uri=${aUri && aUri.spec} where=${aWhere} flags=${aFlags}`; + if (!this.enabled) { + Components.returnCode = Cr.NS_ERROR_ABORT; + return null; + } + if ( lazy.LoadURIDelegate.load( this.window, @@ -408,13 +439,45 @@ export class GeckoViewNavigation extends GeckoViewModule { return null; } - const browser = this.handleNewSession( + const newTab = this.#isNewTab(aWhere); + const promise = this.#handleNewSessionAsync( aUri, aOpenWindowInfo, aWhere, aFlags, null ); + + // Actually, GeckoView's createContentWindow always creates new window even + // if OPEN_NEWTAB. So the browsing context will be observed via + // nsFrameLoader. + if (aOpenWindowInfo && !newTab) { + promise.catch(() => { + aOpenWindowInfo.cancel(); + }); + // If nsIOpenWindowInfo isn't null, caller should use the callback. + // Also, nsIWindowProvider.provideWindow doesn't use callback, if new + // tab option, we have to return browsing context instead of async. + return null; + } + + let browser = undefined; + promise.then( + window => { + browser = window.browser; + }, + () => { + browser = null; + } + ); + + // Wait indefinitely for app to respond with a browser or null. + // if browser is null, return error. + Services.tm.spinEventLoopUntil( + "GeckoViewNavigation.sys.mjs:createContentWindow", + () => this.window.closed || browser !== undefined + ); + if (!browser) { Components.returnCode = Cr.NS_ERROR_ABORT; return null; diff --git a/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs index faa5c5f280..5c176a17f1 100644 --- a/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs @@ -117,19 +117,11 @@ export var GeckoViewSessionStore = { switch (aTopic) { case "browsing-context-did-set-embedder": { - if ( - aSubject && - aSubject === aSubject.top && - aSubject.isContent && - aSubject.embedderElement && - aSubject.embedderElement.permanentKey - ) { - const permanentKey = aSubject.embedderElement.permanentKey; - this._browserSHistoryListener - .get(permanentKey) - ?.unregister(permanentKey); - - this.getOrCreateSHistoryListener(permanentKey, aSubject, true); + if (aSubject === aSubject.top && aSubject.isContent) { + const permanentKey = aSubject.embedderElement?.permanentKey; + if (permanentKey) { + this.maybeRecreateSHistoryListener(permanentKey, aSubject); + } } break; } @@ -151,26 +143,34 @@ export var GeckoViewSessionStore = { }); }, - getOrCreateSHistoryListener( - permanentKey, - browsingContext, - collectImmediately = false - ) { + getOrCreateSHistoryListener(permanentKey, browsingContext) { if (!permanentKey || browsingContext !== browsingContext.top) { return null; } + const listener = this._browserSHistoryListener.get(permanentKey); + if (listener) { + return listener; + } + + return this.createSHistoryListener(permanentKey, browsingContext, false); + }, + + maybeRecreateSHistoryListener(permanentKey, browsingContext) { + const listener = this._browserSHistoryListener.get(permanentKey); + if (!listener || listener._browserId != browsingContext.browserId) { + listener?.unregister(permanentKey); + this.createSHistoryListener(permanentKey, browsingContext, true); + } + }, + + createSHistoryListener(permanentKey, browsingContext, collectImmediately) { const sessionHistory = browsingContext.sessionHistory; if (!sessionHistory) { return null; } - let listener = this._browserSHistoryListener.get(permanentKey); - if (listener) { - return listener; - } - - listener = new SHistoryListener(browsingContext); + const listener = new SHistoryListener(browsingContext); sessionHistory.addSHistoryListener(listener); this._browserSHistoryListener.set(permanentKey, listener); @@ -184,4 +184,51 @@ export var GeckoViewSessionStore = { return listener; }, + + updateSessionStoreFromTabListener( + browser, + browsingContext, + permanentKey, + update, + forStorage = false + ) { + permanentKey = browser?.permanentKey ?? permanentKey; + if (!permanentKey) { + return; + } + + if (browsingContext.isReplaced) { + return; + } + + const listener = this.getOrCreateSHistoryListener( + permanentKey, + browsingContext + ); + + if (listener) { + const historychange = + // If it is not the scheduled update (tab closed, window closed etc), + // try to store the loading non-web-controlled page opened in _blank + // first. + (forStorage && + lazy.SessionHistory.collectNonWebControlledBlankLoadingSession( + browsingContext + )) || + listener.collect(permanentKey, browsingContext, { + collectFull: !!update.sHistoryNeeded, + writeToCache: false, + }); + + if (historychange) { + update.data.historychange = historychange; + } + } + + const win = + browsingContext.embedderElement?.ownerGlobal || + browsingContext.currentWindowGlobal?.browsingContext?.window; + + this.onTabStateUpdate(permanentKey, win, update); + }, }; diff --git a/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs b/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs index ec927b0af6..f2dd7590ab 100644 --- a/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs @@ -6,6 +6,10 @@ import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs" const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", +}); + ChromeUtils.defineLazyGetter(lazy, "MOBILE_USER_AGENT", function () { return Cc["@mozilla.org/network/protocol;1?name=http"].getService( Ci.nsIHttpProtocolHandler @@ -74,6 +78,18 @@ export class GeckoViewSettings extends GeckoViewModule { this.viewportMode = settings.viewportMode; this.useTrackingProtection = !!settings.useTrackingProtection; + if (settings.isExtensionPopup) { + // NOTE: Only add the webextension-view-type and emit extension-browser-inserted + // once, an extension popup should never change webextension-view-type once set. + if (!this.browser.hasAttribute("webextension-view-type")) { + this.browser.setAttribute("webextension-view-type", "popup"); + lazy.ExtensionParent.apiManager.emit( + "extension-browser-inserted", + this.browser + ); + } + } + // When the page is loading from the main process (e.g. from an extension // page) we won't be able to query the actor here. this.getActor("GeckoViewSettings")?.sendAsyncMessage( diff --git a/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs b/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs index a2ca252cdd..f0fdee5d81 100644 --- a/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs @@ -24,12 +24,15 @@ export const GeckoViewTabUtil = { if (subject.name === sessionId) { Services.obs.removeObserver( openingObserver, - "geckoview-window-created" + "browser-delayed-startup-finished" ); resolve(subject); } }; - Services.obs.addObserver(openingObserver, "geckoview-window-created"); + Services.obs.addObserver( + openingObserver, + "browser-delayed-startup-finished" + ); }); try { diff --git a/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs b/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs index 7e74e7bf30..45b43ac9f9 100644 --- a/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs @@ -50,8 +50,11 @@ export class GeckoViewTranslations extends GeckoViewModule { aData.toLanguage ); try { - this.getActor("Translations").translate(fromLanguage, toLanguage); - aCallback.onSuccess(); + this.getActor("Translations") + .translate(fromLanguage, toLanguage) + .then(() => { + aCallback.onSuccess(); + }); } catch (error) { aCallback.onError(`Could not translate: ${error}`); } @@ -101,10 +104,11 @@ export class GeckoViewTranslations extends GeckoViewModule { type: "GeckoView:Translations:Offer", }); break; - case "TranslationsParent:LanguageState": + case "TranslationsParent:LanguageState": { const { detectedLanguages, requestedTranslationPair, + hasVisibleChange, error, isEngineReady, } = aEvent.detail.actor.languageState; @@ -112,6 +116,7 @@ export class GeckoViewTranslations extends GeckoViewModule { const data = { detectedLanguages, requestedTranslationPair, + hasVisibleChange, error, isEngineReady, }; @@ -120,7 +125,9 @@ export class GeckoViewTranslations extends GeckoViewModule { type: "GeckoView:Translations:StateChange", data, }); + break; + } } } } diff --git a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs index 749f753626..421839264a 100644 --- a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs +++ b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs @@ -283,6 +283,16 @@ function exportFlags(aPolicy) { return flags; } +function normalizePermissions(perms) { + if (perms?.permissions) { + perms = { ...perms }; + perms.permissions = perms.permissions.filter( + perm => !perm.startsWith("internal:") + ); + } + return perms; +} + async function exportExtension(aAddon, aPermissions, aSourceURI) { // First, let's make sure the policy is ready if present let policy = WebExtensionPolicy.getByID(aAddon.id); @@ -360,22 +370,12 @@ async function exportExtension(aAddon, aPermissions, aSourceURI) { updateDate = null; } - const normalizePermissions = perms => { - if (perms?.permissions) { - perms = { ...perms }; - perms.permissions = perms.permissions.filter( - perm => !perm.startsWith("internal:") - ); - } - return perms; - }; - const optionalPermissions = aAddon.optionalPermissions?.permissions ?? []; - const optionalOrigins = aAddon.optionalPermissions?.origins ?? []; + const optionalOrigins = aAddon.optionalOriginsNormalized; const grantedPermissions = - normalizePermissions(await lazy.ExtensionPermissions.get(id)) ?? []; - const grantedOptionalPermissions = grantedPermissions?.permissions ?? []; - const grantedOptionalOrigins = grantedPermissions?.origins ?? []; + normalizePermissions(await lazy.ExtensionPermissions.get(id)) ?? {}; + const grantedOptionalPermissions = grantedPermissions.permissions ?? []; + const grantedOptionalOrigins = grantedPermissions.origins ?? []; return { webExtensionId: id, @@ -623,6 +623,32 @@ class AddonManagerListener { // the GeckoView side when it is actually going to be available. this.onExtensionReady = this.onExtensionReady.bind(this); lazy.Management.on("ready", this.onExtensionReady); + lazy.Management.on("change-permissions", this.onOptionalPermissionsChanged); + } + + async onOptionalPermissionsChanged(type, { extensionId }) { + // In xpcshell tests there wil be test extensions that trigger this event while the + // AddonManager has not been started at all, on the contrary on a regular browser + // instance the AddonManager is expected to be already fully started for an extension + // for the extension to be able to reach the "ready" state, and so we just silently + // early exit here if the AddonManager is not ready. + if (!lazy.AddonManager.isReady) { + return; + } + + const addon = await lazy.AddonManager.getAddonByID(extensionId); + if (!addon) { + return; + } + const extension = await exportExtension( + addon, + addon.userPermissions, + /* aSourceURI */ null + ); + lazy.EventDispatcher.instance.sendRequest({ + type: "GeckoView:WebExtension:OnOptionalPermissionsChanged", + extension, + }); } async onExtensionReady(name, extInstance) { diff --git a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java index 399f9417c2..0017298b86 100644 --- a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java +++ b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java @@ -174,7 +174,7 @@ public class TestRunnerActivity extends Activity { public GeckoResult onLoadRequest( final GeckoSession session, final LoadRequest request) { // Allow Gecko to load all URIs - return GeckoResult.fromValue(AllowOrDeny.ALLOW); + return GeckoResult.allow(); } @Override @@ -296,7 +296,7 @@ public class TestRunnerActivity extends Activity { public GeckoResult onCloseTab( @Nullable final WebExtension source, @NonNull final GeckoSession session) { closeSession(session); - return GeckoResult.fromValue(AllowOrDeny.ALLOW); + return GeckoResult.allow(); } @Override @@ -310,7 +310,7 @@ public class TestRunnerActivity extends Activity { mOwnedSessions.addFirst(session); } - return GeckoResult.fromValue(AllowOrDeny.ALLOW); + return GeckoResult.allow(); } }; diff --git a/mobile/android/version.txt b/mobile/android/version.txt index 9931f67c71..4aea959bf8 100644 --- a/mobile/android/version.txt +++ b/mobile/android/version.txt @@ -1 +1 @@ -126.0.1 +127.0 -- cgit v1.2.3