diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/weblayer/browser | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/weblayer/browser')
273 files changed, 12327 insertions, 1531 deletions
diff --git a/chromium/weblayer/browser/DEPS b/chromium/weblayer/browser/DEPS index 36478fd6316..d7477650b25 100644 --- a/chromium/weblayer/browser/DEPS +++ b/chromium/weblayer/browser/DEPS @@ -2,9 +2,11 @@ include_rules = [ "+cc", "+components/autofill/android", "+components/autofill/content/browser", + "+components/autofill/content/common", "+components/autofill/core/browser", "+components/autofill/core/common", "+components/base32", + "+components/blocked_content", "+components/browser_ui", "+components/captive_portal", "+components/cdm/browser", @@ -17,26 +19,30 @@ include_rules = [ "+components/download/public/common", "+components/embedder_support", "+components/find_in_page", + "+components/infobars/content", "+components/infobars/core", "+components/javascript_dialogs", "+components/keyed_service/content", + "+components/keyed_service/core", "+components/language/core/browser", "+components/metrics", "+components/navigation_interception", + "+components/network_session_configurator", "+components/network_time", "+components/page_load_metrics/browser", + "+components/password_manager/content/browser", "+components/permissions", "+components/pref_registry", "+components/prefs", - "+components/url_formatter", - "+components/user_prefs", "+components/resources/android", + "+components/safe_browsing/core", "+components/safe_browsing/core/common", "+components/safe_browsing/core/features.h", "+components/security_interstitials", "+components/security_state/content/content_utils.h", "+components/security_state/core/security_state.h", "+components/sessions", + "+components/site_isolation", "+components/spellcheck/browser", "+components/ssl_errors", "+components/startup_metric_utils", @@ -44,6 +50,10 @@ include_rules = [ "+components/translate/content/browser", "+components/translate/core/browser", "+components/translate/core/common", + "+components/ukm", + "+components/unified_consent/pref_names.h", + "+components/url_formatter", + "+components/user_prefs", "+components/variations", "+components/version_info", "+components/web_cache/browser", diff --git a/chromium/weblayer/browser/android/javatests/BUILD.gn b/chromium/weblayer/browser/android/javatests/BUILD.gn index 867ce21663e..d069ae4c405 100644 --- a/chromium/weblayer/browser/android/javatests/BUILD.gn +++ b/chromium/weblayer/browser/android/javatests/BUILD.gn @@ -9,7 +9,6 @@ import("//build/config/android/rules.gni") android_library("weblayer_java_tests") { testonly = true sources = [ - "src/org/chromium/weblayer/test/BottomControlsTest.java", "src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java", "src/org/chromium/weblayer/test/CookieManagerTest.java", "src/org/chromium/weblayer/test/CrashReporterTest.java", @@ -17,18 +16,17 @@ android_library("weblayer_java_tests") { "src/org/chromium/weblayer/test/DowngradeTest.java", "src/org/chromium/weblayer/test/DownloadCallbackTest.java", "src/org/chromium/weblayer/test/ErrorPageCallbackTest.java", - "src/org/chromium/weblayer/test/EventUtils.java", "src/org/chromium/weblayer/test/ExecuteScriptTest.java", "src/org/chromium/weblayer/test/ExternalNavigationTest.java", "src/org/chromium/weblayer/test/FindInPageTest.java", "src/org/chromium/weblayer/test/FullscreenCallbackTest.java", "src/org/chromium/weblayer/test/InputTypesTest.java", "src/org/chromium/weblayer/test/NavigationTest.java", - "src/org/chromium/weblayer/test/NewTabCallbackImpl.java", "src/org/chromium/weblayer/test/NewTabCallbackTest.java", "src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java", "src/org/chromium/weblayer/test/ProfileTest.java", "src/org/chromium/weblayer/test/RenderingTest.java", + "src/org/chromium/weblayer/test/SiteSettingsTest.java", "src/org/chromium/weblayer/test/SmokeTest.java", "src/org/chromium/weblayer/test/TabCallbackTest.java", "src/org/chromium/weblayer/test/TabListCallbackTest.java", @@ -37,6 +35,7 @@ android_library("weblayer_java_tests") { "src/org/chromium/weblayer/test/TopControlsTest.java", "src/org/chromium/weblayer/test/WebLayerLoadingTest.java", "src/org/chromium/weblayer/test/WebLayerTest.java", + "src/org/chromium/weblayer/test/WebMessageTest.java", "src/org/chromium/weblayer/test/WebViewCompatibilityTest.java", ] deps = [ @@ -49,8 +48,10 @@ android_library("weblayer_java_tests") { "//third_party/android_deps:android_support_v4_java", "//third_party/android_deps:androidx_core_core_java", "//third_party/android_deps:androidx_fragment_fragment_java", + "//third_party/android_deps:espresso_java", "//third_party/android_support_test_runner:rules_java", "//third_party/android_support_test_runner:runner_java", + "//third_party/hamcrest:hamcrest_java", "//third_party/junit:junit", "//weblayer/browser/java:interfaces_java", "//weblayer/public/java", @@ -62,12 +63,17 @@ android_library("weblayer_java_tests") { android_library("weblayer_private_java_tests") { testonly = true sources = [ + "src/org/chromium/weblayer/test/BrowserControlsTest.java", "src/org/chromium/weblayer/test/GeolocationTest.java", + "src/org/chromium/weblayer/test/InfoBarTest.java", "src/org/chromium/weblayer/test/MediaCaptureTest.java", "src/org/chromium/weblayer/test/NetworkChangeNotifierTest.java", + "src/org/chromium/weblayer/test/PopupTest.java", "src/org/chromium/weblayer/test/ResourceLoadingTest.java", + "src/org/chromium/weblayer/test/TranslateTest.java", ] deps = [ + ":weblayer_java_private_test_support", ":weblayer_java_test_support", "//base:base_java", "//base:base_java_test_support", @@ -77,6 +83,7 @@ android_library("weblayer_private_java_tests") { "//third_party/android_deps:androidx_fragment_fragment_java", "//third_party/android_support_test_runner:rules_java", "//third_party/android_support_test_runner:runner_java", + "//third_party/hamcrest:hamcrest_java", "//third_party/junit:junit", "//weblayer/public/java", "//weblayer/public/javatestutil:test_java", @@ -88,11 +95,15 @@ android_library("weblayer_java_test_support") { testonly = true sources = [ "src/org/chromium/weblayer/test/BoundedCountDownLatch.java", + "src/org/chromium/weblayer/test/EventUtils.java", "src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java", "src/org/chromium/weblayer/test/MinWebLayerVersion.java", "src/org/chromium/weblayer/test/MinWebLayerVersionSkipCheck.java", "src/org/chromium/weblayer/test/NavigationWaiter.java", + "src/org/chromium/weblayer/test/NewTabCallbackImpl.java", "src/org/chromium/weblayer/test/ResourceUtil.java", + "src/org/chromium/weblayer/test/SiteSettingsActivityTestRule.java", + "src/org/chromium/weblayer/test/WebLayerActivityTestRule.java", "src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.java", ] deps = [ @@ -101,8 +112,28 @@ android_library("weblayer_java_test_support") { "//content/public/test/android:content_java_test_support", "//net/android:net_java_test_support", "//third_party/android_deps:androidx_core_core_java", + "//third_party/android_deps:androidx_fragment_fragment_java", + "//third_party/android_support_test_runner:rules_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/hamcrest:hamcrest_java", + "//third_party/junit:junit", + "//weblayer/public/java", + "//weblayer/shell/android:weblayer_shell_java", + ] +} + +android_library("weblayer_java_private_test_support") { + testonly = true + sources = [ "src/org/chromium/weblayer/test/BrowserControlsHelper.java" ] + deps = [ + ":weblayer_java_test_support", + "//base:base_java", + "//base:base_java_test_support", + "//content/public/test/android:content_java_test_support", "//third_party/hamcrest:hamcrest_java", + "//third_party/junit:junit", "//weblayer/public/java", + "//weblayer/public/javatestutil:test_java", "//weblayer/shell/android:weblayer_shell_java", ] } @@ -195,11 +226,15 @@ android_test_apk("weblayer_bundle_test_apk") { ] deps = [ ":weblayer_java_test_support", + "//base:base_java", + "//base:base_java_test_support", + "//content/public/test/android:content_java_test_support", "//third_party/android_support_test_runner:rules_java", "//third_party/android_support_test_runner:runner_java", "//third_party/junit:junit", "//weblayer/public/java", "//weblayer/public/javatestutil:test_java", + "//weblayer/shell/android:weblayer_shell_java", ] never_incremental = true } diff --git a/chromium/weblayer/browser/android/javatests/skew/expectations.txt b/chromium/weblayer/browser/android/javatests/skew/expectations.txt index 56665be1d67..365f067e95e 100644 --- a/chromium/weblayer/browser/android/javatests/skew/expectations.txt +++ b/chromium/weblayer/browser/android/javatests/skew/expectations.txt @@ -4,16 +4,13 @@ # Lines tagged with "impl_lte_$VERSION" will be active when testing trunk client # with versions less than or equal to $VERSION of the implementation. # -# tags: [ impl_lte_81 impl_lte_82 impl_lte_83 client_lte_83 ] +# tags: [ impl_lte_83 client_lte_83 client_lte_84 ] # results: [ Skip ] # --------------------------------------------- # Tests against older WebLayer implementations. # --------------------------------------------- -# Strict mode violation was fixed in M83 in https://crrev.com/c/2108603. -[ impl_lte_82 ] org.chromium.weblayer.test.InputTypesTest#testColorInput [ Skip ] - # ExternalNavigationTests are testing intent launching that changed after M83. [ impl_lte_83 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInNewTabLaunchedOnLinkClick [ Skip ] [ impl_lte_83 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentViaOnLoadBlocked [ Skip ] @@ -31,57 +28,9 @@ # Fixed in https://crrev.com/c/2180022, see https://crbug.com/1077243. [ impl_lte_83 ] org.chromium.weblayer.test.FullscreenCallbackTest#testExitFullscreenWhenTabDestroyed [ Skip ] -# https://crbug.com/1079489. -[ impl_lte_83 ] org.chromium.weblayer.test.BottomControlsTest#testBasic [ Skip ] -[ impl_lte_83 ] org.chromium.weblayer.test.BottomControlsTest#testNoTopControl [ Skip ] - # https://crbug.com/1079491. [ impl_lte_83 ] org.chromium.weblayer.test.NavigationTest#testSetUserAgentString [ Skip ] -# DownloadCallback moved from Tab to Profile in M83: https://crrev.com/cc967e92032594c0e54d02e31824f92aff5f30cd -[ impl_lte_82 ] org.chromium.weblayer.test.DownloadCallbackTest#testBasic [ Skip ] -[ impl_lte_82 ] org.chromium.weblayer.test.DownloadCallbackTest#testInterceptDownloadByContentDisposition [ Skip ] -[ impl_lte_82 ] org.chromium.weblayer.test.DownloadCallbackTest#testInterceptDownloadByLinkAttribute [ Skip ] - -# ExternalNavigationTest intent launching changed from using ApplicationContext -# to Activity after M81. -[ impl_lte_81 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectLaunched [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInSameTabLaunchedOnLinkClick [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithFallbackUrlAfterRedirectLaunched [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ExternalNavigationTest#testNonHandledExternalIntentWithFallbackUrlAfterRedirectGoesToFallbackUrl [ Skip ] - -# Many M81 tests are broken, see https://crbug.com/1081102. -[ impl_lte_81 ] org.chromium.weblayer.test.BrowserFragmentLifecycleTest#restoreAfterRecreate [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.BrowserFragmentLifecycleTest#restoreTabGuidAfterRecreate [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.BrowserFragmentLifecycleTest#restoresTabGuid [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testCookieChanged [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testCookieChangedRemoveCallback [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testCookieChangedRemoveCallbackAfterProfileDestroyed [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testGetCookie [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testSetCookie [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testSetCookieInvalid [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testSetCookieNotSet [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CookieManagerTest#testSetCookieNullCallback [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.CrashReporterTest#testCrashReporterLoading [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.DowngradeTest#testDowngradeDeletesData [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.InputTypesTest#testColorInput [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testRepostConfirmation [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testSetRequestHeaderInRedirect [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testSetRequestHeaderInStart [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testSetRequestHeaderThrowsExceptionInCompleted [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testSetRequestHeaderThrowsExceptionWithInvalidValue [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.NavigationTest#testStopFromOnNavigationStarted [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ProfileTest#testDestroyAndDeleteDataFromDisk [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ProfileTest#testDestroyAndDeleteDataFromDiskIncognito [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ProfileTest#testEnumerateAllProfileNames [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.ProfileTest#testReuseProfile [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testDismissTransientUi [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testOnTitleUpdated [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testShowContextMenu [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testShowContextMenuImg [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testTabModalOverlay [ Skip ] -[ impl_lte_81 ] org.chromium.weblayer.test.TabCallbackTest#testTabModalOverlayOnBackgroundTab [ Skip ] - # ------------------------------------- # Tests against older WebLayer clients. # ------------------------------------- @@ -92,3 +41,6 @@ # Replace was removed in https://crrev.com/c/2150968, see https://crbug.com/1070851. [ client_lte_83 ] org.chromium.weblayer.test.NavigationTest#testReplace [ Skip ] + +# Test was made private, https://crbug.com/1087451. +[ client_lte_84 ] org.chromium.weblayer.test.TopControlsTest#testBasic [ Skip ] diff --git a/chromium/weblayer/browser/android/metrics/DEPS b/chromium/weblayer/browser/android/metrics/DEPS index d9ef433896d..44a37a03920 100644 --- a/chromium/weblayer/browser/android/metrics/DEPS +++ b/chromium/weblayer/browser/android/metrics/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+components/metrics", + "+google_apis/google_api_keys.h", ] diff --git a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_accessor.h b/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_accessor.h deleted file mode 100644 index 173e5d51d13..00000000000 --- a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_accessor.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef WEBLAYER_BROWSER_ANDROID_METRICS_WEBLAYER_METRICS_SERVICE_ACCESSOR_H_ -#define WEBLAYER_BROWSER_ANDROID_METRICS_WEBLAYER_METRICS_SERVICE_ACCESSOR_H_ - -#include <stdint.h> -#include <vector> - -#include "components/metrics/metrics_service_accessor.h" - -namespace weblayer { - -// This class limits and documents access to metrics service helper methods. -// Since these methods are private, each user has to be explicitly declared -// as a 'friend' below. -class WebLayerMetricsServiceAccessor : public metrics::MetricsServiceAccessor { - private: - // For RegisterSyntheticMultiGroupFieldTrial. - friend class WebLayerMetricsServiceClient; - - DISALLOW_IMPLICIT_CONSTRUCTORS(WebLayerMetricsServiceAccessor); -}; - -} // namespace weblayer - -#endif // WEBLAYER_BROWSER_ANDROID_METRICS_WEBLAYER_METRICS_SERVICE_ACCESSOR_H_
\ No newline at end of file diff --git a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc b/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc index 4c879a069be..569fe1d44e2 100644 --- a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc +++ b/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc @@ -8,14 +8,18 @@ #include <cstdint> #include <memory> +#include "base/base64.h" #include "base/no_destructor.h" -#include "base/strings/string_number_conversions.h" +#include "components/metrics/metrics_provider.h" #include "components/metrics/metrics_service.h" -#include "components/variations/hashing.h" -#include "components/variations/variations_associated_data.h" +#include "components/page_load_metrics/browser/metrics_web_contents_observer.h" #include "components/version_info/android/channel_getter.h" -#include "weblayer/browser/android/metrics/weblayer_metrics_service_accessor.h" +#include "content/public/browser/browser_context.h" +#include "google_apis/google_api_keys.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/java/jni/MetricsServiceClient_jni.h" +#include "weblayer/browser/system_network_context_manager.h" +#include "weblayer/browser/tab_impl.h" namespace weblayer { @@ -40,6 +44,28 @@ const int kBetaDevCanarySampledInRatePerMille = 990; // consulting with the privacy team. const int kPackageNameLimitRatePerMille = 100; +// MetricsProvider that interfaces with page_load_metrics. +class PageLoadMetricsProvider : public metrics::MetricsProvider { + public: + PageLoadMetricsProvider() = default; + ~PageLoadMetricsProvider() override = default; + + // metrics:MetricsProvider implementation: + void OnAppEnterBackground() override { + auto tabs = TabImpl::GetAllTabImpl(); + for (auto* tab : tabs) { + page_load_metrics::MetricsWebContentsObserver* observer = + page_load_metrics::MetricsWebContentsObserver::FromWebContents( + tab->web_contents()); + if (observer) + observer->FlushMetricsOnAppEnterBackground(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(PageLoadMetricsProvider); +}; + } // namespace // static @@ -49,47 +75,55 @@ WebLayerMetricsServiceClient* WebLayerMetricsServiceClient::GetInstance() { return client.get(); } -WebLayerMetricsServiceClient::WebLayerMetricsServiceClient() = default; -WebLayerMetricsServiceClient::~WebLayerMetricsServiceClient() = default; +WebLayerMetricsServiceClient::WebLayerMetricsServiceClient() { + ProfileImpl::AddProfileObserver(this); +} + +WebLayerMetricsServiceClient::~WebLayerMetricsServiceClient() { + ProfileImpl::RemoveProfileObserver(this); +} -void WebLayerMetricsServiceClient::RegisterSyntheticMultiGroupFieldTrial( - base::StringPiece trial_name, +void WebLayerMetricsServiceClient::RegisterExternalExperiments( const std::vector<int>& experiment_ids) { if (!GetMetricsService()) { if (!IsConsentDetermined()) { post_start_tasks_.push_back(base::BindOnce( - &WebLayerMetricsServiceClient::RegisterSyntheticMultiGroupFieldTrial, - base::Unretained(this), trial_name, experiment_ids)); + &WebLayerMetricsServiceClient::RegisterExternalExperiments, + base::Unretained(this), experiment_ids)); } return; } - std::vector<uint32_t> group_name_hashes; - group_name_hashes.reserve(experiment_ids.size()); - - variations::ActiveGroupId active_group; - active_group.name = variations::HashName(trial_name); - for (int experiment_id : experiment_ids) { - active_group.group = - variations::HashName(base::NumberToString(experiment_id)); - - // Since external experiments are not based on Chrome's low entropy source, - // they are only sent to Google web properties for signed in users to make - // sure that this couldn't be used to identify a user that's not signed in. - variations::AssociateGoogleVariationIDForceHashes( - variations::GOOGLE_WEB_PROPERTIES_SIGNED_IN, active_group, - static_cast<variations::VariationID>(experiment_id)); - group_name_hashes.push_back(active_group.group); - } - - WebLayerMetricsServiceAccessor::RegisterSyntheticMultiGroupFieldTrial( - GetMetricsService(), trial_name, group_name_hashes); + GetMetricsService()->synthetic_trial_registry()->RegisterExternalExperiments( + "WebLayerExperiments", experiment_ids, + variations::SyntheticTrialRegistry::kOverrideExistingIds); } int32_t WebLayerMetricsServiceClient::GetProduct() { return metrics::ChromeUserMetricsExtension::ANDROID_WEBLAYER; } +bool WebLayerMetricsServiceClient::IsExternalExperimentAllowlistEnabled() { + // RegisterExternalExperiments() is actually used to register experiment ids + // coming from the app embedding WebLayer itself, rather than externally. So + // the allowlist shouldn't be applied. + return false; +} + +bool WebLayerMetricsServiceClient::IsUkmAllowedForAllProfiles() { + for (auto* profile : ProfileImpl::GetAllProfiles()) { + if (!profile->GetBooleanSetting(SettingType::UKM_ENABLED)) + return false; + } + return true; +} + +std::string WebLayerMetricsServiceClient::GetUploadSigningKey() { + std::string decoded_key; + base::Base64Decode(google_apis::GetMetricsKey(), &decoded_key); + return decoded_key; +} + int WebLayerMetricsServiceClient::GetSampleRatePerMille() { version_info::Channel channel = version_info::android::GetChannel(); if (channel == version_info::Channel::STABLE || @@ -114,6 +148,38 @@ int WebLayerMetricsServiceClient::GetPackageNameLimitRatePerMille() { return kPackageNameLimitRatePerMille; } +void WebLayerMetricsServiceClient::RegisterAdditionalMetricsProviders( + metrics::MetricsService* service) { + service->RegisterMetricsProvider(std::make_unique<PageLoadMetricsProvider>()); +} + +bool WebLayerMetricsServiceClient::EnablePersistentHistograms() { + return true; +} + +bool WebLayerMetricsServiceClient::IsOffTheRecordSessionActive() { + for (auto* profile : ProfileImpl::GetAllProfiles()) { + if (profile->GetBrowserContext()->IsOffTheRecord()) + return true; + } + + return false; +} + +scoped_refptr<network::SharedURLLoaderFactory> +WebLayerMetricsServiceClient::GetURLLoaderFactory() { + return SystemNetworkContextManager::GetInstance() + ->GetSharedURLLoaderFactory(); +} + +void WebLayerMetricsServiceClient::ProfileCreated(ProfileImpl* profile) { + UpdateUkmService(); +} + +void WebLayerMetricsServiceClient::ProfileDestroyed(ProfileImpl* profile) { + UpdateUkmService(); +} + // static void JNI_MetricsServiceClient_SetHaveMetricsConsent(JNIEnv* env, jboolean user_consent, diff --git a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.h b/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.h index cc07ae5a02c..a25c0516ceb 100644 --- a/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.h +++ b/chromium/weblayer/browser/android/metrics/weblayer_metrics_service_client.h @@ -18,11 +18,13 @@ #include "components/metrics/metrics_service_client.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" +#include "weblayer/browser/profile_impl.h" namespace weblayer { class WebLayerMetricsServiceClient - : public ::metrics::AndroidMetricsServiceClient { + : public ::metrics::AndroidMetricsServiceClient, + public ProfileImpl::ProfileObserver { friend class base::NoDestructor<WebLayerMetricsServiceClient>; public: @@ -31,20 +33,30 @@ class WebLayerMetricsServiceClient WebLayerMetricsServiceClient(); ~WebLayerMetricsServiceClient() override; - void RegisterSyntheticMultiGroupFieldTrial( - base::StringPiece trial_name, - const std::vector<int>& experiment_ids); + void RegisterExternalExperiments(const std::vector<int>& experiment_ids); // metrics::MetricsServiceClient int32_t GetProduct() override; + bool IsExternalExperimentAllowlistEnabled() override; + bool IsUkmAllowedForAllProfiles() override; + std::string GetUploadSigningKey() override; // metrics::AndroidMetricsServiceClient: int GetSampleRatePerMille() override; void OnMetricsStart() override; void OnMetricsNotStarted() override; int GetPackageNameLimitRatePerMille() override; + void RegisterAdditionalMetricsProviders( + metrics::MetricsService* service) override; + bool EnablePersistentHistograms() override; + bool IsOffTheRecordSessionActive() override; + scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override; private: + // ProfileImpl::ProfileObserver: + void ProfileCreated(ProfileImpl* profile) override; + void ProfileDestroyed(ProfileImpl* profile) override; + std::vector<base::OnceClosure> post_start_tasks_; DISALLOW_COPY_AND_ASSIGN(WebLayerMetricsServiceClient); diff --git a/chromium/weblayer/browser/android/resource_mapper.cc b/chromium/weblayer/browser/android/resource_mapper.cc index 24862f4b20a..ae5bcdf81d4 100644 --- a/chromium/weblayer/browser/android/resource_mapper.cc +++ b/chromium/weblayer/browser/android/resource_mapper.cc @@ -38,6 +38,7 @@ void ConstructMap() { (*GetIdMap())[c_id] = resource_id_list[next_id++]; #define DECLARE_RESOURCE_ID(c_id, java_id) \ (*GetIdMap())[c_id] = resource_id_list[next_id++]; +#include "components/resources/android/blocked_content_resource_id.h" #include "components/resources/android/page_info_resource_id.h" #include "components/resources/android/permissions_resource_id.h" #undef LINK_RESOURCE_ID diff --git a/chromium/weblayer/browser/autofill_client_impl.cc b/chromium/weblayer/browser/autofill_client_impl.cc index a5b25e0e0fe..f049ffa513f 100644 --- a/chromium/weblayer/browser/autofill_client_impl.cc +++ b/chromium/weblayer/browser/autofill_client_impl.cc @@ -4,6 +4,7 @@ #include "weblayer/browser/autofill_client_impl.h" +#include "components/autofill/core/browser/ui/suggestion.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/ssl_status.h" #include "content/public/browser/web_contents.h" @@ -203,11 +204,7 @@ void AutofillClientImpl::ScanCreditCard(CreditCardScanCallback callback) { } void AutofillClientImpl::ShowAutofillPopup( - const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<autofill::Suggestion>& suggestions, - bool /*unused_autoselect_first_suggestion*/, - autofill::PopupType popup_type, + const autofill::AutofillClient::PopupOpenArgs& open_args, base::WeakPtr<autofill::AutofillPopupDelegate> delegate) { NOTREACHED(); } @@ -235,6 +232,12 @@ void AutofillClientImpl::PinPopupView() { NOTIMPLEMENTED(); } +autofill::AutofillClient::PopupOpenArgs AutofillClientImpl::GetReopenPopupArgs() + const { + NOTIMPLEMENTED(); + return {}; +} + void AutofillClientImpl::UpdatePopup( const std::vector<autofill::Suggestion>& suggestions, autofill::PopupType popup_type) { diff --git a/chromium/weblayer/browser/autofill_client_impl.h b/chromium/weblayer/browser/autofill_client_impl.h index 4ff8339b28d..c5edc4a218a 100644 --- a/chromium/weblayer/browser/autofill_client_impl.h +++ b/chromium/weblayer/browser/autofill_client_impl.h @@ -97,17 +97,14 @@ class AutofillClientImpl bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; void ShowAutofillPopup( - const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<autofill::Suggestion>& suggestions, - bool /*unused_autoselect_first_suggestion*/, - autofill::PopupType popup_type, + const autofill::AutofillClient::PopupOpenArgs& open_args, base::WeakPtr<autofill::AutofillPopupDelegate> delegate) override; void UpdateAutofillPopupDataListValues( const std::vector<base::string16>& values, const std::vector<base::string16>& labels) override; base::span<const autofill::Suggestion> GetPopupSuggestions() const override; void PinPopupView() override; + autofill::AutofillClient::PopupOpenArgs GetReopenPopupArgs() const override; void UpdatePopup(const std::vector<autofill::Suggestion>& suggestions, autofill::PopupType popup_type) override; void HideAutofillPopup(autofill::PopupHidingReason reason) override; diff --git a/chromium/weblayer/browser/browser_context_impl.cc b/chromium/weblayer/browser/browser_context_impl.cc index bd790631ab0..0e99612fdce 100644 --- a/chromium/weblayer/browser/browser_context_impl.cc +++ b/chromium/weblayer/browser/browser_context_impl.cc @@ -5,6 +5,7 @@ #include "weblayer/browser/browser_context_impl.h" #include "base/threading/thread_restrictions.h" +#include "components/blocked_content/safe_browsing_triggered_popup_blocker.h" #include "components/client_hints/browser/client_hints.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/download/public/common/in_progress_download_manager.h" @@ -19,7 +20,8 @@ #include "components/prefs/pref_service_factory.h" #include "components/safe_browsing/core/common/safe_browsing_prefs.h" #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h" -#include "components/security_state/core/security_state.h" +#include "components/site_isolation/pref_names.h" +#include "components/site_isolation/site_isolation_policy.h" #include "components/translate/core/browser/translate_pref_names.h" #include "components/translate/core/browser/translate_prefs.h" #include "components/user_prefs/user_prefs.h" @@ -29,6 +31,8 @@ #include "content/public/browser/download_request_utils.h" #include "content/public/browser/resource_context.h" #include "content/public/browser/storage_partition.h" +#include "weblayer/browser/browsing_data_remover_delegate.h" +#include "weblayer/browser/browsing_data_remover_delegate_factory.h" #include "weblayer/browser/client_hints_factory.h" #include "weblayer/browser/permissions/permission_manager_factory.h" #include "weblayer/browser/stateful_ssl_host_state_delegate_factory.h" @@ -38,6 +42,7 @@ #include "base/android/path_utils.h" #include "components/cdm/browser/media_drm_storage_impl.h" // nogncheck #include "components/permissions/contexts/geolocation_permission_context_android.h" +#include "components/unified_consent/pref_names.h" #elif defined(OS_WIN) #include <KnownFolders.h> #include <shlobj.h> @@ -63,6 +68,11 @@ void BindWakeLockProvider( } // namespace +namespace prefs { +// Used to persist the public SettingType::UKM_ENABLED API. +const char kUkmEnabled[] = "weblayer.ukm_enabled"; +} // namespace prefs + class ResourceContextImpl : public content::ResourceContext { public: ResourceContextImpl() = default; @@ -82,6 +92,8 @@ BrowserContextImpl::BrowserContextImpl(ProfileImpl* profile_impl, BrowserContextDependencyManager::GetInstance()->CreateBrowserContextServices( this); + + site_isolation::SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(this); } BrowserContextImpl::~BrowserContextImpl() { @@ -178,7 +190,7 @@ BrowserContextImpl::GetBackgroundSyncController() { content::BrowsingDataRemoverDelegate* BrowserContextImpl::GetBrowsingDataRemoverDelegate() { - return nullptr; + return BrowsingDataRemoverDelegateFactory::GetForBrowserContext(this); } download::InProgressDownloadManager* @@ -228,25 +240,32 @@ void BrowserContextImpl::CreateUserPrefService() { void BrowserContextImpl::RegisterPrefs( user_prefs::PrefRegistrySyncable* pref_registry) { + pref_registry->RegisterBooleanPref(prefs::kUkmEnabled, false); + // This pref is used by captive_portal::CaptivePortalService (as well as other // potential use cases in the future, as it is used for various purposes // through //chrome). pref_registry->RegisterBooleanPref( embedder_support::kAlternateErrorPagesEnabled, true); + pref_registry->RegisterListPref( + site_isolation::prefs::kUserTriggeredIsolatedOrigins); StatefulSSLHostStateDelegate::RegisterProfilePrefs(pref_registry); HostContentSettingsMap::RegisterProfilePrefs(pref_registry); safe_browsing::RegisterProfilePrefs(pref_registry); - security_state::RegisterProfilePrefs(pref_registry); language::LanguagePrefs::RegisterProfilePrefs(pref_registry); translate::TranslatePrefs::RegisterProfilePrefs(pref_registry); + blocked_content::SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs( + pref_registry); pref_registry->RegisterBooleanPref( - prefs::kOfferTranslateEnabled, true, + ::prefs::kOfferTranslateEnabled, true, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); #if defined(OS_ANDROID) cdm::MediaDrmStorageImpl::RegisterProfilePrefs(pref_registry); permissions::GeolocationPermissionContextAndroid::RegisterProfilePrefs( pref_registry); + pref_registry->RegisterBooleanPref( + unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false); #endif BrowserContextDependencyManager::GetInstance() @@ -261,7 +280,7 @@ class BrowserContextImpl::WebLayerVariationsClient ~WebLayerVariationsClient() override = default; - bool IsIncognito() const override { + bool IsOffTheRecord() const override { return browser_context_->IsOffTheRecord(); } diff --git a/chromium/weblayer/browser/browser_context_impl.h b/chromium/weblayer/browser/browser_context_impl.h index 90d65f56290..7d888462025 100644 --- a/chromium/weblayer/browser/browser_context_impl.h +++ b/chromium/weblayer/browser/browser_context_impl.h @@ -21,6 +21,11 @@ namespace weblayer { class ProfileImpl; class ResourceContextImpl; +namespace prefs { +// WebLayer specific pref names. +extern const char kUkmEnabled[]; +} // namespace prefs + class BrowserContextImpl : public content::BrowserContext { public: BrowserContextImpl(ProfileImpl* profile_impl, const base::FilePath& path); diff --git a/chromium/weblayer/browser/browser_controls_container_view.cc b/chromium/weblayer/browser/browser_controls_container_view.cc index aa090d4315b..81a1f9e9626 100644 --- a/chromium/weblayer/browser/browser_controls_container_view.cc +++ b/chromium/weblayer/browser/browser_controls_container_view.cc @@ -5,6 +5,8 @@ #include "weblayer/browser/browser_controls_container_view.h" #include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/feature_list.h" #include "cc/layers/ui_resource_layer.h" #include "content/public/browser/android/compositor.h" #include "content/public/browser/render_view_host.h" @@ -16,6 +18,7 @@ #include "ui/android/view_android.h" #include "weblayer/browser/content_view_render_view.h" #include "weblayer/browser/java/jni/BrowserControlsContainerView_jni.h" +#include "weblayer/browser/weblayer_features.h" using base::android::AttachCurrentThread; using base::android::JavaParamRef; @@ -31,9 +34,19 @@ BrowserControlsContainerView::BrowserControlsContainerView( content_view_render_view_(content_view_render_view), is_top_(is_top) { DCHECK(content_view_render_view_); + if (!is_top_) { + content_view_render_view_->SetHeightChangedListener( + base::BindRepeating(&BrowserControlsContainerView::ContentHeightChanged, + base::Unretained(this))); + } } -BrowserControlsContainerView::~BrowserControlsContainerView() = default; +BrowserControlsContainerView::~BrowserControlsContainerView() { + if (!is_top_) { + content_view_render_view_->SetHeightChangedListener( + base::RepeatingClosure()); + } +} int BrowserControlsContainerView::GetControlsHeight() { return controls_layer_ ? controls_layer_->bounds().height() : 0; @@ -80,28 +93,20 @@ void BrowserControlsContainerView::DeleteControlsLayer(JNIEnv* env) { } void BrowserControlsContainerView::SetTopControlsOffset(JNIEnv* env, - int controls_offset_y, int content_offset_y) { DCHECK(is_top_); // |controls_layer_| may not be created if the controls view has 0 height. if (controls_layer_) - controls_layer_->SetPosition(gfx::PointF(0, controls_offset_y)); + controls_layer_->SetPosition(gfx::PointF(0, GetControlsOffset())); if (web_contents()) { web_contents()->GetNativeView()->GetLayer()->SetPosition( gfx::PointF(0, content_offset_y)); } } -void BrowserControlsContainerView::SetBottomControlsOffset( - JNIEnv* env, - int controls_offset_y) { +void BrowserControlsContainerView::SetBottomControlsOffset(JNIEnv* env) { DCHECK(!is_top_); - // |controls_layer_| may not be created if the controls view has 0 height. - if (controls_layer_) { - controls_layer_->SetPosition( - gfx::PointF(0, content_view_render_view_->height() - - GetControlsHeight() + controls_offset_y)); - } + DoSetBottomControlsOffset(); } void BrowserControlsContainerView::SetControlsSize( @@ -110,6 +115,8 @@ void BrowserControlsContainerView::SetControlsSize( int height) { DCHECK(controls_layer_); controls_layer_->SetBounds(gfx::Size(width, height)); + // It's assumed the caller handles triggering SynchronizeVisualProperties() + // being called (this is done in java code). } void BrowserControlsContainerView::UpdateControlsResource(JNIEnv* env) { @@ -139,6 +146,26 @@ void BrowserControlsContainerView::DidToggleFullscreenModeForTab( entered_fullscreen); } +void BrowserControlsContainerView::ContentHeightChanged() { + DCHECK(!is_top_); + DoSetBottomControlsOffset(); +} + +int BrowserControlsContainerView::GetControlsOffset() { + return Java_BrowserControlsContainerView_getControlsOffset( + AttachCurrentThread(), java_browser_controls_container_view_); +} + +void BrowserControlsContainerView::DoSetBottomControlsOffset() { + DCHECK(!is_top_); + // |controls_layer_| may not be created if the controls view has 0 height. + if (!controls_layer_) + return; + controls_layer_->SetPosition( + gfx::PointF(0, content_view_render_view_->height() - GetControlsHeight() + + GetControlsOffset())); +} + static jlong JNI_BrowserControlsContainerView_CreateBrowserControlsContainerView( JNIEnv* env, @@ -151,4 +178,9 @@ JNI_BrowserControlsContainerView_CreateBrowserControlsContainerView( is_top)); } +static jboolean JNI_BrowserControlsContainerView_ShouldDelayVisibilityChange( + JNIEnv* env) { + return !base::FeatureList::IsEnabled(kImmediatelyHideBrowserControlsForTest); +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/browser_controls_container_view.h b/chromium/weblayer/browser/browser_controls_container_view.h index 883ea0c49b6..9231531499d 100644 --- a/chromium/weblayer/browser/browser_controls_container_view.h +++ b/chromium/weblayer/browser/browser_controls_container_view.h @@ -55,9 +55,8 @@ class BrowserControlsContainerView : public content::WebContentsObserver { // Sets the offsets of the controls and content. See // BrowserControlsContainerView's javadoc for details on this. void SetTopControlsOffset(JNIEnv* env, - int controls_offset_y, int content_offset_y); - void SetBottomControlsOffset(JNIEnv* env, int controls_offset_y); + void SetBottomControlsOffset(JNIEnv* env); // Sets the size of |controls_layer_|. void SetControlsSize(JNIEnv* env, @@ -75,6 +74,12 @@ class BrowserControlsContainerView : public content::WebContentsObserver { void DidToggleFullscreenModeForTab(bool entered_fullscreen, bool will_cause_resize) override; + // Only used for bottom controls. + void ContentHeightChanged(); + + int GetControlsOffset(); + void DoSetBottomControlsOffset(); + base::android::ScopedJavaGlobalRef<jobject> java_browser_controls_container_view_; ContentViewRenderView* content_view_render_view_; diff --git a/chromium/weblayer/browser/browser_controls_navigation_state_handler.cc b/chromium/weblayer/browser/browser_controls_navigation_state_handler.cc index 139a6ab86b3..a049f38ca42 100644 --- a/chromium/weblayer/browser/browser_controls_navigation_state_handler.cc +++ b/chromium/weblayer/browser/browser_controls_navigation_state_handler.cc @@ -18,11 +18,10 @@ #include "content/public/browser/web_contents.h" #include "content/public/common/url_constants.h" #include "weblayer/browser/browser_controls_navigation_state_handler_delegate.h" +#include "weblayer/browser/weblayer_features.h" namespace weblayer { namespace { -const base::Feature kImmediatelyHideBrowserControlsForTest{ - "ImmediatelyHideBrowserControlsForTest", base::FEATURE_DISABLED_BY_DEFAULT}; // The time that must elapse after a navigation before the browser controls can // be hidden. This value matches what chrome has in @@ -92,14 +91,6 @@ void BrowserControlsNavigationStateHandler::DidChangeVisibleSecurityState() { UpdateState(); } -void BrowserControlsNavigationStateHandler::DidAttachInterstitialPage() { - UpdateState(); -} - -void BrowserControlsNavigationStateHandler::DidDetachInterstitialPage() { - UpdateState(); -} - void BrowserControlsNavigationStateHandler::RenderProcessGone( base::TerminationStatus status) { is_crashed_ = true; @@ -153,7 +144,6 @@ BrowserControlsNavigationStateHandler::CalculateCurrentState() { if (force_show_during_load_ || web_contents()->IsFullscreen() || web_contents()->IsFocusedElementEditable() || - web_contents()->ShowingInterstitialPage() || web_contents()->IsBeingDestroyed() || web_contents()->IsCrashed()) { return content::BROWSER_CONTROLS_STATE_SHOWN; } @@ -176,7 +166,6 @@ BrowserControlsNavigationStateHandler::CalculateCurrentState() { return content::BROWSER_CONTROLS_STATE_SHOWN; case security_state::NONE: - case security_state::EV_SECURE: case security_state::SECURE: case security_state::SECURE_WITH_POLICY_INSTALLED_CERT: case security_state::SECURITY_LEVEL_COUNT: diff --git a/chromium/weblayer/browser/browser_controls_navigation_state_handler.h b/chromium/weblayer/browser/browser_controls_navigation_state_handler.h index aa7d27dafe7..0ad3b319b3e 100644 --- a/chromium/weblayer/browser/browser_controls_navigation_state_handler.h +++ b/chromium/weblayer/browser/browser_controls_navigation_state_handler.h @@ -48,8 +48,6 @@ class BrowserControlsNavigationStateHandler const GURL& validated_url, int error_code) override; void DidChangeVisibleSecurityState() override; - void DidAttachInterstitialPage() override; - void DidDetachInterstitialPage() override; void RenderProcessGone(base::TerminationStatus status) override; void OnRendererUnresponsive( content::RenderProcessHost* render_process_host) override; diff --git a/chromium/weblayer/browser/browser_impl.cc b/chromium/weblayer/browser/browser_impl.cc index d9344a7b4e6..b7f5dc490b3 100644 --- a/chromium/weblayer/browser/browser_impl.cc +++ b/chromium/weblayer/browser/browser_impl.cc @@ -10,11 +10,14 @@ #include "base/containers/unique_ptr_adapters.h" #include "base/memory/ptr_util.h" #include "base/path_service.h" +#include "base/stl_util.h" #include "components/base32/base32.h" -#include "content/public/browser/browser_context.h" #include "content/public/common/web_preferences.h" +#include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/browser_list.h" #include "weblayer/browser/feature_list_creator.h" #include "weblayer/browser/persistence/browser_persister.h" +#include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/persistence/minimal_browser_persister.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" @@ -26,6 +29,9 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/json/json_writer.h" +#include "components/metrics/metrics_service.h" +#include "components/ukm/ukm_service.h" +#include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/java/jni/BrowserImpl_jni.h" #endif @@ -38,8 +44,43 @@ using base::android::ScopedJavaLocalRef; namespace weblayer { -// TODO(timvolodine): consider using an observer for this, crbug.com/1068713. -int BrowserImpl::browser_count_ = 0; +namespace { + +#if defined(OS_ANDROID) +void UpdateMetricsService() { + static bool s_foreground = false; + // TODO(sky): convert this to observer. + bool foreground = BrowserList::GetInstance()->HasAtLeastOneResumedBrowser(); + + if (foreground == s_foreground) + return; + + s_foreground = foreground; + + auto* metrics_service = + WebLayerMetricsServiceClient::GetInstance()->GetMetricsService(); + if (metrics_service) { + if (foreground) + metrics_service->OnAppEnterForeground(); + else + metrics_service->OnAppEnterBackground(); + } + + auto* ukm_service = + WebLayerMetricsServiceClient::GetInstance()->GetUkmService(); + if (ukm_service) { + if (foreground) + ukm_service->OnAppEnterForeground(); + else + ukm_service->OnAppEnterBackground(); + } +} +#endif // defined(OS_ANDROID) + +} // namespace + +// static +constexpr char BrowserImpl::kPersistenceFilePrefix[]; std::unique_ptr<Browser> Browser::Create( Profile* profile, @@ -60,16 +101,13 @@ BrowserImpl::~BrowserImpl() { DCHECK(tabs_.empty()); #else while (!tabs_.empty()) - RemoveTab(tabs_.back().get()); + DestroyTab(tabs_.back().get()); #endif - profile_->DecrementBrowserImplCount(); - browser_count_--; - DCHECK(browser_count_ >= 0); + BrowserList::GetInstance()->RemoveBrowser(this); #if defined(OS_ANDROID) - if (browser_count_ == 0) { + if (BrowserList::GetInstance()->browsers().empty()) BrowserProcess::GetInstance()->StopSafeBrowsingService(); - } #endif } @@ -79,12 +117,15 @@ TabImpl* BrowserImpl::CreateTabForSessionRestore( std::unique_ptr<TabImpl> tab = std::make_unique<TabImpl>(profile_, std::move(web_contents), guid); #if defined(OS_ANDROID) - Java_BrowserImpl_createTabForSessionRestore( + Java_BrowserImpl_createJavaTabForNativeTab( AttachCurrentThread(), java_impl_, reinterpret_cast<jlong>(tab.get())); #endif - TabImpl* tab_ptr = tab.get(); - AddTab(std::move(tab)); - return tab_ptr; + return AddTab(std::move(tab)); +} + +TabImpl* BrowserImpl::CreateTab( + std::unique_ptr<content::WebContents> web_contents) { + return CreateTabForSessionRestore(std::move(web_contents), std::string()); } #if defined(OS_ANDROID) @@ -95,13 +136,7 @@ bool BrowserImpl::CompositorHasSurface() { void BrowserImpl::AddTab(JNIEnv* env, long native_tab) { - TabImpl* tab = reinterpret_cast<TabImpl*>(native_tab); - std::unique_ptr<Tab> owned_tab; - if (tab->browser()) - owned_tab = tab->browser()->RemoveTab(tab); - else - owned_tab.reset(tab); - AddTab(std::move(owned_tab)); + AddTab(reinterpret_cast<TabImpl*>(native_tab)); } void BrowserImpl::RemoveTab(JNIEnv* env, @@ -158,8 +193,7 @@ ScopedJavaLocalRef<jbyteArray> BrowserImpl::GetBrowserPersisterCryptoKey( ScopedJavaLocalRef<jbyteArray> BrowserImpl::GetMinimalPersistenceState( JNIEnv* env) { - auto state = GetMinimalPersistenceState(); - return base::android::ToJavaByteArray(env, &(state.front()), state.size()); + return base::android::ToJavaByteArray(env, GetMinimalPersistenceState()); } void BrowserImpl::RestoreStateIfNecessary( @@ -203,6 +237,14 @@ void BrowserImpl::OnFragmentStart(JNIEnv* env) { FeatureListCreator::GetInstance()->OnBrowserFragmentStarted(); } +void BrowserImpl::OnFragmentResume(JNIEnv* env) { + UpdateFragmentResumedState(true); +} + +void BrowserImpl::OnFragmentPause(JNIEnv* env) { + UpdateFragmentResumedState(false); +} + #endif std::vector<uint8_t> BrowserImpl::GetMinimalPersistenceState( @@ -223,41 +265,19 @@ void BrowserImpl::SetWebPreferences(content::WebPreferences* prefs) { #endif } -Tab* BrowserImpl::AddTab(std::unique_ptr<Tab> tab) { +void BrowserImpl::AddTab(Tab* tab) { DCHECK(tab); - TabImpl* tab_impl = static_cast<TabImpl*>(tab.get()); - DCHECK(!tab_impl->browser()); - tabs_.push_back(std::move(tab)); - tab_impl->set_browser(this); -#if defined(OS_ANDROID) - Java_BrowserImpl_onTabAdded(AttachCurrentThread(), java_impl_, - tab_impl->GetJavaTab()); -#endif - for (BrowserObserver& obs : browser_observers_) - obs.OnTabAdded(tab_impl); - return tab_impl; -} - -std::unique_ptr<Tab> BrowserImpl::RemoveTab(Tab* tab) { TabImpl* tab_impl = static_cast<TabImpl*>(tab); - DCHECK_EQ(this, tab_impl->browser()); - static_cast<TabImpl*>(tab)->set_browser(nullptr); - auto iter = - std::find_if(tabs_.begin(), tabs_.end(), base::MatchesUniquePtr(tab)); - DCHECK(iter != tabs_.end()); - std::unique_ptr<Tab> owned_tab = std::move(*iter); - tabs_.erase(iter); - const bool active_tab_changed = active_tab_ == tab; - if (active_tab_changed) - SetActiveTab(nullptr); + std::unique_ptr<Tab> owned_tab; + if (tab_impl->browser()) + owned_tab = tab_impl->browser()->RemoveTab(tab_impl); + else + owned_tab.reset(tab_impl); + AddTab(std::move(owned_tab)); +} -#if defined(OS_ANDROID) - Java_BrowserImpl_onTabRemoved(AttachCurrentThread(), java_impl_, - tab ? tab_impl->GetJavaTab() : nullptr); -#endif - for (BrowserObserver& obs : browser_observers_) - obs.OnTabRemoved(tab, active_tab_changed); - return owned_tab; +void BrowserImpl::DestroyTab(Tab* tab) { + RemoveTab(tab); } void BrowserImpl::SetActiveTab(Tab* tab) { @@ -291,6 +311,10 @@ std::vector<Tab*> BrowserImpl::GetTabs() { return tabs; } +Tab* BrowserImpl::CreateTab() { + return CreateTab(nullptr); +} + void BrowserImpl::PrepareForShutdown() { browser_persister_.reset(); } @@ -312,9 +336,18 @@ void BrowserImpl::RemoveObserver(BrowserObserver* observer) { browser_observers_.RemoveObserver(observer); } +void BrowserImpl::VisibleSecurityStateOfActiveTabChanged() { + if (visible_security_state_changed_callback_for_tests_) + std::move(visible_security_state_changed_callback_for_tests_).Run(); + +#if defined(OS_ANDROID) + JNIEnv* env = base::android::AttachCurrentThread(); + Java_BrowserImpl_onVisibleSecurityStateOfActiveTabChanged(env, java_impl_); +#endif +} + BrowserImpl::BrowserImpl(ProfileImpl* profile) : profile_(profile) { - profile_->IncrementBrowserImplCount(); - browser_count_++; + BrowserList::GetInstance()->AddBrowser(this); } void BrowserImpl::RestoreStateIfNecessary( @@ -328,24 +361,59 @@ void BrowserImpl::RestoreStateIfNecessary( } } -void BrowserImpl::VisibleSecurityStateOfActiveTabChanged() { - if (visible_security_state_changed_callback_for_tests_) - std::move(visible_security_state_changed_callback_for_tests_).Run(); +TabImpl* BrowserImpl::AddTab(std::unique_ptr<Tab> tab) { + TabImpl* tab_impl = static_cast<TabImpl*>(tab.get()); + DCHECK(!tab_impl->browser()); + tabs_.push_back(std::move(tab)); + tab_impl->set_browser(this); +#if defined(OS_ANDROID) + Java_BrowserImpl_onTabAdded(AttachCurrentThread(), java_impl_, + tab_impl->GetJavaTab()); +#endif + for (BrowserObserver& obs : browser_observers_) + obs.OnTabAdded(tab_impl); + return tab_impl; +} + +std::unique_ptr<Tab> BrowserImpl::RemoveTab(Tab* tab) { + TabImpl* tab_impl = static_cast<TabImpl*>(tab); + DCHECK_EQ(this, tab_impl->browser()); + static_cast<TabImpl*>(tab)->set_browser(nullptr); + auto iter = + std::find_if(tabs_.begin(), tabs_.end(), base::MatchesUniquePtr(tab)); + DCHECK(iter != tabs_.end()); + std::unique_ptr<Tab> owned_tab = std::move(*iter); + tabs_.erase(iter); + const bool active_tab_changed = active_tab_ == tab; + if (active_tab_changed) + SetActiveTab(nullptr); #if defined(OS_ANDROID) - JNIEnv* env = base::android::AttachCurrentThread(); - Java_BrowserImpl_onVisibleSecurityStateOfActiveTabChanged(env, java_impl_); + Java_BrowserImpl_onTabRemoved(AttachCurrentThread(), java_impl_, + tab ? tab_impl->GetJavaTab() : nullptr); #endif + for (BrowserObserver& obs : browser_observers_) + obs.OnTabRemoved(tab, active_tab_changed); + return owned_tab; } base::FilePath BrowserImpl::GetBrowserPersisterDataPath() { - base::FilePath base_path = profile_->GetBrowserPersisterDataBaseDir(); - DCHECK(!GetPersistenceId().empty()); - const std::string encoded_name = base32::Base32Encode(GetPersistenceId()); - return base_path.AppendASCII("State" + encoded_name); + return BuildPathForBrowserPersister( + profile_->GetBrowserPersisterDataBaseDir(), GetPersistenceId()); } #if defined(OS_ANDROID) +void BrowserImpl::UpdateFragmentResumedState(bool state) { + const bool old_has_at_least_one_active_browser = + BrowserList::GetInstance()->HasAtLeastOneResumedBrowser(); + fragment_resumed_ = state; + UpdateMetricsService(); + if (old_has_at_least_one_active_browser != + BrowserList::GetInstance()->HasAtLeastOneResumedBrowser()) { + BrowserList::GetInstance()->NotifyHasAtLeastOneResumedBrowserChanged(); + } +} + // This function is friended. JNI_BrowserImpl_CreateBrowser can not be // friended, as it requires browser_impl.h to include BrowserImpl_jni.h, which // is problematic (meaning not really supported and generates compile errors). diff --git a/chromium/weblayer/browser/browser_impl.h b/chromium/weblayer/browser/browser_impl.h index b625c089b03..f5487af1cbc 100644 --- a/chromium/weblayer/browser/browser_impl.h +++ b/chromium/weblayer/browser/browser_impl.h @@ -34,6 +34,9 @@ class TabImpl; class BrowserImpl : public Browser { public: + // Prefix used for storing persistence state. + static constexpr char kPersistenceFilePrefix[] = "State"; + BrowserImpl(const BrowserImpl&) = delete; BrowserImpl& operator=(const BrowserImpl&) = delete; ~BrowserImpl() override; @@ -47,6 +50,7 @@ class BrowserImpl : public Browser { TabImpl* CreateTabForSessionRestore( std::unique_ptr<content::WebContents> web_contents, const std::string& guid); + TabImpl* CreateTab(std::unique_ptr<content::WebContents> web_contents); #if defined(OS_ANDROID) bool CompositorHasSurface(); @@ -74,6 +78,10 @@ class BrowserImpl : public Browser { j_minimal_persistence_state); void WebPreferencesChanged(JNIEnv* env); void OnFragmentStart(JNIEnv* env); + void OnFragmentResume(JNIEnv* env); + void OnFragmentPause(JNIEnv* env); + + bool fragment_resumed() { return fragment_resumed_; } #endif // Used in tests to specify a non-default max (0 means use the default). @@ -90,11 +98,12 @@ class BrowserImpl : public Browser { void SetWebPreferences(content::WebPreferences* prefs); // Browser: - Tab* AddTab(std::unique_ptr<Tab> tab) override; - std::unique_ptr<Tab> RemoveTab(Tab* tab) override; + void AddTab(Tab* tab) override; + void DestroyTab(Tab* tab) override; void SetActiveTab(Tab* tab) override; Tab* GetActiveTab() override; std::vector<Tab*> GetTabs() override; + Tab* CreateTab() override; void PrepareForShutdown() override; std::string GetPersistenceId() override; std::vector<uint8_t> GetMinimalPersistenceState() override; @@ -116,10 +125,16 @@ class BrowserImpl : public Browser { void RestoreStateIfNecessary(const PersistenceInfo& persistence_info); + TabImpl* AddTab(std::unique_ptr<Tab> tab); + std::unique_ptr<Tab> RemoveTab(Tab* tab); + // Returns the path used by |browser_persister_|. base::FilePath GetBrowserPersisterDataPath(); #if defined(OS_ANDROID) + void UpdateFragmentResumedState(bool state); + + bool fragment_resumed_ = false; base::android::ScopedJavaGlobalRef<jobject> java_impl_; #endif base::ObserverList<BrowserObserver> browser_observers_; @@ -129,7 +144,6 @@ class BrowserImpl : public Browser { std::string persistence_id_; std::unique_ptr<BrowserPersister> browser_persister_; base::OnceClosure visible_security_state_changed_callback_for_tests_; - static int browser_count_; }; } // namespace weblayer diff --git a/chromium/weblayer/browser/browser_list.cc b/chromium/weblayer/browser/browser_list.cc new file mode 100644 index 00000000000..baff432c616 --- /dev/null +++ b/chromium/weblayer/browser/browser_list.cc @@ -0,0 +1,66 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/browser_list.h" + +#include <algorithm> +#include <functional> + +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/browser_list_observer.h" + +namespace weblayer { + +// static +BrowserList* BrowserList::GetInstance() { + static base::NoDestructor<BrowserList> browser_list; + return browser_list.get(); +} + +#if defined(OS_ANDROID) +bool BrowserList::HasAtLeastOneResumedBrowser() { + return std::any_of(browsers_.begin(), browsers_.end(), + std::mem_fn(&BrowserImpl::fragment_resumed)); +} +#endif + +void BrowserList::AddObserver(BrowserListObserver* observer) { + observers_.AddObserver(observer); +} + +void BrowserList::RemoveObserver(BrowserListObserver* observer) { + observers_.RemoveObserver(observer); +} + +BrowserList::BrowserList() = default; + +BrowserList::~BrowserList() = default; + +void BrowserList::AddBrowser(BrowserImpl* browser) { + DCHECK(!browsers_.contains(browser)); +#if defined(OS_ANDROID) + // Browsers should not start out resumed. + DCHECK(!browser->fragment_resumed()); +#endif + browsers_.insert(browser); +} + +void BrowserList::RemoveBrowser(BrowserImpl* browser) { + DCHECK(browsers_.contains(browser)); +#if defined(OS_ANDROID) + // Browsers should not be resumed when being destroyed. + DCHECK(!browser->fragment_resumed()); +#endif + browsers_.erase(browser); +} + +#if defined(OS_ANDROID) +void BrowserList::NotifyHasAtLeastOneResumedBrowserChanged() { + const bool value = HasAtLeastOneResumedBrowser(); + for (BrowserListObserver& observer : observers_) + observer.OnHasAtLeastOneResumedBrowserStateChanged(value); +} +#endif + +} // namespace weblayer diff --git a/chromium/weblayer/browser/browser_list.h b/chromium/weblayer/browser/browser_list.h new file mode 100644 index 00000000000..6852761d31a --- /dev/null +++ b/chromium/weblayer/browser/browser_list.h @@ -0,0 +1,56 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_BROWSER_LIST_H_ +#define WEBLAYER_BROWSER_BROWSER_LIST_H_ + +#include "base/containers/flat_set.h" +#include "base/no_destructor.h" +#include "base/observer_list.h" +#include "build/build_config.h" + +namespace weblayer { + +class BrowserImpl; +class BrowserListObserver; + +// Tracks the set of browsers. +class BrowserList { + public: + BrowserList(const BrowserList&) = delete; + BrowserList& operator=(const BrowserList&) = delete; + + static BrowserList* GetInstance(); + + const base::flat_set<BrowserImpl*>& browsers() { return browsers_; } + +#if defined(OS_ANDROID) + // Returns true if there is at least one Browser in a resumed state. + bool HasAtLeastOneResumedBrowser(); +#endif + + void AddObserver(BrowserListObserver* observer); + void RemoveObserver(BrowserListObserver* observer); + + private: + friend class BrowserImpl; + friend class base::NoDestructor<BrowserList>; + + BrowserList(); + ~BrowserList(); + + void AddBrowser(BrowserImpl* browser); + void RemoveBrowser(BrowserImpl* browser); + +#if defined(OS_ANDROID) + void NotifyHasAtLeastOneResumedBrowserChanged(); +#endif + + base::flat_set<BrowserImpl*> browsers_; + base::ObserverList<BrowserListObserver> observers_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_BROWSER_LIST_H_ diff --git a/chromium/weblayer/browser/browser_list_observer.h b/chromium/weblayer/browser/browser_list_observer.h new file mode 100644 index 00000000000..2175937226e --- /dev/null +++ b/chromium/weblayer/browser/browser_list_observer.h @@ -0,0 +1,27 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_BROWSER_LIST_OBSERVER_H_ +#define WEBLAYER_BROWSER_BROWSER_LIST_OBSERVER_H_ + +#include "base/observer_list_types.h" +#include "build/build_config.h" + +namespace weblayer { + +class BrowserListObserver : public base::CheckedObserver { + public: +#if defined(OS_ANDROID) + // Called when the value of BrowserList::HasAtLeastOneResumedBrowser() + // changes. + void OnHasAtLeastOneResumedBrowserStateChanged(bool new_value) {} +#endif + + protected: + ~BrowserListObserver() override = default; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_BROWSER_LIST_OBSERVER_H_ diff --git a/chromium/weblayer/browser/browser_main_parts_impl.cc b/chromium/weblayer/browser/browser_main_parts_impl.cc index f0672179029..7f9e8467a13 100644 --- a/chromium/weblayer/browser/browser_main_parts_impl.cc +++ b/chromium/weblayer/browser/browser_main_parts_impl.cc @@ -18,7 +18,6 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/render_process_host.h" -#include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" #include "content/public/common/url_constants.h" #include "services/service_manager/embedder/result_codes.h" @@ -36,15 +35,19 @@ #include "weblayer/public/main.h" #if defined(OS_ANDROID) +#include "base/command_line.h" #include "components/crash/content/browser/child_exit_observer_android.h" #include "components/crash/content/browser/child_process_crash_observer_android.h" #include "components/crash/core/common/crash_key.h" #include "components/javascript_dialogs/android/app_modal_dialog_view_android.h" // nogncheck #include "components/javascript_dialogs/app_modal_dialog_manager.h" // nogncheck #include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" #include "net/android/network_change_notifier_factory_android.h" #include "net/base/network_change_notifier.h" #include "weblayer/browser/android/metrics/uma_utils.h" +#include "weblayer/browser/java/jni/MojoInterfaceRegistrar_jni.h" +#include "weblayer/browser/weblayer_factory_impl_android.h" #endif #if defined(USE_X11) @@ -111,6 +114,15 @@ int BrowserMainPartsImpl::PreCreateThreads() { std::make_unique<crash_reporter::ChildProcessCrashObserver>()); crash_reporter::InitializeCrashKeys(); + + // MediaSession was implemented in M85, and requires both implementation and + // client libraries to be at least that new. The version check has to be in + // the browser process, but the command line flag is automatically propagated + // to renderers. + if (WebLayerFactoryImplAndroid::GetClientMajorVersion() < 85) { + base::CommandLine::ForCurrentProcess()->AppendSwitch( + ::switches::kDisableMediaSessionAPI); + } #endif return service_manager::RESULT_CODE_NORMAL_EXIT; @@ -191,6 +203,9 @@ void BrowserMainPartsImpl::PreMainMessageLoopRun() { base::android::AttachCurrentThread(), controller, controller->web_contents()->GetTopLevelNativeWindow()); })); + + Java_MojoInterfaceRegistrar_registerMojoInterfaces( + base::android::AttachCurrentThread()); #endif } diff --git a/chromium/weblayer/browser/browsing_data_remover_delegate.cc b/chromium/weblayer/browser/browsing_data_remover_delegate.cc new file mode 100644 index 00000000000..2a466fe8cad --- /dev/null +++ b/chromium/weblayer/browser/browsing_data_remover_delegate.cc @@ -0,0 +1,54 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/browsing_data_remover_delegate.h" + +#include "base/callback.h" +#include "components/prefs/pref_service.h" +#include "components/site_isolation//pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" + +namespace weblayer { + +BrowsingDataRemoverDelegate::BrowsingDataRemoverDelegate( + content::BrowserContext* browser_context) + : browser_context_(browser_context) {} + +BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher +BrowsingDataRemoverDelegate::GetOriginTypeMatcher() { + return EmbedderOriginTypeMatcher(); +} + +bool BrowsingDataRemoverDelegate::MayRemoveDownloadHistory() { + return true; +} + +std::vector<std::string> +BrowsingDataRemoverDelegate::GetDomainsForDeferredCookieDeletion( + uint64_t remove_mask) { + return {}; +} + +void BrowsingDataRemoverDelegate::RemoveEmbedderData( + const base::Time& delete_begin, + const base::Time& delete_end, + uint64_t remove_mask, + content::BrowsingDataFilterBuilder* filter_builder, + uint64_t origin_type_mask, + base::OnceClosure callback) { + // Note: if history is ever added to WebLayer, also remove isolated origins + // when history is cleared. + if (remove_mask & DATA_TYPE_ISOLATED_ORIGINS) { + user_prefs::UserPrefs::Get(browser_context_) + ->ClearPref(site_isolation::prefs::kUserTriggeredIsolatedOrigins); + // Note that this does not clear these sites from the in-memory map in + // ChildProcessSecurityPolicy, since that is not supported at runtime. That + // list of isolated sites is not directly exposed to users, though, and + // will be cleared on next restart. + } + std::move(callback).Run(); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/browsing_data_remover_delegate.h b/chromium/weblayer/browser/browsing_data_remover_delegate.h new file mode 100644 index 00000000000..c2a996debba --- /dev/null +++ b/chromium/weblayer/browser/browsing_data_remover_delegate.h @@ -0,0 +1,58 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_H_ +#define WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_H_ + +#include "components/keyed_service/core/keyed_service.h" +#include "content/public/browser/browsing_data_remover.h" +#include "content/public/browser/browsing_data_remover_delegate.h" + +namespace content { +class BrowserContext; +} + +namespace weblayer { + +class BrowsingDataRemoverDelegate : public content::BrowsingDataRemoverDelegate, + public KeyedService { + public: + // This is an extension of content::BrowsingDataRemover::RemoveDataMask which + // includes all datatypes therefrom and adds additional WebLayer-specific + // ones. + enum DataType : uint64_t { + // Embedder can start adding datatypes after the last platform datatype. + DATA_TYPE_EMBEDDER_BEGIN = + content::BrowsingDataRemover::DATA_TYPE_CONTENT_END << 1, + + // WebLayer-specific datatypes. + DATA_TYPE_ISOLATED_ORIGINS = DATA_TYPE_EMBEDDER_BEGIN, + }; + + explicit BrowsingDataRemoverDelegate( + content::BrowserContext* browser_context); + + BrowsingDataRemoverDelegate(const BrowsingDataRemoverDelegate&) = delete; + BrowsingDataRemoverDelegate& operator=(const BrowsingDataRemoverDelegate&) = + delete; + + // content::BrowsingDataRemoverDelegate: + EmbedderOriginTypeMatcher GetOriginTypeMatcher() override; + bool MayRemoveDownloadHistory() override; + std::vector<std::string> GetDomainsForDeferredCookieDeletion( + uint64_t remove_mask) override; + void RemoveEmbedderData(const base::Time& delete_begin, + const base::Time& delete_end, + uint64_t remove_mask, + content::BrowsingDataFilterBuilder* filter_builder, + uint64_t origin_type_mask, + base::OnceClosure callback) override; + + private: + content::BrowserContext* browser_context_ = nullptr; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_H_ diff --git a/chromium/weblayer/browser/browsing_data_remover_delegate_factory.cc b/chromium/weblayer/browser/browsing_data_remover_delegate_factory.cc new file mode 100644 index 00000000000..acc4bcc9da0 --- /dev/null +++ b/chromium/weblayer/browser/browsing_data_remover_delegate_factory.cc @@ -0,0 +1,46 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/browsing_data_remover_delegate_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "weblayer/browser/browsing_data_remover_delegate.h" + +namespace weblayer { + +// static +BrowsingDataRemoverDelegate* +BrowsingDataRemoverDelegateFactory::GetForBrowserContext( + content::BrowserContext* browser_context) { + return static_cast<BrowsingDataRemoverDelegate*>( + GetInstance()->GetServiceForBrowserContext(browser_context, true)); +} + +// static +BrowsingDataRemoverDelegateFactory* +BrowsingDataRemoverDelegateFactory::GetInstance() { + static base::NoDestructor<BrowsingDataRemoverDelegateFactory> factory; + return factory.get(); +} + +BrowsingDataRemoverDelegateFactory::BrowsingDataRemoverDelegateFactory() + : BrowserContextKeyedServiceFactory( + "BrowsingDataRemoverDelegate", + BrowserContextDependencyManager::GetInstance()) {} + +BrowsingDataRemoverDelegateFactory::~BrowsingDataRemoverDelegateFactory() = + default; + +KeyedService* BrowsingDataRemoverDelegateFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new BrowsingDataRemoverDelegate(context); +} + +content::BrowserContext* +BrowsingDataRemoverDelegateFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + return context; +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/browsing_data_remover_delegate_factory.h b/chromium/weblayer/browser/browsing_data_remover_delegate_factory.h new file mode 100644 index 00000000000..b8c616fbfbc --- /dev/null +++ b/chromium/weblayer/browser/browsing_data_remover_delegate_factory.h @@ -0,0 +1,41 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_FACTORY_H_ +#define WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_FACTORY_H_ + +#include "base/no_destructor.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace weblayer { +class BrowsingDataRemoverDelegate; + +class BrowsingDataRemoverDelegateFactory + : public BrowserContextKeyedServiceFactory { + public: + BrowsingDataRemoverDelegateFactory( + const BrowsingDataRemoverDelegateFactory&) = delete; + BrowsingDataRemoverDelegateFactory& operator=( + const BrowsingDataRemoverDelegateFactory&) = delete; + + static BrowsingDataRemoverDelegate* GetForBrowserContext( + content::BrowserContext* browser_context); + static BrowsingDataRemoverDelegateFactory* GetInstance(); + + private: + friend class base::NoDestructor<BrowsingDataRemoverDelegateFactory>; + + BrowsingDataRemoverDelegateFactory(); + ~BrowsingDataRemoverDelegateFactory() override; + + // BrowserContextKeyedServiceFactory methods: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* profile) const override; + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_BROWSING_DATA_REMOVER_DELEGATE_FACTORY_H_ diff --git a/chromium/weblayer/browser/confirm_infobar_android.cc b/chromium/weblayer/browser/confirm_infobar_android.cc new file mode 100644 index 00000000000..2ce1d44fa97 --- /dev/null +++ b/chromium/weblayer/browser/confirm_infobar_android.cc @@ -0,0 +1,95 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/confirm_infobar_android.h" + +#include <memory> +#include <utility> + +#include "base/android/jni_string.h" +#include "components/infobars/core/confirm_infobar_delegate.h" +#include "content/public/browser/web_contents.h" +#include "ui/android/window_android.h" +#include "ui/gfx/android/java_bitmap.h" +#include "ui/gfx/image/image.h" +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/java/jni/ConfirmInfoBar_jni.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace weblayer { + +// InfoBarService ------------------------------------------------------------- + +std::unique_ptr<infobars::InfoBar> InfoBarService::CreateConfirmInfoBar( + std::unique_ptr<ConfirmInfoBarDelegate> delegate) { + return std::make_unique<ConfirmInfoBar>(std::move(delegate)); +} + +// ConfirmInfoBar ------------------------------------------------------------- + +ConfirmInfoBar::ConfirmInfoBar(std::unique_ptr<ConfirmInfoBarDelegate> delegate) + : InfoBarAndroid(std::move(delegate)) {} + +ConfirmInfoBar::~ConfirmInfoBar() = default; + +base::string16 ConfirmInfoBar::GetTextFor( + ConfirmInfoBarDelegate::InfoBarButton button) { + ConfirmInfoBarDelegate* delegate = GetDelegate(); + return (delegate->GetButtons() & button) ? delegate->GetButtonLabel(button) + : base::string16(); +} + +ConfirmInfoBarDelegate* ConfirmInfoBar::GetDelegate() { + return delegate()->AsConfirmInfoBarDelegate(); +} + +ScopedJavaLocalRef<jobject> ConfirmInfoBar::CreateRenderInfoBar(JNIEnv* env) { + ScopedJavaLocalRef<jstring> ok_button_text = + base::android::ConvertUTF16ToJavaString( + env, GetTextFor(ConfirmInfoBarDelegate::BUTTON_OK)); + ScopedJavaLocalRef<jstring> cancel_button_text = + base::android::ConvertUTF16ToJavaString( + env, GetTextFor(ConfirmInfoBarDelegate::BUTTON_CANCEL)); + ConfirmInfoBarDelegate* delegate = GetDelegate(); + ScopedJavaLocalRef<jstring> message_text = + base::android::ConvertUTF16ToJavaString(env, delegate->GetMessageText()); + ScopedJavaLocalRef<jstring> link_text = + base::android::ConvertUTF16ToJavaString(env, delegate->GetLinkText()); + + ScopedJavaLocalRef<jobject> java_bitmap; + if (delegate->GetIconId() == infobars::InfoBarDelegate::kNoIconID && + !delegate->GetIcon().IsEmpty()) { + java_bitmap = gfx::ConvertToJavaBitmap(delegate->GetIcon().ToSkBitmap()); + } + + return Java_ConfirmInfoBar_create(env, GetJavaIconId(), java_bitmap, + message_text, link_text, ok_button_text, + cancel_button_text); +} + +void ConfirmInfoBar::OnLinkClicked(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + if (!owner()) + return; // We're closing; don't call anything, it might access the owner. + + if (GetDelegate()->LinkClicked(WindowOpenDisposition::NEW_FOREGROUND_TAB)) + RemoveSelf(); +} + +void ConfirmInfoBar::ProcessButton(int action) { + if (!owner()) + return; // We're closing; don't call anything, it might access the owner. + + DCHECK((action == InfoBarAndroid::ACTION_OK) || + (action == InfoBarAndroid::ACTION_CANCEL)); + ConfirmInfoBarDelegate* delegate = GetDelegate(); + if ((action == InfoBarAndroid::ACTION_OK) ? delegate->Accept() + : delegate->Cancel()) { + RemoveSelf(); + } +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/confirm_infobar_android.h b/chromium/weblayer/browser/confirm_infobar_android.h new file mode 100644 index 00000000000..9611841ef3f --- /dev/null +++ b/chromium/weblayer/browser/confirm_infobar_android.h @@ -0,0 +1,40 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_CONFIRM_INFOBAR_ANDROID_H_ +#define WEBLAYER_BROWSER_CONFIRM_INFOBAR_ANDROID_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "components/infobars/core/confirm_infobar_delegate.h" +#include "weblayer/browser/infobar_android.h" + +namespace weblayer { + +class ConfirmInfoBar : public InfoBarAndroid { + public: + explicit ConfirmInfoBar(std::unique_ptr<ConfirmInfoBarDelegate> delegate); + ~ConfirmInfoBar() override; + + protected: + ConfirmInfoBarDelegate* GetDelegate(); + base::string16 GetTextFor(ConfirmInfoBarDelegate::InfoBarButton button); + + // InfoBarAndroid overrides. + base::android::ScopedJavaLocalRef<jobject> CreateRenderInfoBar( + JNIEnv* env) override; + + void OnLinkClicked(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj) override; + + void ProcessButton(int action) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ConfirmInfoBar); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_CONFIRM_INFOBAR_ANDROID_H_ diff --git a/chromium/weblayer/browser/content_browser_client_impl.cc b/chromium/weblayer/browser/content_browser_client_impl.cc index 3f6d1ea6266..438122ad99b 100644 --- a/chromium/weblayer/browser/content_browser_client_impl.cc +++ b/chromium/weblayer/browser/content_browser_client_impl.cc @@ -17,6 +17,7 @@ #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/autofill/content/browser/content_autofill_driver_factory.h" +#include "components/blocked_content/popup_blocker.h" #include "components/captive_portal/core/buildflags.h" #include "components/embedder_support/switches.h" #include "components/network_time/network_time_tracker.h" @@ -26,20 +27,26 @@ #include "components/prefs/json_pref_store.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service_factory.h" +#include "components/prefs/scoped_user_pref_update.h" #include "components/security_interstitials/content/ssl_cert_reporter.h" #include "components/security_interstitials/content/ssl_error_handler.h" #include "components/security_interstitials/content/ssl_error_navigation_throttle.h" +#include "components/site_isolation/pref_names.h" +#include "components/site_isolation/preloaded_isolated_origins.h" +#include "components/site_isolation/site_isolation_policy.h" #include "components/strings/grit/components_locale_settings.h" -#include "components/variations/net/variations_http_headers.h" +#include "components/user_prefs/user_prefs.h" #include "components/variations/service/variations_service.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/cors_exempt_headers.h" +#include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/devtools_manager_delegate.h" #include "content/public/browser/generated_code_cache_settings.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_throttle.h" #include "content/public/browser/network_service_instance.h" +#include "content/public/browser/page_navigator.h" #include "content/public/browser/render_process_host.h" +#include "content/public/browser/tts_controller.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/common/service_names.mojom.h" @@ -48,6 +55,7 @@ #include "content/public/common/window_container_type.mojom.h" #include "mojo/public/cpp/bindings/binder_map.h" #include "net/proxy_resolution/proxy_config.h" +#include "net/ssl/client_cert_identity.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/network_service.h" #include "services/network/public/mojom/network_context.mojom.h" @@ -55,6 +63,7 @@ #include "third_party/blink/public/common/loader/url_loader_throttle.h" #include "third_party/blink/public/common/user_agent/user_agent_metadata.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/base/page_transition_types.h" #include "url/gurl.h" #include "url/origin.h" #include "url/url_constants.h" @@ -62,8 +71,12 @@ #include "weblayer/browser/browser_process.h" #include "weblayer/browser/download_manager_delegate_impl.h" #include "weblayer/browser/feature_list_creator.h" +#include "weblayer/browser/host_content_settings_map_factory.h" +#include "weblayer/browser/http_auth_handler_impl.h" #include "weblayer/browser/i18n_util.h" #include "weblayer/browser/navigation_controller_impl.h" +#include "weblayer/browser/password_manager_driver_factory.h" +#include "weblayer/browser/popup_navigation_delegate_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/system_network_context_manager.h" #include "weblayer/browser/tab_impl.h" @@ -86,19 +99,24 @@ #include "base/android/jni_string.h" #include "base/android/path_utils.h" #include "base/bind.h" -#include "base/task/post_task.h" +#include "components/browser_ui/client_certificate/android/ssl_client_certificate_request.h" #include "components/cdm/browser/cdm_message_filter_android.h" #include "components/cdm/browser/media_drm_storage_impl.h" // nogncheck #include "components/crash/content/browser/crash_handler_host_linux.h" #include "components/embedder_support/android/metrics/android_metrics_service_client.h" #include "components/navigation_interception/intercept_navigation_delegate.h" +#include "components/safe_browsing/core/realtime/policy_engine.h" // nogncheck +#include "components/safe_browsing/core/realtime/url_lookup_service.h" // nogncheck #include "components/spellcheck/browser/spell_check_host_impl.h" // nogncheck #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "ui/base/resource/resource_bundle_android.h" +#include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h" #include "weblayer/browser/android_descriptors.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/devtools_manager_delegate_android.h" +#include "weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.h" #include "weblayer/browser/safe_browsing/safe_browsing_service.h" #endif @@ -333,8 +351,6 @@ void ContentBrowserClientImpl::ConfigureNetworkContextParams( proxy_config, net::DefineNetworkTrafficAnnotation("undefined", "Nothing here yet.")); } - content::UpdateCorsExemptHeader(context_params); - variations::UpdateCorsExemptHeaderForVariations(context_params); } void ContentBrowserClientImpl::OnNetworkServiceCreated( @@ -359,8 +375,27 @@ ContentBrowserClientImpl::CreateURLLoaderThrottles( if (base::FeatureList::IsEnabled(features::kWebLayerSafeBrowsing) && IsSafebrowsingSupported()) { #if defined(OS_ANDROID) - result.push_back(GetSafeBrowsingService()->CreateURLLoaderThrottle( - wc_getter, frame_tree_node_id)); + BrowserContextImpl* browser_context_impl = + static_cast<BrowserContextImpl*>(browser_context); + bool is_safe_browsing_enabled = safe_browsing::IsSafeBrowsingEnabled( + *browser_context_impl->pref_service()); + + if (is_safe_browsing_enabled) { + bool is_real_time_lookup_enabled = + safe_browsing::RealTimePolicyEngine::CanPerformFullURLLookup( + browser_context_impl->pref_service(), + browser_context_impl->IsOffTheRecord(), + FeatureListCreator::GetInstance()->variations_service()); + + // |url_lookup_service| is used when real time url check is enabled. + safe_browsing::RealTimeUrlLookupServiceBase* url_lookup_service = + is_real_time_lookup_enabled + ? RealTimeUrlLookupServiceFactory::GetForBrowserContext( + browser_context) + : nullptr; + result.push_back(GetSafeBrowsingService()->CreateURLLoaderThrottle( + wc_getter, frame_tree_node_id, url_lookup_service)); + } #endif } @@ -410,6 +445,49 @@ bool ContentBrowserClientImpl::IsHandledURL(const GURL& url) { return false; } +std::vector<url::Origin> +ContentBrowserClientImpl::GetOriginsRequiringDedicatedProcess() { + return site_isolation::GetBrowserSpecificBuiltInIsolatedOrigins(); +} + +bool ContentBrowserClientImpl::ShouldDisableSiteIsolation() { + return site_isolation::SiteIsolationPolicy:: + ShouldDisableSiteIsolationDueToMemoryThreshold(); +} + +std::vector<std::string> +ContentBrowserClientImpl::GetAdditionalSiteIsolationModes() { + if (site_isolation::SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled()) + return {"Isolate Password Sites"}; + return {}; +} + +void ContentBrowserClientImpl::PersistIsolatedOrigin( + content::BrowserContext* context, + const url::Origin& origin) { + DCHECK(!context->IsOffTheRecord()); + ListPrefUpdate update(user_prefs::UserPrefs::Get(context), + site_isolation::prefs::kUserTriggeredIsolatedOrigins); + base::ListValue* list = update.Get(); + base::Value value(origin.Serialize()); + if (!base::Contains(list->GetList(), value)) + list->Append(std::move(value)); +} + +base::OnceClosure ContentBrowserClientImpl::SelectClientCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + net::ClientCertIdentityList client_certs, + std::unique_ptr<content::ClientCertificateDelegate> delegate) { +#if defined(OS_ANDROID) + return browser_ui::ShowSSLClientCertificateSelector( + web_contents, cert_request_info, std::move(delegate)); +#else + delegate->ContinueWithCertificate(nullptr, nullptr); + return base::OnceClosure(); +#endif +} + bool ContentBrowserClientImpl::CanCreateWindow( content::RenderFrameHost* opener, const GURL& opener_url, @@ -441,11 +519,6 @@ bool ContentBrowserClientImpl::CanCreateWindow( return false; } - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - embedder_support::kDisablePopupBlocking)) { - return true; - } - // WindowOpenDisposition has a *ton* of types, but the following are really // the only ones that should be hit for this code path. switch (disposition) { @@ -461,8 +534,27 @@ bool ContentBrowserClientImpl::CanCreateWindow( return false; } - // TODO(https://crbug.com/1019922): support proper popup blocking. - return user_gesture; + GURL popup_url(target_url); + web_contents->GetMainFrame()->GetProcess()->FilterURL(false, &popup_url); + // Use ui::PAGE_TRANSITION_LINK to match the similar logic in //chrome. + content::OpenURLParams params(popup_url, referrer, disposition, + ui::PAGE_TRANSITION_LINK, + /*is_renderer_initiated*/ true); + params.user_gesture = user_gesture; + params.initiator_origin = source_origin; + params.source_render_frame_id = opener->GetRoutingID(); + params.source_render_process_id = opener->GetProcess()->GetID(); + params.source_site_instance = opener->GetSiteInstance(); + // The content::OpenURLParams are created just for the delegate, and do not + // correspond to actual params created by //content, so pass null for the + // |open_url_params| argument here. + return blocked_content::MaybeBlockPopup( + web_contents, &opener_top_level_frame_url, + std::make_unique<PopupNavigationDelegateImpl>(params, web_contents, + opener), + /*open_url_params*/ nullptr, features, + HostContentSettingsMapFactory::GetForBrowserContext( + web_contents->GetBrowserContext())) != nullptr; } std::vector<std::unique_ptr<content::NavigationThrottle>> @@ -536,6 +628,13 @@ bool ContentBrowserClientImpl::BindAssociatedReceiverFromFrame( render_frame_host); return true; } + if (interface_name == autofill::mojom::PasswordManagerDriver::Name_) { + PasswordManagerDriverFactory::BindPasswordManagerDriver( + mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver>( + std::move(*handle)), + render_frame_host); + return true; + } return false; } @@ -550,9 +649,8 @@ void ContentBrowserClientImpl::ExposeInterfacesToRenderer( mojo::MakeSelfOwnedReceiver(std::make_unique<SpellCheckHostImpl>(), std::move(receiver)); }; - registry->AddInterface( - base::BindRepeating(create_spellcheck_host), - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})); + registry->AddInterface(base::BindRepeating(create_spellcheck_host), + content::GetUIThreadTaskRunner({})); if (base::FeatureList::IsEnabled(features::kWebLayerSafeBrowsing) && IsSafebrowsingSupported()) { @@ -593,6 +691,12 @@ ContentBrowserClientImpl::CreateQuotaPermissionContext() { return base::MakeRefCounted<permissions::QuotaPermissionContextImpl>(); } +content::TtsPlatform* ContentBrowserClientImpl::GetTtsPlatform() { + // TODO(sky): figure out a better way to integrate this. + content::TtsController::GetInstance()->SetStopSpeakingWhenHidden(true); + return nullptr; +} + void ContentBrowserClientImpl::CreateFeatureListAndFieldTrials() { local_state_ = CreateLocalState(); feature_list_creator_ = @@ -690,6 +794,21 @@ ContentBrowserClientImpl::GetWideColorGamutHeuristic() { // flinger. return WideColorGamutHeuristic::kUseWindow; } + +std::unique_ptr<content::LoginDelegate> +ContentBrowserClientImpl::CreateLoginDelegate( + const net::AuthChallengeInfo& auth_info, + content::WebContents* web_contents, + const content::GlobalRequestID& request_id, + bool is_main_frame, + const GURL& url, + scoped_refptr<net::HttpResponseHeaders> response_headers, + bool first_auth_attempt, + LoginAuthRequiredCallback auth_required_callback) { + return std::make_unique<HttpAuthHandlerImpl>( + auth_info, web_contents, first_auth_attempt, + std::move(auth_required_callback)); +} #endif // OS_ANDROID content::SpeechRecognitionManagerDelegate* @@ -697,4 +816,12 @@ ContentBrowserClientImpl::CreateSpeechRecognitionManagerDelegate() { return new WebLayerSpeechRecognitionManagerDelegate(); } +ukm::UkmService* ContentBrowserClientImpl::GetUkmService() { +#if defined(OS_ANDROID) + return WebLayerMetricsServiceClient::GetInstance()->GetUkmService(); +#else + return nullptr; +#endif +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/content_browser_client_impl.h b/chromium/weblayer/browser/content_browser_client_impl.h index 42d9b3c13e9..a8c2ea7c804 100644 --- a/chromium/weblayer/browser/content_browser_client_impl.h +++ b/chromium/weblayer/browser/content_browser_client_impl.h @@ -65,6 +65,16 @@ class ContentBrowserClientImpl : public content::ContentBrowserClient { content::NavigationUIData* navigation_ui_data, int frame_tree_node_id) override; bool IsHandledURL(const GURL& url) override; + std::vector<url::Origin> GetOriginsRequiringDedicatedProcess() override; + bool ShouldDisableSiteIsolation() override; + std::vector<std::string> GetAdditionalSiteIsolationModes() override; + void PersistIsolatedOrigin(content::BrowserContext* context, + const url::Origin& origin) override; + base::OnceClosure SelectClientCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + net::ClientCertIdentityList client_certs, + std::unique_ptr<content::ClientCertificateDelegate> delegate) override; bool CanCreateWindow(content::RenderFrameHost* opener, const GURL& opener_url, const GURL& opener_top_level_frame_url, @@ -111,11 +121,22 @@ class ContentBrowserClientImpl : public content::ContentBrowserClient { int child_process_id) override; #if defined(OS_ANDROID) WideColorGamutHeuristic GetWideColorGamutHeuristic() override; + std::unique_ptr<content::LoginDelegate> CreateLoginDelegate( + const net::AuthChallengeInfo& auth_info, + content::WebContents* web_contents, + const content::GlobalRequestID& request_id, + bool is_main_frame, + const GURL& url, + scoped_refptr<net::HttpResponseHeaders> response_headers, + bool first_auth_attempt, + LoginAuthRequiredCallback auth_required_callback) override; #endif // OS_ANDROID - - void CreateFeatureListAndFieldTrials(); content::SpeechRecognitionManagerDelegate* CreateSpeechRecognitionManagerDelegate() override; + ukm::UkmService* GetUkmService() override; + content::TtsPlatform* GetTtsPlatform() override; + + void CreateFeatureListAndFieldTrials(); private: std::unique_ptr<PrefService> CreateLocalState(); diff --git a/chromium/weblayer/browser/content_view_render_view.cc b/chromium/weblayer/browser/content_view_render_view.cc index 126f8db191d..98cd8bcf37a 100644 --- a/chromium/weblayer/browser/content_view_render_view.cc +++ b/chromium/weblayer/browser/content_view_render_view.cc @@ -8,6 +8,7 @@ #include <android/native_window_jni.h> #include <memory> +#include <utility> #include "base/android/jni_android.h" #include "base/android/jni_string.h" @@ -37,7 +38,15 @@ ContentViewRenderView::ContentViewRenderView(JNIEnv* env, java_obj_.Reset(env, obj); } -ContentViewRenderView::~ContentViewRenderView() = default; +ContentViewRenderView::~ContentViewRenderView() { + DCHECK(height_changed_listener_.is_null()); +} + +void ContentViewRenderView::SetHeightChangedListener( + base::RepeatingClosure callback) { + DCHECK(height_changed_listener_.is_null() || callback.is_null()); + height_changed_listener_ = std::move(callback); +} // static static jlong JNI_ContentViewRenderView_Init( @@ -75,11 +84,15 @@ void ContentViewRenderView::OnPhysicalBackingSizeChanged( const JavaParamRef<jobject>& jweb_contents, jint width, jint height) { + bool height_changed = height_ != height; height_ = height; content::WebContents* web_contents = content::WebContents::FromJavaWebContents(jweb_contents); gfx::Size size(width, height); web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(size); + + if (height_changed && !height_changed_listener_.is_null()) + height_changed_listener_.Run(); } void ContentViewRenderView::SurfaceCreated(JNIEnv* env) { @@ -109,6 +122,10 @@ void ContentViewRenderView::SurfaceChanged( compositor_->SetWindowBounds(gfx::Size(width, height)); } +void ContentViewRenderView::SetNeedsRedraw(JNIEnv* env) { + compositor_->SetNeedsRedraw(); +} + base::android::ScopedJavaLocalRef<jobject> ContentViewRenderView::GetResourceManager(JNIEnv* env) { return compositor_->GetResourceManager().GetJavaObject(); @@ -127,6 +144,13 @@ void ContentViewRenderView::DidSwapFrame(int pending_frames) { } } +void ContentViewRenderView::DidSwapBuffers(const gfx::Size& swap_size) { + JNIEnv* env = base::android::AttachCurrentThread(); + bool matches_window_bounds = swap_size == compositor_->GetWindowBounds(); + Java_ContentViewRenderView_didSwapBuffers(env, java_obj_, + matches_window_bounds); +} + void ContentViewRenderView::EvictCachedSurface(JNIEnv* env) { compositor_->EvictCachedBackBuffer(); } diff --git a/chromium/weblayer/browser/content_view_render_view.h b/chromium/weblayer/browser/content_view_render_view.h index c325db253e7..3a28dac389f 100644 --- a/chromium/weblayer/browser/content_view_render_view.h +++ b/chromium/weblayer/browser/content_view_render_view.h @@ -8,7 +8,7 @@ #include <memory> #include "base/android/jni_weak_ref.h" -#include "base/logging.h" +#include "base/callback.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "content/public/browser/android/compositor_client.h" @@ -38,6 +38,7 @@ class ContentViewRenderView : public content::CompositorClient { // Height, in pixels. int height() const { return height_; } + void SetHeightChangedListener(base::RepeatingClosure callback); // Methods called from Java via JNI ----------------------------------------- void Destroy(JNIEnv* env); @@ -57,12 +58,14 @@ class ContentViewRenderView : public content::CompositorClient { jint width, jint height, const base::android::JavaParamRef<jobject>& surface); + void SetNeedsRedraw(JNIEnv* env); void EvictCachedSurface(JNIEnv* env); base::android::ScopedJavaLocalRef<jobject> GetResourceManager(JNIEnv* env); // CompositorClient implementation void UpdateLayerTreeHost() override; void DidSwapFrame(int pending_frames) override; + void DidSwapBuffers(const gfx::Size& swap_size) override; private: ~ContentViewRenderView() override; @@ -81,6 +84,7 @@ class ContentViewRenderView : public content::CompositorClient { int current_surface_format_ = 0; + base::RepeatingClosure height_changed_listener_; int height_ = 0; DISALLOW_COPY_AND_ASSIGN(ContentViewRenderView); diff --git a/chromium/weblayer/browser/cookie_manager_impl.cc b/chromium/weblayer/browser/cookie_manager_impl.cc index 8cb70b3195a..ae1392cb88e 100644 --- a/chromium/weblayer/browser/cookie_manager_impl.cc +++ b/chromium/weblayer/browser/cookie_manager_impl.cc @@ -19,9 +19,9 @@ namespace weblayer { namespace { void GetCookieComplete(CookieManager::GetCookieCallback callback, - const net::CookieStatusList& cookies, - const net::CookieStatusList& excluded_cookies) { - net::CookieList cookie_list = net::cookie_util::StripStatuses(cookies); + const net::CookieAccessResultList& cookies, + const net::CookieAccessResultList& excluded_cookies) { + net::CookieList cookie_list = net::cookie_util::StripAccessResults(cookies); std::move(callback).Run(net::CanonicalCookie::BuildCookieLine(cookie_list)); } diff --git a/chromium/weblayer/browser/download_manager_delegate_impl.cc b/chromium/weblayer/browser/download_manager_delegate_impl.cc index 7e83c6be7ca..00c35b6b4a7 100644 --- a/chromium/weblayer/browser/download_manager_delegate_impl.cc +++ b/chromium/weblayer/browser/download_manager_delegate_impl.cc @@ -5,13 +5,14 @@ #include "weblayer/browser/download_manager_delegate_impl.h" #include "base/files/file_util.h" -#include "base/task/post_task.h" +#include "base/optional.h" #include "base/task/thread_pool.h" #include "base/threading/sequenced_task_runner_handle.h" #include "build/build_config.h" #include "components/download/public/common/download_item.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item_utils.h" #include "content/public/browser/download_manager.h" #include "net/base/filename_util.h" @@ -42,8 +43,8 @@ void GenerateFilename( base::CreateDirectory(suggested_directory); base::FilePath suggested_path(suggested_directory.Append(generated_name)); - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(std::move(callback), suggested_path)); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), suggested_path)); } } // namespace @@ -89,7 +90,8 @@ bool DownloadManagerDelegateImpl::DetermineDownloadTarget( download::DownloadItem::TARGET_DISPOSITION_OVERWRITE, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, download::DownloadItem::MixedContentStatus::UNKNOWN, - item->GetForcedFilePath(), download::DOWNLOAD_INTERRUPT_REASON_NONE); + item->GetForcedFilePath(), base::nullopt /*download_schedule*/, + download::DOWNLOAD_INTERRUPT_REASON_NONE); return true; } @@ -240,6 +242,7 @@ void DownloadManagerDelegateImpl::OnDownloadPathGenerated( download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, download::DownloadItem::MixedContentStatus::UNKNOWN, suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")), + base::nullopt /*download_schedule*/, download::DOWNLOAD_INTERRUPT_REASON_NONE); } diff --git a/chromium/weblayer/browser/feature_list_creator.cc b/chromium/weblayer/browser/feature_list_creator.cc index 3a4e7460d28..4ff04637b63 100644 --- a/chromium/weblayer/browser/feature_list_creator.cc +++ b/chromium/weblayer/browser/feature_list_creator.cc @@ -85,13 +85,12 @@ void FeatureListCreator::SetUpFieldTrials() { variations_service_->OverridePlatform( variations::Study::PLATFORM_ANDROID_WEBLAYER, "android_weblayer"); - std::set<std::string> unforceable_field_trials; std::vector<std::string> variation_ids; auto feature_list = std::make_unique<base::FeatureList>(); variations_service_->SetupFieldTrials( cc::switches::kEnableGpuBenchmarking, switches::kEnableFeatures, - switches::kDisableFeatures, unforceable_field_trials, variation_ids, + switches::kDisableFeatures, variation_ids, content::GetSwitchDependentFeatureOverrides( *base::CommandLine::ForCurrentProcess()), std::move(feature_list), &weblayer_field_trials_); diff --git a/chromium/weblayer/browser/http_auth_handler_impl.cc b/chromium/weblayer/browser/http_auth_handler_impl.cc new file mode 100644 index 00000000000..bb9823e2a07 --- /dev/null +++ b/chromium/weblayer/browser/http_auth_handler_impl.cc @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/http_auth_handler_impl.h" + +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "net/base/auth.h" +#include "weblayer/browser/tab_impl.h" + +namespace weblayer { + +HttpAuthHandlerImpl::HttpAuthHandlerImpl( + const net::AuthChallengeInfo& auth_info, + content::WebContents* web_contents, + bool first_auth_attempt, + LoginAuthRequiredCallback callback) + : WebContentsObserver(web_contents), callback_(std::move(callback)) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + url_ = auth_info.challenger.GetURL().Resolve(auth_info.path); + + auto* tab = TabImpl::FromWebContents(web_contents); + tab->ShowHttpAuthPrompt(this); +} + +HttpAuthHandlerImpl::~HttpAuthHandlerImpl() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + auto* tab = TabImpl::FromWebContents(web_contents()); + if (tab) + tab->CloseHttpAuthPrompt(); +} + +void HttpAuthHandlerImpl::Proceed(const base::string16& user, + const base::string16& password) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (callback_) { + std::move(callback_).Run(net::AuthCredentials(user, password)); + } +} + +void HttpAuthHandlerImpl::Cancel() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (callback_) { + std::move(callback_).Run(base::nullopt); + } +} + +} // namespace weblayer
\ No newline at end of file diff --git a/chromium/weblayer/browser/http_auth_handler_impl.h b/chromium/weblayer/browser/http_auth_handler_impl.h new file mode 100644 index 00000000000..a68e0c1d581 --- /dev/null +++ b/chromium/weblayer/browser/http_auth_handler_impl.h @@ -0,0 +1,40 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_HTTP_AUTH_HANDLER_IMPL_H_ +#define WEBLAYER_BROWSER_HTTP_AUTH_HANDLER_IMPL_H_ + +#include <memory> +#include <string> + +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/login_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "url/gurl.h" + +namespace weblayer { + +// Implements support for http auth. +class HttpAuthHandlerImpl : public content::LoginDelegate, + public content::WebContentsObserver { + public: + HttpAuthHandlerImpl(const net::AuthChallengeInfo& auth_info, + content::WebContents* web_contents, + bool first_auth_attempt, + LoginAuthRequiredCallback callback); + ~HttpAuthHandlerImpl() override; + + void Proceed(const base::string16& user, const base::string16& password); + void Cancel(); + + GURL url() { return url_; } + + private: + GURL url_; + LoginAuthRequiredCallback callback_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_HTTP_AUTH_HANDLER_IMPL_H_
\ No newline at end of file diff --git a/chromium/weblayer/browser/infobar_android.cc b/chromium/weblayer/browser/infobar_android.cc new file mode 100644 index 00000000000..4aa24af9285 --- /dev/null +++ b/chromium/weblayer/browser/infobar_android.cc @@ -0,0 +1,91 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/infobar_android.h" + +#include <utility> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/strings/string_util.h" +#include "components/infobars/core/infobar.h" +#include "components/infobars/core/infobar_delegate.h" +#include "weblayer/browser/android/resource_mapper.h" +#include "weblayer/browser/java/jni/InfoBar_jni.h" + +using base::android::JavaParamRef; +using base::android::JavaRef; + +namespace weblayer { + +// InfoBarAndroid ------------------------------------------------------------- + +InfoBarAndroid::InfoBarAndroid( + std::unique_ptr<infobars::InfoBarDelegate> delegate) + : infobars::InfoBar(std::move(delegate)) {} + +InfoBarAndroid::~InfoBarAndroid() { + if (!java_info_bar_.is_null()) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_InfoBar_onNativeDestroyed(env, java_info_bar_); + } +} + +void InfoBarAndroid::ReassignJavaInfoBar(InfoBarAndroid* replacement) { + DCHECK(replacement); + if (!java_info_bar_.is_null()) { + replacement->SetJavaInfoBar(java_info_bar_); + java_info_bar_.Reset(); + } +} + +void InfoBarAndroid::SetJavaInfoBar( + const base::android::JavaRef<jobject>& java_info_bar) { + DCHECK(java_info_bar_.is_null()); + java_info_bar_.Reset(java_info_bar); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_InfoBar_setNativeInfoBar(env, java_info_bar, + reinterpret_cast<intptr_t>(this)); +} + +const JavaRef<jobject>& InfoBarAndroid::GetJavaInfoBar() { + return java_info_bar_; +} + +bool InfoBarAndroid::HasSetJavaInfoBar() const { + return !java_info_bar_.is_null(); +} + +int InfoBarAndroid::GetInfoBarIdentifier(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + return delegate()->GetIdentifier(); +} + +void InfoBarAndroid::OnButtonClicked(JNIEnv* env, + const JavaParamRef<jobject>& obj, + jint action) { + ProcessButton(action); +} + +void InfoBarAndroid::OnCloseButtonClicked(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + if (!owner()) + return; // We're closing; don't call anything, it might access the owner. + delegate()->InfoBarDismissed(); + RemoveSelf(); +} + +void InfoBarAndroid::CloseJavaInfoBar() { + if (!java_info_bar_.is_null()) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_InfoBar_closeInfoBar(env, java_info_bar_); + java_info_bar_.Reset(nullptr); + } +} + +int InfoBarAndroid::GetJavaIconId() { + return weblayer::MapToJavaDrawableId(delegate()->GetIconId()); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/infobar_android.h b/chromium/weblayer/browser/infobar_android.h new file mode 100644 index 00000000000..5c319688cff --- /dev/null +++ b/chromium/weblayer/browser/infobar_android.h @@ -0,0 +1,86 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_INFOBAR_ANDROID_H_ +#define WEBLAYER_BROWSER_INFOBAR_ANDROID_H_ + +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "components/infobars/core/infobar.h" + +namespace infobars { +class InfoBarDelegate; +} + +namespace weblayer { + +class InfoBarAndroid : public infobars::InfoBar { + public: + // A Java counterpart will be generated for this enum. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private + // GENERATED_JAVA_PREFIX_TO_STRIP: ACTION_ + enum ActionType { + ACTION_NONE = 0, + // Confirm infobar + ACTION_OK = 1, + ACTION_CANCEL = 2, + // Translate infobar + ACTION_TRANSLATE = 3, + ACTION_TRANSLATE_SHOW_ORIGINAL = 4, + }; + + explicit InfoBarAndroid(std::unique_ptr<infobars::InfoBarDelegate> delegate); + ~InfoBarAndroid() override; + + // InfoBar: + virtual base::android::ScopedJavaLocalRef<jobject> CreateRenderInfoBar( + JNIEnv* env) = 0; + + virtual void SetJavaInfoBar( + const base::android::JavaRef<jobject>& java_info_bar); + const base::android::JavaRef<jobject>& GetJavaInfoBar(); + bool HasSetJavaInfoBar() const; + + // Tells the Java-side counterpart of this InfoBar to point to the replacement + // InfoBar instead of this one. + void ReassignJavaInfoBar(InfoBarAndroid* replacement); + + int GetInfoBarIdentifier(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj); + virtual void OnLinkClicked(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj) {} + void OnButtonClicked(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + jint action); + void OnCloseButtonClicked(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj); + + void CloseJavaInfoBar(); + + // Maps from a Chromium ID (IDR_TRANSLATE) to a Drawable ID. + int GetJavaIconId(); + + // Acquire the java infobar from a different one. This is used to do in-place + // replacements. + virtual void PassJavaInfoBar(InfoBarAndroid* source) {} + + protected: + // Derived classes must implement this method to process the corresponding + // action. + virtual void ProcessButton(int action) = 0; + + void CloseInfoBar(); + InfoBarAndroid* infobar_android() { return this; } + + private: + base::android::ScopedJavaGlobalRef<jobject> java_info_bar_; + + DISALLOW_COPY_AND_ASSIGN(InfoBarAndroid); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_INFOBAR_ANDROID_H_ diff --git a/chromium/weblayer/browser/infobar_container_android.cc b/chromium/weblayer/browser/infobar_container_android.cc new file mode 100644 index 00000000000..368ab764ce8 --- /dev/null +++ b/chromium/weblayer/browser/infobar_container_android.cc @@ -0,0 +1,114 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/infobar_container_android.h" + +#include "base/android/jni_android.h" +#include "base/check.h" +#include "base/metrics/histogram_functions.h" +#include "base/notreached.h" +#include "components/infobars/core/infobar.h" +#include "components/infobars/core/infobar_delegate.h" +#include "content/public/browser/web_contents.h" +#include "weblayer/browser/infobar_android.h" +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/java/jni/InfoBarContainer_jni.h" + +using base::android::JavaParamRef; + +namespace weblayer { + +// InfoBarContainerAndroid ---------------------------------------------------- + +InfoBarContainerAndroid::InfoBarContainerAndroid(JNIEnv* env, jobject obj) + : infobars::InfoBarContainer(NULL), + weak_java_infobar_container_(env, obj) {} + +InfoBarContainerAndroid::~InfoBarContainerAndroid() { + RemoveAllInfoBarsForDestruction(); +} + +void InfoBarContainerAndroid::SetWebContents( + JNIEnv* env, + const JavaParamRef<jobject>& obj, + const JavaParamRef<jobject>& web_contents) { + weblayer::InfoBarService* infobar_service = + web_contents + ? weblayer::InfoBarService::FromWebContents( + content::WebContents::FromJavaWebContents(web_contents)) + : nullptr; + ChangeInfoBarManager(infobar_service); +} + +void InfoBarContainerAndroid::Destroy(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + delete this; +} + +void InfoBarContainerAndroid::PlatformSpecificAddInfoBar( + infobars::InfoBar* infobar, + size_t position) { + DCHECK(infobar); + InfoBarAndroid* android_bar = static_cast<InfoBarAndroid*>(infobar); + if (!android_bar) { + // TODO(bulach): CLANK: implement other types of InfoBars. + NOTIMPLEMENTED() << "CLANK: infobar identifier " + << infobar->delegate()->GetIdentifier(); + return; + } + + AttachJavaInfoBar(android_bar); +} + +void InfoBarContainerAndroid::AttachJavaInfoBar(InfoBarAndroid* android_bar) { + if (android_bar->HasSetJavaInfoBar()) + return; + JNIEnv* env = base::android::AttachCurrentThread(); + + if (Java_InfoBarContainer_hasInfoBars( + env, weak_java_infobar_container_.get(env))) { + base::UmaHistogramSparse("InfoBar.Shown.Hidden", + android_bar->delegate()->GetIdentifier()); + infobars::InfoBarDelegate::InfoBarIdentifier identifier = + static_cast<infobars::InfoBarDelegate::InfoBarIdentifier>( + Java_InfoBarContainer_getTopInfoBarIdentifier( + env, weak_java_infobar_container_.get(env))); + if (identifier != infobars::InfoBarDelegate::InfoBarIdentifier::INVALID) { + base::UmaHistogramSparse("InfoBar.Shown.Hiding", identifier); + } + } else { + base::UmaHistogramSparse("InfoBar.Shown.Visible", + android_bar->delegate()->GetIdentifier()); + } + + base::android::ScopedJavaLocalRef<jobject> java_infobar = + android_bar->CreateRenderInfoBar(env); + android_bar->SetJavaInfoBar(java_infobar); + Java_InfoBarContainer_addInfoBar(env, weak_java_infobar_container_.get(env), + java_infobar); +} + +void InfoBarContainerAndroid::PlatformSpecificReplaceInfoBar( + infobars::InfoBar* old_infobar, + infobars::InfoBar* new_infobar) { + static_cast<InfoBarAndroid*>(new_infobar) + ->PassJavaInfoBar(static_cast<InfoBarAndroid*>(old_infobar)); +} + +void InfoBarContainerAndroid::PlatformSpecificRemoveInfoBar( + infobars::InfoBar* infobar) { + InfoBarAndroid* android_infobar = static_cast<InfoBarAndroid*>(infobar); + android_infobar->CloseJavaInfoBar(); +} + +// Native JNI methods --------------------------------------------------------- + +static jlong JNI_InfoBarContainer_Init(JNIEnv* env, + const JavaParamRef<jobject>& obj) { + InfoBarContainerAndroid* infobar_container = + new InfoBarContainerAndroid(env, obj); + return reinterpret_cast<intptr_t>(infobar_container); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/infobar_container_android.h b/chromium/weblayer/browser/infobar_container_android.h new file mode 100644 index 00000000000..4069cf6b19e --- /dev/null +++ b/chromium/weblayer/browser/infobar_container_android.h @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_INFOBAR_CONTAINER_ANDROID_H_ +#define WEBLAYER_BROWSER_INFOBAR_CONTAINER_ANDROID_H_ + +#include <stddef.h> + +#include <map> +#include <string> + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "components/infobars/core/infobar_container.h" + +namespace weblayer { + +class InfoBarAndroid; + +class InfoBarContainerAndroid : public infobars::InfoBarContainer { + public: + InfoBarContainerAndroid(JNIEnv* env, jobject infobar_container); + void SetWebContents(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + const base::android::JavaParamRef<jobject>& web_contents); + void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj); + + JavaObjectWeakGlobalRef java_container() const { + return weak_java_infobar_container_; + } + + private: + ~InfoBarContainerAndroid() override; + + // InfobarContainer: + void PlatformSpecificAddInfoBar(infobars::InfoBar* infobar, + size_t position) override; + void PlatformSpecificRemoveInfoBar(infobars::InfoBar* infobar) override; + void PlatformSpecificReplaceInfoBar(infobars::InfoBar* old_infobar, + infobars::InfoBar* new_infobar) override; + + // Create the Java equivalent of |android_bar| and add it to the java + // container. + void AttachJavaInfoBar(InfoBarAndroid* android_bar); + + // We're owned by the java infobar, need to use a weak ref so it can destroy + // us. + JavaObjectWeakGlobalRef weak_java_infobar_container_; + + DISALLOW_COPY_AND_ASSIGN(InfoBarContainerAndroid); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_INFOBAR_CONTAINER_ANDROID_H_ diff --git a/chromium/weblayer/browser/infobar_service.cc b/chromium/weblayer/browser/infobar_service.cc new file mode 100644 index 00000000000..37af3091f35 --- /dev/null +++ b/chromium/weblayer/browser/infobar_service.cc @@ -0,0 +1,25 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/infobar_service.h" + +namespace weblayer { + +InfoBarService::InfoBarService(content::WebContents* web_contents) + : infobars::ContentInfoBarManager(web_contents) {} + +InfoBarService::~InfoBarService() {} + +void InfoBarService::WebContentsDestroyed() { + // The WebContents is going away; be aggressively paranoid and delete + // ourselves lest other parts of the system attempt to add infobars or use + // us otherwise during the destruction. + web_contents()->RemoveUserData(UserDataKey()); + // That was the equivalent of "delete this". This object is now destroyed; + // returning from this function is the only safe thing to do. +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(InfoBarService) + +} // namespace weblayer diff --git a/chromium/weblayer/browser/infobar_service.h b/chromium/weblayer/browser/infobar_service.h new file mode 100644 index 00000000000..95cb1ec184b --- /dev/null +++ b/chromium/weblayer/browser/infobar_service.h @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_INFOBAR_SERVICE_H_ +#define WEBLAYER_BROWSER_INFOBAR_SERVICE_H_ + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "build/build_config.h" +#include "components/infobars/content/content_infobar_manager.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace content { +class WebContents; +} + +namespace weblayer { + +// WebLayer's specialization of ContentInfoBarManager, which ties the lifetime +// of ContentInfoBarManager instances to that of the WebContents with which they +// are associated. +class InfoBarService : public infobars::ContentInfoBarManager, + public content::WebContentsUserData<InfoBarService> { + public: + ~InfoBarService() override; + InfoBarService(const InfoBarService&) = delete; + InfoBarService& operator=(const InfoBarService&) = delete; + + // InfoBarManager: + std::unique_ptr<infobars::InfoBar> CreateConfirmInfoBar( + std::unique_ptr<ConfirmInfoBarDelegate> delegate) override; + + protected: + explicit InfoBarService(content::WebContents* web_contents); + + private: + friend class content::WebContentsUserData<InfoBarService>; + + // infobars::ContentInfoBarManager: + void WebContentsDestroyed() override; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_INFOBAR_SERVICE_H_ diff --git a/chromium/weblayer/browser/java/BUILD.gn b/chromium/weblayer/browser/java/BUILD.gn index e5a00fb6c61..38cdda128ce 100644 --- a/chromium/weblayer/browser/java/BUILD.gn +++ b/chromium/weblayer/browser/java/BUILD.gn @@ -9,20 +9,33 @@ import("//weblayer/variables.gni") android_resources("weblayer_resources") { sources = [ + "res/drawable/weblayer_infobar_wrapper_bg.xml", + "res/drawable/weblayer_tab_indicator.xml", "res/layout/site_settings_layout.xml", + "res/layout/weblayer_infobar_translate_compact_content.xml", + "res/layout/weblayer_infobar_translate_tab_content.xml", + "res/layout/weblayer_translate_menu_item.xml", + "res/layout/weblayer_translate_menu_item_checked.xml", "res/layout/weblayer_url_bar.xml", + "res/values/colors.xml", "res/values/dimens.xml", "res/values/styles.xml", ] custom_package = "org.chromium.weblayer_private" deps = [ ":weblayer_strings_grd", + "//components/blocked_content/android:java_resources", + "//components/browser_ui/http_auth/android:java_resources", + "//components/browser_ui/media/android:java_resources", "//components/browser_ui/settings/android:java_resources", "//components/browser_ui/site_settings/android:java_resources", "//components/browser_ui/strings/android:browser_ui_strings_grd", "//components/browser_ui/styles/android:java_resources", + "//components/infobars/android:java_resources", "//components/page_info/android:java_resources", "//components/permissions/android:java_resources", + "//components/translate/content/android:java_resources", + "//third_party/android_deps:com_google_android_material_material_java", "//weblayer:components_java_strings", ] } @@ -35,6 +48,7 @@ java_cpp_template("resource_id_javagen") { sources = [ "ResourceId.template" ] package_path = "org/chromium/weblayer_private/resources" inputs = [ + "//components/resources/android/blocked_content_resource_id.h", "//components/resources/android/page_info_resource_id.h", "//components/resources/android/permissions_resource_id.h", ] @@ -51,6 +65,8 @@ java_strings_grd("weblayer_strings_grd") { java_cpp_enum("generated_enums") { sources = [ "//weblayer/browser/controls_visibility_reason.h", + "//weblayer/browser/infobar_android.h", + "//weblayer/browser/translate_utils.h", "//weblayer/public/download.h", "//weblayer/public/navigation.h", "//weblayer/public/new_tab_delegate.h", @@ -60,7 +76,6 @@ java_cpp_enum("generated_enums") { android_library("java") { sources = [ - "org/chromium/weblayer_private/AccessibilityUtil.java", "org/chromium/weblayer_private/ActionModeCallback.java", "org/chromium/weblayer_private/AutocompleteSchemeClassifierImpl.java", "org/chromium/weblayer_private/AutofillView.java", @@ -69,7 +84,7 @@ android_library("java") { "org/chromium/weblayer_private/BrowserImpl.java", "org/chromium/weblayer_private/BrowserViewController.java", "org/chromium/weblayer_private/ChildProcessServiceImpl.java", - "org/chromium/weblayer_private/ContentView.java", + "org/chromium/weblayer_private/ConfirmInfoBar.java", "org/chromium/weblayer_private/ContentViewRenderView.java", "org/chromium/weblayer_private/CookieManagerImpl.java", "org/chromium/weblayer_private/CrashReporterControllerImpl.java", @@ -80,9 +95,19 @@ android_library("java") { "org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java", "org/chromium/weblayer_private/FragmentWindowAndroid.java", "org/chromium/weblayer_private/FullscreenCallbackProxy.java", + "org/chromium/weblayer_private/InfoBar.java", + "org/chromium/weblayer_private/InfoBarCompactLayout.java", + "org/chromium/weblayer_private/InfoBarContainer.java", + "org/chromium/weblayer_private/InfoBarContainerLayout.java", + "org/chromium/weblayer_private/InfoBarContainerView.java", + "org/chromium/weblayer_private/InfoBarUiItem.java", + "org/chromium/weblayer_private/InfoBarWrapper.java", + "org/chromium/weblayer_private/IntentUtils.java", "org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java", "org/chromium/weblayer_private/LocaleChangedBroadcastReceiver.java", + "org/chromium/weblayer_private/MediaSessionManager.java", "org/chromium/weblayer_private/MediaStreamManager.java", + "org/chromium/weblayer_private/MojoInterfaceRegistrar.java", "org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NewTabCallbackProxy.java", @@ -91,8 +116,15 @@ android_library("java") { "org/chromium/weblayer_private/ProfileManager.java", "org/chromium/weblayer_private/RemoteFragmentImpl.java", "org/chromium/weblayer_private/SiteSettingsFragmentImpl.java", + "org/chromium/weblayer_private/SwipableOverlayView.java", "org/chromium/weblayer_private/TabCallbackProxy.java", "org/chromium/weblayer_private/TabImpl.java", + "org/chromium/weblayer_private/TranslateCompactInfoBar.java", + "org/chromium/weblayer_private/TranslateMenu.java", + "org/chromium/weblayer_private/TranslateMenuHelper.java", + "org/chromium/weblayer_private/TranslateOptions.java", + "org/chromium/weblayer_private/TranslateTabContent.java", + "org/chromium/weblayer_private/TranslateTabLayout.java", "org/chromium/weblayer_private/UrlBarControllerImpl.java", "org/chromium/weblayer_private/WebContentsGestureStateTracker.java", "org/chromium/weblayer_private/WebLayerAccessibilityUtil.java", @@ -103,6 +135,8 @@ android_library("java") { "org/chromium/weblayer_private/WebLayerNotificationChannels.java", "org/chromium/weblayer_private/WebLayerSiteSettingsClient.java", "org/chromium/weblayer_private/WebLayerTabModalPresenter.java", + "org/chromium/weblayer_private/WebMessageReplyProxyImpl.java", + "org/chromium/weblayer_private/WebShareServiceFactory.java", "org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java", "org/chromium/weblayer_private/metrics/MetricsServiceClient.java", "org/chromium/weblayer_private/metrics/UmaUtils.java", @@ -116,26 +150,34 @@ android_library("java") { ":weblayer_resources", "//base:base_java", "//base:jni_java", - "//components/autofill/android:provider_java", + "//components/autofill/android/provider:java", + "//components/browser_ui/client_certificate/android:java", + "//components/browser_ui/http_auth/android:java", + "//components/browser_ui/media/android:java", "//components/browser_ui/modaldialog/android:java", "//components/browser_ui/notifications/android:java", "//components/browser_ui/settings/android:java", + "//components/browser_ui/share/android:java", "//components/browser_ui/site_settings/android:java", "//components/browser_ui/styles/android:java", - "//components/browser_ui/styles/android:java_resources", "//components/browser_ui/util/android:java", + "//components/browser_ui/webshare/android:java", + "//components/browser_ui/widget/android:java", "//components/content_settings/android:java", "//components/crash/android:handler_java", "//components/crash/android:java", "//components/download/internal/common:internal_java", "//components/embedder_support/android:application_java", "//components/embedder_support/android:browser_context_java", + "//components/embedder_support/android:content_view_java", "//components/embedder_support/android:context_menu_java", "//components/embedder_support/android:util_java", "//components/embedder_support/android:web_contents_delegate_java", "//components/embedder_support/android/metrics:java", "//components/external_intents/android:java", "//components/find_in_page/android:java", + "//components/infobars/android:java", + "//components/infobars/core:infobar_enums_java", "//components/javascript_dialogs/android:java", "//components/location/android:settings_java", "//components/metrics:metrics_java", @@ -150,11 +192,22 @@ android_library("java") { "//components/url_formatter/android:url_formatter_java", "//components/variations/android:variations_java", "//components/version_info/android:version_constants_java", + "//components/webapk/android/libs/client:java", + "//components/webapk/android/libs/common:java", "//components/webrtc/android:java", "//content/public/android:content_java", + "//mojo/public/java:bindings_java", "//net/android:net_java", + "//services/network/public/mojom:cookies_mojom_java", "//services/network/public/mojom:mojom_java", + "//services/service_manager/public/java:service_manager_java", + "//third_party/android_deps:androidx_appcompat_appcompat_java", + "//third_party/android_deps:androidx_appcompat_appcompat_resources_java", "//third_party/android_deps:androidx_core_core_java", + "//third_party/android_deps:androidx_fragment_fragment_java", + "//third_party/android_deps:androidx_preference_preference_java", + "//third_party/android_deps:com_google_android_material_material_java", + "//third_party/blink/public/mojom:android_mojo_bindings_java", "//ui/android:ui_full_java", "//ui/android:ui_java", "//url:gurl_java", @@ -188,9 +241,16 @@ android_resources("weblayer_test_resources") { android_library("test_java") { testonly = true - sources = [ "org/chromium/weblayer_private/test/TestWebLayerImpl.java" ] + sources = [ + "org/chromium/weblayer_private/test/TestInfoBar.java", + "org/chromium/weblayer_private/test/TestWebLayerImpl.java", + ] deps = [ + ":interfaces_java", + ":java", ":weblayer_test_resources", + "//base:jni_java", + "//components/location/android:location_java", "//components/permissions/android:java", "//content/public/test/android:content_java_test_support", "//net/android:net_java", @@ -199,6 +259,15 @@ android_library("test_java") { "//ui/android:ui_full_java", ] srcjar_deps = [ ":test_aidl" ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] +} + +generate_jni("test_jni") { + testonly = true + sources = [ + "org/chromium/weblayer_private/test/TestInfoBar.java", + "org/chromium/weblayer_private/test/TestWebLayerImpl.java", + ] } generate_jni("jni") { @@ -206,25 +275,30 @@ generate_jni("jni") { "org/chromium/weblayer_private/AutocompleteSchemeClassifierImpl.java", "org/chromium/weblayer_private/BrowserControlsContainerView.java", "org/chromium/weblayer_private/BrowserImpl.java", + "org/chromium/weblayer_private/ConfirmInfoBar.java", "org/chromium/weblayer_private/ContentViewRenderView.java", "org/chromium/weblayer_private/CookieManagerImpl.java", "org/chromium/weblayer_private/DownloadCallbackProxy.java", "org/chromium/weblayer_private/DownloadImpl.java", "org/chromium/weblayer_private/ErrorPageCallbackProxy.java", "org/chromium/weblayer_private/FullscreenCallbackProxy.java", + "org/chromium/weblayer_private/InfoBar.java", + "org/chromium/weblayer_private/InfoBarContainer.java", "org/chromium/weblayer_private/LocaleChangedBroadcastReceiver.java", "org/chromium/weblayer_private/MediaStreamManager.java", + "org/chromium/weblayer_private/MojoInterfaceRegistrar.java", "org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NewTabCallbackProxy.java", "org/chromium/weblayer_private/ProfileImpl.java", "org/chromium/weblayer_private/TabCallbackProxy.java", "org/chromium/weblayer_private/TabImpl.java", + "org/chromium/weblayer_private/TranslateCompactInfoBar.java", "org/chromium/weblayer_private/UrlBarControllerImpl.java", "org/chromium/weblayer_private/WebLayerExceptionFilter.java", "org/chromium/weblayer_private/WebLayerFactoryImpl.java", "org/chromium/weblayer_private/WebLayerImpl.java", - "org/chromium/weblayer_private/WebLayerSiteSettingsClient.java", + "org/chromium/weblayer_private/WebMessageReplyProxyImpl.java", "org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java", "org/chromium/weblayer_private/metrics/MetricsServiceClient.java", "org/chromium/weblayer_private/metrics/UmaUtils.java", @@ -246,11 +320,13 @@ android_library("interfaces_java") { "org/chromium/weblayer_private/interfaces/NavigationState.java", "org/chromium/weblayer_private/interfaces/NewTabType.java", "org/chromium/weblayer_private/interfaces/ObjectWrapper.java", + "org/chromium/weblayer_private/interfaces/ScrollNotificationType.java", "org/chromium/weblayer_private/interfaces/SettingType.java", "org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java", "org/chromium/weblayer_private/interfaces/SiteSettingsIntentHelper.java", "org/chromium/weblayer_private/interfaces/StrictModeWorkaround.java", "org/chromium/weblayer_private/interfaces/UrlBarOptionsKeys.java", + "org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java", ] deps = [ "//third_party/android_deps:androidx_annotation_annotation_java" ] @@ -316,6 +392,8 @@ android_aidl("aidl") { "org/chromium/weblayer_private/interfaces/IWebLayer.aidl", "org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl", "org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl", + "org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl", + "org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl", ] } diff --git a/chromium/weblayer/browser/java/DEPS b/chromium/weblayer/browser/java/DEPS index 5218c75b075..819b5a6c915 100644 --- a/chromium/weblayer/browser/java/DEPS +++ b/chromium/weblayer/browser/java/DEPS @@ -1,11 +1,14 @@ include_rules = [ + "+components/browser_ui/http_auth", "+components/browser_ui/util/android", "+components/content_settings/android/java", "+components/crash/android/java", "+components/external_intents", + "+components/infobars/android", "+components/location/android/java/src/org/chromium/components/location", "+components/minidump_uploader", "+components/page_info/android/java", + "+components/webapk/android/libs", "+services/device/public/java/src/org/chromium/device/geolocation", # WebLayerNotificationBuilder should be used for all notifications. diff --git a/chromium/weblayer/browser/java/ResourceId.template b/chromium/weblayer/browser/java/ResourceId.template index 0f5352d7e40..1e16c782f49 100644 --- a/chromium/weblayer/browser/java/ResourceId.template +++ b/chromium/weblayer/browser/java/ResourceId.template @@ -11,6 +11,7 @@ class ResourceId { int[] resourceList = { #define LINK_RESOURCE_ID(c_id,java_id) java_id, #define DECLARE_RESOURCE_ID(c_id,java_id) java_id, +#include "components/resources/android/blocked_content_resource_id.h" #include "components/resources/android/page_info_resource_id.h" #include "components/resources/android/permissions_resource_id.h" }; diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java deleted file mode 100644 index 5382c726ad7..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// TODO(sky): this is a forked copy of that from src/chrome, refactor and share. - -package org.chromium.weblayer_private; - -import android.accessibilityservice.AccessibilityServiceInfo; -import android.content.Context; -import android.content.res.Configuration; -import android.os.Build; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; -import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.ContextUtils; -import org.chromium.base.ObserverList; -import org.chromium.base.TraceEvent; -import org.chromium.base.task.PostTask; -import org.chromium.content_public.browser.UiThreadTaskTraits; - -import java.util.List; - -/** - * Exposes information about the current accessibility state. - */ -public class AccessibilityUtil { - /** - * An observer to be notified of accessibility status changes. - */ - public interface Observer { - /** - * @param enabled Whether a touch exploration or an accessibility service that performs can - * perform gestures is enabled. Indicates that the UI must be fully navigable using - * the accessibility view tree. - */ - void onAccessibilityModeChanged(boolean enabled); - } - - private Boolean mIsAccessibilityEnabled; - private ObserverList<Observer> mObservers; - private final class ModeChangeHandler - implements AccessibilityStateChangeListener, TouchExplorationStateChangeListener { - // AccessibilityStateChangeListener - - @Override - public final void onAccessibilityStateChanged(boolean enabled) { - updateIsAccessibilityEnabledAndNotify(); - } - - // TouchExplorationStateChangeListener - - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - updateIsAccessibilityEnabledAndNotify(); - } - } - - private ModeChangeHandler mModeChangeHandler; - - protected AccessibilityUtil() {} - - /** - * Checks to see that this device has accessibility and touch exploration enabled. - * @return Whether or not accessibility and touch exploration are enabled. - */ - public boolean isAccessibilityEnabled() { - if (mModeChangeHandler == null) registerModeChangeListeners(); - if (mIsAccessibilityEnabled != null) return mIsAccessibilityEnabled; - - TraceEvent.begin("AccessibilityManager::isAccessibilityEnabled"); - - AccessibilityManager manager = getAccessibilityManager(); - boolean accessibilityEnabled = - manager != null && manager.isEnabled() && manager.isTouchExplorationEnabled(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && manager != null - && manager.isEnabled() && !accessibilityEnabled) { - List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList( - AccessibilityServiceInfo.FEEDBACK_ALL_MASK); - for (AccessibilityServiceInfo service : services) { - if (canPerformGestures(service)) { - accessibilityEnabled = true; - break; - } - } - } - - mIsAccessibilityEnabled = accessibilityEnabled; - - TraceEvent.end("AccessibilityManager::isAccessibilityEnabled"); - return mIsAccessibilityEnabled; - } - - /** - * Add {@link Observer} object. The observer will be notified of the current accessibility - * mode immediately. - * @param observer Observer object monitoring a11y mode change. - */ - public void addObserver(Observer observer) { - getObservers().addObserver(observer); - - // Notify mode change to a new observer so things are initialized correctly when Chrome - // has been re-started after closing due to the last tab being closed when homepage is - // enabled. See crbug.com/541546. - observer.onAccessibilityModeChanged(isAccessibilityEnabled()); - } - - /** - * Remove {@link Observer} object. - * @param observer Observer object monitoring a11y mode change. - */ - public void removeObserver(Observer observer) { - getObservers().removeObserver(observer); - } - - /** - * @return True if a hardware keyboard is detected. - */ - public static boolean isHardwareKeyboardAttached(Configuration c) { - return c.keyboard != Configuration.KEYBOARD_NOKEYS; - } - - private AccessibilityManager getAccessibilityManager() { - return (AccessibilityManager) ContextUtils.getApplicationContext().getSystemService( - Context.ACCESSIBILITY_SERVICE); - } - - private void registerModeChangeListeners() { - assert mModeChangeHandler == null; - mModeChangeHandler = new ModeChangeHandler(); - AccessibilityManager manager = getAccessibilityManager(); - manager.addAccessibilityStateChangeListener(mModeChangeHandler); - manager.addTouchExplorationStateChangeListener(mModeChangeHandler); - } - - /** - * Removes all global state tracking observers/listeners as well as any observers added to this. - * As this removes all observers, be very careful in calling. In general, only call when the - * application is going to be destroyed. - */ - protected void stopTrackingStateAndRemoveObservers() { - if (mObservers != null) mObservers.clear(); - if (mModeChangeHandler == null) return; - AccessibilityManager manager = getAccessibilityManager(); - manager.removeAccessibilityStateChangeListener(mModeChangeHandler); - manager.removeTouchExplorationStateChangeListener(mModeChangeHandler); - } - - /** - * Forces recalculating the value of isAccessibilityEnabled(). If the value has changed observer - * are notified. - */ - protected void updateIsAccessibilityEnabledAndNotify() { - boolean oldIsAccessibilityEnabled = isAccessibilityEnabled(); - // Setting to null forces the next call to isAccessibilityEnabled() to update the value. - mIsAccessibilityEnabled = null; - if (oldIsAccessibilityEnabled != isAccessibilityEnabled()) notifyModeChange(); - } - - private ObserverList<Observer> getObservers() { - if (mObservers == null) mObservers = new ObserverList<>(); - return mObservers; - } - - /** - * Notify all the observers of the mode change. - */ - private void notifyModeChange() { - boolean enabled = isAccessibilityEnabled(); - for (Observer observer : getObservers()) { - observer.onAccessibilityModeChanged(enabled); - } - } - - /** - * Checks whether the given {@link AccessibilityServiceInfo} can perform gestures. - * @param service The service to check. - * @return Whether the {@code service} can perform gestures. On N+, this relies on the - * capabilities the service can perform. On L & M, this looks specifically for - * Switch Access. - */ - private boolean canPerformGestures(AccessibilityServiceInfo service) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return (service.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) - != 0; - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return service.getResolveInfo() != null - && service.getResolveInfo().toString().contains("switchaccess"); - } - return false; - } - - /** - * Set whether the device has accessibility enabled. Should be reset back to null after the test - * has finished. - * @param isEnabled whether the device has accessibility enabled. - */ - @VisibleForTesting - public void setAccessibilityEnabledForTesting(@Nullable Boolean isEnabled) { - mIsAccessibilityEnabled = isEnabled; - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, this::notifyModeChange); - } -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java index 1b2992d55b4..ca8217eae14 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java @@ -12,9 +12,12 @@ import android.view.ViewStructure; import android.view.autofill.AutofillValue; import android.widget.FrameLayout; +import org.chromium.base.annotations.VerifiesOnO; + /** * View which handles autofill support for a tab. */ +@VerifiesOnO public class AutofillView extends FrameLayout { private TabImpl mTab; diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java index d860d7cdef4..ce10368c02c 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java @@ -267,15 +267,32 @@ class BrowserControlsContainerView extends FrameLayout { if (mView == null) return; int width = right - left; int height = bottom - top; - if (height != mLastHeight || width != mLastWidth) { - mLastWidth = width; - mLastHeight = height; - if (mLastWidth > 0 && mLastHeight > 0) { - if (mViewResourceAdapter == null) { - createAdapterAndLayer(); + boolean heightChanged = height != mLastHeight; + if (!heightChanged && width == mLastWidth) return; + + mLastWidth = width; + mLastHeight = height; + if (mLastWidth > 0 && mLastHeight > 0 && mViewResourceAdapter == null) { + createAdapterAndLayer(); + } else if (mViewResourceAdapter != null) { + BrowserControlsContainerViewJni.get().setControlsSize( + mNativeBrowserControlsContainerView, mLastWidth, mLastHeight); + if (mWebContents != null) mWebContents.notifyBrowserControlsHeightChanged(); + if (heightChanged) { + // When the height changes cc doesn't generate a new frame, which means this code + // must process the change now. If cc generated a new frame, it would likely be at + // the wrong size. + if (mControlsOffset == 0) { + // The controls are completely visible. + onOffsetsChanged(0, height); } else { - BrowserControlsContainerViewJni.get().setControlsSize( - mNativeBrowserControlsContainerView, mLastWidth, mLastHeight); + // The controls are partially (and possibly completely) hidden. Snap to + // completely hidden. + if (mIsTop) { + onOffsetsChanged(-height, height); + } else { + onOffsetsChanged(height, 0); + } } } } @@ -333,7 +350,11 @@ class BrowserControlsContainerView extends FrameLayout { private void finishScroll(int contentOffsetY) { mInScroll = false; setControlsOffset(0, contentOffsetY); - mContentViewRenderView.postOnAnimation(() -> showControls()); + if (BrowserControlsContainerViewJni.get().shouldDelayVisibilityChange()) { + mContentViewRenderView.postOnAnimation(() -> showControls()); + } else { + showControls(); + } } private void setControlsOffset(int controlsOffsetY, int contentOffsetY) { @@ -350,16 +371,20 @@ class BrowserControlsContainerView extends FrameLayout { } if (mIsTop) { BrowserControlsContainerViewJni.get().setTopControlsOffset( - mNativeBrowserControlsContainerView, mControlsOffset, mContentOffset); + mNativeBrowserControlsContainerView, mContentOffset); } else { BrowserControlsContainerViewJni.get().setBottomControlsOffset( - mNativeBrowserControlsContainerView, mControlsOffset); + mNativeBrowserControlsContainerView); } } private void prepareForScroll() { mInScroll = true; - mContentViewRenderView.postOnAnimation(() -> hideControls()); + if (BrowserControlsContainerViewJni.get().shouldDelayVisibilityChange()) { + mContentViewRenderView.postOnAnimation(() -> hideControls()); + } else { + hideControls(); + } } private void hideControls() { @@ -371,6 +396,11 @@ class BrowserControlsContainerView extends FrameLayout { } @CalledByNative + private int getControlsOffset() { + return mControlsOffset; + } + + @CalledByNative private void didToggleFullscreenModeForTab(final boolean isFullscreen) { // Delay hiding until after the animation. This comes from Chrome code. if (mSystemUiFullscreenResizeRunnable != null) { @@ -410,11 +440,11 @@ class BrowserControlsContainerView extends FrameLayout { void deleteBrowserControlsContainerView(long nativeBrowserControlsContainerView); void createControlsLayer(long nativeBrowserControlsContainerView, int id); void deleteControlsLayer(long nativeBrowserControlsContainerView); - void setTopControlsOffset( - long nativeBrowserControlsContainerView, int controlsOffsetY, int contentOffsetY); - void setBottomControlsOffset(long nativeBrowserControlsContainerView, int controlsOffsetY); + void setTopControlsOffset(long nativeBrowserControlsContainerView, int contentOffsetY); + void setBottomControlsOffset(long nativeBrowserControlsContainerView); void setControlsSize(long nativeBrowserControlsContainerView, int width, int height); void updateControlsResource(long nativeBrowserControlsContainerView); void setWebContents(long nativeBrowserControlsContainerView, WebContents webContents); + boolean shouldDelayVisibilityChange(); } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java index 33a8af8ce37..9ccb13e1704 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java @@ -4,6 +4,7 @@ package org.chromium.weblayer_private; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -127,7 +128,8 @@ public class BrowserFragmentImpl extends RemoteFragmentImpl { @Override public void onStop() { super.onStop(); - mBrowser.onFragmentStop(); + Activity activity = getActivity(); + mBrowser.onFragmentStop(activity != null && activity.getChangingConfigurations() != 0); } @Override diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java index fc12eccb248..66433bde80a 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java @@ -63,6 +63,7 @@ public class BrowserImpl extends IBrowser.Stub { private final UrlBarControllerImpl mUrlBarController; private boolean mFragmentStarted; private boolean mFragmentResumed; + private boolean mFragmentStoppedForConfigurationChange; // Cache the value instead of querying system every time. private Boolean mPasswordEchoEnabled; private Boolean mDarkThemeEnabled; @@ -106,6 +107,8 @@ public class BrowserImpl extends IBrowser.Stub { ? savedInstanceState.getByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY) : null; + windowAndroid.restoreInstanceState(savedInstanceState); + createAttachmentState(embedderAppContext, windowAndroid); mNativeBrowser = BrowserImplJni.get().createBrowser(profile.getNativeProfile(), this); mUrlBarController = new UrlBarControllerImpl(this, mNativeBrowser); @@ -160,6 +163,10 @@ public class BrowserImpl extends IBrowser.Stub { outState.putByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY, BrowserImplJni.get().getMinimalPersistenceState(mNativeBrowser)); } + + if (mWindowAndroid != null) { + mWindowAndroid.saveInstanceState(outState); + } } public void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -188,6 +195,13 @@ public class BrowserImpl extends IBrowser.Stub { } @Override + public TabImpl createTab() { + TabImpl tab = new TabImpl(mProfile, mWindowAndroid); + addTab(tab); + return tab; + } + + @Override public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) { StrictModeWorkaround.apply(); getViewController().setSupportsEmbedding(enable, @@ -230,7 +244,7 @@ public class BrowserImpl extends IBrowser.Stub { } @CalledByNative - private void createTabForSessionRestore(long nativeTab) { + private void createJavaTabForNativeTab(long nativeTab) { new TabImpl(mProfile, mWindowAndroid, nativeTab); } @@ -309,7 +323,7 @@ public class BrowserImpl extends IBrowser.Stub { @CalledByNative private void onActiveTabChanged(TabImpl tab) { - mViewController.setActiveTab(tab); + if (mViewController != null) mViewController.setActiveTab(tab); if (mInDestroy) return; try { if (mClient != null) { @@ -388,10 +402,8 @@ public class BrowserImpl extends IBrowser.Stub { updateAllTabsAndSetActive(); } else if (persistenceInfo.mPersistenceId == null || persistenceInfo.mPersistenceId.isEmpty()) { - TabImpl tab = new TabImpl(mProfile, mWindowAndroid); - addTab(tab); - boolean set_active_result = setActiveTab(tab); - assert set_active_result; + boolean setActiveResult = setActiveTab(createTab()); + assert setActiveResult; } // else case is session restore, which will asynchronously create tabs. } @@ -404,7 +416,6 @@ public class BrowserImpl extends IBrowser.Stub { } private void destroyTabImpl(TabImpl tab) { - BrowserImplJni.get().removeTab(mNativeBrowser, tab.getNativeTab()); tab.destroy(); } @@ -438,24 +449,31 @@ public class BrowserImpl extends IBrowser.Stub { } public void onFragmentStart() { + mFragmentStoppedForConfigurationChange = false; mFragmentStarted = true; BrowserImplJni.get().onFragmentStart(mNativeBrowser); updateAllTabs(); checkPreferences(); } - public void onFragmentStop() { + public void onFragmentStop(boolean forConfigurationChange) { + mFragmentStoppedForConfigurationChange = forConfigurationChange; mFragmentStarted = false; + if (mFragmentStoppedForConfigurationChange) { + destroyAttachmentState(); + } updateAllTabs(); } public void onFragmentResume() { mFragmentResumed = true; WebLayerAccessibilityUtil.get().onBrowserResumed(); + BrowserImplJni.get().onFragmentResume(mNativeBrowser); } public void onFragmentPause() { mFragmentResumed = false; + BrowserImplJni.get().onFragmentPause(mNativeBrowser); } public boolean isStarted() { @@ -466,6 +484,10 @@ public class BrowserImpl extends IBrowser.Stub { return mFragmentResumed; } + public boolean isFragmentStoppedForConfigurationChange() { + return mFragmentStoppedForConfigurationChange; + } + private void destroyAttachmentState() { if (mLocaleReceiver != null) { mLocaleReceiver.destroy(); @@ -515,5 +537,7 @@ public class BrowserImpl extends IBrowser.Stub { byte[] persistenceCryptoKey, byte[] minimalPersistenceState); void webPreferencesChanged(long nativeBrowserImpl); void onFragmentStart(long nativeBrowserImpl); + void onFragmentResume(long nativeBrowserImpl); + void onFragmentPause(long nativeBrowserImpl); } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java index c9ae777bd63..436113adde3 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java @@ -17,6 +17,7 @@ import android.widget.RelativeLayout; import org.chromium.base.annotations.JNINamespace; import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; +import org.chromium.components.embedder_support.view.ContentView; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -79,7 +80,7 @@ public final class BrowserViewController new BrowserControlsContainerView(context, mContentViewRenderView, this, false); mBottomControlsContainerView.setId(View.generateViewId()); mContentView = ContentView.createContentView( - context, mTopControlsContainerView.getEventOffsetHandler()); + context, mTopControlsContainerView.getEventOffsetHandler(), null /* webContents */); mContentViewRenderView.addView(mContentView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); @@ -125,6 +126,11 @@ public final class BrowserViewController return mContentViewRenderView; } + /** Returns the ViewGroup into which the InfoBarContainer should be parented. **/ + public ViewGroup getInfoBarContainerParentView() { + return mContentViewRenderView; + } + public ViewGroup getContentView() { return mContentView; } @@ -137,6 +143,14 @@ public final class BrowserViewController return mAutofillView; } + // Returns the index at which the infobar container view should be inserted. + public int getDesiredInfoBarContainerViewIndex() { + // Ensure that infobars are positioned behind WebContents overlays in z-order. + // TODO(blundell): Should infobars instead be hidden while a WebContents overlay is + // presented? + return mContentViewRenderView.indexOfChild(mWebContentsOverlayView) - 1; + } + public void setActiveTab(TabImpl tab) { if (tab == mTab) return; @@ -160,8 +174,8 @@ public final class BrowserViewController new WebContentsGestureStateTracker(mContentView, webContents, this); } mAutofillView.setTab(mTab); - mContentView.setTab(mTab); + mContentView.setWebContents(webContents); mContentViewRenderView.setWebContents(webContents); mTopControlsContainerView.setWebContents(webContents); mBottomControlsContainerView.setWebContents(webContents); @@ -184,6 +198,10 @@ public final class BrowserViewController mBottomControlsContainerView.setView(view); } + public int getBottomContentHeightDelta() { + return mBottomControlsContainerView.getContentHeightDelta(); + } + public boolean compositorHasSurface() { return mContentViewRenderView.hasSurface(); } @@ -210,19 +228,24 @@ public final class BrowserViewController } @Override - public void onDialogShown(PropertyModel model) { + public void onDialogAdded(PropertyModel model) { onDialogVisibilityChanged(true); } @Override - public void onDialogHidden(PropertyModel model) { + public void onLastDialogDismissed() { onDialogVisibilityChanged(false); } private void onDialogVisibilityChanged(boolean showing) { if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return; - if (mModalDialogManager.getCurrentType() == ModalDialogType.TAB) { + // ModalDialogManager.onLastDialogDismissed() may be called if |mTab| is currently null. + // This is because in some situations ModalDialogManager calls onLastDialogDismissed() even + // if there were no dialogs present and dismissDialog() is called. This matters as + // dismissDialog() may be called when |mTab| is null. + // TODO(sky): fix ModalDialogManager and remove mTab conditional. + if (mModalDialogManager.getCurrentType() == ModalDialogType.TAB && mTab != null) { try { mTab.getClient().onTabModalStateChanged(showing); } catch (RemoteException e) { diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java new file mode 100644 index 00000000000..4b49a4e8ebf --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java @@ -0,0 +1,81 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.graphics.Bitmap; + +import androidx.annotation.ColorRes; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.components.infobars.InfoBarLayout; + +/** + * An infobar that presents the user with several buttons. + * + * TODO(newt): merge this into InfoBar.java. + */ +public class ConfirmInfoBar extends InfoBar { + /** Text shown on the primary button, e.g. "OK". */ + private final String mPrimaryButtonText; + + /** Text shown on the secondary button, e.g. "Cancel".*/ + private final String mSecondaryButtonText; + + /** Text shown on the link, e.g. "Learn more". */ + private final String mLinkText; + + protected ConfirmInfoBar(int iconDrawableId, @ColorRes int iconTintId, Bitmap iconBitmap, + String message, String linkText, String primaryButtonText, String secondaryButtonText) { + super(iconDrawableId, iconTintId, message, iconBitmap); + mPrimaryButtonText = primaryButtonText; + mSecondaryButtonText = secondaryButtonText; + mLinkText = linkText; + } + + @Override + public void createContent(InfoBarLayout layout) { + setButtons(layout, mPrimaryButtonText, mSecondaryButtonText); + if (mLinkText != null && !mLinkText.isEmpty()) layout.appendMessageLinkText(mLinkText); + } + + /** + * If your custom infobar overrides this function, YOU'RE PROBABLY DOING SOMETHING WRONG. + * + * Adds buttons to the infobar. This should only be overridden in cases where an infobar + * requires adding something other than a button for its secondary View on the bottom row + * (almost never). + * + * @param primaryText Text to display on the primary button. + * @param secondaryText Text to display on the secondary button. May be null. + */ + protected void setButtons(InfoBarLayout layout, String primaryText, String secondaryText) { + layout.setButtons(primaryText, secondaryText); + } + + @Override + public void onButtonClicked(final boolean isPrimaryButton) { + int action = isPrimaryButton ? ActionType.OK : ActionType.CANCEL; + onButtonClicked(action); + } + + /** + * Creates and begins the process for showing a ConfirmInfoBar. + * @param iconId ID corresponding to the icon that will be shown for the infobar. + * @param iconBitmap Bitmap to use if there is no equivalent Java resource for + * iconId. + * @param message Message to display to the user indicating what the infobar is for. + * @param linkText Link text to display in addition to the message. + * @param buttonOk String to display on the OK button. + * @param buttonCancel String to display on the Cancel button. + */ + @CalledByNative + private static ConfirmInfoBar create(int iconId, Bitmap iconBitmap, String message, + String linkText, String buttonOk, String buttonCancel) { + ConfirmInfoBar infoBar = new ConfirmInfoBar( + iconId, 0, iconBitmap, message, linkText, buttonOk, buttonCancel); + + return infoBar; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java deleted file mode 100644 index 44accce66d8..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.weblayer_private; - -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.view.DragEvent; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnSystemUiVisibilityChangeListener; -import android.view.ViewGroup.OnHierarchyChangeListener; -import android.view.ViewStructure; -import android.view.accessibility.AccessibilityNodeProvider; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.widget.RelativeLayout; - -import org.chromium.base.ObserverList; -import org.chromium.base.TraceEvent; -import org.chromium.base.compat.ApiHelperForO; -import org.chromium.content_public.browser.ImeAdapter; -import org.chromium.content_public.browser.RenderCoordinates; -import org.chromium.content_public.browser.SmartClipProvider; -import org.chromium.content_public.browser.ViewEventSink; -import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.browser.WebContentsAccessibility; -import org.chromium.ui.base.EventForwarder; -import org.chromium.ui.base.EventOffsetHandler; - -/** - * The containing view for {@link WebContents} that exists in the Android UI hierarchy and exposes - * the various {@link View} functionality to it. - */ -public class ContentView extends RelativeLayout - implements ViewEventSink.InternalAccessDelegate, SmartClipProvider, - OnHierarchyChangeListener, OnSystemUiVisibilityChangeListener { - private static final String TAG = "ContentView"; - - // Default value to signal that the ContentView's size need not be overridden. - public static final int DEFAULT_MEASURE_SPEC = - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - - private TabImpl mTab; - private WebContents mWebContents; - private boolean mIsObscuredForAccessibility; - private final ObserverList<OnHierarchyChangeListener> mHierarchyChangeListeners = - new ObserverList<>(); - private final ObserverList<OnSystemUiVisibilityChangeListener> mSystemUiChangeListeners = - new ObserverList<>(); - - /** - * The desired size of this view in {@link MeasureSpec}. Set by the host - * when it should be different from that of the parent. - */ - private int mDesiredWidthMeasureSpec = DEFAULT_MEASURE_SPEC; - private int mDesiredHeightMeasureSpec = DEFAULT_MEASURE_SPEC; - - private EventOffsetHandler mEventOffsetHandler; - - /** - * Constructs a new ContentView for the appropriate Android version. - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param webContents The WebContents managing this content view. - * @return an instance of a ContentView. - */ - public static ContentView createContentView( - Context context, EventOffsetHandler eventOffsetHandler) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return new ContentViewApi23(context, eventOffsetHandler); - } - return new ContentView(context, eventOffsetHandler); - } - - /** - * Creates an instance of a ContentView. - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param webContents A pointer to the WebContents managing this content view. - */ - ContentView(Context context, EventOffsetHandler eventOffsetHandler) { - super(context, null, android.R.attr.webViewStyle); - - if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) { - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(false); - } - - mEventOffsetHandler = eventOffsetHandler; - - setFocusable(true); - setFocusableInTouchMode(true); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ApiHelperForO.setDefaultFocusHighlightEnabled(this, false); - } - - setOnHierarchyChangeListener(this); - setOnSystemUiVisibilityChangeListener(this); - } - - protected WebContentsAccessibility getWebContentsAccessibility() { - return mWebContents != null && !mWebContents.isDestroyed() - ? WebContentsAccessibility.fromWebContents(mWebContents) - : null; - } - - protected TabImpl getTab() { - return mTab; - } - - public void setTab(TabImpl tab) { - mTab = tab; - boolean wasFocused = isFocused(); - boolean wasWindowFocused = hasWindowFocus(); - boolean wasAttached = isAttachedToWindow(); - boolean wasObscured = mIsObscuredForAccessibility; - if (wasFocused) onFocusChanged(false, View.FOCUS_FORWARD, null); - if (wasWindowFocused) onWindowFocusChanged(false); - if (wasAttached) onDetachedFromWindow(); - if (wasObscured) setIsObscuredForAccessibility(false); - mWebContents = mTab != null ? mTab.getWebContents() : null; - if (wasFocused) onFocusChanged(true, View.FOCUS_FORWARD, null); - if (wasWindowFocused) onWindowFocusChanged(true); - if (wasAttached) onAttachedToWindow(); - if (wasObscured) setIsObscuredForAccessibility(true); - } - - /** - * Control whether WebContentsAccessibility will respond to accessibility requests. - */ - public void setIsObscuredForAccessibility(boolean isObscured) { - if (mIsObscuredForAccessibility == isObscured) return; - mIsObscuredForAccessibility = isObscured; - WebContentsAccessibility wcax = getWebContentsAccessibility(); - if (wcax == null) return; - wcax.setObscuredByAnotherView(mIsObscuredForAccessibility); - } - - @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - WebContentsAccessibility wcax = getWebContentsAccessibility(); - return wcax != null && wcax.supportsAction(action) - ? wcax.performAction(action, arguments) - : super.performAccessibilityAction(action, arguments); - } - - /** - * Set the desired size of the view. The values are in {@link MeasureSpec}. - * @param width The width of the content view. - * @param height The height of the content view. - */ - public void setDesiredMeasureSpec(int width, int height) { - mDesiredWidthMeasureSpec = width; - mDesiredHeightMeasureSpec = height; - } - - @Override - public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { - assert listener == this : "Use add/removeOnHierarchyChangeListener instead."; - super.setOnHierarchyChangeListener(listener); - } - - /** - * Registers the given listener to receive state changes for the content view hierarchy. - * @param listener Listener to receive view hierarchy state changes. - */ - public void addOnHierarchyChangeListener(OnHierarchyChangeListener listener) { - mHierarchyChangeListeners.addObserver(listener); - } - - /** - * Unregisters the given listener from receiving state changes for the content view hierarchy. - * @param listener Listener that doesn't want to receive view hierarchy state changes. - */ - public void removeOnHierarchyChangeListener(OnHierarchyChangeListener listener) { - mHierarchyChangeListeners.removeObserver(listener); - } - - @Override - public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) { - assert listener == this : "Use add/removeOnSystemUiVisibilityChangeListener instead."; - super.setOnSystemUiVisibilityChangeListener(listener); - } - - /** - * Registers the given listener to receive system UI visibility state changes. - * @param listener Listener to receive system UI visibility state changes. - */ - public void addOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) { - mSystemUiChangeListeners.addObserver(listener); - } - - /** - * Unregisters the given listener from receiving system UI visibility state changes. - * @param listener Listener that doesn't want to receive state changes. - */ - public void removeOnSystemUiVisibilityChangeListener( - OnSystemUiVisibilityChangeListener listener) { - mSystemUiChangeListeners.removeObserver(listener); - } - - // View.OnHierarchyChangeListener implementation - - @Override - public void onChildViewRemoved(View parent, View child) { - for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) { - listener.onChildViewRemoved(parent, child); - } - } - - @Override - public void onChildViewAdded(View parent, View child) { - for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) { - listener.onChildViewAdded(parent, child); - } - } - - // View.OnHierarchyChangeListener implementation - - @Override - public void onSystemUiVisibilityChange(int visibility) { - for (OnSystemUiVisibilityChangeListener listener : mSystemUiChangeListeners) { - listener.onSystemUiVisibilityChange(visibility); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mDesiredWidthMeasureSpec != DEFAULT_MEASURE_SPEC) { - widthMeasureSpec = mDesiredWidthMeasureSpec; - } - if (mDesiredHeightMeasureSpec != DEFAULT_MEASURE_SPEC) { - heightMeasureSpec = mDesiredHeightMeasureSpec; - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider() { - WebContentsAccessibility wcax = getWebContentsAccessibility(); - AccessibilityNodeProvider provider = - (wcax != null) ? wcax.getAccessibilityNodeProvider() : null; - return (provider != null) ? provider : super.getAccessibilityNodeProvider(); - } - - // Needed by ViewEventSink.InternalAccessDelegate - @Override - public void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - // Calls may come while/after WebContents is destroyed. See https://crbug.com/821750#c8. - if (mWebContents == null || mWebContents.isDestroyed()) return null; - return ImeAdapter.fromWebContents(mWebContents).onCreateInputConnection(outAttrs); - } - - @Override - public boolean onCheckIsTextEditor() { - if (mWebContents == null || mWebContents.isDestroyed()) return false; - return ImeAdapter.fromWebContents(mWebContents).onCheckIsTextEditor(); - } - - @Override - protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { - try { - TraceEvent.begin("ContentView.onFocusChanged"); - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - if (mWebContents != null) { - getViewEventSink().setHideKeyboardOnBlur(true); - getViewEventSink().onViewFocusChanged(gainFocus); - } - } finally { - TraceEvent.end("ContentView.onFocusChanged"); - } - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if (mWebContents != null) { - getViewEventSink().onWindowFocusChanged(hasWindowFocus); - } - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - EventForwarder forwarder = getEventForwarder(); - return forwarder != null ? forwarder.onKeyUp(keyCode, event) : false; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (!isFocused()) return super.dispatchKeyEvent(event); - EventForwarder forwarder = getEventForwarder(); - return forwarder != null ? forwarder.dispatchKeyEvent(event) : false; - } - - @Override - public boolean onDragEvent(DragEvent event) { - EventForwarder forwarder = getEventForwarder(); - return forwarder != null ? forwarder.onDragEvent(event, this) : false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - boolean ret = super.onInterceptTouchEvent(e); - mEventOffsetHandler.onInterceptTouchEvent(e); - return ret; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - EventForwarder forwarder = getEventForwarder(); - boolean ret = forwarder != null ? forwarder.onTouchEvent(event) : false; - mEventOffsetHandler.onTouchEvent(event); - return ret; - } - - @Override - public boolean onInterceptHoverEvent(MotionEvent e) { - mEventOffsetHandler.onInterceptHoverEvent(e); - return super.onInterceptHoverEvent(e); - } - - @Override - public boolean dispatchDragEvent(DragEvent e) { - mEventOffsetHandler.onPreDispatchDragEvent(e.getAction()); - boolean ret = super.dispatchDragEvent(e); - mEventOffsetHandler.onPostDispatchDragEvent(e.getAction()); - return ret; - } - - /** - * Mouse move events are sent on hover enter, hover move and hover exit. - * They are sent on hover exit because sometimes it acts as both a hover - * move and hover exit. - */ - @Override - public boolean onHoverEvent(MotionEvent event) { - EventForwarder forwarder = getEventForwarder(); - boolean consumed = forwarder != null ? forwarder.onHoverEvent(event) : false; - WebContentsAccessibility wcax = getWebContentsAccessibility(); - if (wcax != null && !wcax.isTouchExplorationEnabled()) super.onHoverEvent(event); - return consumed; - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - EventForwarder forwarder = getEventForwarder(); - return forwarder != null ? forwarder.onGenericMotionEvent(event) : false; - } - - private EventForwarder getEventForwarder() { - return mWebContents != null ? mWebContents.getEventForwarder() : null; - } - - private ViewEventSink getViewEventSink() { - return mWebContents != null ? ViewEventSink.from(mWebContents) : null; - } - - @Override - public boolean performLongClick() { - return false; - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - if (mWebContents != null) { - getViewEventSink().onConfigurationChanged(newConfig); - } - super.onConfigurationChanged(newConfig); - } - - /** - * Currently the ContentView scrolling happens in the native side. In - * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo() - * are overridden, so that View's mScrollX and mScrollY will be unchanged at - * (0, 0). This is critical for drawing ContentView correctly. - */ - @Override - public void scrollBy(int x, int y) { - EventForwarder forwarder = getEventForwarder(); - if (forwarder != null) forwarder.scrollBy(x, y); - } - - @Override - public void scrollTo(int x, int y) { - EventForwarder forwarder = getEventForwarder(); - if (forwarder != null) forwarder.scrollTo(x, y); - } - - @Override - protected int computeHorizontalScrollExtent() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getLastFrameViewportWidthPixInt() : 0; - } - - @Override - protected int computeHorizontalScrollOffset() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getScrollXPixInt() : 0; - } - - @Override - protected int computeHorizontalScrollRange() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getContentWidthPixInt() : 0; - } - - @Override - protected int computeVerticalScrollExtent() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getLastFrameViewportHeightPixInt() : 0; - } - - @Override - protected int computeVerticalScrollOffset() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getScrollYPixInt() : 0; - } - - @Override - protected int computeVerticalScrollRange() { - RenderCoordinates rc = getRenderCoordinates(); - return rc != null ? rc.getContentHeightPixInt() : 0; - } - - private RenderCoordinates getRenderCoordinates() { - return mWebContents != null ? RenderCoordinates.fromWebContents(mWebContents) : null; - } - - // End RelativeLayout overrides. - - @Override - public boolean awakenScrollBars(int startDelay, boolean invalidate) { - // For the default implementation of ContentView which draws the scrollBars on the native - // side, calling this function may get us into a bad state where we keep drawing the - // scrollBars, so disable it by always returning false. - if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) return false; - return super.awakenScrollBars(startDelay, invalidate); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mWebContents != null) { - getViewEventSink().onAttachedToWindow(); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mWebContents != null) { - getViewEventSink().onDetachedFromWindow(); - } - } - - // Implements SmartClipProvider - @Override - public void extractSmartClipData(int x, int y, int width, int height) { - if (mWebContents != null) { - mWebContents.requestSmartClipExtract(x, y, width, height); - } - } - - // Implements SmartClipProvider - @Override - public void setSmartClipResultHandler(final Handler resultHandler) { - if (mWebContents != null) { - mWebContents.setSmartClipResultHandler(resultHandler); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Start Implementation of ViewEventSink.InternalAccessDelegate // - /////////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public boolean super_onKeyUp(int keyCode, KeyEvent event) { - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean super_dispatchKeyEvent(KeyEvent event) { - return super.dispatchKeyEvent(event); - } - - @Override - public boolean super_onGenericMotionEvent(MotionEvent event) { - return super.onGenericMotionEvent(event); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // End Implementation of ViewEventSink.InternalAccessDelegate // - /////////////////////////////////////////////////////////////////////////////////////////////// - - private static class ContentViewApi23 extends ContentView { - public ContentViewApi23(Context context, EventOffsetHandler eventOffsetHandler) { - super(context, eventOffsetHandler); - } - - @Override - public void onProvideVirtualStructure(final ViewStructure structure) { - WebContentsAccessibility wcax = getWebContentsAccessibility(); - if (wcax != null) wcax.onProvideVirtualStructure(structure, false); - } - } -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java index b74f5266590..ac0235eaeb2 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java @@ -86,6 +86,7 @@ public class ContentViewRenderView extends RelativeLayout { int width, int height); // |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap. void surfaceDestroyed(boolean cacheBackBuffer); + void surfaceRedrawNeededAsync(Runnable drawingFinished); } private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>(); @@ -160,6 +161,11 @@ public class ContentViewRenderView extends RelativeLayout { mNativeContentViewRenderView, cacheBackBuffer); mCompositorHasSurface = false; } + + @Override + public void surfaceRedrawNeededAsync(Runnable drawingFinished) { + assert false; // NOTREACHED. + } } // Abstract differences between SurfaceView and TextureView behind this class. @@ -220,6 +226,7 @@ public class ContentViewRenderView extends RelativeLayout { private final TextureViewSurfaceTextureListener mSurfaceTextureListener; private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>(); + private ArrayList<Runnable> mSurfaceRedrawNeededCallbacks; public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener, int backgroundColor, Runnable evict) { @@ -302,6 +309,7 @@ public class ContentViewRenderView extends RelativeLayout { mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction); mNeedsOnSurfaceDestroyed = false; } + runSurfaceRedrawNeededCallbacks(); if (mMode == MODE_SURFACE_VIEW) { mSurfaceView.getHolder().removeCallback(mSurfaceCallback); @@ -403,6 +411,15 @@ public class ContentViewRenderView extends RelativeLayout { return false; } + public void runSurfaceRedrawNeededCallbacks() { + ArrayList<Runnable> callbacks = mSurfaceRedrawNeededCallbacks; + mSurfaceRedrawNeededCallbacks = null; + if (callbacks == null) return; + for (Runnable r : callbacks) { + r.run(); + } + } + private void destroyPreviousData() { if (mPrevSurfaceDataNeedsDestroy != null) { mPrevSurfaceDataNeedsDestroy.destroy(); @@ -445,6 +462,22 @@ public class ContentViewRenderView extends RelativeLayout { assert mNeedsOnSurfaceDestroyed; mListener.surfaceDestroyed(cacheBackBuffer); mNeedsOnSurfaceDestroyed = false; + runSurfaceRedrawNeededCallbacks(); + } + + @Override + public void surfaceRedrawNeededAsync(Runnable drawingFinished) { + if (mMarkedForDestroy) { + drawingFinished.run(); + return; + } + assert mNativeContentViewRenderView != 0; + assert this == ContentViewRenderView.this.mCurrent; + if (mSurfaceRedrawNeededCallbacks == null) { + mSurfaceRedrawNeededCallbacks = new ArrayList<>(); + } + mSurfaceRedrawNeededCallbacks.add(drawingFinished); + ContentViewRenderViewJni.get().setNeedsRedraw(mNativeContentViewRenderView); } private void runCallbacks() { @@ -470,7 +503,7 @@ public class ContentViewRenderView extends RelativeLayout { } // Adapter for SurfaceHoolder.Callback. - private static class SurfaceHolderCallback implements SurfaceHolder.Callback { + private static class SurfaceHolderCallback implements SurfaceHolder.Callback2 { private final SurfaceEventListener mListener; public SurfaceHolderCallback(SurfaceEventListener listener) { @@ -491,6 +524,16 @@ public class ContentViewRenderView extends RelativeLayout { public void surfaceDestroyed(SurfaceHolder holder) { mListener.surfaceDestroyed(false /* cacheBackBuffer */); } + + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + // Intentionally not implemented. + } + + @Override + public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) { + mListener.surfaceRedrawNeededAsync(drawingFinished); + } } // Adapter for TextureView.SurfaceTextureListener. @@ -697,7 +740,9 @@ public class ContentViewRenderView extends RelativeLayout { mWebContents = webContents; if (webContents != null) { - updateWebContentsSize(); + if (getWidth() != 0 && getHeight() != 0) { + updateWebContentsSize(); + } ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( mNativeContentViewRenderView, webContents, mPhysicalWidth, mPhysicalHeight); } @@ -719,6 +764,13 @@ public class ContentViewRenderView extends RelativeLayout { return mCurrent.didSwapFrame(); } + @CalledByNative + private void didSwapBuffers(boolean sizeMatches) { + assert mCurrent != null; + if (!sizeMatches) return; + mCurrent.runSurfaceRedrawNeededCallbacks(); + } + private void evictCachedSurface() { if (mNativeContentViewRenderView == 0) return; ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView); @@ -752,6 +804,7 @@ public class ContentViewRenderView extends RelativeLayout { void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer); void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl, int format, int width, int height, Surface surface); + void setNeedsRedraw(long nativeContentViewRenderView); void evictCachedSurface(long nativeContentViewRenderView); ResourceManager getResourceManager(long nativeContentViewRenderView); } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java index 1f147c0467c..5318bcc20d0 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java @@ -204,6 +204,7 @@ public final class CrashReporterControllerImpl extends ICrashReporterController. private String[] processNewMinidumpsOnBackgroundThread() { Map<String, Map<String, String>> crashesInfoMap = getCrashFileManager().importMinidumpsCrashKeys(); + if (crashesInfoMap == null) return new String[0]; ArrayList<String> localIds = new ArrayList<>(crashesInfoMap.size()); for (Map.Entry<String, Map<String, String>> entry : crashesInfoMap.entrySet()) { JSONObject crashKeysJson = new JSONObject(entry.getValue()); diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java index 694e8839498..b1046065bc3 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java @@ -24,7 +24,6 @@ import org.chromium.components.browser_ui.notifications.NotificationManagerProxy import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl; import org.chromium.components.browser_ui.notifications.NotificationMetadata; import org.chromium.components.browser_ui.notifications.PendingIntentProvider; -import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer; import org.chromium.components.browser_ui.util.DownloadUtils; import org.chromium.weblayer_private.interfaces.APICallException; import org.chromium.weblayer_private.interfaces.DownloadError; @@ -342,18 +341,14 @@ public final class DownloadImpl extends IDownload.Stub { PendingIntentProvider deletePendingIntent = PendingIntentProvider.getBroadcast(context, mNotificationId, deleteIntent, 0); - ChannelsInitializer channelsInitializer = new ChannelsInitializer(notificationManager, - WebLayerNotificationChannels.getInstance(), context.getResources()); - @DownloadState int state = getState(); String channelId = state == DownloadState.COMPLETE ? WebLayerNotificationChannels.ChannelId.COMPLETED_DOWNLOADS : WebLayerNotificationChannels.ChannelId.ACTIVE_DOWNLOADS; - WebLayerNotificationBuilder builder = - new WebLayerNotificationBuilder(context, channelId, channelsInitializer, - new NotificationMetadata(0, NOTIFICATION_TAG, mNotificationId)); + WebLayerNotificationBuilder builder = WebLayerNotificationBuilder.create( + channelId, new NotificationMetadata(0, NOTIFICATION_TAG, mNotificationId)); builder.setOngoing(true) .setDeleteIntent(deletePendingIntent) .setPriorityBeforeO(NotificationCompat.PRIORITY_DEFAULT); diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java index 6cd383bebbf..253c35212d0 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java @@ -8,6 +8,8 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.ResolveInfo; +import androidx.annotation.Nullable; + import org.chromium.base.ContextUtils; import org.chromium.base.PackageManagerUtils; import org.chromium.components.embedder_support.util.UrlUtilities; @@ -16,14 +18,18 @@ import org.chromium.components.external_intents.ExternalNavigationDelegate.Start import org.chromium.components.external_intents.ExternalNavigationHandler; import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; import org.chromium.components.external_intents.ExternalNavigationParams; +import org.chromium.components.webapk.lib.client.ChromeWebApkHostSignature; +import org.chromium.components.webapk.lib.client.WebApkValidator; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.base.WindowAndroid; +import org.chromium.url.Origin; /** * WebLayer's implementation of the {@link ExternalNavigationDelegate}. */ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegate { + private static boolean sWebApkValidatorInitialized; private final TabImpl mTab; private boolean mTabDestroyed; @@ -154,7 +160,8 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat @Override // This is relevant only if the intent ends up being handled by this app, which does not happen // for WebLayer. - public void maybeSetUserGesture(Intent intent) {} + public void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture, + boolean isRendererInitiated, @Nullable Origin initiatorOrigin) {} @Override // This is relevant only if the intent ends up being handled by this app, which does not happen @@ -205,8 +212,12 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat @Override public boolean isValidWebApk(String packageName) { - // TODO(crbug.com/1063874): Determine whether to refine this. - return false; + if (!sWebApkValidatorInitialized) { + WebApkValidator.init(ChromeWebApkHostSignature.EXPECTED_SIGNATURE, + ChromeWebApkHostSignature.PUBLIC_KEY); + sWebApkValidatorInitialized = true; + } + return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName); } @Override diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java new file mode 100644 index 00000000000..69e4754d98b --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java @@ -0,0 +1,331 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.ColorRes; +import androidx.annotation.Nullable; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.chrome.browser.infobar.InfoBarIdentifier; +import org.chromium.components.infobars.InfoBarInteractionHandler; +import org.chromium.components.infobars.InfoBarLayout; +import org.chromium.ui.modelutil.PropertyModel; + +/** + * The base class for all InfoBar classes. + * Note that infobars expire by default when a new navigation occurs. + * Make sure to use setExpireOnNavigation(false) if you want an infobar to be sticky. + */ +@JNINamespace("weblayer") +public abstract class InfoBar implements InfoBarInteractionHandler, InfoBarUiItem { + private static final String TAG = "InfoBar"; + + /** + * Interface for InfoBar to interact with its container. + */ + public interface Container { + /** + * @return True if the infobar is in front. + */ + boolean isFrontInfoBar(InfoBar infoBar); + + /** + * Remove the infobar from its container. + * @param infoBar InfoBar to remove from the View hierarchy. + */ + void removeInfoBar(InfoBar infoBar); + + /** + * Notifies that an infobar's View ({@link InfoBar#getView}) has changed. + */ + void notifyInfoBarViewChanged(); + + /** + * @return True if the container's destroy() method has been called. + */ + boolean isDestroyed(); + } + + private final int mIconDrawableId; + private final Bitmap mIconBitmap; + private final @ColorRes int mIconTintId; + private final CharSequence mMessage; + + private @Nullable Container mContainer; + private @Nullable View mView; + private @Nullable Context mContext; + + private boolean mIsDismissed; + private boolean mControlsEnabled = true; + + private @Nullable PropertyModel mModel; + + // This points to the InfoBarAndroid class not any of its subclasses. + private long mNativeInfoBarPtr; + + /** + * Constructor for regular infobars. + * @param iconDrawableId ID of the resource to use for the Icon. If 0, no icon will be shown. + * @param iconTintId The {@link ColorRes} used as tint for the {@code iconDrawableId}. + * @param message The message to show in the infobar. + * @param iconBitmap Icon to draw, in bitmap form. Used mainly for generated icons. + */ + public InfoBar( + int iconDrawableId, @ColorRes int iconTintId, CharSequence message, Bitmap iconBitmap) { + mIconDrawableId = iconDrawableId; + mIconBitmap = iconBitmap; + mIconTintId = iconTintId; + mMessage = message; + } + + /** + * Stores a pointer to the native-side counterpart of this InfoBar. + * @param nativeInfoBarPtr Pointer to the native InfoBarAndroid, not to its subclass. + */ + @CalledByNative + private final void setNativeInfoBar(long nativeInfoBarPtr) { + mNativeInfoBarPtr = nativeInfoBarPtr; + } + + @CalledByNative + protected void onNativeDestroyed() { + mNativeInfoBarPtr = 0; + } + + /** + * Sets the Context used when creating the InfoBar. + */ + public void setContext(Context context) { + mContext = context; + } + + /** + * @return The {@link Context} used to create the InfoBar. This will be null before the InfoBar + * is added to an {@link InfoBarContainer}, or after the InfoBar is closed. + */ + @Nullable + protected Context getContext() { + return mContext; + } + + /** + * Creates the View that represents the InfoBar. + * @return The View representing the InfoBar. + */ + public final View createView() { + assert mContext != null; + + if (usesCompactLayout()) { + InfoBarCompactLayout layout = new InfoBarCompactLayout( + mContext, this, mIconDrawableId, mIconTintId, mIconBitmap); + createCompactLayoutContent(layout); + mView = layout; + } else { + InfoBarLayout layout = new InfoBarLayout( + mContext, this, mIconDrawableId, mIconTintId, mIconBitmap, mMessage); + createContent(layout); + layout.onContentCreated(); + mView = layout; + } + + return mView; + } + + /** + * @return The model for this infobar if one was created. + */ + @Nullable + PropertyModel getModel() { + return mModel; + } + + /** + * If this returns true, the infobar contents will be replaced with a one-line layout. + * When overriding this, also override {@link #getAccessibilityMessage}. + */ + protected boolean usesCompactLayout() { + return false; + } + + /** + * Prepares the InfoBar for display and adds InfoBar-specific controls to the layout. + * @param layout Layout containing all of the controls. + */ + protected void createContent(InfoBarLayout layout) {} + + /** + * Prepares and inserts views into an {@link InfoBarCompactLayout}. + * {@link #usesCompactLayout} must return 'true' for this function to be called. + * @param layout Layout to plug views into. + */ + protected void createCompactLayoutContent(InfoBarCompactLayout layout) {} + + /** + * Replaces the View currently shown in the infobar with the given View. Triggers the swap + * animation via the InfoBarContainer. + */ + protected void replaceView(View newView) { + mView = newView; + mContainer.notifyInfoBarViewChanged(); + } + + /** + * Returns the View shown in this infobar. Only valid after createView() has been called. + */ + @Override + public View getView() { + return mView; + } + + /** + * Returns the accessibility message to announce when this infobar is first shown. + * Override this if the InfoBar doesn't have {@link R.id.infobar_message}. It is usually the + * case when it is in CompactLayout. + */ + protected CharSequence getAccessibilityMessage(CharSequence defaultTitle) { + return defaultTitle == null ? "" : defaultTitle; + } + + @Override + public CharSequence getAccessibilityText() { + if (mView == null) return ""; + + CharSequence title = null; + TextView messageView = (TextView) mView.findViewById(R.id.infobar_message); + if (messageView != null) { + title = messageView.getText(); + } + title = getAccessibilityMessage(title); + if (title.length() > 0) { + title = title + " "; + } + // TODO(crbug/773717): Avoid string concatenation due to i18n. + return title + mContext.getString(R.string.weblayer_bottom_bar_screen_position); + } + + @Override + public int getPriority() { + return InfoBarPriority.PAGE_TRIGGERED; + } + + @Override + @InfoBarIdentifier + public int getInfoBarIdentifier() { + if (mNativeInfoBarPtr == 0) return InfoBarIdentifier.INVALID; + return InfoBarJni.get().getInfoBarIdentifier(mNativeInfoBarPtr, InfoBar.this); + } + + /** + * @return whether the infobar actually needed closing. + */ + @CalledByNative + private boolean closeInfoBar() { + if (!mIsDismissed) { + mIsDismissed = true; + if (!mContainer.isDestroyed()) { + // If the container was destroyed, it's already been emptied of all its infobars. + onStartedHiding(); + mContainer.removeInfoBar(this); + } + mContainer = null; + mView = null; + mContext = null; + return true; + } + return false; + } + + /** + * @return If the infobar is the front infobar (i.e. visible and not hidden behind other + * infobars). + */ + public boolean isFrontInfoBar() { + return mContainer.isFrontInfoBar(this); + } + + /** + * Called just before the Java infobar has begun hiding. Give the chance to clean up any child + * UI that may remain open. + */ + protected void onStartedHiding() {} + + /** + * Returns pointer to native InfoBarAndroid instance. + * TODO(crbug/1056346): The function is used in subclasses typically to get Tab reference. When + * Tab is modularized, replace this function with the one that returns Tab reference. + */ + protected long getNativeInfoBarPtr() { + return mNativeInfoBarPtr; + } + + /** + * Sets the Container that displays the InfoBar. + */ + public void setContainer(Container container) { + mContainer = container; + } + + /** + * @return Whether or not this InfoBar is already dismissed (i.e. closed). + */ + protected boolean isDismissed() { + return mIsDismissed; + } + + @Override + public boolean areControlsEnabled() { + return mControlsEnabled; + } + + @Override + public void setControlsEnabled(boolean state) { + mControlsEnabled = state; + } + + @Override + public void onClick() { + setControlsEnabled(false); + } + + @Override + public void onButtonClicked(boolean isPrimaryButton) {} + + @Override + public void onLinkClicked() { + if (mNativeInfoBarPtr != 0) InfoBarJni.get().onLinkClicked(mNativeInfoBarPtr, InfoBar.this); + } + + /** + * Performs some action related to the button being clicked. + * @param action The type of action defined in {@link ActionType} in this class. + */ + protected void onButtonClicked(@ActionType int action) { + if (mNativeInfoBarPtr != 0) { + InfoBarJni.get().onButtonClicked(mNativeInfoBarPtr, InfoBar.this, action); + } + } + + @Override + public void onCloseButtonClicked() { + if (mNativeInfoBarPtr != 0 && !mIsDismissed) { + InfoBarJni.get().onCloseButtonClicked(mNativeInfoBarPtr, InfoBar.this); + } + } + + @NativeMethods + interface Natives { + int getInfoBarIdentifier(long nativeInfoBarAndroid, InfoBar caller); + void onLinkClicked(long nativeInfoBarAndroid, InfoBar caller); + void onButtonClicked(long nativeInfoBarAndroid, InfoBar caller, int action); + void onCloseButtonClicked(long nativeInfoBarAndroid, InfoBar caller); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java new file mode 100644 index 00000000000..c2303eaea6b --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java @@ -0,0 +1,238 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.view.Gravity; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.ColorRes; +import androidx.annotation.StringRes; +import androidx.appcompat.content.res.AppCompatResources; + +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.base.Callback; +import org.chromium.components.infobars.InfoBarInteractionHandler; +import org.chromium.components.infobars.InfoBarLayout; +import org.chromium.components.infobars.InfoBarMessageView; +import org.chromium.ui.text.NoUnderlineClickableSpan; +import org.chromium.ui.widget.ChromeImageButton; + +/** + * Lays out controls along a line, sandwiched between an (optional) icon and close button. + * This should only be used by the {@link InfoBar} class, and is created when the InfoBar subclass + * declares itself to be using a compact layout via {@link InfoBar#usesCompactLayout}. + */ +public class InfoBarCompactLayout extends LinearLayout implements View.OnClickListener { + private final InfoBarInteractionHandler mInfoBar; + private final int mCompactInfoBarSize; + private final int mIconWidth; + private final View mCloseButton; + + /** + * Constructs a compat layout for the specified infobar. + * @param context The context used to render. + * @param infoBar {@link InfoBarInteractionHandler} that listens to events. + * @param iconResourceId Resource ID of the icon to use for the infobar. + * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}. + * @param iconBitmap Bitmap for the icon to use, if {@code iconResourceId} is not set. + */ + // TODO(crbug/1056346): ctor is made public to allow access from InfoBar. Once + // InfoBar is modularized, restore access to package private. + public InfoBarCompactLayout(Context context, InfoBarInteractionHandler infoBar, + int iconResourceId, @ColorRes int iconTintId, Bitmap iconBitmap) { + super(context); + mInfoBar = infoBar; + mCompactInfoBarSize = + context.getResources().getDimensionPixelOffset(R.dimen.infobar_compact_size); + mIconWidth = context.getResources().getDimensionPixelOffset(R.dimen.infobar_big_icon_size); + + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + prepareIcon(iconResourceId, iconTintId, iconBitmap); + mCloseButton = prepareCloseButton(); + } + + @Override + public void onClick(View view) { + if (view.getId() == R.id.infobar_close_button) { + mInfoBar.onCloseButtonClicked(); + } else { + assert false; + } + } + + /** + * Inserts a view before the close button. + * @param view View to insert. + * @param weight Weight to assign to it. + */ + // TODO(crbug/1056346): addContent is made public to allow access from InfoBar. Once + // InfoBar is modularized, restore access to protected. + public void addContent(View view, float weight) { + LinearLayout.LayoutParams params; + if (weight <= 0.0f) { + params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, mCompactInfoBarSize); + } else { + params = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, weight); + } + view.setMinimumHeight(mCompactInfoBarSize); + params.gravity = Gravity.BOTTOM; + addView(view, indexOfChild(mCloseButton), params); + } + + /** + * Adds an icon to the start of the infobar, if the infobar requires one. + * @param iconResourceId Resource ID of the icon to use. + * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}. + * @param iconBitmap Raw {@link Bitmap} to use instead of a resource. + */ + private void prepareIcon(int iconResourceId, @ColorRes int iconTintId, Bitmap iconBitmap) { + ImageView iconView = + InfoBarLayout.createIconView(getContext(), iconResourceId, iconTintId, iconBitmap); + if (iconView != null) { + LinearLayout.LayoutParams iconParams = + new LinearLayout.LayoutParams(mIconWidth, mCompactInfoBarSize); + addView(iconView, iconParams); + } + } + + /** + * Creates a close button that can be inserted into an infobar. + * NOTE: This was forked from //chrome's InfoBarLayout.java, as WebLayer supports only compact + * infobars and does not have a corresponding InfoBarLayout.java. + * @param context Context to grab resources from. + * @return {@link ImageButton} that represents a close button. + */ + static ImageButton createCloseButton(Context context) { + final ColorStateList tint = + AppCompatResources.getColorStateList(context, R.color.default_icon_color); + TypedArray a = + context.obtainStyledAttributes(new int[] {android.R.attr.selectableItemBackground}); + Drawable closeButtonBackground = a.getDrawable(0); + a.recycle(); + + ChromeImageButton closeButton = new ChromeImageButton(context); + closeButton.setId(R.id.infobar_close_button); + closeButton.setImageResource(R.drawable.btn_close); + ApiCompatibilityUtils.setImageTintList(closeButton, tint); + closeButton.setBackground(closeButtonBackground); + closeButton.setContentDescription(context.getString(R.string.weblayer_infobar_close)); + closeButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + + return closeButton; + } + + /** Adds a close button to the end of the infobar. */ + private View prepareCloseButton() { + ImageButton closeButton = createCloseButton(getContext()); + closeButton.setOnClickListener(this); + LinearLayout.LayoutParams closeParams = + new LinearLayout.LayoutParams(mCompactInfoBarSize, mCompactInfoBarSize); + addView(closeButton, closeParams); + return closeButton; + } + + /** + * Helps building a standard message to display in a compact InfoBar. The message can feature + * a link to perform and action from this infobar. + */ + public static class MessageBuilder { + private final InfoBarCompactLayout mLayout; + private CharSequence mMessage; + private CharSequence mLink; + + /** @param layout The layout we are building a message view for. */ + public MessageBuilder(InfoBarCompactLayout layout) { + mLayout = layout; + } + + public MessageBuilder withText(CharSequence message) { + assert mMessage == null; + mMessage = message; + + return this; + } + + public MessageBuilder withText(@StringRes int messageResId) { + assert mMessage == null; + mMessage = mLayout.getResources().getString(messageResId); + + return this; + } + + /** Appends a link after the main message, its displayed text being the specified string. */ + public MessageBuilder withLink(CharSequence label, Callback<View> onTapCallback) { + assert mLink == null; + + final Resources resources = mLayout.getResources(); + SpannableString link = new SpannableString(label); + link.setSpan(new NoUnderlineClickableSpan(resources, onTapCallback), 0, label.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + mLink = link; + + return this; + } + + /** + * Appends a link after the main message, its displayed text being constructed from the + * given resource ID. + */ + public MessageBuilder withLink(@StringRes int textResId, Callback<View> onTapCallback) { + final Resources resources = mLayout.getResources(); + String label = resources.getString(textResId); + return withLink(label, onTapCallback); + } + + /** Finalizes the message view as set up in the builder and inserts it into the layout. */ + public void buildAndInsert() { + mLayout.addContent(build(), 1f); + } + + /** + * Finalizes the message view as set up in the builder. The caller is responsible for adding + * it to the parent layout. + */ + public View build() { + // TODO(dgn): Should be able to handle ReaderMode and Survey infobars but they have non + // standard interaction models (no button/link, whole bar is a button) or style (large + // rather than default text). Revisit after snowflake review. + + assert mMessage != null; + + final int messagePadding = mLayout.getResources().getDimensionPixelOffset( + R.dimen.infobar_compact_message_vertical_padding); + + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(mMessage); + if (mLink != null) builder.append(" ").append(mLink); + + TextView prompt = new InfoBarMessageView(mLayout.getContext()); + ApiCompatibilityUtils.setTextAppearance( + prompt, R.style.TextAppearance_TextMedium_Primary); + prompt.setText(builder); + prompt.setGravity(Gravity.CENTER_VERTICAL); + prompt.setPadding(0, messagePadding, 0, messagePadding); + + if (mLink != null) prompt.setMovementMethod(LinkMovementMethod.getInstance()); + + return prompt; + } + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java new file mode 100644 index 00000000000..15037971602 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java @@ -0,0 +1,486 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.ObserverList; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.chrome.browser.infobar.InfoBarIdentifier; +import org.chromium.content_public.browser.NavigationHandle; +import org.chromium.content_public.browser.WebContents; +import org.chromium.content_public.browser.WebContentsObserver; +import org.chromium.ui.KeyboardVisibilityDelegate.KeyboardVisibilityListener; +import org.chromium.ui.util.AccessibilityUtil; + +import java.util.ArrayList; + +/** + * A container for all the infobars of a specific tab. + * Note that infobars creation can be initiated from Java or from native code. + * When initiated from native code, special code is needed to keep the Java and native infobar in + * sync, see NativeInfoBar. + */ +@JNINamespace("weblayer") +public class InfoBarContainer implements KeyboardVisibilityListener, InfoBar.Container { + private static final String TAG = "InfoBarContainer"; + + // Number of instances that have not been destroyed. + private static int sInstanceCount; + + // InfoBarContainer's handling of accessibility is a global toggle, and thus a static observer + // suffices. However, observing accessibility events has the wrinkle that all accessibility + // observers are removed when there are no more Browsers and are not re-added if a new Browser + // is subsequently created. To handle this wrinkle, |sAccessibilityObserver| is added as an + // observer whenever the number of non-destroyed InfoBarContainers becomes non-zero and removed + // whenever that number flips to zero. + private static final AccessibilityUtil.Observer sAccessibilityObserver; + static { + sAccessibilityObserver = (enabled) -> setIsAllowedToAutoHide(!enabled); + } + + /** + * A listener for the InfoBar animations. + */ + public interface InfoBarAnimationListener { + public static final int ANIMATION_TYPE_SHOW = 0; + public static final int ANIMATION_TYPE_SWAP = 1; + public static final int ANIMATION_TYPE_HIDE = 2; + + /** + * Notifies the subscriber when an animation is completed. + */ + void notifyAnimationFinished(int animationType); + + /** + * Notifies the subscriber when all animations are finished. + * @param frontInfoBar The frontmost infobar or {@code null} if none are showing. + */ + void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar); + } + + /** + * An observer that is notified of changes to a {@link InfoBarContainer} object. + */ + public interface InfoBarContainerObserver { + /** + * Called when an {@link InfoBar} is about to be added (before the animation). + * @param container The notifying {@link InfoBarContainer} + * @param infoBar An {@link InfoBar} being added + * @param isFirst Whether the infobar container was empty + */ + void onAddInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isFirst); + + /** + * Called when an {@link InfoBar} is about to be removed (before the animation). + * @param container The notifying {@link InfoBarContainer} + * @param infoBar An {@link InfoBar} being removed + * @param isLast Whether the infobar container is going to be empty + */ + void onRemoveInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isLast); + + /** + * Called when the InfobarContainer is attached to the window. + * @param hasInfobars True if infobar container has infobars to show. + */ + void onInfoBarContainerAttachedToWindow(boolean hasInfobars); + + /** + * A notification that the shown ratio of the infobar container has changed. + * @param container The notifying {@link InfoBarContainer} + * @param shownRatio The shown ratio of the infobar container. + */ + void onInfoBarContainerShownRatioChanged(InfoBarContainer container, float shownRatio); + } + + /** + * Resets the visibility of the InfoBarContainer when the user navigates, following Chrome's + * behavior. In particular in Chrome some features hide the infobar container. This hiding is + * always on a per-URL basis that should be undone on navigation. While no feature in WebLayer + * yet does this, we put this * defensive behavior in place so that any such added features + * don't end up inadvertently hiding the infobar container "forever" in a given tab. + */ + private final WebContentsObserver mWebContentsObserver = new WebContentsObserver() { + @Override + public void didFinishNavigation(NavigationHandle navigation) { + if (navigation.hasCommitted() && navigation.isInMainFrame()) { + setHidden(false); + } + } + }; + + public void onTabDidGainActive() { + initializeContainerView(mTab.getBrowser().getContext()); + updateWebContents(); + mInfoBarContainerView.addToParentView(); + } + + public void onTabDidLoseActive() { + mInfoBarContainerView.removeFromParentView(); + destroyContainerView(); + } + + /** The list of all InfoBars in this container, regardless of whether they've been shown yet. */ + private final ArrayList<InfoBar> mInfoBars = new ArrayList<>(); + + private final ObserverList<InfoBarContainerObserver> mObservers = new ObserverList<>(); + private final ObserverList<InfoBarAnimationListener> mAnimationListeners = new ObserverList<>(); + + private final InfoBarContainerView.ContainerViewObserver mContainerViewObserver = + new InfoBarContainerView.ContainerViewObserver() { + @Override + public void notifyAnimationFinished(int animationType) { + for (InfoBarAnimationListener listener : mAnimationListeners) { + listener.notifyAnimationFinished(animationType); + } + } + + @Override + public void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar) { + for (InfoBarAnimationListener listener : mAnimationListeners) { + listener.notifyAllAnimationsFinished(frontInfoBar); + } + } + + @Override + public void onShownRatioChanged(float shownFraction) { + for (InfoBarContainer.InfoBarContainerObserver observer : mObservers) { + observer.onInfoBarContainerShownRatioChanged( + InfoBarContainer.this, shownFraction); + } + } + }; + + /** The tab that hosts this infobar container. */ + private final TabImpl mTab; + + /** Native InfoBarContainer pointer which will be set by InfoBarContainerJni.get().init(). */ + private long mNativeInfoBarContainer; + + /** True when this container has been emptied and its native counterpart has been destroyed. */ + private boolean mDestroyed; + + /** Whether or not this View should be hidden. */ + private boolean mIsHidden; + + /** + * The view for this {@link InfoBarContainer}. It will be null when the {@link Tab} is detached + * from a {@link ChromeActivity}. + */ + private @Nullable InfoBarContainerView mInfoBarContainerView; + + InfoBarContainer(TabImpl tab) { + if (++sInstanceCount == 1) { + WebLayerAccessibilityUtil.get().addObserver(sAccessibilityObserver); + } + + mTab = tab; + mTab.getWebContents().addObserver(mWebContentsObserver); + + // Chromium's InfoBarContainer may add an InfoBar immediately during this initialization + // call, so make sure everything in the InfoBarContainer is completely ready beforehand. + mNativeInfoBarContainer = InfoBarContainerJni.get().init(InfoBarContainer.this); + } + + /** + * Adds an {@link InfoBarContainerObserver}. + * @param observer The {@link InfoBarContainerObserver} to add. + */ + public void addObserver(InfoBarContainerObserver observer) { + mObservers.addObserver(observer); + } + + /** + * Removes a {@link InfoBarContainerObserver}. + * @param observer The {@link InfoBarContainerObserver} to remove. + */ + public void removeObserver(InfoBarContainerObserver observer) { + mObservers.removeObserver(observer); + } + + /** + * Sets the parent {@link ViewGroup} that contains the {@link InfoBarContainer}. + */ + public void setParentView(ViewGroup parent) { + assert mTab.getBrowser().getActiveTab() == mTab; + if (mInfoBarContainerView != null) mInfoBarContainerView.setParentView(parent); + } + + @VisibleForTesting + public void addAnimationListener(InfoBarAnimationListener listener) { + mAnimationListeners.addObserver(listener); + } + + /** + * Removes the passed in {@link InfoBarAnimationListener} from the {@link InfoBarContainer}. + */ + public void removeAnimationListener(InfoBarAnimationListener listener) { + mAnimationListeners.removeObserver(listener); + } + + /** + * Adds an InfoBar to the view hierarchy. + * @param infoBar InfoBar to add to the View hierarchy. + */ + @CalledByNative + private void addInfoBar(InfoBar infoBar) { + assert !mDestroyed; + if (infoBar == null) { + return; + } + if (mInfoBars.contains(infoBar)) { + assert false : "Trying to add an info bar that has already been added."; + return; + } + + infoBar.setContext(mInfoBarContainerView.getContext()); + infoBar.setContainer(this); + + // We notify observers immediately (before the animation starts). + for (InfoBarContainerObserver observer : mObservers) { + observer.onAddInfoBar(this, infoBar, mInfoBars.isEmpty()); + } + + assert mInfoBarContainerView != null : "The container view is null when adding an InfoBar"; + + // We add the infobar immediately to mInfoBars but we wait for the animation to end to + // notify it's been added, as tests rely on this notification but expects the infobar view + // to be available when they get the notification. + mInfoBars.add(infoBar); + + mInfoBarContainerView.addInfoBar(infoBar); + } + + @VisibleForTesting + public View getViewForTesting() { + return mInfoBarContainerView; + } + + /** + * Adds an InfoBar to the view hierarchy. + * @param infoBar InfoBar to add to the View hierarchy. + */ + @VisibleForTesting + public void addInfoBarForTesting(InfoBar infoBar) { + addInfoBar(infoBar); + } + + @Override + public void notifyInfoBarViewChanged() { + assert !mDestroyed; + if (mInfoBarContainerView != null) mInfoBarContainerView.notifyInfoBarViewChanged(); + } + + /** + * Sets the visibility for the {@link InfoBarContainerView}. + * @param visibility One of {@link View#GONE}, {@link View#INVISIBLE}, or {@link View#VISIBLE}. + */ + public void setVisibility(int visibility) { + if (mInfoBarContainerView != null) mInfoBarContainerView.setVisibility(visibility); + } + + /** + * @return The visibility of the {@link InfoBarContainerView}. + */ + public int getVisibility() { + return mInfoBarContainerView != null ? mInfoBarContainerView.getVisibility() : View.GONE; + } + + @Override + public void removeInfoBar(InfoBar infoBar) { + assert !mDestroyed; + + if (!mInfoBars.remove(infoBar)) { + assert false : "Trying to remove an InfoBar that is not in this container."; + return; + } + + // Notify observers immediately, before any animations begin. + for (InfoBarContainerObserver observer : mObservers) { + observer.onRemoveInfoBar(this, infoBar, mInfoBars.isEmpty()); + } + + assert mInfoBarContainerView + != null : "The container view is null when removing an InfoBar."; + mInfoBarContainerView.removeInfoBar(infoBar); + } + + @Override + public boolean isDestroyed() { + return mDestroyed; + } + + public void destroy() { + mTab.getWebContents().removeObserver(mWebContentsObserver); + + if (--sInstanceCount == 0) { + WebLayerAccessibilityUtil.get().removeObserver(sAccessibilityObserver); + } + + if (mInfoBarContainerView != null) destroyContainerView(); + if (mNativeInfoBarContainer != 0) { + InfoBarContainerJni.get().destroy(mNativeInfoBarContainer, InfoBarContainer.this); + mNativeInfoBarContainer = 0; + } + mDestroyed = true; + } + + /** + * @return all of the InfoBars held in this container. + */ + @VisibleForTesting + public ArrayList<InfoBar> getInfoBarsForTesting() { + return mInfoBars; + } + + /** + * @return True if the container has any InfoBars. + */ + @CalledByNative + public boolean hasInfoBars() { + return !mInfoBars.isEmpty(); + } + + /** + * @return InfoBarIdentifier of the InfoBar which is currently at the top of the infobar stack, + * or InfoBarIdentifier.INVALID if there are no infobars. + */ + @CalledByNative + private @InfoBarIdentifier int getTopInfoBarIdentifier() { + if (!hasInfoBars()) return InfoBarIdentifier.INVALID; + return mInfoBars.get(0).getInfoBarIdentifier(); + } + + /** + * Hides or stops hiding this View. + * + * @param isHidden Whether this View is should be hidden. + */ + public void setHidden(boolean isHidden) { + mIsHidden = isHidden; + if (mInfoBarContainerView == null) return; + mInfoBarContainerView.setHidden(isHidden); + } + + /** + * Sets whether the InfoBarContainer is allowed to auto-hide when the user scrolls the page. + * Expected to be called when Touch Exploration is enabled. + * @param isAllowed Whether auto-hiding is allowed. + */ + private static void setIsAllowedToAutoHide(boolean isAllowed) { + InfoBarContainerView.setIsAllowedToAutoHide(isAllowed); + } + + // KeyboardVisibilityListener implementation. + @Override + public void keyboardVisibilityChanged(boolean isKeyboardShowing) { + assert mInfoBarContainerView != null; + boolean isShowing = (mInfoBarContainerView.getVisibility() == View.VISIBLE); + if (isKeyboardShowing) { + if (isShowing) { + mInfoBarContainerView.setVisibility(View.INVISIBLE); + } + } else { + if (!isShowing && !mIsHidden) { + mInfoBarContainerView.setVisibility(View.VISIBLE); + } + } + } + + private void updateWebContents() { + // When the tab is detached, we don't update the InfoBarContainer web content so that it + // stays null until the tab is attached to some ChromeActivity. + if (mInfoBarContainerView == null) return; + WebContents webContents = mTab.getWebContents(); + + if (webContents != null && webContents != mInfoBarContainerView.getWebContents()) { + mInfoBarContainerView.setWebContents(webContents); + if (mNativeInfoBarContainer != 0) { + InfoBarContainerJni.get().setWebContents( + mNativeInfoBarContainer, InfoBarContainer.this, webContents); + } + } + } + + private void initializeContainerView(Context chromeActivity) { + assert chromeActivity + != null + : "ChromeActivity should not be null when initializing InfoBarContainerView"; + mInfoBarContainerView = new InfoBarContainerView(chromeActivity, mContainerViewObserver, + mTab, /*isTablet=*/!mTab.getBrowser().isWindowOnSmallDevice()); + + mInfoBarContainerView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + for (InfoBarContainer.InfoBarContainerObserver observer : mObservers) { + observer.onInfoBarContainerAttachedToWindow(!mInfoBars.isEmpty()); + } + } + + @Override + public void onViewDetachedFromWindow(View view) {} + }); + + mInfoBarContainerView.setHidden(mIsHidden); + setParentView(mTab.getBrowser().getViewController().getInfoBarContainerParentView()); + + mTab.getBrowser().getWindowAndroid().getKeyboardDelegate().addKeyboardVisibilityListener( + this); + } + + private void destroyContainerView() { + if (mInfoBarContainerView != null) { + mInfoBarContainerView.setWebContents(null); + if (mNativeInfoBarContainer != 0) { + InfoBarContainerJni.get().setWebContents( + mNativeInfoBarContainer, InfoBarContainer.this, null); + } + mInfoBarContainerView.destroy(); + mInfoBarContainerView = null; + } + + mTab.getBrowser().getWindowAndroid().getKeyboardDelegate().removeKeyboardVisibilityListener( + this); + } + + @Override + public boolean isFrontInfoBar(InfoBar infoBar) { + if (mInfoBars.isEmpty()) return false; + return mInfoBars.get(0) == infoBar; + } + + /** + * Returns true if any animations are pending or in progress. + */ + @VisibleForTesting + public boolean isAnimating() { + assert mInfoBarContainerView != null; + return mInfoBarContainerView.isAnimating(); + } + + /** + * @return The {@link InfoBarContainerView} this class holds. + */ + @VisibleForTesting + public InfoBarContainerView getContainerViewForTesting() { + return mInfoBarContainerView; + } + + @NativeMethods + interface Natives { + long init(InfoBarContainer caller); + void setWebContents(long nativeInfoBarContainerAndroid, InfoBarContainer caller, + WebContents webContents); + void destroy(long nativeInfoBarContainerAndroid, InfoBarContainer caller); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java new file mode 100644 index 00000000000..4f91d6f437d --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java @@ -0,0 +1,852 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +import org.chromium.ui.widget.OptimizedFrameLayout; +import org.chromium.weblayer_private.InfoBarContainer.InfoBarAnimationListener; + +import java.util.ArrayList; + +/** + * Layout that displays infobars in a stack. Handles all the animations when adding or removing + * infobars and when swapping infobar contents. + * + * The first infobar to be added is visible at the front of the stack. Later infobars peek up just + * enough behind the front infobar to signal their existence; their contents aren't visible at all. + * The stack has a max depth of three infobars. If additional infobars are added beyond this, they + * won't be visible at all until infobars in front of them are dismissed. + * + * Animation details: + * - Newly added infobars slide up from the bottom and then their contents fade in. + * - Disappearing infobars slide down and away. The remaining infobars, if any, resize to the + * new front infobar's size, then the content of the new front infobar fades in. + * - When swapping the front infobar's content, the old content fades out, the infobar resizes to + * the new content's size, then the new content fades in. + * - Only a single animation happens at a time. If several infobars are added and/or removed in + * quick succession, the animations will be queued and run sequentially. + * + * Note: this class depends only on Android view code; it intentionally does not depend on any other + * infobar code. This is an explicit design decision and should remain this way. + * + * TODO(newt): what happens when detached from window? Do animations run? Do animations jump to end + * values? Should they jump to end values? Does requestLayout() get called when detached + * from window? Probably not; it probably just gets called later when reattached. + * + * TODO(newt): use hardware acceleration? See + * http://blog.danlew.net/2015/10/20/using-hardware-layers-to-improve-animation-performance/ + * and http://developer.android.com/guide/topics/graphics/hardware-accel.html#layers + * + * TODO(newt): handle tall infobars on small devices. Use a ScrollView inside the InfoBarWrapper? + * Make sure InfoBarContainerLayout doesn't extend into tabstrip on tablet. + * + * TODO(newt): Disable key events during animations, perhaps by overriding dispatchKeyEvent(). + * Or can we just call setEnabled() false on the infobar wrapper? Will this cause the buttons + * visual state to change (i.e. to turn gray)? + * + * TODO(newt): finalize animation timings and interpolators. + */ +public class InfoBarContainerLayout extends OptimizedFrameLayout { + /** + * Creates an empty InfoBarContainerLayout. + */ + InfoBarContainerLayout(Context context, Runnable makeContainerVisibleRunnable, + InfoBarAnimationListener animationListener) { + super(context, null); + Resources res = context.getResources(); + mBackInfobarHeight = res.getDimensionPixelSize(R.dimen.infobar_peeking_height); + mFloatingBehavior = new FloatingBehavior(this); + mAnimationListener = animationListener; + mMakeContainerVisibleRunnable = makeContainerVisibleRunnable; + } + + /** + * Adds an infobar to the container. The infobar appearing animation will happen after the + * current animation, if any, finishes. + */ + void addInfoBar(InfoBarUiItem item) { + mItems.add(findInsertIndex(item), item); + processPendingAnimations(); + } + + /** + * Finds the appropriate index in the infobar stack for inserting this item. + * @param item The infobar to be inserted. + */ + private int findInsertIndex(InfoBarUiItem item) { + for (int i = 0; i < mItems.size(); ++i) { + if (item.getPriority() < mItems.get(i).getPriority()) { + return i; + } + } + + return mItems.size(); + } + + /** + * Removes an infobar from the container. The infobar will be animated off the screen if it's + * currently visible. + */ + void removeInfoBar(InfoBarUiItem item) { + mItems.remove(item); + processPendingAnimations(); + } + + /** + * Notifies that an infobar's View ({@link InfoBarUiItem#getView}) has changed. If the infobar + * is visible in the front of the stack, the infobar will fade out the old contents, resize, + * then fade in the new contents. + */ + void notifyInfoBarViewChanged() { + processPendingAnimations(); + } + + /** + * Returns true if any animations are pending or in progress. + */ + boolean isAnimating() { + return mAnimation != null; + } + + ///////////////////////////////////////// + // Implementation details + ///////////////////////////////////////// + + /** The maximum number of infobars visible at any time. */ + private static final int MAX_STACK_DEPTH = 3; + + // Animation durations. + private static final int DURATION_SLIDE_UP_MS = 250; + private static final int DURATION_SLIDE_DOWN_MS = 250; + private static final int DURATION_FADE_MS = 100; + private static final int DURATION_FADE_OUT_MS = 200; + + /** + * Base class for animations inside the InfoBarContainerLayout. + * + * Provides a standardized way to prepare for, run, and clean up after animations. Each subclass + * should implement prepareAnimation(), createAnimator(), and onAnimationEnd() as needed. + */ + private abstract class InfoBarAnimation { + private Animator mAnimator; + + final boolean isStarted() { + return mAnimator != null; + } + + final void start() { + Animator.AnimatorListener listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + InfoBarAnimation.this.onAnimationEnd(); + mAnimation = null; + mAnimationListener.notifyAnimationFinished(getAnimationType()); + processPendingAnimations(); + } + }; + + mAnimator = createAnimator(); + mAnimator.addListener(listener); + mAnimator.start(); + } + + /** + * Returns an animator that animates an InfoBarWrapper's y-translation from its current + * value to endValue and updates the side shadow positions on each frame. + */ + ValueAnimator createTranslationYAnimator(final InfoBarWrapper wrapper, float endValue) { + ValueAnimator animator = ValueAnimator.ofFloat(wrapper.getTranslationY(), endValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + wrapper.setTranslationY((float) animation.getAnimatedValue()); + mFloatingBehavior.updateShadowPosition(); + } + }); + return animator; + } + + /** + * Called before the animation begins. This is the time to add views to the hierarchy and + * adjust layout parameters. + */ + void prepareAnimation() {} + + /** + * Called to create an Animator which will control the animation. Called after + * prepareAnimation() and after a subsequent layout has happened. + */ + abstract Animator createAnimator(); + + /** + * Called after the animation completes. This is the time to do post-animation cleanup, such + * as removing views from the hierarchy. + */ + void onAnimationEnd() {} + + /** + * Returns the InfoBarAnimationListener.ANIMATION_TYPE_* constant that corresponds to this + * type of animation (showing, swapping, etc). + */ + abstract int getAnimationType(); + } + + /** + * The animation to show the first infobar. The infobar slides up from the bottom; then its + * content fades in. + */ + private class FirstInfoBarAppearingAnimation extends InfoBarAnimation { + private InfoBarUiItem mFrontItem; + private InfoBarWrapper mFrontWrapper; + private View mFrontContents; + + FirstInfoBarAppearingAnimation(InfoBarUiItem frontItem) { + mFrontItem = frontItem; + } + + @Override + void prepareAnimation() { + mFrontContents = mFrontItem.getView(); + mFrontWrapper = new InfoBarWrapper(getContext(), mFrontItem); + mFrontWrapper.addView(mFrontContents); + addWrapper(mFrontWrapper); + } + + @Override + Animator createAnimator() { + mFrontWrapper.setTranslationY(mFrontWrapper.getHeight()); + mFrontContents.setAlpha(0f); + + AnimatorSet animator = new AnimatorSet(); + animator.playSequentially( + createTranslationYAnimator(mFrontWrapper, 0f).setDuration(DURATION_SLIDE_UP_MS), + ObjectAnimator.ofFloat(mFrontContents, View.ALPHA, 1f) + .setDuration(DURATION_FADE_MS)); + return animator; + } + + @Override + void onAnimationEnd() { + announceForAccessibility(mFrontItem.getAccessibilityText()); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_SHOW; + } + } + + /** + * The animation to show the a new front-most infobar in front of existing visible infobars. The + * infobar slides up from the bottom; then its content fades in. The previously visible infobars + * will be resized simulatenously to the new desired size. + */ + private class FrontInfoBarAppearingAnimation extends InfoBarAnimation { + private InfoBarUiItem mFrontItem; + private InfoBarWrapper mFrontWrapper; + private InfoBarWrapper mOldFrontWrapper; + private View mFrontContents; + + FrontInfoBarAppearingAnimation(InfoBarUiItem frontItem) { + mFrontItem = frontItem; + } + + @Override + void prepareAnimation() { + mOldFrontWrapper = mInfoBarWrappers.get(0); + + mFrontContents = mFrontItem.getView(); + mFrontWrapper = new InfoBarWrapper(getContext(), mFrontItem); + mFrontWrapper.addView(mFrontContents); + addWrapperToFront(mFrontWrapper); + } + + @Override + Animator createAnimator() { + // After adding the new wrapper, the new front item's view, and the old front item's + // view are both in their wrappers, and the height of the stack as determined by + // FrameLayout will take both into account. This means the height of the container will + // be larger than it needs to be, if the previous old front item is larger than the sum + // of the new front item and mBackInfobarHeight. + // + // First work out how much the container will grow or shrink by. + int heightDelta = + mFrontWrapper.getHeight() + mBackInfobarHeight - mOldFrontWrapper.getHeight(); + + // Now work out where to animate the new front item to / from. + int newFrontStart = mFrontWrapper.getHeight(); + int newFrontEnd = 0; + if (heightDelta < 0) { + // If the container is shrinking, this won't be reflected in the layout just yet. + // The layout will have extra space in it for the previous front infobar, which the + // animation of the new front infobar has to take into account. + newFrontStart -= heightDelta; + newFrontEnd -= heightDelta; + } + mFrontWrapper.setTranslationY(newFrontStart); + mFrontContents.setAlpha(0f); + + // Since we are adding the infobar to the top of the stack, make the container fully + // visible since it could be at hidden or partially hidden state. + mMakeContainerVisibleRunnable.run(); + + AnimatorSet animator = new AnimatorSet(); + animator.play(createTranslationYAnimator(mFrontWrapper, newFrontEnd) + .setDuration(DURATION_SLIDE_UP_MS)); + + // If the container is shrinking, the back infobars need to animate down (from 0 to the + // positive delta). Otherwise they have to animate up (from the negative delta to 0). + int backStart = Math.max(0, heightDelta); + int backEnd = Math.max(-heightDelta, 0); + for (int i = 1; i < mInfoBarWrappers.size(); i++) { + mInfoBarWrappers.get(i).setTranslationY(backStart); + animator.play(createTranslationYAnimator(mInfoBarWrappers.get(i), backEnd) + .setDuration(DURATION_SLIDE_UP_MS)); + } + + animator.play(ObjectAnimator.ofFloat(mFrontContents, View.ALPHA, 1f) + .setDuration(DURATION_FADE_MS)) + .after(DURATION_SLIDE_UP_MS); + + return animator; + } + + @Override + void onAnimationEnd() { + // Remove the old front wrappers view so it won't affect the height of the container any + // more. + mOldFrontWrapper.removeAllViews(); + + // Now set any Y offsets to 0 as there is no need to account for the old front wrapper + // making the container higher than it should be. + for (int i = 0; i < mInfoBarWrappers.size(); i++) { + mInfoBarWrappers.get(i).setTranslationY(0); + } + updateLayoutParams(); + announceForAccessibility(mFrontItem.getAccessibilityText()); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_SHOW; + } + } + + /** + * The animation to show a back infobar. The infobar slides up behind the existing infobars, so + * its top edge peeks out just a bit. + */ + private class BackInfoBarAppearingAnimation extends InfoBarAnimation { + private InfoBarWrapper mAppearingWrapper; + + BackInfoBarAppearingAnimation(InfoBarUiItem appearingItem) { + mAppearingWrapper = new InfoBarWrapper(getContext(), appearingItem); + } + + @Override + void prepareAnimation() { + addWrapper(mAppearingWrapper); + } + + @Override + Animator createAnimator() { + mAppearingWrapper.setTranslationY(mAppearingWrapper.getHeight()); + return createTranslationYAnimator(mAppearingWrapper, 0f) + .setDuration(DURATION_SLIDE_UP_MS); + } + + @Override + public void onAnimationEnd() { + mAppearingWrapper.removeView(mAppearingWrapper.getItem().getView()); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_SHOW; + } + } + + /** + * The animation to hide the front infobar and reveal the second-to-front infobar. The front + * infobar slides down and off the screen. The back infobar(s) will adjust to the size of the + * new front infobar, and then the new front infobar's contents will fade in. + */ + private class FrontInfoBarDisappearingAndRevealingAnimation extends InfoBarAnimation { + private InfoBarWrapper mOldFrontWrapper; + private InfoBarWrapper mNewFrontWrapper; + private View mNewFrontContents; + + @Override + void prepareAnimation() { + mOldFrontWrapper = mInfoBarWrappers.get(0); + mNewFrontWrapper = mInfoBarWrappers.get(1); + mNewFrontContents = mNewFrontWrapper.getItem().getView(); + mNewFrontWrapper.addView(mNewFrontContents); + } + + @Override + Animator createAnimator() { + // The amount by which mNewFrontWrapper will grow (negative value indicates shrinking). + int deltaHeight = (mNewFrontWrapper.getHeight() - mBackInfobarHeight) + - mOldFrontWrapper.getHeight(); + int startTranslationY = Math.max(deltaHeight, 0); + int endTranslationY = Math.max(-deltaHeight, 0); + + // Slide the front infobar down and away. + AnimatorSet animator = new AnimatorSet(); + mOldFrontWrapper.setTranslationY(startTranslationY); + animator.play(createTranslationYAnimator( + mOldFrontWrapper, startTranslationY + mOldFrontWrapper.getHeight()) + .setDuration(DURATION_SLIDE_UP_MS)); + + // Slide the other infobars to their new positions. + // Note: animator.play() causes these animations to run simultaneously. + for (int i = 1; i < mInfoBarWrappers.size(); i++) { + mInfoBarWrappers.get(i).setTranslationY(startTranslationY); + animator.play(createTranslationYAnimator(mInfoBarWrappers.get(i), endTranslationY) + .setDuration(DURATION_SLIDE_UP_MS)); + } + + mNewFrontContents.setAlpha(0f); + animator.play(ObjectAnimator.ofFloat(mNewFrontContents, View.ALPHA, 1f) + .setDuration(DURATION_FADE_MS)) + .after(DURATION_SLIDE_UP_MS); + + return animator; + } + + @Override + void onAnimationEnd() { + mOldFrontWrapper.removeAllViews(); + removeWrapper(mOldFrontWrapper); + for (int i = 0; i < mInfoBarWrappers.size(); i++) { + mInfoBarWrappers.get(i).setTranslationY(0); + } + announceForAccessibility(mNewFrontWrapper.getItem().getAccessibilityText()); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_HIDE; + } + } + + /** + * The animation to hide the backmost infobar, or the front infobar if there's only one infobar. + * The infobar simply slides down out of the container. + */ + private class InfoBarDisappearingAnimation extends InfoBarAnimation { + private InfoBarWrapper mDisappearingWrapper; + + @Override + void prepareAnimation() { + mDisappearingWrapper = mInfoBarWrappers.get(mInfoBarWrappers.size() - 1); + } + + @Override + Animator createAnimator() { + return createTranslationYAnimator( + mDisappearingWrapper, mDisappearingWrapper.getHeight()) + .setDuration(DURATION_SLIDE_DOWN_MS); + } + + @Override + void onAnimationEnd() { + mDisappearingWrapper.removeAllViews(); + removeWrapper(mDisappearingWrapper); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_HIDE; + } + } + + /** + * The animation to swap the contents of the front infobar. The current contents fade out, + * then the infobar resizes to fit the new contents, then the new contents fade in. + */ + private class FrontInfoBarSwapContentsAnimation extends InfoBarAnimation { + private InfoBarWrapper mFrontWrapper; + private View mOldContents; + private View mNewContents; + + @Override + void prepareAnimation() { + mFrontWrapper = mInfoBarWrappers.get(0); + mOldContents = mFrontWrapper.getChildAt(0); + mNewContents = mFrontWrapper.getItem().getView(); + mFrontWrapper.addView(mNewContents); + } + + @Override + Animator createAnimator() { + int deltaHeight = mNewContents.getHeight() - mOldContents.getHeight(); + InfoBarContainerLayout.this.setTranslationY(Math.max(0, deltaHeight)); + mNewContents.setAlpha(0f); + + AnimatorSet animator = new AnimatorSet(); + animator.playSequentially(ObjectAnimator.ofFloat(mOldContents, View.ALPHA, 0f) + .setDuration(DURATION_FADE_OUT_MS), + ObjectAnimator + .ofFloat(InfoBarContainerLayout.this, View.TRANSLATION_Y, + Math.max(0, -deltaHeight)) + .setDuration(DURATION_SLIDE_UP_MS), + ObjectAnimator.ofFloat(mNewContents, View.ALPHA, 1f) + .setDuration(DURATION_FADE_OUT_MS)); + return animator; + } + + @Override + void onAnimationEnd() { + mFrontWrapper.removeViewAt(0); + InfoBarContainerLayout.this.setTranslationY(0f); + mFrontWrapper.getItem().setControlsEnabled(true); + announceForAccessibility(mFrontWrapper.getItem().getAccessibilityText()); + } + + @Override + int getAnimationType() { + return InfoBarAnimationListener.ANIMATION_TYPE_SWAP; + } + } + + /** + * Controls whether infobars fill the full available width, or whether they "float" in the + * middle of the available space. The latter case happens if the available space is wider than + * the max width allowed for infobars. + * + * Also handles the shadows on the sides of the infobars in floating mode. The side shadows are + * separate views -- rather than being part of each InfoBarWrapper -- to avoid a double-shadow + * effect, which would happen during animations when two InfoBarWrappers overlap each other. + */ + private static class FloatingBehavior { + /** The InfoBarContainerLayout. */ + private FrameLayout mLayout; + + /** + * The max width of the infobars. If the available space is wider than this, the infobars + * will switch to floating mode. + */ + private final int mMaxWidth; + + /** The width of the left and right shadows. */ + private final int mShadowWidth; + + /** Whether the layout is currently floating. */ + private boolean mIsFloating; + + /** The shadows that appear on the sides of the infobars in floating mode. */ + private View mLeftShadowView; + private View mRightShadowView; + + FloatingBehavior(FrameLayout layout) { + mLayout = layout; + Resources res = mLayout.getContext().getResources(); + mMaxWidth = res.getDimensionPixelSize(R.dimen.infobar_max_width); + mShadowWidth = res.getDimensionPixelSize(R.dimen.infobar_shadow_width); + } + + /** + * This should be called in onMeasure() before super.onMeasure(). The return value is a new + * widthMeasureSpec that should be passed to super.onMeasure(). + */ + int beforeOnMeasure(int widthMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + boolean isFloating = width > mMaxWidth; + if (isFloating != mIsFloating) { + mIsFloating = isFloating; + onIsFloatingChanged(); + } + + if (isFloating) { + int mode = MeasureSpec.getMode(widthMeasureSpec); + width = Math.min(width, mMaxWidth + 2 * mShadowWidth); + widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, mode); + } + return widthMeasureSpec; + } + + /** + * This should be called in onMeasure() after super.onMeasure(). + */ + void afterOnMeasure(int measuredHeight) { + if (!mIsFloating) return; + // Measure side shadows to match the parent view's height. + int widthSpec = MeasureSpec.makeMeasureSpec(mShadowWidth, MeasureSpec.EXACTLY); + int heightSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY); + mLeftShadowView.measure(widthSpec, heightSpec); + mRightShadowView.measure(widthSpec, heightSpec); + } + + /** + * This should be called whenever the Y-position of an infobar changes. + */ + void updateShadowPosition() { + if (!mIsFloating) return; + float minY = mLayout.getHeight(); + int childCount = mLayout.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mLayout.getChildAt(i); + if (child != mLeftShadowView && child != mRightShadowView) { + minY = Math.min(minY, child.getY()); + } + } + mLeftShadowView.setY(minY); + mRightShadowView.setY(minY); + } + + private void onIsFloatingChanged() { + if (mIsFloating) { + initShadowViews(); + mLayout.setPadding(mShadowWidth, 0, mShadowWidth, 0); + mLayout.setClipToPadding(false); + mLayout.addView(mLeftShadowView); + mLayout.addView(mRightShadowView); + } else { + mLayout.setPadding(0, 0, 0, 0); + mLayout.removeView(mLeftShadowView); + mLayout.removeView(mRightShadowView); + } + } + + @SuppressLint("RtlHardcoded") + private void initShadowViews() { + if (mLeftShadowView != null) return; + + mLeftShadowView = new View(mLayout.getContext()); + mLeftShadowView.setBackgroundResource(R.drawable.infobar_shadow_left); + LayoutParams leftLp = new FrameLayout.LayoutParams(0, 0, Gravity.LEFT); + leftLp.leftMargin = -mShadowWidth; + mLeftShadowView.setLayoutParams(leftLp); + + mRightShadowView = new View(mLayout.getContext()); + mRightShadowView.setBackgroundResource(R.drawable.infobar_shadow_left); + LayoutParams rightLp = new FrameLayout.LayoutParams(0, 0, Gravity.RIGHT); + rightLp.rightMargin = -mShadowWidth; + mRightShadowView.setScaleX(-1f); + mRightShadowView.setLayoutParams(rightLp); + } + } + + /** + * The height of back infobars, i.e. the distance between the top of the front infobar and the + * top of the next infobar back. + */ + private final int mBackInfobarHeight; + + /** + * All the Items, in front to back order. + * This list is updated immediately when addInfoBar(), removeInfoBar(), and swapInfoBar() are + * called; so during animations, it does *not* match the currently visible views. + */ + private final ArrayList<InfoBarUiItem> mItems = new ArrayList<>(); + + /** + * The currently visible InfoBarWrappers, in front to back order. + */ + private final ArrayList<InfoBarWrapper> mInfoBarWrappers = new ArrayList<>(); + + /** A observer that is notified when animations finish. */ + private final InfoBarAnimationListener mAnimationListener; + + /** The current animation, or null if no animation is happening currently. */ + private InfoBarAnimation mAnimation; + + private FloatingBehavior mFloatingBehavior; + + /** The runnable to make infobar container fully visible. */ + private Runnable mMakeContainerVisibleRunnable; + + /** + * Determines whether any animations need to run in order to make the visible views match the + * current list of Items in mItems. If so, kicks off the next animation that's needed. + */ + private void processPendingAnimations() { + // If an animation is running, wait until it finishes before beginning the next animation. + if (mAnimation != null) return; + + // The steps below are ordered to minimize movement during animations. In particular, + // removals happen before additions or swaps, and changes are made to back infobars before + // front infobars. + + // First, remove any infobars that are no longer in mItems, if any. Check the back infobars + // before the front. + for (int i = mInfoBarWrappers.size() - 1; i >= 0; i--) { + InfoBarUiItem visibleItem = mInfoBarWrappers.get(i).getItem(); + if (!mItems.contains(visibleItem)) { + if (i == 0 && mInfoBarWrappers.size() >= 2) { + // Remove the front infobar and reveal the second-to-front infobar. + runAnimation(new FrontInfoBarDisappearingAndRevealingAnimation()); + return; + + } else { + // Move the infobar to the very back if it's not already there. + InfoBarWrapper wrapper = mInfoBarWrappers.get(i); + if (i != mInfoBarWrappers.size() - 1) { + removeWrapper(wrapper); + addWrapper(wrapper); + } + + // Remove the backmost infobar (which may be the front infobar). + runAnimation(new InfoBarDisappearingAnimation()); + return; + } + } + } + + // Second, run swap animation on front infobar if needed. + if (!mInfoBarWrappers.isEmpty()) { + InfoBarUiItem frontItem = mInfoBarWrappers.get(0).getItem(); + View frontContents = mInfoBarWrappers.get(0).getChildAt(0); + if (frontContents != frontItem.getView()) { + runAnimation(new FrontInfoBarSwapContentsAnimation()); + return; + } + } + + // Third, check if we should add any infobars in front of visible infobars. This can happen + // if an infobar has been inserted into mItems, in front of the currently visible item. To + // detect this the items at the beginning of mItems are compared against the first item in + // mInfoBarWrappers. + if (!mInfoBarWrappers.isEmpty()) { + // Find the infobar with the highest index that isn't currently being shown. + InfoBarUiItem currentVisibleItem = mInfoBarWrappers.get(0).getItem(); + InfoBarUiItem itemToInsert = null; + for (int checkIndex = 0; checkIndex < mItems.size(); checkIndex++) { + if (mItems.get(checkIndex) == currentVisibleItem) { + // There are no remaining infobars that can possibly override the + // currently displayed one. + break; + } else { + // Found an infobar that isn't being displayed yet. Track it so that + // it can be animated in. + itemToInsert = mItems.get(checkIndex); + } + } + if (itemToInsert != null) { + runAnimation(new FrontInfoBarAppearingAnimation(itemToInsert)); + return; + } + } + + // Fourth, check if we should add any infobars at the back. + int desiredChildCount = Math.min(mItems.size(), MAX_STACK_DEPTH); + if (mInfoBarWrappers.size() < desiredChildCount) { + InfoBarUiItem itemToShow = mItems.get(mInfoBarWrappers.size()); + runAnimation(mInfoBarWrappers.isEmpty() + ? new FirstInfoBarAppearingAnimation(itemToShow) + : new BackInfoBarAppearingAnimation(itemToShow)); + return; + } + + // Fifth, now that we've stabilized, let listeners know that we have no more animations. + InfoBarUiItem frontItem = + mInfoBarWrappers.size() > 0 ? mInfoBarWrappers.get(0).getItem() : null; + mAnimationListener.notifyAllAnimationsFinished(frontItem); + } + + private void runAnimation(InfoBarAnimation animation) { + mAnimation = animation; + mAnimation.prepareAnimation(); + if (isLayoutRequested()) { + // onLayout() will call mAnimation.start(). + } else { + mAnimation.start(); + } + } + + private void addWrapper(InfoBarWrapper wrapper) { + addView(wrapper, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + mInfoBarWrappers.add(wrapper); + updateLayoutParams(); + } + + private void addWrapperToFront(InfoBarWrapper wrapper) { + addView(wrapper, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + mInfoBarWrappers.add(0, wrapper); + updateLayoutParams(); + } + + private void removeWrapper(InfoBarWrapper wrapper) { + removeView(wrapper); + mInfoBarWrappers.remove(wrapper); + updateLayoutParams(); + } + + private void updateLayoutParams() { + // Stagger the top margins so the back infobars peek out a bit. + int childCount = mInfoBarWrappers.size(); + for (int i = 0; i < childCount; i++) { + View child = mInfoBarWrappers.get(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.topMargin = (childCount - 1 - i) * mBackInfobarHeight; + child.setLayoutParams(lp); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + widthMeasureSpec = mFloatingBehavior.beforeOnMeasure(widthMeasureSpec); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mFloatingBehavior.afterOnMeasure(getMeasuredHeight()); + } + + @Override + public void announceForAccessibility(CharSequence text) { + if (TextUtils.isEmpty(text)) return; + super.announceForAccessibility(text); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mFloatingBehavior.updateShadowPosition(); + + // Animations start after a layout has completed, at which point all views are guaranteed + // to have valid sizes and positions. + if (mAnimation != null && !mAnimation.isStarted()) { + mAnimation.start(); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // Trap any attempts to fiddle with the infobars while we're animating. + return super.onInterceptTouchEvent(ev) || mAnimation != null + || (!mInfoBarWrappers.isEmpty() + && !mInfoBarWrappers.get(0).getItem().areControlsEnabled()); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + // Consume all touch events so they do not reach the ContentView. + return true; + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + super.onHoverEvent(event); + // Consume all hover events so they do not reach the ContentView. In touch exploration mode, + // this prevents the user from interacting with the part of the ContentView behind the + // infobars. http://crbug.com/430701 + return true; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java new file mode 100644 index 00000000000..553608310f2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java @@ -0,0 +1,257 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.MathUtils; +import org.chromium.ui.display.DisplayAndroid; +import org.chromium.ui.display.DisplayUtil; + +/** + * The {@link View} for the {@link InfoBarContainer}. + */ +public class InfoBarContainerView extends SwipableOverlayView { + /** + * Observes container view changes. + */ + public interface ContainerViewObserver extends InfoBarContainer.InfoBarAnimationListener { + /** + * Called when the height of shown content changed. + * @param shownFraction The ratio of height of shown content to the height of the container + * view. + */ + void onShownRatioChanged(float shownFraction); + } + + /** Top margin, including the toolbar and tabstrip height and 48dp of web contents. */ + private static final int TOP_MARGIN_PHONE_DP = 104; + private static final int TOP_MARGIN_TABLET_DP = 144; + + /** Length of the animation to fade the InfoBarContainer back into View. */ + private static final long REATTACH_FADE_IN_MS = 250; + + /** Whether or not the InfoBarContainer is allowed to hide when the user scrolls. */ + private static boolean sIsAllowedToAutoHide = true; + + private final ContainerViewObserver mContainerViewObserver; + private final InfoBarContainerLayout mLayout; + + /** Parent view that contains the InfoBarContainerLayout. */ + private ViewGroup mParentView; + + private TabImpl mTab; + + /** Animation used to snap the container to the nearest state if scroll direction changes. */ + private Animator mScrollDirectionChangeAnimation; + + /** Whether or not the current scroll is downward. */ + private boolean mIsScrollingDownward; + + /** Tracks the previous event's scroll offset to determine if a scroll is up or down. */ + private int mLastScrollOffsetY; + + /** + * @param context The {@link Context} that this view is attached to. + * @param containerViewObserver The {@link ContainerViewObserver} that gets notified on + * container view changes. + * @param isTablet Whether this view is displayed on tablet or not. + */ + InfoBarContainerView(@NonNull Context context, + @NonNull ContainerViewObserver containerViewObserver, TabImpl tab, boolean isTablet) { + super(context, null); + mTab = tab; + mContainerViewObserver = containerViewObserver; + + // TODO(newt): move this workaround into the infobar views if/when they're scrollable. + // Workaround for http://crbug.com/407149. See explanation in onMeasure() below. + setVerticalScrollBarEnabled(false); + + updateLayoutParams(context, isTablet); + + Runnable makeContainerVisibleRunnable = () -> runUpEventAnimation(true); + mLayout = new InfoBarContainerLayout(context, makeContainerVisibleRunnable, + new InfoBarContainer.InfoBarAnimationListener() { + @Override + public void notifyAnimationFinished(int animationType) { + mContainerViewObserver.notifyAnimationFinished(animationType); + } + + @Override + public void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar) { + mContainerViewObserver.notifyAllAnimationsFinished(frontInfoBar); + } + }); + + addView(mLayout, + new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL)); + } + + void destroy() { + removeFromParentView(); + mTab = null; + } + + // SwipableOverlayView implementation. + @Override + @VisibleForTesting + public boolean isAllowedToAutoHide() { + return sIsAllowedToAutoHide; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (getVisibility() != View.GONE) { + setVisibility(VISIBLE); + setAlpha(0f); + animate().alpha(1f).setDuration(REATTACH_FADE_IN_MS); + } + } + + @Override + protected void runUpEventAnimation(boolean visible) { + if (mScrollDirectionChangeAnimation != null) mScrollDirectionChangeAnimation.cancel(); + super.runUpEventAnimation(visible); + } + + @Override + protected boolean isIndependentlyAnimating() { + return mScrollDirectionChangeAnimation != null; + } + + // View implementation. + @Override + public void setTranslationY(float translationY) { + int contentHeightDelta = mTab != null + ? mTab.getBrowser().getViewController().getBottomContentHeightDelta() + : 0; + + // Push the infobar container up by any delta caused by the bottom toolbar while ensuring + // that it does not ascend beyond the top of the bottom toolbar nor descend beyond its own + // height. + float newTranslationY = MathUtils.clamp( + translationY - contentHeightDelta, -contentHeightDelta, getHeight()); + + super.setTranslationY(newTranslationY); + + float shownFraction = 0; + if (getHeight() > 0) { + shownFraction = contentHeightDelta > 0 ? 1f : 1f - (translationY / getHeight()); + } + mContainerViewObserver.onShownRatioChanged(shownFraction); + } + + /** + * Sets whether the InfoBarContainer is allowed to auto-hide when the user scrolls the page. + * Expected to be called when Touch Exploration is enabled. + * @param isAllowed Whether auto-hiding is allowed. + */ + public static void setIsAllowedToAutoHide(boolean isAllowed) { + sIsAllowedToAutoHide = isAllowed; + } + + /** + * Notifies that an infobar's View ({@link InfoBar#getView}) has changed. If the infobar is + * visible, a view swapping animation will be run. + */ + void notifyInfoBarViewChanged() { + mLayout.notifyInfoBarViewChanged(); + } + + /** + * Sets the parent {@link ViewGroup} that contains the {@link InfoBarContainer}. + */ + void setParentView(ViewGroup parent) { + mParentView = parent; + // Don't attach the container to the new parent if it is not previously attached. + if (removeFromParentView()) addToParentView(); + } + + /** + * Adds this class to the parent view {@link #mParentView}. + */ + void addToParentView() { + // If mTab is null, destroy() was called. This should not be added after destroyed. + assert mTab != null; + super.addToParentView(mParentView, + mTab.getBrowser().getViewController().getDesiredInfoBarContainerViewIndex()); + } + + /** + * Adds an {@link InfoBar} to the layout. + * @param infoBar The {@link InfoBar} to be added. + */ + void addInfoBar(InfoBar infoBar) { + infoBar.createView(); + mLayout.addInfoBar(infoBar); + } + + /** + * Removes an {@link InfoBar} from the layout. + * @param infoBar The {@link InfoBar} to be removed. + */ + void removeInfoBar(InfoBar infoBar) { + mLayout.removeInfoBar(infoBar); + } + + /** + * Hides or stops hiding this View. + * @param isHidden Whether this View is should be hidden. + */ + void setHidden(boolean isHidden) { + setVisibility(isHidden ? View.GONE : View.VISIBLE); + } + + /** + * Run an animation when the scrolling direction of a gesture has changed (this does not mean + * the gesture has ended). + * @param visible Whether or not the view should be visible. + */ + private void runDirectionChangeAnimation(boolean visible) { + mScrollDirectionChangeAnimation = createVerticalSnapAnimation(visible); + mScrollDirectionChangeAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mScrollDirectionChangeAnimation = null; + } + }); + mScrollDirectionChangeAnimation.start(); + } + + @Override + // Ensure that this view's custom layout params are passed when adding it to its parent. + public ViewGroup.MarginLayoutParams createLayoutParams() { + return (ViewGroup.MarginLayoutParams) getLayoutParams(); + } + + private void updateLayoutParams(Context context, boolean isTablet) { + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + int topMarginDp = isTablet ? TOP_MARGIN_TABLET_DP : TOP_MARGIN_PHONE_DP; + lp.topMargin = DisplayUtil.dpToPx(DisplayAndroid.getNonMultiDisplay(context), topMarginDp); + setLayoutParams(lp); + } + + /** + * Returns true if any animations are pending or in progress. + */ + @VisibleForTesting + public boolean isAnimating() { + return mLayout.isAnimating(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java new file mode 100644 index 00000000000..5a653d069c3 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java @@ -0,0 +1,69 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.view.View; + +import androidx.annotation.IntDef; + +import org.chromium.chrome.browser.infobar.InfoBarIdentifier; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An interface for items that can be added to an InfoBarContainerLayout. + */ +public interface InfoBarUiItem { + // The infobar priority. + @IntDef({InfoBarPriority.CRITICAL, InfoBarPriority.USER_TRIGGERED, + InfoBarPriority.PAGE_TRIGGERED, InfoBarPriority.BACKGROUND}) + @Retention(RetentionPolicy.SOURCE) + public @interface InfoBarPriority { + int CRITICAL = 0; + int USER_TRIGGERED = 1; + int PAGE_TRIGGERED = 2; + int BACKGROUND = 3; + } + + /** + * Returns the View that represents this infobar. This should have no background or borders; + * a background and shadow will be added by a wrapper view. + */ + View getView(); + + /** + * Returns whether controls for this View should be clickable. If false, all input events on + * this item will be ignored. + */ + boolean areControlsEnabled(); + + /** + * Sets whether or not controls for this View should be clickable. This does not affect the + * visual state of the infobar. + * @param state If false, all input events on this Item will be ignored. + */ + void setControlsEnabled(boolean state); + + /** + * Returns the accessibility text to announce when this infobar is first shown. + */ + CharSequence getAccessibilityText(); + + /** + * Returns the priority of an infobar. High priority infobar is shown in front of low + * priority infobar. If infobars have the same priorities, the most recently added one + * is shown behind previous ones. + * + */ + int getPriority(); + + /** + * Returns the type of infobar, as best as can be determined at this time. See + * components/infobars/core/infobar_delegate.h. + */ + @InfoBarIdentifier + int getInfoBarIdentifier(); +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java new file mode 100644 index 00000000000..8a574254a96 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.content.res.Resources; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +/** + * Layout that holds an infobar's contents and provides a background color and a top shadow. + */ +class InfoBarWrapper extends FrameLayout { + private final InfoBarUiItem mItem; + + /** + * Constructor for inflating from Java. + */ + InfoBarWrapper(Context context, InfoBarUiItem item) { + super(context); + mItem = item; + Resources res = context.getResources(); + int peekingHeight = res.getDimensionPixelSize(R.dimen.infobar_peeking_height); + int shadowHeight = res.getDimensionPixelSize(R.dimen.infobar_shadow_height); + setMinimumHeight(peekingHeight + shadowHeight); + + // setBackgroundResource() changes the padding, so call setPadding() second. + setBackgroundResource(R.drawable.weblayer_infobar_wrapper_bg); + setPadding(0, shadowHeight, 0, 0); + } + + InfoBarUiItem getItem() { + return mItem; + } + + @Override + public void onViewAdded(View child) { + child.setLayoutParams(new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.TOP)); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java new file mode 100644 index 00000000000..5d2dfec5c69 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java @@ -0,0 +1,48 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Intent; +import android.os.RemoteException; +import android.util.AndroidRuntimeException; + +/** A utility class for creating and handling common intents. */ +public class IntentUtils { + private static final String sExtraTabId = "TAB_ID"; + private static final String sActivateTabAction = + "org.chromium.weblayer.intent_utils.ACTIVATE_TAB"; + + /** + * Handles an intent generated by this class. + * @return true if the intent was handled, or false if the intent wasn't generated by this + * class. + */ + public static boolean handleIntent(Intent intent) { + if (!intent.getAction().equals(sActivateTabAction)) return false; + + int tabId = intent.getIntExtra(sExtraTabId, -1); + TabImpl tab = TabImpl.getTabById(tabId); + if (tab == null) return true; + + try { + tab.getClient().bringTabToFront(); + } catch (RemoteException e) { + throw new AndroidRuntimeException(e); + } + return true; + } + + /** + * Creates an intent to bring a tab to the foreground. + * This intent should also bring the app to the foreground. + * @param tabId the identifier for the tab. + */ + public static Intent createBringTabToFrontIntent(int tabId) { + Intent intent = WebLayerImpl.createIntent(); + intent.putExtra(sExtraTabId, tabId); + intent.setAction(sActivateTabAction); + return intent; + } +}; diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java new file mode 100644 index 00000000000..a36125ace70 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java @@ -0,0 +1,140 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; + +import org.chromium.components.browser_ui.media.MediaNotificationController; +import org.chromium.components.browser_ui.media.MediaNotificationInfo; +import org.chromium.components.browser_ui.media.MediaSessionHelper; +import org.chromium.components.browser_ui.notifications.ChromeNotification; +import org.chromium.components.browser_ui.notifications.ChromeNotificationBuilder; +import org.chromium.components.browser_ui.notifications.ForegroundServiceUtils; +import org.chromium.components.browser_ui.notifications.NotificationMetadata; + +/** + * A glue class for MediaSession. + * This class defines delegates that provide WebLayer-specific behavior to shared MediaSession code. + * It also manages the lifetime of {@link MediaNotificationController} and the {@link Service} + * associated with the notification. + */ +class MediaSessionManager { + // This is a singleton because there's only at most one MediaSession active at a time. + @SuppressLint("StaticFieldLeak") + static MediaNotificationController sController; + + private static int sNotificationId = 0; + + static void serviceStarted(Service service, Intent intent) { + if (sController != null && sController.processIntent(service, intent)) return; + + // The service has been started with startForegroundService() but the + // notification hasn't been shown. See similar logic in {@link + // ChromeMediaNotificationControllerDelegate}. + MediaNotificationController.finishStartingForegroundServiceOnO( + service, createChromeNotificationBuilder().buildChromeNotification()); + // Call stopForeground to guarantee Android unset the foreground bit. + ForegroundServiceUtils.getInstance().stopForeground( + service, Service.STOP_FOREGROUND_REMOVE); + service.stopSelf(); + } + + static void serviceDestroyed() { + if (sController != null) sController.onServiceDestroyed(); + sController = null; + } + + static MediaSessionHelper.Delegate createMediaSessionHelperDelegate(int tabId) { + return new MediaSessionHelper.Delegate() { + @Override + public Intent createBringTabToFrontIntent() { + return IntentUtils.createBringTabToFrontIntent(tabId); + } + + @Override + public boolean fetchLargeFaviconImage() { + // TODO(crbug.com/1076463): WebLayer doesn't support favicons. + return false; + } + + @Override + public MediaNotificationInfo.Builder createMediaNotificationInfoBuilder() { + ensureNotificationId(); + return new MediaNotificationInfo.Builder().setInstanceId(tabId).setId( + sNotificationId); + } + + @Override + public void showMediaNotification(MediaNotificationInfo notificationInfo) { + assert notificationInfo.id == sNotificationId; + if (sController == null) { + sController = new MediaNotificationController( + new WebLayerMediaNotificationControllerDelegate()); + } + sController.mThrottler.queueNotification(notificationInfo); + } + + @Override + public void hideMediaNotification() { + if (sController != null) sController.hideNotification(tabId); + } + + @Override + public void activateAndroidMediaSession() { + if (sController != null) sController.activateAndroidMediaSession(tabId); + } + }; + } + + private static class WebLayerMediaNotificationControllerDelegate + implements MediaNotificationController.Delegate { + @Override + public Intent createServiceIntent() { + return WebLayerImpl.createMediaSessionServiceIntent(); + } + + @Override + public String getAppName() { + return WebLayerImpl.getClientApplicationName(); + } + + @Override + public String getNotificationGroupName() { + return "org.chromium.weblayer.MediaSession"; + } + + @Override + public ChromeNotificationBuilder createChromeNotificationBuilder() { + return MediaSessionManager.createChromeNotificationBuilder(); + } + + @Override + public void onMediaSessionUpdated(MediaSessionCompat session) { + // This is only relevant when casting. + } + + @Override + public void logNotificationShown(ChromeNotification notification) {} + } + + private static ChromeNotificationBuilder createChromeNotificationBuilder() { + ensureNotificationId(); + + // Only the null tag will work as expected, because {@link Service#startForeground()} only + // takes an ID and no tag. If we pass a tag here, then the notification that's used to + // display a paused state (no foreground service) will not be identified as the same one + // that's used with the foreground service. + return WebLayerNotificationBuilder.create( + WebLayerNotificationChannels.ChannelId.MEDIA_PLAYBACK, + new NotificationMetadata(0, null /*notificationTag*/, sNotificationId)); + } + + private static void ensureNotificationId() { + if (sNotificationId == 0) sNotificationId = WebLayerImpl.getMediaSessionNotificationId(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java index 446af44ea47..90925f10df5 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java @@ -21,7 +21,6 @@ import org.chromium.components.browser_ui.notifications.NotificationManagerProxy import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl; import org.chromium.components.browser_ui.notifications.NotificationMetadata; import org.chromium.components.browser_ui.notifications.PendingIntentProvider; -import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer; import org.chromium.components.webrtc.MediaCaptureNotificationUtil; import org.chromium.components.webrtc.MediaCaptureNotificationUtil.MediaType; import org.chromium.content_public.browser.WebContents; @@ -65,6 +64,7 @@ public class MediaStreamManager { /** * @return a string that prefixes all intents that can be handled by {@link forwardIntent}. + * @Deprecated in M85+, this class does not handle intents. Remove in M88. */ public static String getIntentPrefix() { return WEBRTC_PREFIX; @@ -73,6 +73,7 @@ public class MediaStreamManager { /** * Handles an intent coming from a media streaming notification. * @param intent the intent which was previously posted via {@link update}. + * @Deprecated in M85+, this class does not handle intents. Remove in M88. */ public static void forwardIntent(Intent intent) { assert intent.getAction().equals(ACTIVATE_TAB_INTENT); @@ -208,28 +209,28 @@ public class MediaStreamManager { } Context appContext = ContextUtils.getApplicationContext(); - Intent intent = WebLayerImpl.createIntent(); - intent.putExtra(EXTRA_TAB_ID, mNotificationId); - intent.setAction(ACTIVATE_TAB_INTENT); + Intent intent = null; + if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) { + intent = IntentUtils.createBringTabToFrontIntent(mNotificationId); + } else { + intent = WebLayerImpl.createIntent(); + intent.putExtra(EXTRA_TAB_ID, mNotificationId); + intent.setAction(ACTIVATE_TAB_INTENT); + } PendingIntentProvider contentIntent = PendingIntentProvider.getBroadcast(appContext, mNotificationId, intent, 0); int mediaType = audio && video ? MediaType.AUDIO_AND_VIDEO : audio ? MediaType.AUDIO_ONLY : MediaType.VIDEO_ONLY; - NotificationManagerProxy notificationManagerProxy = getNotificationManager(); - ChannelsInitializer channelsInitializer = new ChannelsInitializer(notificationManagerProxy, - WebLayerNotificationChannels.getInstance(), appContext.getResources()); - // TODO(crbug/1076098): don't pass a URL in incognito. ChromeNotification notification = MediaCaptureNotificationUtil.createNotification( - new WebLayerNotificationBuilder(appContext, + WebLayerNotificationBuilder.create( WebLayerNotificationChannels.ChannelId.WEBRTC_CAM_AND_MIC, - channelsInitializer, new NotificationMetadata(0, AV_STREAM_TAG, mNotificationId)), mediaType, mTab.getWebContents().getVisibleUrl().getSpec(), WebLayerImpl.getClientApplicationName(), contentIntent, null /*stopIntent*/); - notificationManagerProxy.notify(notification); + getNotificationManager().notify(notification); updateActiveNotifications(true); notifyClient(audio, video); diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java new file mode 100644 index 00000000000..d124792cd0d --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java @@ -0,0 +1,28 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.content_public.browser.InterfaceRegistrar; +import org.chromium.content_public.browser.WebContents; +import org.chromium.services.service_manager.InterfaceRegistry; +import org.chromium.webshare.mojom.ShareService; + +/** + * Registers Java implementations of mojo interfaces. + */ +class MojoInterfaceRegistrar { + @CalledByNative + private static void registerMojoInterfaces() { + InterfaceRegistrar.Registry.addWebContentsRegistrar(new WebContentsInterfaceRegistrar()); + } + + private static class WebContentsInterfaceRegistrar implements InterfaceRegistrar<WebContents> { + @Override + public void registerInterfaces(InterfaceRegistry registry, final WebContents webContents) { + registry.addInterface(ShareService.MANAGER, new WebShareServiceFactory(webContents)); + } + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java index c44b3b66031..3957ed46557 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java @@ -113,6 +113,13 @@ public final class NavigationControllerImpl extends INavigationController.Stub { mNativeNavigationController, index); } + @Override + public boolean isNavigationEntrySkippable(int index) { + StrictModeWorkaround.apply(); + return NavigationControllerImplJni.get().isNavigationEntrySkippable( + mNativeNavigationController, index); + } + @CalledByNative private NavigationImpl createNavigation(long nativeNavigationImpl) { return new NavigationImpl(mNavigationControllerClient, nativeNavigationImpl); @@ -159,6 +166,12 @@ public final class NavigationControllerImpl extends INavigationController.Stub { mNavigationControllerClient.onFirstContentfulPaint(); } + @CalledByNative + private void onOldPageNoLongerRendered(String uri) throws RemoteException { + if (WebLayerFactoryImpl.getClientMajorVersion() < 85) return; + mNavigationControllerClient.onOldPageNoLongerRendered(uri); + } + @NativeMethods interface Natives { void setNavigationControllerImpl( @@ -178,5 +191,6 @@ public final class NavigationControllerImpl extends INavigationController.Stub { int getNavigationListCurrentIndex(long nativeNavigationControllerImpl); String getNavigationEntryDisplayUri(long nativeNavigationControllerImpl, int index); String getNavigationEntryTitle(long nativeNavigationControllerImpl, int index); + boolean isNavigationEntrySkippable(long nativeNavigationControllerImpl, int index); } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java index c5d665b7c08..25645acdc76 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java @@ -48,12 +48,10 @@ public final class NewTabCallbackProxy { } @CalledByNative - public void onNewTab(long nativeTab, @ImplNewTabType int mode) throws RemoteException { + public void onNewTab(TabImpl tab, @ImplNewTabType int mode) throws RemoteException { // This class should only be created while the tab is attached to a fragment. assert mTab.getBrowser() != null; - TabImpl tab = - new TabImpl(mTab.getProfile(), mTab.getBrowser().getWindowAndroid(), nativeTab); - mTab.getBrowser().addTab(tab); + assert mTab.getBrowser().equals(tab.getBrowser()); mTab.getClient().onNewTab(tab.getId(), mode); } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java index 687be42fe1a..314b6b7ff29 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java @@ -7,10 +7,15 @@ package org.chromium.weblayer_private; import android.content.Context; import android.content.Intent; +import androidx.annotation.NonNull; + import org.chromium.base.StrictModeContext; import org.chromium.base.supplier.Supplier; +import org.chromium.components.content_settings.CookieControlsBridge; +import org.chromium.components.content_settings.CookieControlsObserver; import org.chromium.components.embedder_support.util.UrlConstants; import org.chromium.components.page_info.PageInfoControllerDelegate; +import org.chromium.content_public.browser.WebContents; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.url.GURL; import org.chromium.weblayer_private.interfaces.SiteSettingsIntentHelper; @@ -20,18 +25,27 @@ import org.chromium.weblayer_private.interfaces.SiteSettingsIntentHelper; */ public class PageInfoControllerDelegateImpl extends PageInfoControllerDelegate { private final Context mContext; + private final WebContents mWebContents; private final String mProfileName; - public PageInfoControllerDelegateImpl(Context context, String profileName, GURL url, - Supplier<ModalDialogManager> modalDialogManager) { + static PageInfoControllerDelegateImpl create(WebContents webContents) { + TabImpl tab = TabImpl.fromWebContents(webContents); + assert tab != null; + return new PageInfoControllerDelegateImpl(tab.getBrowser().getContext(), webContents, + tab.getProfile(), tab.getBrowser().getWindowAndroid()::getModalDialogManager); + } + + private PageInfoControllerDelegateImpl(Context context, WebContents webContents, + ProfileImpl profile, Supplier<ModalDialogManager> modalDialogManager) { super(modalDialogManager, new AutocompleteSchemeClassifierImpl(), /** vrHandler= */ null, /** isSiteSettingsAvailable= */ - UrlConstants.HTTP_SCHEME.equals(url.getScheme()) - || UrlConstants.HTTPS_SCHEME.equals(url.getScheme()), - /** cookieControlsShown= */ false); + isHttpOrHttps(webContents.getVisibleUrl()), + /** cookieControlsShown= */ + CookieControlsBridge.isCookieControlsEnabled(profile)); mContext = context; - mProfileName = profileName; + mWebContents = webContents; + mProfileName = profile.getName(); } /** @@ -47,4 +61,18 @@ public class PageInfoControllerDelegateImpl extends PageInfoControllerDelegate { mContext.startActivity(intent); } } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public CookieControlsBridge createCookieControlsBridge(CookieControlsObserver observer) { + return new CookieControlsBridge(observer, mWebContents, null); + } + + private static boolean isHttpOrHttps(GURL url) { + String scheme = url.getScheme(); + return UrlConstants.HTTP_SCHEME.equals(scheme) || UrlConstants.HTTPS_SCHEME.equals(scheme); + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java index ba5cc8c56dd..c359dc3513d 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java @@ -5,6 +5,7 @@ package org.chromium.weblayer_private; import android.content.Intent; +import android.text.TextUtils; import android.webkit.ValueCallback; import androidx.annotation.NonNull; @@ -25,7 +26,10 @@ import org.chromium.weblayer_private.interfaces.SettingType; import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Implementation of IProfile. @@ -168,6 +172,40 @@ public final class ProfileImpl extends IProfile.Stub implements BrowserContextHa return mCookieManager; } + @Override + public void getBrowserPersistenceIds(@NonNull IObjectWrapper callback) { + StrictModeWorkaround.apply(); + checkNotDestroyed(); + ValueCallback<Set<String>> valueCallback = + (ValueCallback<Set<String>>) ObjectWrapper.unwrap(callback, ValueCallback.class); + Callback<String[]> baseCallback = (String[] result) -> { + valueCallback.onReceiveValue(new HashSet<String>(Arrays.asList(result))); + }; + ProfileImplJni.get().getBrowserPersistenceIds(mNativeProfile, baseCallback); + } + + @Override + public void removeBrowserPersistenceStorage(String[] ids, @NonNull IObjectWrapper callback) { + StrictModeWorkaround.apply(); + checkNotDestroyed(); + ValueCallback<Boolean> valueCallback = + (ValueCallback<Boolean>) ObjectWrapper.unwrap(callback, ValueCallback.class); + Callback<Boolean> baseCallback = valueCallback::onReceiveValue; + for (String id : ids) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must be non-null and non-empty"); + } + } + ProfileImplJni.get().removeBrowserPersistenceStorage(mNativeProfile, ids, baseCallback); + } + + @Override + public void prepareForPossibleCrossOriginNavigation() { + StrictModeWorkaround.apply(); + checkNotDestroyed(); + ProfileImplJni.get().prepareForPossibleCrossOriginNavigation(mNativeProfile); + } + void checkNotDestroyed() { if (!mBeingDeleted) return; throw new IllegalArgumentException("Profile being destroyed: " + mName); @@ -232,5 +270,9 @@ public final class ProfileImpl extends IProfile.Stub implements BrowserContextHa void ensureBrowserContextInitialized(long nativeProfileImpl); void setBooleanSetting(long nativeProfileImpl, int type, boolean value); boolean getBooleanSetting(long nativeProfileImpl, int type); + void getBrowserPersistenceIds(long nativeProfileImpl, Callback<String[]> callback); + void removeBrowserPersistenceStorage( + long nativeProfileImpl, String[] ids, Callback<Boolean> callback); + void prepareForPossibleCrossOriginNavigation(long nativeProfileImpl); } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md b/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md new file mode 100644 index 00000000000..35e0aedea89 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md @@ -0,0 +1,36 @@ +# Which Context should I use? + +The code in this directory references different types of contexts. Please read about what each +represents before deciding which one you should use. + +## Embedder's Activity Context + +The fragment that WebLayer is loaded in holds a reference to the activity that it is currently +attached to. This is what's referred to by [`mEmbedderActivityContext`][link1] in BrowserImpl and +BrowserFragmentImpl. It should be used to reference anything associated with the activity. For +instance, embedder-specific resources, like Color resources which are resolved according to the +theme of the embedding activity. + +[link1]: https://source.chromium.org/chromium/chromium/src/+/6c336f4d55231595c038756f58a9e61d416a9c8f:weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java;bpv=1;bpt=1 + +## WebLayer's Activity Context + +WebLayer has a lot of resources of its own which need to be accessed by the implementation code. We +thus wrap the embedder's activity context so that resource and assert look-ups against the wrapped +context go to the WebView or WebLayer support APK and not the embedder's APK. This wrapped Context +is what's returned by [`BrowserImpl.getContext()`][link2]. Use this when referencing WebLayer specific +resources. This is expected to be the most common use case. + +[link2]: https://source.chromium.org/chromium/chromium/src/+/master:weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java?q=f:browserimpl%20getContext&ss=chromium%2Fchromium%2Fsrc + +## Embedder's Application Context + +Occasionally, we need the embedder's application context, as opposed to its activity context. For +instance, fetching the current locale which applies to the entire application. +Similar to WebLayer's Activity Context, this context is also wrapped in our implementation so we can +reference WebLayer-specific resources. This is what's returned by +[`ContextUtils.getApplicationContext()`][link3]. +It shouldn't be downcast to Application (or any subclass thereof) since it's wrapped in a +ContextWrapper. + +[link3]: https://source.chromium.org/chromium/chromium/src/+/master:base/android/java/src/org/chromium/base/ContextUtils.java?q=f:base%2FContextUtils%20getApplicationContext()&ss=chromium%2Fchromium%2Fsrc diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java index d30bc58aed9..072df069fb9 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java @@ -12,15 +12,18 @@ import android.os.Handler; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; import android.view.Window; +import androidx.appcompat.app.AppCompatDelegate; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentController; import androidx.fragment.app.FragmentHostCallback; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import org.chromium.components.browser_ui.settings.SettingsUtils; import org.chromium.components.browser_ui.site_settings.SingleCategorySettings; import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings; import org.chromium.components.browser_ui.site_settings.SiteSettings; @@ -59,6 +62,7 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl { // resource IDs. private Context mContext; + private boolean mStarted; private FragmentController mFragmentController; /** @@ -78,6 +82,12 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl { private PassthroughFragmentActivity(SiteSettingsFragmentImpl fragmentImpl) { mFragmentImpl = fragmentImpl; attachBaseContext(mFragmentImpl.getWebLayerContext()); + // This class doesn't extend AppCompatActivity, so some appcompat functionality doesn't + // get initialized, which leads to some appcompat widgets (like switches) rendering + // incorrectly. There are some resource issues with having this class extend + // AppCompatActivity, but until we sort those out, creating an AppCompatDelegate will + // perform the necessary initialization. + AppCompatDelegate.create(this, null); } @Override @@ -182,8 +192,9 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl { @Override public LayoutInflater onGetLayoutInflater() { - return (LayoutInflater) mFragmentImpl.getWebLayerContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); + Context context = mFragmentImpl.getWebLayerContext(); + return ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .cloneInContext(context); } @Override @@ -271,6 +282,24 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl { throw new RuntimeException("Failed to create Site Settings Fragment", e); } } + + root.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + // Add the shadow scroll listener here once the View is attached to the Window. + SiteSettingsPreferenceFragment preferenceFragment = + (SiteSettingsPreferenceFragment) mFragmentController + .getSupportFragmentManager() + .findFragmentByTag(FRAGMENT_TAG); + ViewGroup listView = preferenceFragment.getListView(); + listView.getViewTreeObserver().addOnScrollChangedListener( + SettingsUtils.getShowShadowOnScrollListener( + listView, view.findViewById(R.id.shadow))); + } + + @Override + public void onViewDetachedFromWindow(View v) {} + }); return root; } @@ -298,7 +327,11 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl { @Override public void onStart() { super.onStart(); - mFragmentController.dispatchActivityCreated(); + + if (!mStarted) { + mStarted = true; + mFragmentController.dispatchActivityCreated(); + } mFragmentController.noteStateNotSaved(); mFragmentController.execPendingActions(); mFragmentController.dispatchStart(); diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java new file mode 100644 index 00000000000..7f46f8afcd2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java @@ -0,0 +1,421 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Region; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; + +import androidx.annotation.IntDef; + +import org.chromium.base.MathUtils; +import org.chromium.content_public.browser.GestureListenerManager; +import org.chromium.content_public.browser.GestureStateListenerWithScroll; +import org.chromium.content_public.browser.WebContents; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * View that slides up from the bottom of the page and slides away as the user scrolls the page. + * Meant to be tacked onto the {@link org.chromium.content_public.browser.WebContents}'s view and + * alerted when either the page scroll position or viewport size changes. + * + * GENERAL BEHAVIOR + * This View is brought onto the screen by sliding upwards from the bottom of the screen. Afterward + * the View slides onto and off of the screen vertically as the user scrolls upwards or + * downwards on the page. + * + * As the scroll offset or the viewport height are updated via a scroll or fling, the difference + * from the initial value is used to determine the View's Y-translation. If a gesture is stopped, + * the View will be snapped back into the center of the screen or entirely off of the screen, based + * on how much of the View is visible, or where the user is currently located on the page. + */ +public abstract class SwipableOverlayView extends FrameLayout { + private static final float FULL_THRESHOLD = 0.5f; + private static final float VERTICAL_FLING_SHOW_THRESHOLD = 0.2f; + private static final float VERTICAL_FLING_HIDE_THRESHOLD = 0.9f; + + @IntDef({Gesture.NONE, Gesture.SCROLLING, Gesture.FLINGING}) + @Retention(RetentionPolicy.SOURCE) + private @interface Gesture { + int NONE = 0; + int SCROLLING = 1; + int FLINGING = 2; + } + + private static final long ANIMATION_DURATION_MS = 250; + + /** Detects when the user is dragging the WebContents. */ + private final GestureStateListenerWithScroll mGestureStateListener; + + /** Listens for changes in the layout. */ + private final View.OnLayoutChangeListener mLayoutChangeListener; + + /** Interpolator used for the animation. */ + private final Interpolator mInterpolator; + + /** Tracks whether the user is scrolling or flinging. */ + private @Gesture int mGestureState; + + /** Animation currently being used to translate the View. */ + private Animator mCurrentAnimation; + + /** Used to determine when the layout has changed and the Viewport must be updated. */ + private int mParentHeight; + + /** Offset from the top of the page when the current gesture was first started. */ + private int mInitialOffsetY; + + /** How tall the View is, including its margins. */ + private int mTotalHeight; + + /** Whether or not the View ever been fully displayed. */ + private boolean mIsBeingDisplayedForFirstTime; + + /** The WebContents to which the overlay is added. */ + private WebContents mWebContents; + + /** + * Creates a SwipableOverlayView. + * @param context Context for acquiring resources. + * @param attrs Attributes from the XML layout inflation. + */ + public SwipableOverlayView(Context context, AttributeSet attrs) { + super(context, attrs); + mGestureStateListener = createGestureStateListener(); + mGestureState = Gesture.NONE; + mLayoutChangeListener = createLayoutChangeListener(); + mInterpolator = new DecelerateInterpolator(1.0f); + + // We make this view 'draw' to provide a placeholder for its animations. + setWillNotDraw(false); + } + + /** + * Set the given WebContents for scrolling changes. + */ + public void setWebContents(WebContents webContents) { + if (mWebContents != null) { + GestureListenerManager.fromWebContents(mWebContents) + .removeListener(mGestureStateListener); + } + + mWebContents = webContents; + // See comment in onLayout() as to why the listener is only attached if mTotalHeight is > 0. + if (mWebContents != null && mTotalHeight > 0) { + GestureListenerManager.fromWebContents(mWebContents).addListener(mGestureStateListener); + } + } + + public WebContents getWebContents() { + return mWebContents; + } + + protected void addToParentView(ViewGroup parentView, int index) { + if (parentView == null) return; + if (getParent() == null) { + parentView.addView(this, index, createLayoutParams()); + + // Listen for the layout to know when to animate the View coming onto the screen. + addOnLayoutChangeListener(mLayoutChangeListener); + } + } + + /** + * Removes the SwipableOverlayView from its parent and stops monitoring the WebContents. + * @return Whether the View was removed from its parent. + */ + public boolean removeFromParentView() { + if (getParent() == null) return false; + + ((ViewGroup) getParent()).removeView(this); + removeOnLayoutChangeListener(mLayoutChangeListener); + return true; + } + + /** + * Creates a set of LayoutParams that makes the View hug the bottom of the screen. Override it + * for other types of behavior. + * @return LayoutParams for use when adding the View to its parent. + */ + public ViewGroup.MarginLayoutParams createLayoutParams() { + return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!isAllowedToAutoHide()) setTranslationY(0.0f); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (!isAllowedToAutoHide()) setTranslationY(0.0f); + } + + /** + * See {@link #android.view.ViewGroup.onLayout(boolean, int, int, int, int)}. + */ + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // Update the viewport height when the parent View's height changes (e.g. after rotation). + int currentParentHeight = getParent() == null ? 0 : ((View) getParent()).getHeight(); + if (mParentHeight != currentParentHeight) { + mParentHeight = currentParentHeight; + mGestureState = Gesture.NONE; + if (mCurrentAnimation != null) mCurrentAnimation.end(); + } + + // Update the known effective height of the View. + MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); + mTotalHeight = getMeasuredHeight() + params.topMargin + params.bottomMargin; + + // Adding a listener to GestureListenerManager results in extra IPCs on every frame, which + // is very costly. Only attach the listener if needed. + if (mWebContents != null) { + if (mTotalHeight > 0) { + GestureListenerManager.fromWebContents(mWebContents) + .addListener(mGestureStateListener); + } else { + GestureListenerManager.fromWebContents(mWebContents) + .removeListener(mGestureStateListener); + } + } + + super.onLayout(changed, l, t, r, b); + } + + /** + * Creates a listener than monitors the WebContents for scrolls and flings. + * The listener updates the location of this View to account for the user's gestures. + * @return GestureStateListenerWithScroll to send to the WebContents. + */ + private GestureStateListenerWithScroll createGestureStateListener() { + return new GestureStateListenerWithScroll() { + /** Tracks the previous event's scroll offset to determine if a scroll is up or down. */ + private int mLastScrollOffsetY; + + /** Location of the View when the current gesture was first started. */ + private float mInitialTranslationY; + + /** The initial extent of the scroll when triggered. */ + private float mInitialExtentY; + + @Override + public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY) { + if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; + resetInternalScrollState(scrollOffsetY, scrollExtentY); + mGestureState = Gesture.FLINGING; + } + + @Override + public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) { + if (mGestureState != Gesture.FLINGING) return; + mGestureState = Gesture.NONE; + + updateTranslation(scrollOffsetY, scrollExtentY); + + boolean isScrollingDownward = scrollOffsetY > mLastScrollOffsetY; + + boolean isVisibleInitially = mInitialTranslationY < mTotalHeight; + float percentageVisible = 1.0f - (getTranslationY() / mTotalHeight); + float visibilityThreshold = isVisibleInitially ? VERTICAL_FLING_HIDE_THRESHOLD + : VERTICAL_FLING_SHOW_THRESHOLD; + boolean isVisibleEnough = percentageVisible > visibilityThreshold; + boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_THRESHOLD); + + boolean show = (!isScrollingDownward && isVisibleEnough) || isNearTopOfPage; + + runUpEventAnimation(show); + } + + @Override + public void onScrollStarted(int scrollOffsetY, int scrollExtentY) { + if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; + resetInternalScrollState(scrollOffsetY, scrollExtentY); + mLastScrollOffsetY = scrollOffsetY; + mGestureState = Gesture.SCROLLING; + } + + @Override + public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { + if (mGestureState != Gesture.SCROLLING) return; + mGestureState = Gesture.NONE; + + updateTranslation(scrollOffsetY, scrollExtentY); + + runUpEventAnimation(shouldSnapToVisibleState(scrollOffsetY)); + } + + @Override + public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) { + mLastScrollOffsetY = scrollOffsetY; + + if (!shouldConsumeScroll(scrollOffsetY, scrollExtentY)) { + resetInternalScrollState(scrollOffsetY, scrollExtentY); + return; + } + + // This function is called for both fling and scrolls. + if (mGestureState == Gesture.NONE || !cancelCurrentAnimation() + || isIndependentlyAnimating()) { + return; + } + + updateTranslation(scrollOffsetY, scrollExtentY); + } + + private void updateTranslation(int scrollOffsetY, int scrollExtentY) { + float scrollDiff = + (scrollOffsetY - mInitialOffsetY) + (scrollExtentY - mInitialExtentY); + float translation = + MathUtils.clamp(mInitialTranslationY + scrollDiff, mTotalHeight, 0); + + // If the container has reached the completely shown position, reset the initial + // scroll so any movement will start hiding it again. + if (translation <= 0f) resetInternalScrollState(scrollOffsetY, scrollExtentY); + + setTranslationY(translation); + } + + /** + * Resets the internal values that a scroll or fling will base its calculations off of. + */ + private void resetInternalScrollState(int scrollOffsetY, int scrollExtentY) { + mInitialOffsetY = scrollOffsetY; + mInitialExtentY = scrollExtentY; + mInitialTranslationY = getTranslationY(); + } + }; + } + + /** + * @param scrollOffsetY The current scroll offset on the Y axis. + * @param scrollExtentY The current scroll extent on the Y axis. + * @return Whether or not the scroll should be consumed by the view. + */ + protected boolean shouldConsumeScroll(int scrollOffsetY, int scrollExtentY) { + return true; + } + + /** + * @param scrollOffsetY The current scroll offset on the Y axis. + * @return Whether the view should snap to a visible state. + */ + protected boolean shouldSnapToVisibleState(int scrollOffsetY) { + boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_THRESHOLD); + boolean isVisibleEnough = getTranslationY() < mTotalHeight * FULL_THRESHOLD; + return isNearTopOfPage || isVisibleEnough; + } + + /** + * @return Whether or not the view is animating independent of the user's scroll position. + */ + protected boolean isIndependentlyAnimating() { + return false; + } + + /** + * Creates a listener that is used only to animate the View coming onto the screen. + * @return The SimpleOnGestureListener that will monitor the View. + */ + private View.OnLayoutChangeListener createLayoutChangeListener() { + return new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + removeOnLayoutChangeListener(mLayoutChangeListener); + + // Animate the View coming in from the bottom of the screen. + setTranslationY(mTotalHeight); + mIsBeingDisplayedForFirstTime = true; + runUpEventAnimation(true); + } + }; + } + + /** + * Create an animation that snaps the View into position vertically. + * @param visible If true, snaps the View to the bottom-center of the screen. If false, + * translates the View below the bottom-center of the screen so that it is + * effectively invisible. + * @return An animator with the snap animation. + */ + protected Animator createVerticalSnapAnimation(boolean visible) { + float targetTranslationY = visible ? 0.0f : mTotalHeight; + float yDifference = Math.abs(targetTranslationY - getTranslationY()) / mTotalHeight; + long duration = Math.max(0, (long) (ANIMATION_DURATION_MS * yDifference)); + + Animator animator = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, targetTranslationY); + animator.setDuration(duration); + animator.setInterpolator(mInterpolator); + + return animator; + } + + /** + * Run an animation when a gesture has ended (an 'up' motion event). + * @param visible Whether or not the view should be visible. + */ + protected void runUpEventAnimation(boolean visible) { + if (mCurrentAnimation != null) mCurrentAnimation.cancel(); + mCurrentAnimation = createVerticalSnapAnimation(visible); + mCurrentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mGestureState = Gesture.NONE; + mCurrentAnimation = null; + mIsBeingDisplayedForFirstTime = false; + } + }); + mCurrentAnimation.start(); + } + + /** + * Cancels the current animation, unless the View is coming onto the screen for the first time. + * @return True if the animation was canceled or wasn't running, false otherwise. + */ + private boolean cancelCurrentAnimation() { + if (mIsBeingDisplayedForFirstTime) return false; + if (mCurrentAnimation != null) mCurrentAnimation.cancel(); + return true; + } + + /** + * @return Whether the SwipableOverlayView is allowed to hide itself on scroll. + */ + protected boolean isAllowedToAutoHide() { + return true; + } + + /** + * Override gatherTransparentRegion to make this view's layout a placeholder for its + * animations. This is only called during layout, so it doesn't really make sense to apply + * post-layout properties like it does by default. Together with setWillNotDraw(false), + * this ensures no child animation within this view's layout will be clipped by a SurfaceView. + */ + @Override + public boolean gatherTransparentRegion(Region region) { + float translationY = getTranslationY(); + setTranslationY(0); + boolean result = super.gatherTransparentRegion(region); + // Restoring TranslationY invalidates this view unnecessarily. However, this function + // is called as part of layout, which implies a full redraw is about to occur anyway. + setTranslationY(translationY); + return result; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java index 38d6cf9557c..382bfb7c605 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java @@ -18,13 +18,16 @@ import android.view.ViewStructure; import android.view.autofill.AutofillValue; import android.webkit.ValueCallback; +import androidx.annotation.VisibleForTesting; + import org.chromium.base.Callback; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.NativeMethods; import org.chromium.components.autofill.AutofillActionModeCallback; import org.chromium.components.autofill.AutofillProvider; -import org.chromium.components.autofill.AutofillProviderImpl; +import org.chromium.components.browser_ui.http_auth.LoginPrompt; +import org.chromium.components.browser_ui.media.MediaSessionHelper; import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate; import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate; import org.chromium.components.embedder_support.contextmenu.ContextMenuParams; @@ -55,7 +58,9 @@ import org.chromium.weblayer_private.interfaces.INavigationControllerClient; import org.chromium.weblayer_private.interfaces.IObjectWrapper; import org.chromium.weblayer_private.interfaces.ITab; import org.chromium.weblayer_private.interfaces.ITabClient; +import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient; import org.chromium.weblayer_private.interfaces.ObjectWrapper; +import org.chromium.weblayer_private.interfaces.ScrollNotificationType; import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; import java.util.ArrayList; @@ -67,7 +72,7 @@ import java.util.Map; * Implementation of ITab. */ @JNINamespace("weblayer") -public final class TabImpl extends ITab.Stub { +public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { private static int sNextId = 1; // Map from id to TabImpl. private static final Map<Integer, TabImpl> sTabMap = new HashMap<Integer, TabImpl>(); @@ -83,6 +88,7 @@ public final class TabImpl extends ITab.Stub { private TabViewAndroidDelegate mViewAndroidDelegate; // BrowserImpl this TabImpl is in. This is only null during creation. private BrowserImpl mBrowser; + private LoginPrompt mLoginPrompt; /** * The AutofillProvider that integrates with system-level autofill. This is null until * updateFromBrowser() is invoked. @@ -107,10 +113,12 @@ public final class TabImpl extends ITab.Stub { private boolean mWaitingForMatchRects; private InterceptNavigationDelegateClientImpl mInterceptNavigationDelegateClient; private InterceptNavigationDelegateImpl mInterceptNavigationDelegate; + private InfoBarContainer mInfoBarContainer; + private MediaSessionHelper mMediaSessionHelper; private boolean mPostContainerViewInitDone; - private AccessibilityUtil.Observer mAccessibilityObserver; + private WebLayerAccessibilityUtil.Observer mAccessibilityObserver; private static class InternalAccessDelegateImpl implements ViewEventSink.InternalAccessDelegate { @@ -165,6 +173,37 @@ public final class TabImpl extends ITab.Stub { viewController.onBottomControlsChanged(bottomControlsOffsetY); } } + + @Override + public void onBackgroundColorChanged(int color) { + if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) { + try { + mClient.onBackgroundColorChanged(color); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + } + + @Override + protected void onVerticalScrollDirectionChanged( + boolean directionUp, float currentScrollRatio) { + if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) { + try { + mClient.onScrollNotification(directionUp + ? ScrollNotificationType.DIRECTION_CHANGED_UP + : ScrollNotificationType.DIRECTION_CHANGED_DOWN, + currentScrollRatio); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + } + } + + public static TabImpl fromWebContents(WebContents webContents) { + if (webContents == null || webContents.isDestroyed()) return null; + return TabImplJni.get().fromWebContents(webContents); } public static TabImpl getTabById(int tabId) { @@ -223,12 +262,21 @@ public final class TabImpl extends ITab.Stub { mInterceptNavigationDelegateClient.initializeWithDelegate(mInterceptNavigationDelegate); sTabMap.put(mId, this); + mInfoBarContainer = new InfoBarContainer(this); mAccessibilityObserver = (boolean enabled) -> { setBrowserControlsVisibilityConstraint(ImplControlsVisibilityReason.ACCESSIBILITY, enabled ? BrowserControlsState.SHOWN : BrowserControlsState.BOTH); }; // addObserver() calls to observer when added. WebLayerAccessibilityUtil.get().addObserver(mAccessibilityObserver); + + // MediaSession only works if the client is new enough. Sadly, passing + // kDisableMediaSessionAPI does not fully disable the API, so a check is also necessary + // before installing this observer. + if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) { + mMediaSessionHelper = new MediaSessionHelper( + mWebContents, MediaSessionManager.createMediaSessionHelperDelegate(mId)); + } } private void doInitAfterSettingContainerView() { @@ -280,7 +328,7 @@ public final class TabImpl extends ITab.Stub { // Set up |mAutofillProvider| to operate in the new Context. It's safe to assume // the context won't change unless it is first nulled out, since the fragment // must be detached before it can be reattached to a new Context. - mAutofillProvider = new AutofillProviderImpl( + mAutofillProvider = new AutofillProvider( mBrowser.getContext(), mBrowser.getAutofillView(), "WebLayer"); TabImplJni.get().onAutofillProviderChanged(mNativeTab, mAutofillProvider); } @@ -333,17 +381,27 @@ public final class TabImpl extends ITab.Stub { assert mBrowser != null; TabImplJni.get().setBrowserControlsContainerViews( mNativeTab, topControlsContainerViewHandle, bottomControlsContainerViewHandle); + mInfoBarContainer.onTabDidGainActive(); updateWebContentsVisibility(); - mWebContents.onShow(); } /** * Called when this TabImpl is no longer the active TabImpl. */ public void onDidLoseActive() { + if (mAutofillProvider != null) { + mAutofillProvider.hidePopup(); + } + hideFindInPageUiAndNotifyClient(); - mWebContents.onHide(); updateWebContentsVisibility(); + + // This method is called as part of the final phase of TabImpl destruction, at which + // point mInfoBarContainer has already been destroyed. + if (mInfoBarContainer != null) { + mInfoBarContainer.onTabDidLoseActive(); + } + TabImplJni.get().setBrowserControlsContainerViews(mNativeTab, 0, 0); } @@ -351,7 +409,8 @@ public final class TabImpl extends ITab.Stub { * Returns whether this Tab is visible. */ public boolean isVisible() { - return (mBrowser.getActiveTab() == this && mBrowser.isStarted()); + return (mBrowser.getActiveTab() == this + && (mBrowser.isStarted() || mBrowser.isFragmentStoppedForConfigurationChange())); } private void updateWebContentsVisibility() { @@ -379,10 +438,17 @@ public final class TabImpl extends ITab.Stub { return mWebContents; } - long getNativeTab() { + // Public for tests. + @VisibleForTesting + public long getNativeTab() { return mNativeTab; } + @VisibleForTesting + public InfoBarContainer getInfoBarContainerForTesting() { + return mInfoBarContainer; + } + @Override public NavigationControllerImpl createNavigationController(INavigationControllerClient client) { StrictModeWorkaround.apply(); @@ -522,6 +588,7 @@ public final class TabImpl extends ITab.Stub { @Override public boolean dismissTransientUi() { + StrictModeWorkaround.apply(); BrowserViewController viewController = getViewController(); if (viewController != null && viewController.dismissTabModalOverlay()) return true; @@ -541,10 +608,34 @@ public final class TabImpl extends ITab.Stub { @Override public String getGuid() { + StrictModeWorkaround.apply(); return TabImplJni.get().getGuid(mNativeTab); } @Override + public boolean setData(Map data) { + StrictModeWorkaround.apply(); + String[] flattenedMap = new String[data.size() * 2]; + int i = 0; + for (Map.Entry<String, String> entry : ((Map<String, String>) data).entrySet()) { + flattenedMap[i++] = entry.getKey(); + flattenedMap[i++] = entry.getValue(); + } + return TabImplJni.get().setData(mNativeTab, flattenedMap); + } + + @Override + public Map getData() { + StrictModeWorkaround.apply(); + String[] data = TabImplJni.get().getData(mNativeTab); + Map<String, String> map = new HashMap<>(); + for (int i = 0; i < data.length; i += 2) { + map.put(data[i], data[i + 1]); + } + return map; + } + + @Override public void captureScreenShot(float scale, IObjectWrapper valueCallback) { StrictModeWorkaround.apply(); ValueCallback<Pair<Bitmap, Integer>> unwrappedCallback = @@ -553,6 +644,18 @@ public final class TabImpl extends ITab.Stub { TabImplJni.get().captureScreenShot(mNativeTab, scale, unwrappedCallback); } + @Override + public boolean canTranslate() { + StrictModeWorkaround.apply(); + return TabImplJni.get().canTranslate(mNativeTab); + } + + @Override + public void showTranslateUi() { + StrictModeWorkaround.apply(); + TabImplJni.get().showTranslateUi(mNativeTab); + } + @CalledByNative private static void runCaptureScreenShotCallback( ValueCallback<Pair<Bitmap, Integer>> callback, Bitmap bitmap, int errorCode) { @@ -634,6 +737,53 @@ public final class TabImpl extends ITab.Stub { getBrowser().destroyTab(this); } + @CalledByNative + private void showHttpAuthPrompt(String host, String url) { + mLoginPrompt = new LoginPrompt(mBrowser.getContext(), host, url, this); + mLoginPrompt.show(); + } + + @CalledByNative + private void closeHttpAuthPrompt() { + mLoginPrompt = null; + } + + @Override + public void cancel() { + TabImplJni.get().cancelHttpAuth(mNativeTab); + } + + @Override + public void proceed(String username, String password) { + TabImplJni.get().setHttpAuth(mNativeTab, username, password); + } + + @Override + public void registerWebMessageCallback( + String jsObjectName, List<String> allowedOrigins, IWebMessageCallbackClient client) { + if (jsObjectName.isEmpty()) { + throw new IllegalArgumentException("JS object name must not be empty"); + } + if (allowedOrigins.isEmpty()) { + throw new IllegalArgumentException("At least one origin must be specified"); + } + for (String origin : allowedOrigins) { + if (TextUtils.isEmpty(origin)) { + throw new IllegalArgumentException("Origin must not be non-empty"); + } + } + String registerError = TabImplJni.get().registerWebMessageCallback(mNativeTab, jsObjectName, + allowedOrigins.toArray(new String[allowedOrigins.size()]), client); + if (!TextUtils.isEmpty(registerError)) { + throw new IllegalArgumentException(registerError); + } + } + + @Override + public void unregisterWebMessageCallback(String jsObjectName) { + TabImplJni.get().unregisterWebMessageCallback(mNativeTab, jsObjectName); + } + public void destroy() { // Ensure that this method isn't called twice. assert mInterceptNavigationDelegate != null; @@ -668,6 +818,9 @@ public final class TabImpl extends ITab.Stub { mInterceptNavigationDelegateClient.destroy(); mInterceptNavigationDelegate = null; + mInfoBarContainer.destroy(); + mInfoBarContainer = null; + mMediaStreamManager.destroy(); mMediaStreamManager = null; @@ -734,6 +887,11 @@ public final class TabImpl extends ITab.Stub { onBrowserControlsStateUpdated(mBrowserControlsVisibility.get()); } + @VisibleForTesting + public boolean canBrowserControlsScrollForTesting() { + return mBrowserControlsVisibility.get() == BrowserControlsState.BOTH; + } + private void onBrowserControlsStateUpdated(int state) { // If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to // dismiss when entering fullscreen. @@ -770,8 +928,14 @@ public final class TabImpl extends ITab.Stub { return (mBrowser.getActiveTab() == this) ? mBrowser.getViewController() : null; } + @VisibleForTesting + public boolean canInfoBarContainerScrollForTesting() { + return mInfoBarContainer.getContainerViewForTesting().isAllowedToAutoHide(); + } + @NativeMethods interface Natives { + TabImpl fromWebContents(WebContents webContents); long createTab(long profile, TabImpl caller); void setJavaImpl(long nativeTabImpl, TabImpl impl); void onAutofillProviderChanged(long nativeTabImpl, AutofillProvider autofillProvider); @@ -786,6 +950,15 @@ public final class TabImpl extends ITab.Stub { String getGuid(long nativeTabImpl); void captureScreenShot(long nativeTabImpl, float scale, ValueCallback<Pair<Bitmap, Integer>> valueCallback); + boolean setData(long nativeTabImpl, String[] data); + String[] getData(long nativeTabImpl); boolean isRendererControllingBrowserControlsOffsets(long nativeTabImpl); + void setHttpAuth(long nativeTabImpl, String username, String password); + void cancelHttpAuth(long nativeTabImpl); + String registerWebMessageCallback(long nativeTabImpl, String jsObjectName, + String[] allowedOrigins, IWebMessageCallbackClient client); + void unregisterWebMessageCallback(long nativeTabImpl, String jsObjectName); + boolean canTranslate(long nativeTabImpl); + void showTranslateUi(long nativeTabImpl); } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java new file mode 100644 index 00000000000..a97315e6fa4 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java @@ -0,0 +1,578 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLayoutChangeListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import androidx.core.content.ContextCompat; + +import com.google.android.material.tabs.TabLayout; + +import org.chromium.base.StrictModeContext; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.ui.widget.Toast; + +/** + * Java version of the compact translate infobar. + */ +@JNINamespace("weblayer") +public class TranslateCompactInfoBar extends InfoBar + implements TabLayout.OnTabSelectedListener, TranslateMenuHelper.TranslateMenuListener { + public static final int TRANSLATING_INFOBAR = 1; + public static final int AFTER_TRANSLATING_INFOBAR = 2; + + private static final int SOURCE_TAB_INDEX = 0; + private static final int TARGET_TAB_INDEX = 1; + + // Action ID for Snackbar. + // Actions performed by clicking on on the overflow menu. + public static final int ACTION_OVERFLOW_ALWAYS_TRANSLATE = 0; + public static final int ACTION_OVERFLOW_NEVER_SITE = 1; + public static final int ACTION_OVERFLOW_NEVER_LANGUAGE = 2; + // Actions triggered automatically. (when translation or denied count reaches the threshold.) + public static final int ACTION_AUTO_ALWAYS_TRANSLATE = 3; + public static final int ACTION_AUTO_NEVER_LANGUAGE = 4; + + private final int mInitialStep; + private final int mDefaultTextColor; + private final TranslateOptions mOptions; + + private long mNativeTranslateInfoBarPtr; + private TranslateTabLayout mTabLayout; + + // Metric to track the total number of translations in a page, including reverts to original. + private int mTotalTranslationCount; + + // Histogram names for logging metrics. + private static final String INFOBAR_HISTOGRAM_TRANSLATE_LANGUAGE = + "Translate.CompactInfobar.Language.Translate"; + private static final String INFOBAR_HISTOGRAM_MORE_LANGUAGES_LANGUAGE = + "Translate.CompactInfobar.Language.MoreLanguages"; + private static final String INFOBAR_HISTOGRAM_PAGE_NOT_IN_LANGUAGE = + "Translate.CompactInfobar.Language.PageNotIn"; + private static final String INFOBAR_HISTOGRAM_ALWAYS_TRANSLATE_LANGUAGE = + "Translate.CompactInfobar.Language.AlwaysTranslate"; + private static final String INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE = + "Translate.CompactInfobar.Language.NeverTranslate"; + private static final String INFOBAR_HISTOGRAM = "Translate.CompactInfobar.Event"; + private static final String INFOBAR_HISTOGRAM_TRANSLATION_COUNT = + "Translate.CompactInfobar.TranslationsPerPage"; + + /** + * This is used to back a UMA histogram, so it should be treated as + * append-only. The values should not be changed or reused, and + * INFOBAR_HISTOGRAM_BOUNDARY should be the last. + */ + private static final int INFOBAR_IMPRESSION = 0; + private static final int INFOBAR_TARGET_TAB_TRANSLATE = 1; + private static final int INFOBAR_DECLINE = 2; + private static final int INFOBAR_OPTIONS = 3; + private static final int INFOBAR_MORE_LANGUAGES = 4; + private static final int INFOBAR_MORE_LANGUAGES_TRANSLATE = 5; + private static final int INFOBAR_PAGE_NOT_IN = 6; + private static final int INFOBAR_ALWAYS_TRANSLATE = 7; + private static final int INFOBAR_NEVER_TRANSLATE = 8; + private static final int INFOBAR_NEVER_TRANSLATE_SITE = 9; + private static final int INFOBAR_SCROLL_HIDE = 10; + private static final int INFOBAR_SCROLL_SHOW = 11; + private static final int INFOBAR_REVERT = 12; + private static final int INFOBAR_SNACKBAR_ALWAYS_TRANSLATE_IMPRESSION = 13; + private static final int INFOBAR_SNACKBAR_NEVER_TRANSLATE_IMPRESSION = 14; + private static final int INFOBAR_SNACKBAR_NEVER_TRANSLATE_SITE_IMPRESSION = 15; + private static final int INFOBAR_SNACKBAR_CANCEL_ALWAYS = 16; + private static final int INFOBAR_SNACKBAR_CANCEL_NEVER_SITE = 17; + private static final int INFOBAR_SNACKBAR_CANCEL_NEVER = 18; + private static final int INFOBAR_ALWAYS_TRANSLATE_UNDO = 19; + private static final int INFOBAR_CLOSE_DEPRECATED = 20; + private static final int INFOBAR_SNACKBAR_AUTO_ALWAYS_IMPRESSION = 21; + private static final int INFOBAR_SNACKBAR_AUTO_NEVER_IMPRESSION = 22; + private static final int INFOBAR_SNACKBAR_CANCEL_AUTO_ALWAYS = 23; + private static final int INFOBAR_SNACKBAR_CANCEL_AUTO_NEVER = 24; + private static final int INFOBAR_HISTOGRAM_BOUNDARY = 25; + + // Need 2 instances of TranslateMenuHelper to prevent a race condition bug which happens when + // showing language menu after dismissing overflow menu. + private TranslateMenuHelper mOverflowMenuHelper; + private TranslateMenuHelper mLanguageMenuHelper; + + private ImageButton mMenuButton; + private InfoBarCompactLayout mParent; + + private boolean mMenuExpanded; + private boolean mIsFirstLayout = true; + private boolean mUserInteracted; + + @CalledByNative + private static InfoBar create(TabImpl tab, int initialStep, String sourceLanguageCode, + String targetLanguageCode, boolean alwaysTranslate, boolean triggeredFromMenu, + String[] languages, String[] languageCodes, int[] hashCodes, int tabTextColor) { + recordInfobarAction(INFOBAR_IMPRESSION); + return new TranslateCompactInfoBar(initialStep, sourceLanguageCode, targetLanguageCode, + alwaysTranslate, triggeredFromMenu, languages, languageCodes, hashCodes, + tabTextColor); + } + + TranslateCompactInfoBar(int initialStep, String sourceLanguageCode, String targetLanguageCode, + boolean alwaysTranslate, boolean triggeredFromMenu, String[] languages, + String[] languageCodes, int[] hashCodes, int tabTextColor) { + super(R.drawable.infobar_translate_compact, 0, null, null); + + mInitialStep = initialStep; + mDefaultTextColor = tabTextColor; + mOptions = TranslateOptions.create(sourceLanguageCode, targetLanguageCode, languages, + languageCodes, alwaysTranslate, triggeredFromMenu, hashCodes); + } + + @Override + protected boolean usesCompactLayout() { + return true; + } + + @Override + protected void createCompactLayoutContent(InfoBarCompactLayout parent) { + LinearLayout content; + // LayoutInflater may trigger accessing the disk. + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + content = (LinearLayout) LayoutInflater.from(getContext()) + .inflate(R.layout.weblayer_infobar_translate_compact_content, parent, + false); + } + + // When parent tab is being switched out (view detached), dismiss all menus and snackbars. + content.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) {} + + @Override + public void onViewDetachedFromWindow(View view) { + dismissMenusAndSnackbars(); + } + }); + + mTabLayout = + (TranslateTabLayout) content.findViewById(R.id.weblayer_translate_infobar_tabs); + if (mDefaultTextColor > 0) { + mTabLayout.setTabTextColors( + ContextCompat.getColor(getContext(), R.color.default_text_color), + ContextCompat.getColor( + getContext(), R.color.weblayer_tab_layout_selected_tab_color)); + } + mTabLayout.addTabs(mOptions.sourceLanguageName(), mOptions.targetLanguageName()); + + if (mInitialStep == TRANSLATING_INFOBAR) { + // Set translating status in the beginning for pages translated automatically. + mTabLayout.getTabAt(TARGET_TAB_INDEX).select(); + mTabLayout.showProgressBarOnTab(TARGET_TAB_INDEX); + mUserInteracted = true; + } else if (mInitialStep == AFTER_TRANSLATING_INFOBAR) { + // Focus on target tab since we are after translation. + mTabLayout.getTabAt(TARGET_TAB_INDEX).select(); + } + + mTabLayout.addOnTabSelectedListener(this); + + // Dismiss all menus and end scrolling animation when there is layout changed. + mTabLayout.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { + // Dismiss all menus to prevent menu misplacement. + dismissMenus(); + + if (mIsFirstLayout) { + // Scrolls to the end to make sure the target language tab is visible when + // language tabs is too long. + mTabLayout.startScrollingAnimationIfNeeded(); + mIsFirstLayout = false; + return; + } + + // End scrolling animation when layout changed. + mTabLayout.endScrollingAnimationIfPlaying(); + } + } + }); + + mMenuButton = content.findViewById(R.id.weblayer_translate_infobar_menu_button); + mMenuButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mTabLayout.endScrollingAnimationIfPlaying(); + recordInfobarAction(INFOBAR_OPTIONS); + initMenuHelper(TranslateMenu.MENU_OVERFLOW); + mOverflowMenuHelper.show(TranslateMenu.MENU_OVERFLOW, getParentWidth()); + mMenuExpanded = true; + } + }); + + parent.addContent(content, 1.0f); + mParent = parent; + } + + private void initMenuHelper(int menuType) { + boolean isIncognito = TranslateCompactInfoBarJni.get().isIncognito( + mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this); + switch (menuType) { + case TranslateMenu.MENU_OVERFLOW: + if (mOverflowMenuHelper == null) { + mOverflowMenuHelper = new TranslateMenuHelper( + getContext(), mMenuButton, mOptions, this, isIncognito); + } + return; + case TranslateMenu.MENU_TARGET_LANGUAGE: + case TranslateMenu.MENU_SOURCE_LANGUAGE: + if (mLanguageMenuHelper == null) { + mLanguageMenuHelper = new TranslateMenuHelper( + getContext(), mMenuButton, mOptions, this, isIncognito); + } + return; + default: + assert false : "Unsupported Menu Item Id"; + } + } + + private void startTranslating(int tabPosition) { + if (TARGET_TAB_INDEX == tabPosition) { + // Already on the target tab. + mTabLayout.showProgressBarOnTab(TARGET_TAB_INDEX); + onButtonClicked(ActionType.TRANSLATE); + mUserInteracted = true; + } else { + mTabLayout.getTabAt(TARGET_TAB_INDEX).select(); + } + } + + @CalledByNative + private void onPageTranslated(int errorType) { + incrementAndRecordTranslationsPerPageCount(); + if (mTabLayout != null) { + mTabLayout.hideProgressBar(); + if (errorType != 0) { + Toast.makeText(getContext(), R.string.translate_infobar_error, Toast.LENGTH_SHORT) + .show(); + // Disable OnTabSelectedListener then revert selection. + mTabLayout.removeOnTabSelectedListener(this); + mTabLayout.getTabAt(SOURCE_TAB_INDEX).select(); + // Add OnTabSelectedListener back. + mTabLayout.addOnTabSelectedListener(this); + } + } + } + + @CalledByNative + private void setNativePtr(long nativePtr) { + mNativeTranslateInfoBarPtr = nativePtr; + } + + @CalledByNative + private void setAutoAlwaysTranslate() { + createAndShowSnackbar(ACTION_AUTO_ALWAYS_TRANSLATE); + } + + @Override + protected void onNativeDestroyed() { + mNativeTranslateInfoBarPtr = 0; + super.onNativeDestroyed(); + } + + private void closeInfobar(boolean explicitly) { + if (isDismissed()) return; + + if (!mUserInteracted) { + recordInfobarAction(INFOBAR_DECLINE); + } + + // NOTE: In Chrome there is a check for whether auto "never translate" should be triggered + // via a snackbar here. However, WebLayer does not have snackbars and thus does not have + // this check as there would be no way to inform the user of the functionality being + // triggered. The user of course has the option of choosing "never translate" from the + // overflow menu. + + // This line will dismiss this infobar. + super.onCloseButtonClicked(); + } + + @Override + public void onCloseButtonClicked() { + mTabLayout.endScrollingAnimationIfPlaying(); + closeInfobar(true); + } + + @Override + public void onTabSelected(TabLayout.Tab tab) { + switch (tab.getPosition()) { + case SOURCE_TAB_INDEX: + incrementAndRecordTranslationsPerPageCount(); + recordInfobarAction(INFOBAR_REVERT); + onButtonClicked(ActionType.TRANSLATE_SHOW_ORIGINAL); + return; + case TARGET_TAB_INDEX: + recordInfobarAction(INFOBAR_TARGET_TAB_TRANSLATE); + recordInfobarLanguageData( + INFOBAR_HISTOGRAM_TRANSLATE_LANGUAGE, mOptions.targetLanguageCode()); + startTranslating(TARGET_TAB_INDEX); + return; + default: + assert false : "Unexpected Tab Index"; + } + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) {} + + @Override + public void onTabReselected(TabLayout.Tab tab) {} + + @Override + public void onOverflowMenuItemClicked(int itemId) { + switch (itemId) { + case TranslateMenu.ID_OVERFLOW_MORE_LANGUAGE: + recordInfobarAction(INFOBAR_MORE_LANGUAGES); + initMenuHelper(TranslateMenu.MENU_TARGET_LANGUAGE); + mLanguageMenuHelper.show(TranslateMenu.MENU_TARGET_LANGUAGE, getParentWidth()); + return; + case TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE: + // Only show snackbar when "Always Translate" is enabled. + if (!mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)) { + recordInfobarAction(INFOBAR_ALWAYS_TRANSLATE); + recordInfobarLanguageData(INFOBAR_HISTOGRAM_ALWAYS_TRANSLATE_LANGUAGE, + mOptions.sourceLanguageCode()); + createAndShowSnackbar(ACTION_OVERFLOW_ALWAYS_TRANSLATE); + } else { + recordInfobarAction(INFOBAR_ALWAYS_TRANSLATE_UNDO); + handleTranslateOptionPostSnackbar(ACTION_OVERFLOW_ALWAYS_TRANSLATE); + } + return; + case TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE: + recordInfobarAction(INFOBAR_NEVER_TRANSLATE); + recordInfobarLanguageData( + INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE, mOptions.sourceLanguageCode()); + createAndShowSnackbar(ACTION_OVERFLOW_NEVER_LANGUAGE); + return; + case TranslateMenu.ID_OVERFLOW_NEVER_SITE: + recordInfobarAction(INFOBAR_NEVER_TRANSLATE_SITE); + createAndShowSnackbar(ACTION_OVERFLOW_NEVER_SITE); + return; + case TranslateMenu.ID_OVERFLOW_NOT_THIS_LANGUAGE: + recordInfobarAction(INFOBAR_PAGE_NOT_IN); + initMenuHelper(TranslateMenu.MENU_SOURCE_LANGUAGE); + mLanguageMenuHelper.show(TranslateMenu.MENU_SOURCE_LANGUAGE, getParentWidth()); + return; + default: + assert false : "Unexpected overflow menu code"; + } + } + + @Override + public void onTargetMenuItemClicked(String code) { + // Reset target code in both UI and native. + if (mNativeTranslateInfoBarPtr != 0 && mOptions.setTargetLanguage(code)) { + recordInfobarAction(INFOBAR_MORE_LANGUAGES_TRANSLATE); + recordInfobarLanguageData( + INFOBAR_HISTOGRAM_MORE_LANGUAGES_LANGUAGE, mOptions.targetLanguageCode()); + TranslateCompactInfoBarJni.get().applyStringTranslateOption(mNativeTranslateInfoBarPtr, + TranslateCompactInfoBar.this, TranslateOption.TARGET_CODE, code); + // Adjust UI. + mTabLayout.replaceTabTitle(TARGET_TAB_INDEX, mOptions.getRepresentationFromCode(code)); + startTranslating(mTabLayout.getSelectedTabPosition()); + } + } + + @Override + public void onSourceMenuItemClicked(String code) { + // Reset source code in both UI and native. + if (mNativeTranslateInfoBarPtr != 0 && mOptions.setSourceLanguage(code)) { + recordInfobarLanguageData( + INFOBAR_HISTOGRAM_PAGE_NOT_IN_LANGUAGE, mOptions.sourceLanguageCode()); + TranslateCompactInfoBarJni.get().applyStringTranslateOption(mNativeTranslateInfoBarPtr, + TranslateCompactInfoBar.this, TranslateOption.SOURCE_CODE, code); + // Adjust UI. + mTabLayout.replaceTabTitle(SOURCE_TAB_INDEX, mOptions.getRepresentationFromCode(code)); + startTranslating(mTabLayout.getSelectedTabPosition()); + } + } + + // Dismiss all overflow menus that remains open. + // This is called when infobar started hiding or layout changed. + private void dismissMenus() { + if (mOverflowMenuHelper != null) mOverflowMenuHelper.dismiss(); + if (mLanguageMenuHelper != null) mLanguageMenuHelper.dismiss(); + } + + // Dismiss all overflow menus and snackbars that belong to this infobar and remain open. + private void dismissMenusAndSnackbars() { + dismissMenus(); + } + + @Override + protected void onStartedHiding() { + dismissMenusAndSnackbars(); + } + + @Override + protected CharSequence getAccessibilityMessage(CharSequence defaultMessage) { + return getContext().getString(R.string.translate_button); + } + + /** + * Returns true if overflow menu is showing. This is only used for automation testing. + */ + public boolean isShowingOverflowMenuForTesting() { + if (mOverflowMenuHelper == null) return false; + return mOverflowMenuHelper.isShowing(); + } + + /** + * Returns true if language menu is showing. This is only used for automation testing. + */ + public boolean isShowingLanguageMenuForTesting() { + if (mLanguageMenuHelper == null) return false; + return mLanguageMenuHelper.isShowing(); + } + + /** + * Returns true if the tab at the given |tabIndex| is selected. This is only used for automation + * testing. + */ + private boolean isTabSelectedForTesting(int tabIndex) { + return mTabLayout.getTabAt(tabIndex).isSelected(); + } + + /** + * Returns true if the target tab is selected. This is only used for automation testing. + */ + public boolean isSourceTabSelectedForTesting() { + return this.isTabSelectedForTesting(SOURCE_TAB_INDEX); + } + + /** + * Returns true if the target tab is selected. This is only used for automation testing. + */ + public boolean isTargetTabSelectedForTesting() { + return this.isTabSelectedForTesting(TARGET_TAB_INDEX); + } + + private void createAndShowSnackbar(int actionId) { + // NOTE: WebLayer doesn't have snackbars, so the relevant action is just taken directly. + // TODO(blundell): If WebLayer ends up staying with this implementation long-term, update + // the nomenclature of this file to avoid any references to snackbars. + handleTranslateOptionPostSnackbar(actionId); + } + + private void handleTranslateOptionPostSnackbar(int actionId) { + // Quit if native is destroyed. + if (mNativeTranslateInfoBarPtr == 0) return; + + switch (actionId) { + case ACTION_OVERFLOW_ALWAYS_TRANSLATE: + toggleAlwaysTranslate(); + // Start translating if always translate is selected and if page is not already + // translated to the target language. + if (mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE) + && mTabLayout.getSelectedTabPosition() == SOURCE_TAB_INDEX) { + startTranslating(mTabLayout.getSelectedTabPosition()); + } + return; + case ACTION_AUTO_ALWAYS_TRANSLATE: + toggleAlwaysTranslate(); + return; + case ACTION_OVERFLOW_NEVER_LANGUAGE: + case ACTION_AUTO_NEVER_LANGUAGE: + mUserInteracted = true; + mOptions.toggleNeverTranslateLanguageState( + !mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE)); + TranslateCompactInfoBarJni.get().applyBoolTranslateOption( + mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this, + TranslateOption.NEVER_TRANSLATE, + mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE)); + return; + case ACTION_OVERFLOW_NEVER_SITE: + mUserInteracted = true; + mOptions.toggleNeverTranslateDomainState( + !mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN)); + TranslateCompactInfoBarJni.get().applyBoolTranslateOption( + mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this, + TranslateOption.NEVER_TRANSLATE_SITE, + mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN)); + return; + default: + assert false : "Unsupported Menu Item Id, in handle post snackbar"; + } + } + + private void toggleAlwaysTranslate() { + mOptions.toggleAlwaysTranslateLanguageState( + !mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)); + TranslateCompactInfoBarJni.get().applyBoolTranslateOption(mNativeTranslateInfoBarPtr, + TranslateCompactInfoBar.this, TranslateOption.ALWAYS_TRANSLATE, + mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)); + } + + private static void recordInfobarAction(int action) { + RecordHistogram.recordEnumeratedHistogram( + INFOBAR_HISTOGRAM, action, INFOBAR_HISTOGRAM_BOUNDARY); + } + + private void recordInfobarLanguageData(String histogram, String langCode) { + Integer hashCode = mOptions.getUMAHashCodeFromCode(langCode); + if (hashCode != null) { + RecordHistogram.recordSparseHistogram(histogram, hashCode); + } + } + + private void incrementAndRecordTranslationsPerPageCount() { + RecordHistogram.recordCountHistogram( + INFOBAR_HISTOGRAM_TRANSLATION_COUNT, ++mTotalTranslationCount); + } + + // Return the width of parent in pixels. Return 0 if there is no parent. + private int getParentWidth() { + return mParent != null ? mParent.getWidth() : 0; + } + + @CalledByNative + // Selects the tab corresponding to |actionType| to simulate the user pressing on this tab. + private void selectTabForTesting(int actionType) { + if (actionType == ActionType.TRANSLATE) { + mTabLayout.getTabAt(TARGET_TAB_INDEX).select(); + } else if (actionType == ActionType.TRANSLATE_SHOW_ORIGINAL) { + mTabLayout.getTabAt(SOURCE_TAB_INDEX).select(); + } else { + assert false; + } + } + + @CalledByNative + // Simulates a click of the overflow menu item for "never translate this language." + private void clickNeverTranslateLanguageMenuItemForTesting() { + onOverflowMenuItemClicked(TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE); + } + + @CalledByNative + // Simulates a click of the overflow menu item for "never translate this site." + private void clickNeverTranslateSiteMenuItemForTesting() { + onOverflowMenuItemClicked(TranslateMenu.ID_OVERFLOW_NEVER_SITE); + } + + @NativeMethods + interface Natives { + void applyStringTranslateOption(long nativeTranslateCompactInfoBar, + TranslateCompactInfoBar caller, int option, String value); + void applyBoolTranslateOption(long nativeTranslateCompactInfoBar, + TranslateCompactInfoBar caller, int option, boolean value); + boolean shouldAutoNeverTranslate(long nativeTranslateCompactInfoBar, + TranslateCompactInfoBar caller, boolean menuExpanded); + boolean isIncognito(long nativeTranslateCompactInfoBar, TranslateCompactInfoBar caller); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java new file mode 100644 index 00000000000..cfb1a06c2f5 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java @@ -0,0 +1,75 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import java.util.ArrayList; +import java.util.List; + +/** + * Translate menu config and its item entity definition. + */ +public final class TranslateMenu { + /** + * The menu item entity. + */ + static final class MenuItem { + public final int mType; + public final int mId; + public final String mCode; + public final boolean mWithDivider; + + MenuItem(int itemType, int itemId, boolean withDivider) { + this(itemType, itemId, EMPTY_STRING, withDivider); + } + + MenuItem(int itemType, int itemId, String code) { + this(itemType, itemId, code, false); + } + + MenuItem(int itemType, int itemId, String code, boolean withDivider) { + mType = itemType; + mId = itemId; + mCode = code; + mWithDivider = withDivider; + } + } + + public static final String EMPTY_STRING = ""; + + // Menu type config. + public static final int MENU_OVERFLOW = 0; + public static final int MENU_TARGET_LANGUAGE = 1; + public static final int MENU_SOURCE_LANGUAGE = 2; + + // Menu item type config. + public static final int ITEM_LANGUAGE = 0; + public static final int ITEM_CHECKBOX_OPTION = 1; + public static final int MENU_ITEM_TYPE_COUNT = 2; + + // Menu Item ID config for MENU_OVERFLOW. + public static final int ID_OVERFLOW_MORE_LANGUAGE = 0; + public static final int ID_OVERFLOW_ALWAYS_TRANSLATE = 1; + public static final int ID_OVERFLOW_NEVER_SITE = 2; + public static final int ID_OVERFLOW_NEVER_LANGUAGE = 3; + public static final int ID_OVERFLOW_NOT_THIS_LANGUAGE = 4; + + /** + * Build overflow menu item list. + */ + static List<MenuItem> getOverflowMenu(boolean isIncognito) { + List<MenuItem> menu = new ArrayList<MenuItem>(); + menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_MORE_LANGUAGE, true)); + if (!isIncognito) { + // "Always translate" does nothing in incognito mode, so just hide it. + menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_ALWAYS_TRANSLATE, false)); + } + menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NEVER_LANGUAGE, false)); + menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NEVER_SITE, false)); + menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NOT_THIS_LANGUAGE, false)); + return menu; + } + + private TranslateMenu() {} +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java new file mode 100644 index 00000000000..16454817172 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java @@ -0,0 +1,321 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListPopupWindow; +import android.widget.PopupWindow; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Helper class for managing the Translate Overflow Menu. + */ +public class TranslateMenuHelper implements AdapterView.OnItemClickListener { + private final TranslateMenuListener mMenuListener; + private final TranslateOptions mOptions; + + private ContextThemeWrapper mContextWrapper; + private TranslateMenuAdapter mAdapter; + private View mAnchorView; + private ListPopupWindow mPopup; + private boolean mIsIncognito; + + /** + * Interface for receiving the click event of menu item. + */ + public interface TranslateMenuListener { + void onOverflowMenuItemClicked(int itemId); + void onTargetMenuItemClicked(String code); + void onSourceMenuItemClicked(String code); + } + + public TranslateMenuHelper(Context context, View anchorView, TranslateOptions options, + TranslateMenuListener itemListener, boolean isIncognito) { + mContextWrapper = new ContextThemeWrapper(context, R.style.OverflowMenuThemeOverlay); + mAnchorView = anchorView; + mOptions = options; + mMenuListener = itemListener; + mIsIncognito = isIncognito; + } + + /** + * Build translate menu by menu type. + */ + private List<TranslateMenu.MenuItem> getMenuList(int menuType) { + List<TranslateMenu.MenuItem> menuList = new ArrayList<TranslateMenu.MenuItem>(); + if (menuType == TranslateMenu.MENU_OVERFLOW) { + // TODO(googleo): Add language short list above static menu after its data is ready. + menuList.addAll(TranslateMenu.getOverflowMenu(mIsIncognito)); + } else { + for (int i = 0; i < mOptions.allLanguages().size(); ++i) { + String code = mOptions.allLanguages().get(i).mLanguageCode; + // Avoid source language in the source language list. + if (menuType == TranslateMenu.MENU_SOURCE_LANGUAGE + && code.equals(mOptions.sourceLanguageCode())) { + continue; + } + // Avoid target language in the target language list. + if (menuType == TranslateMenu.MENU_TARGET_LANGUAGE + && code.equals(mOptions.targetLanguageCode())) { + continue; + } + menuList.add(new TranslateMenu.MenuItem(TranslateMenu.ITEM_LANGUAGE, i, code)); + } + } + return menuList; + } + + /** + * Show the overflow menu. + * @param menuType The type of overflow menu to show. + * @param maxwidth Maximum width of menu. Set to 0 when not specified. + */ + public void show(int menuType, int maxWidth) { + if (mPopup == null) { + mPopup = new ListPopupWindow(mContextWrapper, null, android.R.attr.popupMenuStyle); + mPopup.setModal(true); + mPopup.setAnchorView(mAnchorView); + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + + // Need to explicitly set the background here. Relying on it being set in the style + // caused an incorrectly drawn background. + // TODO(martiw): We might need a new menu background here. + mPopup.setBackgroundDrawable( + ContextCompat.getDrawable(mContextWrapper, R.drawable.popup_bg_tinted)); + + mPopup.setOnItemClickListener(this); + + // The menu must be shifted down by the height of the anchor view in order to be + // displayed over and above it. + int anchorHeight = mAnchorView.getHeight(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // Setting a positive offset here shifts the menu down. + mPopup.setVerticalOffset(anchorHeight); + } else { + // The framework's PopupWindow positioning changed between N and M. Setting + // a negative offset here shifts the menu down rather than up. + mPopup.setVerticalOffset(-anchorHeight); + } + + mAdapter = new TranslateMenuAdapter(menuType); + mPopup.setAdapter(mAdapter); + } else { + mAdapter.refreshMenu(menuType); + } + + if (menuType == TranslateMenu.MENU_OVERFLOW) { + // Use measured width when it is a overflow menu. + Rect bgPadding = new Rect(); + mPopup.getBackground().getPadding(bgPadding); + int measuredWidth = measureMenuWidth(mAdapter) + bgPadding.left + bgPadding.right; + mPopup.setWidth((maxWidth > 0 && measuredWidth > maxWidth) ? maxWidth : measuredWidth); + } else { + // Use fixed width otherwise. + int popupWidth = mContextWrapper.getResources().getDimensionPixelSize( + R.dimen.weblayer_infobar_translate_menu_width); + mPopup.setWidth(popupWidth); + } + + // When layout is RTL, set the horizontal offset to align the menu with the left side of the + // screen. + if (mAnchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + int[] tempLocation = new int[2]; + mAnchorView.getLocationOnScreen(tempLocation); + mPopup.setHorizontalOffset(-tempLocation[0]); + } + + if (!mPopup.isShowing()) { + mPopup.show(); + mPopup.getListView().setItemsCanFocus(true); + } + } + + private int measureMenuWidth(TranslateMenuAdapter adapter) { + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + + final int count = adapter.getCount(); + int width = 0; + int itemType = 0; + View itemView = null; + for (int i = 0; i < count; i++) { + final int positionType = adapter.getItemViewType(i); + if (positionType != itemType) { + itemType = positionType; + itemView = null; + } + itemView = adapter.getView(i, itemView, null); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + width = Math.max(width, itemView.getMeasuredWidth()); + } + return width; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + dismiss(); + + TranslateMenu.MenuItem item = mAdapter.getItem(position); + switch (mAdapter.mMenuType) { + case TranslateMenu.MENU_OVERFLOW: + mMenuListener.onOverflowMenuItemClicked(item.mId); + return; + case TranslateMenu.MENU_TARGET_LANGUAGE: + mMenuListener.onTargetMenuItemClicked(item.mCode); + return; + case TranslateMenu.MENU_SOURCE_LANGUAGE: + mMenuListener.onSourceMenuItemClicked(item.mCode); + return; + default: + assert false : "Unsupported Menu Item Id"; + } + } + + /** + * Dismisses the translate option menu. + */ + public void dismiss() { + if (isShowing()) { + mPopup.dismiss(); + } + } + + /** + * @return Whether the menu is currently showing. + */ + public boolean isShowing() { + if (mPopup == null) { + return false; + } + return mPopup.isShowing(); + } + + /** + * The provides the views of the menu items and dividers. + */ + private final class TranslateMenuAdapter extends ArrayAdapter<TranslateMenu.MenuItem> { + private final LayoutInflater mInflater; + private int mMenuType; + + public TranslateMenuAdapter(int menuType) { + super(mContextWrapper, R.layout.weblayer_translate_menu_item, getMenuList(menuType)); + mInflater = LayoutInflater.from(mContextWrapper); + mMenuType = menuType; + } + + private void refreshMenu(int menuType) { + // MENU_OVERFLOW is static and it should not reload. + if (menuType == TranslateMenu.MENU_OVERFLOW) return; + + clear(); + + mMenuType = menuType; + addAll(getMenuList(menuType)); + notifyDataSetChanged(); + } + + private String getItemViewText(TranslateMenu.MenuItem item) { + if (mMenuType == TranslateMenu.MENU_OVERFLOW) { + // Overflow menu items are manually defined one by one. + String source = mOptions.sourceLanguageName(); + switch (item.mId) { + case TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE: + return mContextWrapper.getString( + R.string.translate_option_always_translate, source); + case TranslateMenu.ID_OVERFLOW_MORE_LANGUAGE: + return mContextWrapper.getString(R.string.translate_option_more_language); + case TranslateMenu.ID_OVERFLOW_NEVER_SITE: + return mContextWrapper.getString(R.string.translate_never_translate_site); + case TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE: + return mContextWrapper.getString( + R.string.translate_option_never_translate, source); + case TranslateMenu.ID_OVERFLOW_NOT_THIS_LANGUAGE: + return mContextWrapper.getString( + R.string.translate_option_not_source_language, source); + default: + assert false : "Unexpected Overflow Item Id"; + } + } else { + // Get source and target language menu items text by language code. + return mOptions.getRepresentationFromCode(item.mCode); + } + return ""; + } + + @Override + public int getItemViewType(int position) { + return getItem(position).mType; + } + + @Override + public int getViewTypeCount() { + return TranslateMenu.MENU_ITEM_TYPE_COUNT; + } + + private View getItemView( + View menuItemView, int position, ViewGroup parent, int resourceId) { + if (menuItemView == null) { + menuItemView = mInflater.inflate(resourceId, parent, false); + } + ((TextView) menuItemView.findViewById(R.id.weblayer_menu_item_text)) + .setText(getItemViewText(getItem(position))); + return menuItemView; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View menuItemView = convertView; + switch (getItemViewType(position)) { + case TranslateMenu.ITEM_CHECKBOX_OPTION: + menuItemView = getItemView(menuItemView, position, parent, + R.layout.weblayer_translate_menu_item_checked); + + ImageView checkboxIcon = + menuItemView.findViewById(R.id.weblayer_menu_item_icon); + if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE + && mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)) { + checkboxIcon.setVisibility(View.VISIBLE); + } else if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE + && mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE)) { + checkboxIcon.setVisibility(View.VISIBLE); + } else if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_NEVER_SITE + && mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN)) { + checkboxIcon.setVisibility(View.VISIBLE); + } else { + checkboxIcon.setVisibility(View.INVISIBLE); + } + + View divider = + (View) menuItemView.findViewById(R.id.weblayer_menu_item_divider); + if (getItem(position).mWithDivider) { + divider.setVisibility(View.VISIBLE); + } + break; + case TranslateMenu.ITEM_LANGUAGE: + menuItemView = getItemView( + menuItemView, position, parent, R.layout.weblayer_translate_menu_item); + break; + default: + assert false : "Unexpected MenuItem type"; + } + return menuItemView; + } + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java new file mode 100644 index 00000000000..ba38d4e26f6 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java @@ -0,0 +1,278 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.text.TextUtils; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A class that keeps the state of the different translation options and + * languages. + */ +public class TranslateOptions { + /** + * A container for Language Code and it's translated representation and it's native UMA + * specific hashcode. + * For example for Spanish when viewed from a French locale, this will contain es, Espagnol, + * 114573335 + **/ + public static class TranslateLanguageData { + public final String mLanguageCode; + public final String mLanguageRepresentation; + public final Integer mLanguageUMAHashCode; + + public TranslateLanguageData( + String languageCode, String languageRepresentation, Integer uMAhashCode) { + assert languageCode != null; + assert languageRepresentation != null; + mLanguageCode = languageCode; + mLanguageRepresentation = languageRepresentation; + mLanguageUMAHashCode = uMAhashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TranslateLanguageData)) return false; + TranslateLanguageData other = (TranslateLanguageData) obj; + return this.mLanguageCode.equals(other.mLanguageCode) + && this.mLanguageRepresentation.equals(other.mLanguageRepresentation) + && this.mLanguageUMAHashCode.equals(other.mLanguageUMAHashCode); + } + + @Override + public int hashCode() { + return (mLanguageCode + mLanguageRepresentation).hashCode(); + } + + @Override + public String toString() { + return "mLanguageCode:" + mLanguageCode + " - mlanguageRepresentation " + + mLanguageRepresentation + " - mLanguageUMAHashCode " + mLanguageUMAHashCode; + } + } + + // Values must be numerated from 0 and can't have gaps + // (they're used for indexing mOptions). + @IntDef({Type.NEVER_LANGUAGE, Type.NEVER_DOMAIN, Type.ALWAYS_LANGUAGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + int NEVER_LANGUAGE = 0; + int NEVER_DOMAIN = 1; + int ALWAYS_LANGUAGE = 2; + + int NUM_ENTRIES = 3; + } + + private String mSourceLanguageCode; + private String mTargetLanguageCode; + + private final ArrayList<TranslateLanguageData> mAllLanguages; + + // language code to translated language name map + // Conceptually final + private Map<String, String> mCodeToRepresentation; + + // Language code to its UMA hashcode representation. + private Map<String, Integer> mCodeToUMAHashCode; + + // Will reflect the state before the object was ever modified + private final boolean[] mOriginalOptions; + + private final String mOriginalSourceLanguageCode; + private final String mOriginalTargetLanguageCode; + private final boolean mTriggeredFromMenu; + + private final boolean[] mOptions; + + private TranslateOptions(String sourceLanguageCode, String targetLanguageCode, + ArrayList<TranslateLanguageData> allLanguages, boolean neverLanguage, + boolean neverDomain, boolean alwaysLanguage, boolean triggeredFromMenu, + boolean[] originalOptions) { + assert Type.NUM_ENTRIES == 3; + mOptions = new boolean[Type.NUM_ENTRIES]; + mOptions[Type.NEVER_LANGUAGE] = neverLanguage; + mOptions[Type.NEVER_DOMAIN] = neverDomain; + mOptions[Type.ALWAYS_LANGUAGE] = alwaysLanguage; + + mOriginalOptions = originalOptions == null ? mOptions.clone() : originalOptions.clone(); + + mSourceLanguageCode = sourceLanguageCode; + mTargetLanguageCode = targetLanguageCode; + mOriginalSourceLanguageCode = mSourceLanguageCode; + mOriginalTargetLanguageCode = mTargetLanguageCode; + mTriggeredFromMenu = triggeredFromMenu; + + mAllLanguages = allLanguages; + mCodeToRepresentation = new HashMap<String, String>(); + mCodeToUMAHashCode = new HashMap<String, Integer>(); + for (TranslateLanguageData language : allLanguages) { + mCodeToRepresentation.put(language.mLanguageCode, language.mLanguageRepresentation); + mCodeToUMAHashCode.put(language.mLanguageCode, language.mLanguageUMAHashCode); + } + } + + /** + * Creates a TranslateOptions by the given data. + */ + public static TranslateOptions create(String sourceLanguageCode, String targetLanguageCode, + String[] languages, String[] codes, boolean alwaysTranslate, boolean triggeredFromMenu, + int[] hashCodes) { + assert languages.length == codes.length; + + ArrayList<TranslateLanguageData> languageList = new ArrayList<TranslateLanguageData>(); + for (int i = 0; i < languages.length; ++i) { + Integer hashCode = hashCodes != null ? Integer.valueOf(hashCodes[i]) : null; + languageList.add(new TranslateLanguageData(codes[i], languages[i], hashCode)); + } + return new TranslateOptions(sourceLanguageCode, targetLanguageCode, languageList, false, + false, alwaysTranslate, triggeredFromMenu, null); + } + + /** + * Returns a copy of the current instance. + */ + TranslateOptions copy() { + return new TranslateOptions(mSourceLanguageCode, mTargetLanguageCode, mAllLanguages, + mOptions[Type.NEVER_LANGUAGE], mOptions[Type.NEVER_DOMAIN], + mOptions[Type.ALWAYS_LANGUAGE], mTriggeredFromMenu, mOriginalOptions); + } + + public String sourceLanguageName() { + return getRepresentationFromCode(mSourceLanguageCode); + } + + public String targetLanguageName() { + return getRepresentationFromCode(mTargetLanguageCode); + } + + public String sourceLanguageCode() { + return mSourceLanguageCode; + } + + public String targetLanguageCode() { + return mTargetLanguageCode; + } + + public boolean triggeredFromMenu() { + return mTriggeredFromMenu; + } + + public boolean optionsChanged() { + return (!mSourceLanguageCode.equals(mOriginalSourceLanguageCode)) + || (!mTargetLanguageCode.equals(mOriginalTargetLanguageCode)) + || (mOptions[Type.NEVER_LANGUAGE] != mOriginalOptions[Type.NEVER_LANGUAGE]) + || (mOptions[Type.NEVER_DOMAIN] != mOriginalOptions[Type.NEVER_DOMAIN]) + || (mOptions[Type.ALWAYS_LANGUAGE] != mOriginalOptions[Type.ALWAYS_LANGUAGE]); + } + + public List<TranslateLanguageData> allLanguages() { + return mAllLanguages; + } + + public boolean getTranslateState(@Type int type) { + return mOptions[type]; + } + + public boolean setSourceLanguage(String languageCode) { + boolean canSet = canSetLanguage(languageCode, mTargetLanguageCode); + if (canSet) mSourceLanguageCode = languageCode; + return canSet; + } + + public boolean setTargetLanguage(String languageCode) { + boolean canSet = canSetLanguage(mSourceLanguageCode, languageCode); + if (canSet) mTargetLanguageCode = languageCode; + return canSet; + } + + /** + * Sets the new state of never translate domain. + * + * @return true if the toggling was possible + */ + public void toggleNeverTranslateDomainState(boolean value) { + mOptions[Type.NEVER_DOMAIN] = value; + } + + /** + * Sets the new state of never translate language. + * + * @return true if the toggling was possible + */ + public boolean toggleNeverTranslateLanguageState(boolean value) { + // Do not toggle if we are activating NeverLanguage but AlwaysTranslate + // for a language pair with the same source language is already active. + if (mOptions[Type.ALWAYS_LANGUAGE] && value) return false; + mOptions[Type.NEVER_LANGUAGE] = value; + return true; + } + + /** + * Sets the new state of never translate a language pair. + * + * @return true if the toggling was possible + */ + public boolean toggleAlwaysTranslateLanguageState(boolean value) { + // Do not toggle if we are activating AlwaysLanguage but NeverLanguage is active already. + if (mOptions[Type.NEVER_LANGUAGE] && value) return false; + mOptions[Type.ALWAYS_LANGUAGE] = value; + return true; + } + + /** + * Gets the language's translated representation from a given language code. + * @param languageCode ISO code for the language + * @return The translated representation of the language, or "" if not found. + */ + public String getRepresentationFromCode(String languageCode) { + return isValidLanguageCode(languageCode) ? mCodeToRepresentation.get(languageCode) : ""; + } + + /** + * Gets the language's UMA hashcode representation from a given language code. + * @param languageCode ISO code for the language + * @return The UMA hashcode representation of the language, or null if not found. + */ + public Integer getUMAHashCodeFromCode(String languageCode) { + return isValidLanguageUMAHashCode(languageCode) ? mCodeToUMAHashCode.get(languageCode) + : null; + } + + private boolean isValidLanguageCode(String languageCode) { + return !TextUtils.isEmpty(languageCode) && mCodeToRepresentation.containsKey(languageCode); + } + + private boolean isValidLanguageUMAHashCode(String languageCode) { + return !TextUtils.isEmpty(languageCode) && mCodeToUMAHashCode.containsKey(languageCode); + } + + private boolean canSetLanguage(String sourceCode, String targetCode) { + return isValidLanguageCode(sourceCode) && isValidLanguageCode(targetCode); + } + + @Override + public String toString() { + return new StringBuilder() + .append(sourceLanguageCode()) + .append(" -> ") + .append(targetLanguageCode()) + .append(" - ") + .append("Never Language:") + .append(mOptions[Type.NEVER_LANGUAGE]) + .append(" Always Language:") + .append(mOptions[Type.ALWAYS_LANGUAGE]) + .append(" Never Domain:") + .append(mOptions[Type.NEVER_DOMAIN]) + .toString(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java new file mode 100644 index 00000000000..4cde0b46193 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java @@ -0,0 +1,63 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * The content of the tab shown in the TranslateTabLayout. + */ +public class TranslateTabContent extends FrameLayout { + private TextView mTextView; + private ProgressBar mProgressBar; + + /** + * Constructor for inflating from XML. + */ + public TranslateTabContent(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTextView = (TextView) findViewById(R.id.weblayer_translate_infobar_tab_text); + mProgressBar = (ProgressBar) findViewById(R.id.weblayer_translate_infobar_tab_progressbar); + } + + /** + * Sets the text color for all the states (normal, selected, focused) to be this color. + * @param colors The color state list of the title text. + */ + public void setTextColor(ColorStateList colors) { + mTextView.setTextColor(colors); + } + + /** + * Set the title text for this tab. + * @param tabTitle The new title string. + */ + public void setText(CharSequence tabTitle) { + mTextView.setText(tabTitle); + } + + /** Hide progress bar and show text. */ + public void hideProgressBar() { + mProgressBar.setVisibility(View.INVISIBLE); + mTextView.setVisibility(View.VISIBLE); + } + + /** Show progress bar and hide text. */ + public void showProgressBar() { + mTextView.setVisibility(View.INVISIBLE); + mProgressBar.setVisibility(View.VISIBLE); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java new file mode 100644 index 00000000000..37c27f37cc6 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java @@ -0,0 +1,240 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; + +import com.google.android.material.tabs.TabLayout; + +import org.chromium.base.StrictModeContext; +import org.chromium.components.browser_ui.widget.animation.Interpolators; + +/** + * TabLayout shown in the TranslateCompactInfoBar. + */ +public class TranslateTabLayout extends TabLayout { + /** The tab in which a spinning progress bar is showing. */ + private Tab mTabShowingProgressBar; + + /** The amount of waiting time before starting the scrolling animation. */ + private static final long START_POSITION_WAIT_DURATION_MS = 1000; + + /** The amount of time it takes to scroll to the end during the scrolling animation. */ + private static final long SCROLL_DURATION_MS = 300; + + /** We define the keyframes of the scrolling animation in this object. */ + ObjectAnimator mScrollToEndAnimator; + + /** Start padding of a Tab. Used for width calculation only. Will not be applied to views. */ + private int mTabPaddingStart; + + /** End padding of a Tab. Used for width calculation only. Will not be applied to views. */ + private int mTabPaddingEnd; + + /** + * Constructor for inflating from XML. + */ + @SuppressLint("CustomViewStyleable") // TODO(crbug.com/807725): Remove and fix. + public TranslateTabLayout(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.TabLayout, 0, R.style.Widget_Design_TabLayout); + mTabPaddingStart = mTabPaddingEnd = + a.getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); + mTabPaddingStart = + a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, mTabPaddingStart); + mTabPaddingEnd = + a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, mTabPaddingEnd); + } + + /** + * Add new Tabs with title strings. + * @param titles Titles of the tabs to be added. + */ + public void addTabs(CharSequence... titles) { + for (CharSequence title : titles) { + addTabWithTitle(title); + } + } + + /** + * Add a new Tab with the title string. + * @param tabTitle Title string of the new tab. + */ + public void addTabWithTitle(CharSequence tabTitle) { + TranslateTabContent tabContent; + // LayoutInflater may trigger accessing the disk. + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + tabContent = + (TranslateTabContent) LayoutInflater.from(getContext()) + .inflate(R.layout.weblayer_infobar_translate_tab_content, this, false); + } + // Set text color using tabLayout's ColorStateList. So that the title text will change + // color when selected and unselected. + tabContent.setTextColor(getTabTextColors()); + tabContent.setText(tabTitle); + + Tab tab = newTab(); + tab.setCustomView(tabContent); + tab.setContentDescription(tabTitle); + super.addTab(tab); + } + + /** + * Replace the title string of a tab. + * @param tabPos The position of the tab to modify. + * @param tabTitle The new title string. + */ + public void replaceTabTitle(int tabPos, CharSequence tabTitle) { + if (tabPos < 0 || tabPos >= getTabCount()) { + return; + } + Tab tab = getTabAt(tabPos); + ((TranslateTabContent) tab.getCustomView()).setText(tabTitle); + tab.setContentDescription(tabTitle); + } + + /** + * Show the spinning progress bar on a specified tab. + * @param tabPos The position of the tab to show the progress bar. + */ + public void showProgressBarOnTab(int tabPos) { + if (tabPos < 0 || tabPos >= getTabCount() || mTabShowingProgressBar != null) { + return; + } + mTabShowingProgressBar = getTabAt(tabPos); + + // TODO(martiw) See if we need to setContentDescription as "Translating" here. + + if (tabIsSupported(mTabShowingProgressBar)) { + ((TranslateTabContent) mTabShowingProgressBar.getCustomView()).showProgressBar(); + } + } + + /** + * Hide the spinning progress bar in the tabs. + */ + public void hideProgressBar() { + if (mTabShowingProgressBar == null) return; + + if (tabIsSupported(mTabShowingProgressBar)) { + ((TranslateTabContent) mTabShowingProgressBar.getCustomView()).hideProgressBar(); + } + + mTabShowingProgressBar = null; + } + + // Overridden to block children's touch event when showing progress bar. + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // Allow touches to propagate to children only if the layout can be interacted with. + if (mTabShowingProgressBar != null) { + return true; + } + endScrollingAnimationIfPlaying(); + return super.onInterceptTouchEvent(ev); + } + + /** Check if the tab is supported in TranslateTabLayout. */ + private boolean tabIsSupported(Tab tab) { + return (tab.getCustomView() instanceof TranslateTabContent); + } + + // Overridden to make sure only supported Tabs can be added. + @Override + public void addTab(@NonNull Tab tab, int position, boolean setSelected) { + if (!tabIsSupported(tab)) { + throw new IllegalArgumentException(); + } + super.addTab(tab, position, setSelected); + } + + // Overrided to make sure only supported Tabs can be added. + @Override + public void addTab(@NonNull Tab tab, boolean setSelected) { + if (!tabIsSupported(tab)) { + throw new IllegalArgumentException(); + } + super.addTab(tab, setSelected); + } + + /** + * Calculate and return the width of a specified tab. Tab doesn't provide a means of getting + * the width so we need to calculate the width by summing up the tab paddings and content width. + * @param position Tab position. + * @return Tab's width in pixels. + */ + private int getTabWidth(int position) { + if (getTabAt(position) == null) return 0; + return getTabAt(position).getCustomView().getWidth() + mTabPaddingStart + mTabPaddingEnd; + } + + /** + * Calculate the total width of all tabs and return it. + * @return Total width of all tabs in pixels. + */ + private int getTabsTotalWidth() { + int totalWidth = 0; + for (int i = 0; i < getTabCount(); i++) { + totalWidth += getTabWidth(i); + } + return totalWidth; + } + + /** + * Calculate the maximum scroll distance (by subtracting layout width from total width of tabs) + * and return it. + * @return Maximum scroll distance in pixels. + */ + private int maxScrollDistance() { + int scrollDistance = getTabsTotalWidth() - getWidth(); + return scrollDistance > 0 ? scrollDistance : 0; + } + + /** + * Perform the scrolling animation if this tablayout has any scrollable distance. + */ + // TODO(crbug.com/900912): Figure out whether setScrollX is actually available. + @SuppressLint("ObjectAnimatorBinding") + public void startScrollingAnimationIfNeeded() { + int maxScrollDistance = maxScrollDistance(); + if (maxScrollDistance == 0) { + return; + } + // The steps of the scrolling animation: + // 1. wait for START_POSITION_WAIT_DURATION_MS. + // 2. scroll to the end in SCROLL_DURATION_MS. + mScrollToEndAnimator = ObjectAnimator.ofInt(this, "scrollX", + getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : maxScrollDistance); + mScrollToEndAnimator.setStartDelay(START_POSITION_WAIT_DURATION_MS); + mScrollToEndAnimator.setDuration(SCROLL_DURATION_MS); + mScrollToEndAnimator.setInterpolator(Interpolators.DECELERATE_INTERPOLATOR); + mScrollToEndAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mScrollToEndAnimator = null; + } + }); + mScrollToEndAnimator.start(); + } + + /** + * End the scrolling animation if it is playing. + */ + public void endScrollingAnimationIfPlaying() { + if (mScrollToEndAnimator != null) mScrollToEndAnimator.end(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java index 31a6a5484b2..4f3d7b439f1 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java @@ -28,6 +28,7 @@ import org.chromium.components.omnibox.SecurityButtonAnimationDelegate; import org.chromium.components.omnibox.SecurityStatusIcon; import org.chromium.components.page_info.PageInfoController; import org.chromium.components.page_info.PermissionParamsListBuilderDelegate; +import org.chromium.content_public.browser.WebContents; import org.chromium.weblayer_private.interfaces.IObjectWrapper; import org.chromium.weblayer_private.interfaces.IUrlBarController; import org.chromium.weblayer_private.interfaces.ObjectWrapper; @@ -159,20 +160,19 @@ public class UrlBarControllerImpl extends IUrlBarController.Stub { ContextCompat.getColor(embedderContext, mUrlIconColor))); } - mSecurityButton.setOnClickListener(v -> { showPageInfoUi(v); }); if (mShowPageInfoWhenUrlTextClicked) { - mUrlTextView.setOnClickListener(v -> { showPageInfoUi(v); }); + setOnClickListener(v -> { showPageInfoUi(v); }); + } else { + mSecurityButton.setOnClickListener(v -> { showPageInfoUi(v); }); } } private void showPageInfoUi(View v) { + WebContents webContents = mBrowserImpl.getActiveTab().getWebContents(); PageInfoController.show(mBrowserImpl.getWindowAndroid().getActivity().get(), - mBrowserImpl.getActiveTab().getWebContents(), + webContents, /* contentPublisher= */ null, PageInfoController.OpenedFromSource.TOOLBAR, - new PageInfoControllerDelegateImpl(mBrowserImpl.getContext(), - mBrowserImpl.getProfile().getName(), - mBrowserImpl.getActiveTab().getWebContents().getVisibleUrl(), - mBrowserImpl.getWindowAndroid()::getModalDialogManager), + PageInfoControllerDelegateImpl.create(webContents), new PermissionParamsListBuilderDelegate(mBrowserImpl.getProfile())); } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java index 9054076d7b4..6cce5a8b27d 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java @@ -4,6 +4,8 @@ package org.chromium.weblayer_private; +import org.chromium.ui.util.AccessibilityUtil; + /** * Exposes information about the current accessibility state. */ diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java index ad83409a452..9e79ff718ec 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java @@ -12,6 +12,7 @@ import org.chromium.components.version_info.VersionConstants; import org.chromium.weblayer_private.interfaces.IWebLayer; import org.chromium.weblayer_private.interfaces.IWebLayerFactory; import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; +import org.chromium.weblayer_private.interfaces.WebLayerVersionConstants; /** * Factory used to create WebLayer as well as verify compatibility. @@ -49,7 +50,13 @@ public final class WebLayerFactoryImpl extends IWebLayerFactory.Stub { @Override public boolean isClientSupported() { StrictModeWorkaround.apply(); - return Math.abs(sClientMajorVersion - getImplementationMajorVersion()) <= 4; + int implMajorVersion = getImplementationMajorVersion(); + // While the client always calls this method, the most recently shipped product gets to + // decide compatibility. If we instead let the implementation always decide, then we would + // not be able to change the allowed skew of older implementations, even if the client could + // support it. + if (sClientMajorVersion > implMajorVersion) return true; + return implMajorVersion - sClientMajorVersion <= WebLayerVersionConstants.MAX_SKEW; } /** diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java index 7150b1d5ca7..c9223c62146 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java @@ -4,6 +4,7 @@ package org.chromium.weblayer_private; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -321,6 +322,9 @@ public final class WebLayerImpl extends IWebLayer.Stub { public void onReceivedBroadcast(IObjectWrapper appContextWrapper, Intent intent) { StrictModeWorkaround.apply(); Context context = ObjectWrapper.unwrap(appContextWrapper, Context.class); + + if (IntentUtils.handleIntent(intent)) return; + if (intent.getAction().startsWith(DownloadImpl.getIntentPrefix())) { DownloadImpl.forwardIntent(context, intent, mProfileManager); } else if (intent.getAction().startsWith(MediaStreamManager.getIntentPrefix())) { @@ -329,6 +333,19 @@ public final class WebLayerImpl extends IWebLayer.Stub { } @Override + public void onMediaSessionServiceStarted(IObjectWrapper sessionService, Intent intent) { + StrictModeWorkaround.apply(); + MediaSessionManager.serviceStarted( + ObjectWrapper.unwrap(sessionService, Service.class), intent); + } + + @Override + public void onMediaSessionServiceDestroyed() { + StrictModeWorkaround.apply(); + MediaSessionManager.serviceDestroyed(); + } + + @Override public void enumerateAllProfileNames(IObjectWrapper valueCallback) { StrictModeWorkaround.apply(); final ValueCallback<String[]> callback = @@ -380,6 +397,30 @@ public final class WebLayerImpl extends IWebLayer.Stub { } } + public static Intent createMediaSessionServiceIntent() { + if (sClient == null) { + throw new IllegalStateException("WebLayer should have been initialized already."); + } + + try { + return sClient.createMediaSessionServiceIntent(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public static int getMediaSessionNotificationId() { + if (sClient == null) { + throw new IllegalStateException("WebLayer should have been initialized already."); + } + + try { + return sClient.getMediaSessionNotificationId(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + public static String getClientApplicationName() { Context context = ContextUtils.getApplicationContext(); return new StringBuilder() diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java index f591a671aa2..50984bb6403 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java @@ -4,19 +4,37 @@ package org.chromium.weblayer_private; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.graphics.drawable.Icon; import android.os.Build; import android.webkit.WebViewFactory; +import androidx.annotation.NonNull; + +import org.chromium.base.ContextUtils; import org.chromium.components.browser_ui.notifications.ChromeNotificationBuilder; import org.chromium.components.browser_ui.notifications.NotificationBuilder; +import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl; import org.chromium.components.browser_ui.notifications.NotificationMetadata; import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer; /** A notification builder for WebLayer which has extra logic to make icons work correctly. */ final class WebLayerNotificationBuilder extends NotificationBuilder { - public WebLayerNotificationBuilder(Context context, String channelId, + /** Creates a notification builder. */ + public static WebLayerNotificationBuilder create( + @WebLayerNotificationChannels.ChannelId String channelId, + @NonNull NotificationMetadata metadata) { + Context appContext = ContextUtils.getApplicationContext(); + ChannelsInitializer initializer = + new ChannelsInitializer(new NotificationManagerProxyImpl(appContext), + WebLayerNotificationChannels.getInstance(), appContext.getResources()); + return new WebLayerNotificationBuilder(appContext, channelId, initializer, metadata); + } + + private WebLayerNotificationBuilder(Context context, String channelId, ChannelsInitializer channelsInitializer, NotificationMetadata metadata) { super(context, channelId, channelsInitializer, metadata); } @@ -25,17 +43,67 @@ final class WebLayerNotificationBuilder extends NotificationBuilder { public ChromeNotificationBuilder setSmallIcon(int icon) { if (WebLayerImpl.isAndroidResource(icon)) { super.setSmallIcon(icon); - return this; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + super.setSmallIcon(createIcon(icon)); + } else { + // Some fallback is required, or the notification won't appear. + super.setSmallIcon(getFallbackAndroidResource(icon)); } + return this; + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - super.setSmallIcon( - Icon.createWithResource(WebViewFactory.getLoadedPackageInfo().packageName, - WebLayerImpl.getResourceIdForSystemUi(icon))); + @Override + @SuppressWarnings("deprecation") + public ChromeNotificationBuilder addAction(int icon, CharSequence title, PendingIntent intent) { + if (WebLayerImpl.isAndroidResource(icon)) { + super.addAction(icon, title, intent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + super.addAction( + new Notification.Action.Builder(createIcon(icon), title, intent).build()); } else { - // Some fallback is required, or the notification won't appear. - super.setSmallIcon(android.R.drawable.radiobutton_on_background); + super.addAction(getFallbackAndroidResource(icon), title, intent); } return this; } + + @TargetApi(Build.VERSION_CODES.M) + private Icon createIcon(int resId) { + return Icon.createWithResource(WebViewFactory.getLoadedPackageInfo().packageName, + WebLayerImpl.getResourceIdForSystemUi(resId)); + } + + /** + * Finds a reasonable replacement for the given app-defined resource from among stock android + * resources. This is useful when {@link Icon} is not available. + */ + private int getFallbackAndroidResource(int appResourceId) { + if (appResourceId == R.drawable.ic_play_arrow_white_36dp) { + return android.R.drawable.ic_media_play; + } + if (appResourceId == R.drawable.ic_pause_white_36dp) { + return android.R.drawable.ic_media_pause; + } + if (appResourceId == R.drawable.ic_stop_white_36dp) { + // There's no ic_media_stop. This standin is at least a square. In practice this + // shouldn't ever come up as stop is only used in (Chrome) cast notifications. + return android.R.drawable.checkbox_off_background; + } + if (appResourceId == R.drawable.ic_skip_previous_white_36dp) { + return android.R.drawable.ic_media_previous; + } + if (appResourceId == R.drawable.ic_skip_next_white_36dp) { + return android.R.drawable.ic_media_next; + } + if (appResourceId == R.drawable.ic_fast_forward_white_36dp) { + return android.R.drawable.ic_media_ff; + } + if (appResourceId == R.drawable.ic_fast_rewind_white_36dp) { + return android.R.drawable.ic_media_rew; + } + if (appResourceId == R.drawable.audio_playing) { + return android.R.drawable.ic_lock_silent_mode_off; + } + + return android.R.drawable.radiobutton_on_background; + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java index 264421f20eb..5967d90d6ec 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java @@ -51,12 +51,13 @@ class WebLayerNotificationChannels extends ChannelDefinitions { * channel, remove the ID from this StringDef, remove its entry from Predefined Channels.MAP, * and add it to the return value of {@link #getLegacyChannelIds()}. */ - @StringDef({ChannelId.ACTIVE_DOWNLOADS, ChannelId.COMPLETED_DOWNLOADS, + @StringDef({ChannelId.ACTIVE_DOWNLOADS, ChannelId.COMPLETED_DOWNLOADS, ChannelId.MEDIA_PLAYBACK, ChannelId.WEBRTC_CAM_AND_MIC}) @Retention(RetentionPolicy.SOURCE) public @interface ChannelId { String ACTIVE_DOWNLOADS = "org.chromium.weblayer.active_downloads"; String COMPLETED_DOWNLOADS = "org.chromium.weblayer.completed_downloads"; + String MEDIA_PLAYBACK = "org.chromium.weblayer.media_playback"; String WEBRTC_CAM_AND_MIC = "org.chromium.weblayer.webrtc_cam_and_mic"; } @@ -81,6 +82,10 @@ class WebLayerNotificationChannels extends ChannelDefinitions { PredefinedChannel.create(ChannelId.COMPLETED_DOWNLOADS, R.string.notification_category_completed_downloads, NotificationManager.IMPORTANCE_LOW, ChannelGroupId.WEBLAYER)); + map.put(ChannelId.MEDIA_PLAYBACK, + PredefinedChannel.create(ChannelId.MEDIA_PLAYBACK, + R.string.notification_category_media_playback, + NotificationManager.IMPORTANCE_LOW, ChannelGroupId.WEBLAYER)); map.put(ChannelId.WEBRTC_CAM_AND_MIC, PredefinedChannel.create(ChannelId.WEBRTC_CAM_AND_MIC, R.string.notification_category_webrtc_cam_and_mic, diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java index d047261bcb4..cabbef5f87a 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java @@ -11,8 +11,6 @@ import androidx.annotation.Nullable; import androidx.preference.Preference; import org.chromium.base.Callback; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.NativeMethods; import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate; import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory.Type; import org.chromium.components.browser_ui.site_settings.SiteSettingsClient; @@ -28,7 +26,6 @@ import java.util.Set; /** * A SiteSettingsClient instance that contains WebLayer-specific Site Settings logic. */ -@JNINamespace("weblayer") public class WebLayerSiteSettingsClient implements SiteSettingsClient, ManagedPreferenceDelegate, SiteSettingsHelpClient, SiteSettingsPrefClient, WebappSettingsClient { @@ -75,8 +72,8 @@ public class WebLayerSiteSettingsClient public boolean isCategoryVisible(@Type int type) { return type == Type.ALL_SITES || type == Type.AUTOMATIC_DOWNLOADS || type == Type.CAMERA || type == Type.COOKIES || type == Type.DEVICE_LOCATION || type == Type.JAVASCRIPT - || type == Type.MICROPHONE || type == Type.PROTECTED_MEDIA || type == Type.SOUND - || type == Type.USE_STORAGE; + || type == Type.MICROPHONE || type == Type.POPUPS || type == Type.PROTECTED_MEDIA + || type == Type.SOUND || type == Type.USE_STORAGE; } @Override @@ -122,32 +119,6 @@ public class WebLayerSiteSettingsClient public void launchProtectedContentHelpAndFeedbackActivity(Activity currentActivity) {} // SiteSettingsPrefClient implementation: - // TODO(crbug.com/1071603): Once PrefServiceBridge is componentized we can get rid of the JNI - // methods here and call PrefServiceBridge directly. - - @Override - public boolean getBlockThirdPartyCookies() { - return WebLayerSiteSettingsClientJni.get().getBlockThirdPartyCookies(mBrowserContextHandle); - } - @Override - public void setBlockThirdPartyCookies(boolean newValue) { - WebLayerSiteSettingsClientJni.get().setBlockThirdPartyCookies( - mBrowserContextHandle, newValue); - } - @Override - public boolean isBlockThirdPartyCookiesManaged() { - // WebLayer doesn't support managed prefs. - return false; - } - - @Override - public int getCookieControlsMode() { - return WebLayerSiteSettingsClientJni.get().getCookieControlsMode(mBrowserContextHandle); - } - @Override - public void setCookieControlsMode(int newValue) { - WebLayerSiteSettingsClientJni.get().setCookieControlsMode(mBrowserContextHandle, newValue); - } // The quiet notification UI is a Chrome-specific feature for now. @Override @@ -187,13 +158,4 @@ public class WebLayerSiteSettingsClient public String getNotificationDelegatePackageNameForOrigin(Origin origin) { return null; } - - @NativeMethods - interface Natives { - boolean getBlockThirdPartyCookies(BrowserContextHandle browserContextHandle); - void setBlockThirdPartyCookies(BrowserContextHandle browserContextHandle, boolean newValue); - - int getCookieControlsMode(BrowserContextHandle browserContextHandle); - void setCookieControlsMode(BrowserContextHandle browserContextHandle, int newValue); - } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java new file mode 100644 index 00000000000..d639a42cf3a --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java @@ -0,0 +1,76 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import android.os.RemoteException; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.APICallException; +import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient; +import org.chromium.weblayer_private.interfaces.IWebMessageReplyProxy; + +/** + * WebMessageReplyProxyImpl is responsible for both sending and receiving WebMessages. + */ +@JNINamespace("weblayer") +public final class WebMessageReplyProxyImpl extends IWebMessageReplyProxy.Stub { + private long mNativeWebMessageReplyProxyImpl; + private final IWebMessageCallbackClient mClient; + // Unique id (scoped to the call to Tab.registerWebMessageCallback()) for this proxy. This is + // sent over AIDL. + private final int mId; + + private WebMessageReplyProxyImpl(long nativeWebMessageReplyProxyImpl, int id, + IWebMessageCallbackClient client, boolean isMainFrame, String sourceOrigin) { + mNativeWebMessageReplyProxyImpl = nativeWebMessageReplyProxyImpl; + mClient = client; + mId = id; + try { + client.onNewReplyProxy(this, mId, isMainFrame, sourceOrigin); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + @CalledByNative + private static WebMessageReplyProxyImpl create(long nativeWebMessageReplyProxyImpl, int id, + IWebMessageCallbackClient client, boolean isMainFrame, String sourceOrigin) { + return new WebMessageReplyProxyImpl( + nativeWebMessageReplyProxyImpl, id, client, isMainFrame, sourceOrigin); + } + + @CalledByNative + private void onNativeDestroyed() { + mNativeWebMessageReplyProxyImpl = 0; + try { + mClient.onReplyProxyDestroyed(mId); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + @CalledByNative + private void onPostMessage(String message) { + try { + mClient.onPostMessage(mId, message); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + @Override + public void postMessage(String message) { + if (mNativeWebMessageReplyProxyImpl != 0) { + WebMessageReplyProxyImplJni.get().postMessage(mNativeWebMessageReplyProxyImpl, message); + } + } + + @NativeMethods + interface Natives { + void postMessage(long nativeWebMessageReplyProxyImpl, String message); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java new file mode 100644 index 00000000000..2005e53d2fd --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java @@ -0,0 +1,40 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private; + +import org.chromium.components.browser_ui.share.ShareHelper; +import org.chromium.components.browser_ui.share.ShareParams; +import org.chromium.components.browser_ui.webshare.ShareServiceImpl; +import org.chromium.content_public.browser.WebContents; +import org.chromium.services.service_manager.InterfaceFactory; +import org.chromium.webshare.mojom.ShareService; + +/** + * Factory that creates instances of ShareService. + */ +public class WebShareServiceFactory implements InterfaceFactory<ShareService> { + private final WebContents mWebContents; + + public WebShareServiceFactory(WebContents webContents) { + mWebContents = webContents; + } + + @Override + public ShareService createImpl() { + ShareServiceImpl.WebShareDelegate delegate = new ShareServiceImpl.WebShareDelegate() { + @Override + public boolean canShare() { + return mWebContents.getTopLevelNativeWindow().getActivity() != null; + } + + @Override + public void share(ShareParams params) { + ShareHelper.shareWithUi(params); + } + }; + + return new ShareServiceImpl(mWebContents, delegate); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl index 8093e226baa..ed9a123fe9c 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl @@ -34,4 +34,6 @@ interface IBrowser { IUrlBarController getUrlBarController() = 9; void setBottomView(in IObjectWrapper view) = 10; + + ITab createTab() = 11; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl index 3a21e9b518b..653b9a6af12 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl @@ -33,4 +33,7 @@ interface INavigationController { // Added in 82, removed in 83. // void replace(in String uri) = 12; + + // Added in 85. + boolean isNavigationEntrySkippable(int index) = 13; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl index 60132f87c71..73432f8bd19 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl @@ -6,6 +6,7 @@ package org.chromium.weblayer_private.interfaces; import org.chromium.weblayer_private.interfaces.IClientNavigation; import org.chromium.weblayer_private.interfaces.INavigation; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; /** * Interface used by NavigationController to inform the client of changes. This largely duplicates @@ -29,4 +30,7 @@ interface INavigationControllerClient { void loadProgressChanged(double progress) = 7; void onFirstContentfulPaint() = 8; + + // Added in M85. + void onOldPageNoLongerRendered(in String uri) = 9; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl index 75966ad04a4..6ec60700f85 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl @@ -30,4 +30,10 @@ interface IProfile { // Added in Version 84. void setBooleanSetting(int type, boolean value) = 7; boolean getBooleanSetting(int type) = 8; + + // Added in Version 85. + void getBrowserPersistenceIds(in IObjectWrapper resultCallback) = 9; + void removeBrowserPersistenceStorage(in String[] ids, + in IObjectWrapper resultCallback) = 10; + void prepareForPossibleCrossOriginNavigation() = 11; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl index b396292d4da..c029b9a6c18 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl @@ -4,6 +4,8 @@ package org.chromium.weblayer_private.interfaces; +import java.util.List; + import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient; import org.chromium.weblayer_private.interfaces.IFindInPageCallbackClient; @@ -13,6 +15,7 @@ import org.chromium.weblayer_private.interfaces.INavigationController; import org.chromium.weblayer_private.interfaces.INavigationControllerClient; import org.chromium.weblayer_private.interfaces.IObjectWrapper; import org.chromium.weblayer_private.interfaces.ITabClient; +import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient; interface ITab { void setClient(in ITabClient client) = 0; @@ -51,4 +54,16 @@ interface ITab { // Added in 84 void captureScreenShot(in float scale, in IObjectWrapper resultCallback) = 16; + + // Added in 85 + boolean setData(in Map data) = 17; + + // Added in 85 + Map getData() = 18; + void registerWebMessageCallback(in String jsObjectName, + in List<String> allowedOrigins, + in IWebMessageCallbackClient client) = 19; + void unregisterWebMessageCallback(in String jsObjectName) = 20; + boolean canTranslate() = 21; + void showTranslateUi() = 22; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl index 7313c2c8cec..12b6c4cfeda 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl @@ -36,4 +36,11 @@ interface ITabClient { // Added in M84. void onTabDestroyed() = 8; + + // Added in M85. + void onBackgroundColorChanged(in int color) = 9; + + // Added in M85 + void onScrollNotification( + in int notificationType, in float currentScrollRatio) = 10; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl index cdaa4ebb3e3..7c8af14c1f6 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl @@ -96,4 +96,8 @@ interface IWebLayer { ISiteSettingsFragment createSiteSettingsFragmentImpl( in IRemoteFragmentClient remoteFragmentClient, in IObjectWrapper fragmentArgs) = 16; + + // Added in Version 85. + void onMediaSessionServiceStarted(in IObjectWrapper sessionService, in Intent intent) = 17; + void onMediaSessionServiceDestroyed() = 18; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl index 9857b597c48..e152bef181b 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl @@ -8,4 +8,6 @@ import android.content.Intent; interface IWebLayerClient { Intent createIntent() = 0; + Intent createMediaSessionServiceIntent() = 1; + int getMediaSessionNotificationId() = 2; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl new file mode 100644 index 00000000000..91ce8b87153 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl @@ -0,0 +1,16 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private.interfaces; + +import org.chromium.weblayer_private.interfaces.IWebMessageReplyProxy; + +interface IWebMessageCallbackClient { + void onNewReplyProxy(in IWebMessageReplyProxy proxy, + in int proxyId, + in boolean isMainFrame, + in String sourceOrigin) = 0; + void onPostMessage(in int proxyId, in String message) = 1; + void onReplyProxyDestroyed(in int proxyId) = 2; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl new file mode 100644 index 00000000000..208c78d53ef --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl @@ -0,0 +1,9 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private.interfaces; + +interface IWebMessageReplyProxy { + void postMessage(in String message) = 0; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java new file mode 100644 index 00000000000..424442ffed3 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java @@ -0,0 +1,18 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private.interfaces; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({ScrollNotificationType.DIRECTION_CHANGED_UP, + ScrollNotificationType.DIRECTION_CHANGED_DOWN}) +@Retention(RetentionPolicy.SOURCE) +public @interface ScrollNotificationType { + int DIRECTION_CHANGED_UP = 0; + int DIRECTION_CHANGED_DOWN = 1; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java index b6fd35e49e5..1b37b08d9b3 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java @@ -9,8 +9,13 @@ import androidx.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -@IntDef({SettingType.BASIC_SAFE_BROWSING_ENABLED}) +@IntDef({SettingType.BASIC_SAFE_BROWSING_ENABLED, SettingType.UKM_ENABLED, + SettingType.EXTENDED_REPORTING_SAFE_BROWSING_ENABLED, + SettingType.REAL_TIME_SAFE_BROWSING_ENABLED}) @Retention(RetentionPolicy.SOURCE) public @interface SettingType { int BASIC_SAFE_BROWSING_ENABLED = 0; + int UKM_ENABLED = 1; + int EXTENDED_REPORTING_SAFE_BROWSING_ENABLED = 2; + int REAL_TIME_SAFE_BROWSING_ENABLED = 3; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java new file mode 100644 index 00000000000..a76b13c3ba8 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.weblayer_private.interfaces; + +/** + * Versioning related constants. + */ +public interface WebLayerVersionConstants { + /** + * Maximum allowed version skew. If the skew is greater than this, the implementation and client + * are not considered compatible, and WebLayer is unusable. The skew is the absolute value of + * the difference between the client major version and the implementation major version. + * + * @see WebLayer#isAvailable() + */ + int MAX_SKEW = 4; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl index 3664df20f44..ac1cd24f40f 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl @@ -4,6 +4,9 @@ package org.chromium.weblayer_private.test_interfaces; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.ITab; + interface ITestWebLayer { // Force network connectivity state. boolean isNetworkChangeAutoDetectOn() = 1; @@ -19,4 +22,26 @@ interface ITestWebLayer { // Forces the system location setting to enabled. void setSystemLocationSettingEnabled(boolean enabled) = 6; + + // See comments in TestWebLayer for details. + void waitForBrowserControlsMetadataState(in ITab tab, + in int top, + in int bottom, + in IObjectWrapper runnable) = 7; + + void setAccessibilityEnabled(in boolean enabled) = 8; + + boolean canBrowserControlsScroll(in ITab tab) = 9; + + // Creates and shows a test infobar in |tab|, calling |runnable| when the addition (including + // animations) is complete. + void addInfoBar(in ITab tab, in IObjectWrapper runnable) = 10; + + // Gets the infobar container view associated with |tab|. + IObjectWrapper getInfoBarContainerView(in ITab tab) = 11; + + void setIgnoreMissingKeyForTranslateManager(in boolean ignore) = 12; + void forceNetworkConnectivityState(in boolean networkAvailable) = 13; + + boolean canInfoBarContainerScroll(in ITab tab) = 14; } diff --git a/chromium/weblayer/browser/java/res/drawable/weblayer_infobar_wrapper_bg.xml b/chromium/weblayer/browser/java/res/drawable/weblayer_infobar_wrapper_bg.xml new file mode 100644 index 00000000000..e119c1148fa --- /dev/null +++ b/chromium/weblayer/browser/java/res/drawable/weblayer_infobar_wrapper_bg.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2015 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item> + <bitmap + android:src="@drawable/infobar_shadow_top" + android:gravity="top|fill_horizontal" + android:tileMode="disabled" /> + </item> + <item + android:top="@dimen/infobar_shadow_height" + android:drawable="@color/infobar_background_color" /> +</layer-list> diff --git a/chromium/weblayer/browser/java/res/drawable/weblayer_tab_indicator.xml b/chromium/weblayer/browser/java/res/drawable/weblayer_tab_indicator.xml new file mode 100644 index 00000000000..c8ab7e06481 --- /dev/null +++ b/chromium/weblayer/browser/java/res/drawable/weblayer_tab_indicator.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2019 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="@dimen/weblayer_tab_indicator_padding" + android:right="@dimen/weblayer_tab_indicator_padding" > + <shape android:shape="rectangle" > + <corners + android:topRightRadius="@dimen/weblayer_tab_indicator_radius" + android:topLeftRadius="@dimen/weblayer_tab_indicator_radius" /> + </shape> + </item> +</layer-list> diff --git a/chromium/weblayer/browser/java/res/layout/site_settings_layout.xml b/chromium/weblayer/browser/java/res/layout/site_settings_layout.xml index 7bb3c0b82f1..c9cf58e79f1 100644 --- a/chromium/weblayer/browser/java/res/layout/site_settings_layout.xml +++ b/chromium/weblayer/browser/java/res/layout/site_settings_layout.xml @@ -3,9 +3,14 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<FrameLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" - android:id="@+id/site_settings_container"> -</FrameLayout> + android:layout_height="match_parent"> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/site_settings_container"> + </FrameLayout> + <include layout="@layout/settings_action_bar_shadow" /> +</RelativeLayout> diff --git a/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_compact_content.xml b/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_compact_content.xml new file mode 100644 index 00000000000..82149b5b142 --- /dev/null +++ b/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_compact_content.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/weblayer_translate_infobar_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + <!-- TODO(huayinz): Change app:tabIndicatorColor to some common color reference --> + <org.chromium.weblayer_private.TranslateTabLayout + android:id="@+id/weblayer_translate_infobar_tabs" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:requiresFadingEdge="horizontal" + android:fadingEdgeLength="@dimen/weblayer_infobar_translate_fade_edge_length" + app:tabIndicator="@drawable/weblayer_tab_indicator" + app:tabIndicatorFullWidth="false" + app:tabIndicatorHeight="3dp" + app:tabSelectedTextColor="@color/weblayer_tab_layout_selected_tab_color" + app:tabGravity="fill" + app:tabMode="scrollable" /> + + <org.chromium.ui.widget.ChromeImageButton + android:id="@+id/weblayer_translate_infobar_menu_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minHeight="@dimen/min_touch_target_size" + android:minWidth="@dimen/min_touch_target_size" + android:scaleType="center" + android:background="?attr/selectableItemBackground" + android:contentDescription="@string/accessibility_toolbar_btn_menu" + android:src="@drawable/ic_more_vert_24dp" + app:tint="@color/default_icon_color_tint_list" /> +</LinearLayout> diff --git a/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_tab_content.xml b/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_tab_content.xml new file mode 100644 index 00000000000..3be8f467c43 --- /dev/null +++ b/chromium/weblayer/browser/java/res/layout/weblayer_infobar_translate_tab_content.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<org.chromium.weblayer_private.TranslateTabContent + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/weblayer_translate_tabcontent" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <!-- Add both the textView and progressBar to the tab, and only keep one of them visible. + This way the width of the Tab will always be fixed no matter which one is visible. --> + <TextView + android:id="@+id/weblayer_translate_infobar_tab_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:textAppearance="@style/TextAppearance.Design.Tab" + android:visibility="visible" + android:singleLine="true" /> + <ProgressBar + android:id="@+id/weblayer_translate_infobar_tab_progressbar" + android:layout_width="@dimen/infobar_small_icon_size" + android:layout_height="@dimen/infobar_small_icon_size" + android:layout_gravity="center" + android:indeterminate="true" + android:visibility="invisible" /> +</org.chromium.weblayer_private.TranslateTabContent> diff --git a/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item.xml b/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item.xml new file mode 100644 index 00000000000..a7948450da3 --- /dev/null +++ b/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/WebLayerAppMenuItem" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <TextView + android:id="@+id/weblayer_menu_item_text" + android:textAppearance="?android:attr/textAppearanceLargePopupMenu" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:gravity="center_vertical" + android:paddingTop="13dp" + android:paddingBottom="13dp" /> + +</FrameLayout> diff --git a/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item_checked.xml b/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item_checked.xml new file mode 100644 index 00000000000..a4bd2cf4692 --- /dev/null +++ b/chromium/weblayer/browser/java/res/layout/weblayer_translate_menu_item_checked.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + style="@style/WebLayerAppMenuItem" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <TextView + android:id="@+id/weblayer_menu_item_text" + android:textAppearance="?android:attr/textAppearanceLargePopupMenu" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="start" + android:gravity="center_vertical" + android:paddingTop="13dp" + android:paddingBottom="13dp" + android:paddingEnd="16dp" /> + <org.chromium.ui.widget.ChromeImageView + android:id="@+id/weblayer_menu_item_icon" + android:src="@drawable/ic_check_googblue_24dp" + android:layout_width="24dp" + android:layout_height="match_parent" + android:layout_gravity="end" + android:gravity="center_vertical" + app:tint="@color/default_icon_color_tint_list" /> + + </LinearLayout> + + <View + android:id="@+id/weblayer_menu_item_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/divider_line_bg_color" + android:visibility="gone" /> + +</LinearLayout> + diff --git a/chromium/weblayer/browser/java/res/layout/weblayer_url_bar.xml b/chromium/weblayer/browser/java/res/layout/weblayer_url_bar.xml index 1e8828d7f25..e5f2b57b80e 100644 --- a/chromium/weblayer/browser/java/res/layout/weblayer_url_bar.xml +++ b/chromium/weblayer/browser/java/res/layout/weblayer_url_bar.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:maxLines="1" + android:singleLine="true" android:paddingEnd="@dimen/url_text_edge_padding" android:paddingStart="@dimen/url_text_edge_padding" android:ellipsize="start" diff --git a/chromium/weblayer/browser/java/res/values/colors.xml b/chromium/weblayer/browser/java/res/values/colors.xml new file mode 100644 index 00000000000..bb00b313960 --- /dev/null +++ b/chromium/weblayer/browser/java/res/values/colors.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Please see src/ui/android/java/res/values/colors.xml for the shared common colors. --> + + <color name="weblayer_tab_layout_selected_tab_color">@color/default_text_color_blue</color> + +</resources> diff --git a/chromium/weblayer/browser/java/res/values/dimens.xml b/chromium/weblayer/browser/java/res/values/dimens.xml index dfce8d8e51e..0eee3fc9758 100644 --- a/chromium/weblayer/browser/java/res/values/dimens.xml +++ b/chromium/weblayer/browser/java/res/values/dimens.xml @@ -6,4 +6,13 @@ <resources> <dimen name="security_status_icon_size">18dp</dimen> <dimen name="url_text_edge_padding">5dp</dimen> -</resources>
\ No newline at end of file + + <!-- Dimensions for compact translate infobar. --> + <dimen name="weblayer_infobar_translate_fade_edge_length">18dp</dimen> + <dimen name="weblayer_infobar_translate_menu_width">260dp</dimen> + + <!-- Dimens of tab indicator --> + <dimen name="weblayer_tab_indicator_radius">3dp</dimen> + <dimen name="weblayer_tab_indicator_padding">2dp</dimen> + +</resources> diff --git a/chromium/weblayer/browser/java/res/values/styles.xml b/chromium/weblayer/browser/java/res/values/styles.xml index b90beb2e45c..f362fd9ab69 100644 --- a/chromium/weblayer/browser/java/res/values/styles.xml +++ b/chromium/weblayer/browser/java/res/values/styles.xml @@ -10,40 +10,13 @@ <item name="alertDialogTheme">@style/Theme.Chromium.AlertDialog</item> </style> - <style name="PreferenceTheme"> - <item name="preferenceStyle">@style/PreferenceItem</item> - <item name="preferenceFragmentCompatStyle">@style/SettingsFragment</item> - <item name="preferenceFragmentListStyle">@style/SettingsFragmentList</item> - <item name="dialogPreferenceStyle">@style/DialogPreference</item> - <item name="checkBoxPreferenceStyle">@style/CheckBoxPreference</item> - <item name="switchPreferenceCompatStyle">@style/SwitchPreference</item> - </style> - - <style name="PreferenceItem"> - <item name="android:layout">@layout/preference_compat</item> - </style> - - <style name="SettingsFragment"> - <item name="android:divider">?android:attr/listDivider</item> - </style> - - <style name="SettingsFragmentList"> - <item name="android:paddingStart">0dp</item> - <item name="android:paddingEnd">0dp</item> - </style> - - <style name="DialogPreference"> - <item name="android:layout">@layout/preference_compat</item> - <item name="android:negativeButtonText">@android:string/cancel</item> - </style> - - <style name="CheckBoxPreference"> - <item name="android:layout">@layout/preference_compat</item> - <item name="android:widgetLayout">@layout/preference_widget_checkbox</item> - </style> - - <style name="SwitchPreference"> - <item name="android:layout">@layout/preference_compat</item> - <item name="android:widgetLayout">@layout/preference_widget_switch_compat</item> + <!-- The following styles may be used to style views provided by a CustomViewBinder or attached + to the app menu as headers or footers. --> + + <!-- Styling for an app menu item row. --> + <style name="WebLayerAppMenuItem"> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">16dp</item> + <item name="android:background">?attr/listChoiceBackgroundIndicator</item> </style> </resources> diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_af.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_af.xtb index 403610204ab..62d289ff62c 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_af.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_af.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="af"> +<translation id="1068672505746868501">Moet nooit bladsye in <ph name="SOURCE_LANGUAGE" /> vertaal nie</translation> +<translation id="124116460088058876">Meer tale</translation> +<translation id="1285320974508926690">Moet nooit hierdie werf vertaal nie</translation> +<translation id="290376772003165898">Bladsy is nie in <ph name="LANGUAGE" /> nie?</translation> +<translation id="5684874026226664614">Oeps. Hierdie bladsy kon nie vertaal word nie.</translation> +<translation id="6040143037577758943">Maak toe</translation> +<translation id="6831043979455480757">Vertaal</translation> +<translation id="7243308994586599757">Opsies is naby die onderkant van die skerm beskikbaar</translation> +<translation id="773466115871691567">Vertaal altyd bladsye in <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Webblaaieraktiwiteit</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_am.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_am.xtb index 2fd6e46a16e..ec9bad225cb 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_am.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_am.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="am"> +<translation id="1068672505746868501">በ<ph name="SOURCE_LANGUAGE" /> ውስጥ ገጾችን በጭራሽ አትተርጉም</translation> +<translation id="124116460088058876">ተጨማሪ ቋንቋዎች</translation> +<translation id="1285320974508926690">ይህን ጣቢያ በጭራሽ አትተርጉም</translation> +<translation id="290376772003165898">ገጽ በ<ph name="LANGUAGE" /> አይደለም?</translation> +<translation id="5684874026226664614">ውይ። ይህ ገጽ ሊተረጎም አይችልም።</translation> +<translation id="6040143037577758943">ዝጋ</translation> +<translation id="6831043979455480757">መተርጎም</translation> +<translation id="7243308994586599757">አማራጮች ከማያ ገጹ ግርጌ አጠገብ ይገኛሉ</translation> +<translation id="773466115871691567">ገጾችን ሁልጊዜ በ<ph name="SOURCE_LANGUAGE" /> ተርጉም</translation> <translation id="8298278839890148234">የድር አሳሽ እንቅስቃሴ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ar.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ar.xtb index 55fcefece9b..b631d5aa80b 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ar.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ar.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ar"> +<translation id="1068672505746868501">عدم ترجمة الصفحات باللغة <ph name="SOURCE_LANGUAGE" /> مُطلقًا</translation> +<translation id="124116460088058876">مزيد من اللغات</translation> +<translation id="1285320974508926690">عدم ترجمة هذا الموقع مطلقًا</translation> +<translation id="290376772003165898">أليست الصفحة باللغة <ph name="LANGUAGE" />؟</translation> +<translation id="5684874026226664614">عفوًا. تعذرت ترجمة هذه الصفحة.</translation> +<translation id="6040143037577758943">إغلاق</translation> +<translation id="6831043979455480757">ترجمة</translation> +<translation id="7243308994586599757">الخيارات المتاحة بالقرب من الجزء السفلي من الشاشة</translation> +<translation id="773466115871691567">ترجمة الصفحات باللغة <ph name="SOURCE_LANGUAGE" /> دائمًا</translation> <translation id="8298278839890148234">نشاط التصفُّح على الويب</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_as.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_as.xtb index db63c417200..409550b1d05 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_as.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_as.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="as"> +<translation id="1068672505746868501">পৃষ্ঠা <ph name="SOURCE_LANGUAGE" />লৈ কেতিয়াও অনুবাদ নকৰিব</translation> +<translation id="124116460088058876">অধিক ভাষা</translation> +<translation id="1285320974508926690">এই ছাইটটো কেতিয়াও অনুবাদ নকৰিব</translation> +<translation id="290376772003165898">পৃষ্ঠাটো <ph name="LANGUAGE" /> ভাষাত নাই নেকি?</translation> +<translation id="5684874026226664614">ওঁহ এই পৃষ্ঠাটো অনুবাদ কৰিব পৰা নগ’ল।</translation> +<translation id="6040143037577758943">বন্ধ কৰক</translation> +<translation id="6831043979455480757">অনুবাদ কৰক</translation> +<translation id="7243308994586599757">স্ক্ৰীণৰ কাষৰ বুটামত উপলব্ধ বিকল্প</translation> +<translation id="773466115871691567">সদায় পৃষ্ঠাসমূহ <ph name="SOURCE_LANGUAGE" />লৈ অনুবাদ কৰক</translation> <translation id="8298278839890148234">ৱেব ব্ৰাউজাৰৰ কার্যকলাপ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_az.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_az.xtb index 684871cc3c3..cd8a4a44f50 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_az.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_az.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="az"> +<translation id="1068672505746868501">Heç vaxt <ph name="SOURCE_LANGUAGE" /> dilində olan səhifələri tərcümə etməyin</translation> +<translation id="124116460088058876">Digər dillər</translation> +<translation id="1285320974508926690">Bu saytı heç vaxt tərcümə etməyin</translation> +<translation id="290376772003165898">Səhifə <ph name="LANGUAGE" /> dilində deyil?</translation> +<translation id="5684874026226664614">Bu səhifə tərcümə edilə bilmir. Niyəsini bilmirik.</translation> +<translation id="6040143037577758943">Qapat</translation> +<translation id="6831043979455480757">Tərcümə et</translation> +<translation id="7243308994586599757">Seçənəklər ekranın aşağısına yaxın yerdə əlçatandır</translation> +<translation id="773466115871691567">Səhifələri həmişə <ph name="SOURCE_LANGUAGE" /> dilinə tərcümə edin</translation> <translation id="8298278839890148234">Veb axtarış fəaliyyəti</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_be.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_be.xtb index 4c18cf28a2f..eff070e867a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_be.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_be.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="be"> +<translation id="1068672505746868501">Ніколі не перакладаць старонкі на наступнай мове: <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Іншыя мовы</translation> +<translation id="1285320974508926690">Ніколі не перакладаць гэты сайт</translation> +<translation id="290376772003165898">Мова старонкі – не <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Памылка. Не ўдалося перакласці гэту старонку.</translation> +<translation id="6040143037577758943">Закрыць</translation> +<translation id="6831043979455480757">Перакласці</translation> +<translation id="7243308994586599757">Параметры знаходзяцца ў ніжняй частцы экрана</translation> +<translation id="773466115871691567">Заўсёды перакладаць старонкі на наступнай мове: <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Дзеянні ў вэб-браўзеры</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_bg.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_bg.xtb index 1e7789939c0..a55fe167dff 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_bg.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_bg.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="bg"> +<translation id="1068672505746868501">Страниците на <ph name="SOURCE_LANGUAGE" /> да не се превеждат</translation> +<translation id="124116460088058876">Още езици</translation> +<translation id="1285320974508926690">Този сайт да не се превежда никога</translation> +<translation id="290376772003165898">Страницата не е на <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ами сега! Тази страница не можа да се преведе.</translation> +<translation id="6040143037577758943">Затваряне</translation> +<translation id="6831043979455480757">Превод</translation> +<translation id="7243308994586599757">Опциите са в долната част на екрана</translation> +<translation id="773466115871691567">Страниците на <ph name="SOURCE_LANGUAGE" /> да се превеждат винаги</translation> <translation id="8298278839890148234">Активност в уеб браузъра</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_bn.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_bn.xtb index 374b81798d7..81f76d848f2 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_bn.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_bn.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="bn"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> ভাষার পৃষ্ঠার অনুবাদ কখনও দেখতে চাই না</translation> +<translation id="124116460088058876">আরও ভাষা</translation> +<translation id="1285320974508926690">কখনই এই সাইটটিকে অনুবাদ করবেন না</translation> +<translation id="290376772003165898">পৃষ্ঠাটি <ph name="LANGUAGE" /> ভাষায় নয়?</translation> +<translation id="5684874026226664614">ওহো৷ এই পৃষ্ঠাটির অনুবাদ করা যাবে না৷</translation> +<translation id="6040143037577758943">বন্ধ</translation> +<translation id="6831043979455480757">অনুবাদ</translation> +<translation id="7243308994586599757">স্ক্রীনের প্রায় নীচের দিকে বিকল্পগুলি উপলব্ধ</translation> +<translation id="773466115871691567">সব সময় <ph name="SOURCE_LANGUAGE" /> ভাষার পৃষ্ঠার অনুবাদ দেখতে চাই</translation> <translation id="8298278839890148234">ওয়েব ব্রাউজার অ্যাক্টিভিটি</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_bs.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_bs.xtb index 4fb2dbb390e..5d1aefcaea1 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_bs.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_bs.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="bs"> +<translation id="1068672505746868501">Nemoj nikada prevoditi stranice na <ph name="SOURCE_LANGUAGE" /> jezik</translation> +<translation id="124116460088058876">Više jezika</translation> +<translation id="1285320974508926690">Nikada ne prevodi ovu web lokaciju</translation> +<translation id="290376772003165898">Ovo nije <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups. Prijevod ove stranice nije uspio.</translation> +<translation id="6040143037577758943">Zatvori</translation> +<translation id="6831043979455480757">Prevedi</translation> +<translation id="7243308994586599757">Opcije su dostupne pri dnu ekrana</translation> +<translation id="773466115871691567">Uvijek prevedi stranice na <ph name="SOURCE_LANGUAGE" /> jezik</translation> <translation id="8298278839890148234">Aktivnost web preglednika</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ca.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ca.xtb index ba2d4e4da24..3c181c2e235 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ca.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ca.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ca"> +<translation id="1068672505746868501">No tradueixis mai les pàgines en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Més idiomes</translation> +<translation id="1285320974508926690">No tradueixis mai aquest lloc</translation> +<translation id="290376772003165898">La pàgina no està en <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Aquesta pàgina no s'ha pogut traduir.</translation> +<translation id="6040143037577758943">Tanca</translation> +<translation id="6831043979455480757">Tradueix</translation> +<translation id="7243308994586599757">Opcions disponibles a la part inferior de la pantalla</translation> +<translation id="773466115871691567">Tradueix sempre les pàgines en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Activitat del navegador web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_cs.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_cs.xtb index 5710c42229a..7e7b91a9744 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_cs.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_cs.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="cs"> +<translation id="1068672505746868501">Stránky v jazyce <ph name="SOURCE_LANGUAGE" /> nikdy nepřekládat</translation> +<translation id="124116460088058876">Další jazyky</translation> +<translation id="1285320974508926690">Tento web nikdy nepřekládat</translation> +<translation id="290376772003165898">Stránka není v jazyce <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Jejda. Tuto stránku se nepodařilo přeložit.</translation> +<translation id="6040143037577758943">Zavřít</translation> +<translation id="6831043979455480757">Přeložit</translation> +<translation id="7243308994586599757">Možnosti jsou k dispozici ve spodní části obrazovky</translation> +<translation id="773466115871691567">Stránky v jazyce <ph name="SOURCE_LANGUAGE" /> vždy překládat</translation> <translation id="8298278839890148234">Aktivita ve webovém prohlížeči</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_da.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_da.xtb index 63df7f53aa1..571f36b83ac 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_da.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_da.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="da"> +<translation id="1068672505746868501">Oversæt aldrig sider på <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Flere sprog</translation> +<translation id="1285320974508926690">Oversæt aldrig dette website</translation> +<translation id="290376772003165898">Er siden ikke på <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups! Denne side kunne ikke oversættes.</translation> +<translation id="6040143037577758943">Luk</translation> +<translation id="6831043979455480757">Oversæt</translation> +<translation id="7243308994586599757">Du finder indstillingerne nederst på skærmen</translation> +<translation id="773466115871691567">Oversæt altid sider på <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Webbrowseraktivitet</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_de.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_de.xtb index a6c724e09b6..65cd87c466a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_de.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_de.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="de"> +<translation id="1068672505746868501">Seiten auf <ph name="SOURCE_LANGUAGE" /> nie übersetzen</translation> +<translation id="124116460088058876">Weitere Sprachen</translation> +<translation id="1285320974508926690">Diese Website nie übersetzen</translation> +<translation id="290376772003165898">Diese Seite ist nicht auf <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Hoppla! Diese Seite konnte nicht übersetzt werden.</translation> +<translation id="6040143037577758943">Schließen</translation> +<translation id="6831043979455480757">Übersetzen</translation> +<translation id="7243308994586599757">Optionen unten auf dem Bildschirm verfügbar</translation> +<translation id="773466115871691567">Seiten auf <ph name="SOURCE_LANGUAGE" /> immer übersetzen</translation> <translation id="8298278839890148234">Webbrowseraktivitäten</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_el.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_el.xtb index 91f7efb7d08..1e22b4a980c 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_el.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_el.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="el"> +<translation id="1068672505746868501">Να μην γίνεται μετάφραση σελίδων στα <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Περισσότερες γλώσσες</translation> +<translation id="1285320974508926690">Να μην γίνεται ποτέ μετάφραση αυτού του ιστότοπου</translation> +<translation id="290376772003165898">Η σελίδα δεν είναι στα <ph name="LANGUAGE" />;</translation> +<translation id="5684874026226664614">Ωχ. Δεν ήταν δυνατή η μετάφραση αυτής της σελίδας.</translation> +<translation id="6040143037577758943">Κλείσιμο</translation> +<translation id="6831043979455480757">Μετάφραση</translation> +<translation id="7243308994586599757">Διαθέσιμες επιλογές κοντά κάτω μέρος της οθόνης</translation> +<translation id="773466115871691567">Να μεταφράζονται πάντα οι σελίδες προς τα <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Δραστηριότητα προγράμματος περιήγησης στον ιστό</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_en-GB.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_en-GB.xtb index 078cecbe647..8f63e62c0db 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_en-GB.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_en-GB.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="en-GB"> +<translation id="1068672505746868501">Never translate pages in <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">More languages</translation> +<translation id="1285320974508926690">Never translate this site</translation> +<translation id="290376772003165898">Page is not in <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Oops. This page could not be translated.</translation> +<translation id="6040143037577758943">Close</translation> +<translation id="6831043979455480757">Translate</translation> +<translation id="7243308994586599757">Options available near bottom of the screen</translation> +<translation id="773466115871691567">Always translate pages in <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Web browser activity</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_es-419.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_es-419.xtb index e529fa34f49..276fe173c58 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_es-419.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_es-419.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="es-419"> +<translation id="1068672505746868501">No traducir nunca páginas en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Más idiomas</translation> +<translation id="1285320974508926690">Nunca traducir este sitio</translation> +<translation id="290376772003165898">¿La página no está en <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">No se puede traducir esta página.</translation> +<translation id="6040143037577758943">Cerrar</translation> +<translation id="6831043979455480757">Traducir</translation> +<translation id="7243308994586599757">Opciones disponibles junto a la parte inferior de la pantalla</translation> +<translation id="773466115871691567">Traducir siempre las páginas en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Actividad del navegador web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_es.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_es.xtb index fcf6da55978..8b61d2a834e 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_es.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_es.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="es"> +<translation id="1068672505746868501">No traducir nunca las páginas en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Más idiomas</translation> +<translation id="1285320974508926690">No traducir nunca este sitio</translation> +<translation id="290376772003165898">¿La página no está en <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">¡Vaya! No se ha podido traducir esta página.</translation> +<translation id="6040143037577758943">Cerrar</translation> +<translation id="6831043979455480757">Traducir</translation> +<translation id="7243308994586599757">Opciones disponibles cerca de la parte inferior de la pantalla</translation> +<translation id="773466115871691567">Traducir siempre las páginas en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Actividad del navegador web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_et.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_et.xtb index 37ada0e9df4..ff14c3ea03e 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_et.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_et.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="et"> +<translation id="1068672505746868501">Ära tõlgi kunagi <ph name="SOURCE_LANGUAGE" /> keeles olevaid lehti</translation> +<translation id="124116460088058876">Rohkem keeli</translation> +<translation id="1285320974508926690">Ära kunagi seda saiti tõlgi</translation> +<translation id="290376772003165898">Kas leht ei ole <ph name="LANGUAGE" /> keeles?</translation> +<translation id="5684874026226664614">Vabandust. Lehte ei õnnestunud tõlkida.</translation> +<translation id="6040143037577758943">Sulge</translation> +<translation id="6831043979455480757">Tõlgi</translation> +<translation id="7243308994586599757">Valikud on saadaval ekraani allosas</translation> +<translation id="773466115871691567">Tõlgi alati <ph name="SOURCE_LANGUAGE" /> keeles olevad lehed</translation> <translation id="8298278839890148234">Veebibrauseri tegevused</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_eu.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_eu.xtb index 2698f9fb351..0fe579c1cb0 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_eu.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_eu.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="eu"> +<translation id="1068672505746868501">Ez itzuli inoiz <ph name="SOURCE_LANGUAGE" /> darabilten orriak</translation> +<translation id="124116460088058876">Hizkuntza gehiago</translation> +<translation id="1285320974508926690">Ez itzuli inoiz webgune hau</translation> +<translation id="290376772003165898">Ez al da <ph name="LANGUAGE" /> orriko hizkuntza?</translation> +<translation id="5684874026226664614">Ezin izan da orria itzuli.</translation> +<translation id="6040143037577758943">Itxi</translation> +<translation id="6831043979455480757">Itzuli</translation> +<translation id="7243308994586599757">Pantailaren behealdean agertzen dira dauden aukerak</translation> +<translation id="773466115871691567">Itzuli beti <ph name="SOURCE_LANGUAGE" /> darabilten orriak</translation> <translation id="8298278839890148234">Sareko arakatze-jarduerak</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_fa.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_fa.xtb index 77daea83d86..b3cf59018eb 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_fa.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_fa.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="fa"> +<translation id="1068672505746868501">هرگز صفحههای <ph name="SOURCE_LANGUAGE" /> ترجمه نشوند</translation> +<translation id="124116460088058876">زبانهای بیشتر</translation> +<translation id="1285320974508926690">این سایت هرگز ترجمه نشود</translation> +<translation id="290376772003165898">صفحه به زبان <ph name="LANGUAGE" /> وجود ندارد؟</translation> +<translation id="5684874026226664614">متأسفیم. این صفحه ترجمه نشد.</translation> +<translation id="6040143037577758943">بستن</translation> +<translation id="6831043979455480757">ترجمه</translation> +<translation id="7243308994586599757">گزینهها در نزدیک پایین صفحه نمایش در دسترس هستند</translation> +<translation id="773466115871691567">صفحههای <ph name="SOURCE_LANGUAGE" /> همیشه ترجمه شوند</translation> <translation id="8298278839890148234">فعالیت مرورگر وب</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_fi.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_fi.xtb index 78a8d793d2e..e8e035371e0 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_fi.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_fi.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="fi"> +<translation id="1068672505746868501">Älä koskaan käännä kielellä <ph name="SOURCE_LANGUAGE" /> kirjoitettuja sivuja.</translation> +<translation id="124116460088058876">Lisää kieliä</translation> +<translation id="1285320974508926690">Älä käännä tätä sivustoa</translation> +<translation id="290376772003165898">Eikö sivu ole kirjoitettu kielellä <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Hups, tätä sivua ei voi kääntää.</translation> +<translation id="6040143037577758943">Sulje</translation> +<translation id="6831043979455480757">Käännä</translation> +<translation id="7243308994586599757">Asetukset löytyvät näytön alalaidasta.</translation> +<translation id="773466115871691567">Käännä aina kielellä <ph name="SOURCE_LANGUAGE" /> kirjoitut sivut</translation> <translation id="8298278839890148234">Verkon selaustoiminta</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_fil.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_fil.xtb index 08f97ffffd4..01ddf5eb7ec 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_fil.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_fil.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="fil"> +<translation id="1068672505746868501">Huwag kailanman isalin ang mga page sa <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Higit pang wika</translation> +<translation id="1285320974508926690">Huwag isalin kailanman ang site na ito</translation> +<translation id="290376772003165898">Hindi nakasalin ang page sa <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Oops. Hindi maisalin ang pahinang ito.</translation> +<translation id="6040143037577758943">Isara</translation> +<translation id="6831043979455480757">Isalin</translation> +<translation id="7243308994586599757">May mga opsyon malapit sa ibaba ng screen</translation> +<translation id="773466115871691567">Palaging isalin ang mga page sa <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktibidad ng web browser</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_fr-CA.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_fr-CA.xtb index 537978cc551..2415250b42a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_fr-CA.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_fr-CA.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="fr-CA"> +<translation id="1068672505746868501">Ne jamais traduire les pages en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Plus de langues</translation> +<translation id="1285320974508926690">Ne jamais traduire ce site</translation> +<translation id="290376772003165898">Cette page n'est pas en <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Oups… Impossible de traduire cette page.</translation> +<translation id="6040143037577758943">Fermer</translation> +<translation id="6831043979455480757">Traduire</translation> +<translation id="7243308994586599757">Options disponibles vers le bas de l’écran</translation> +<translation id="773466115871691567">Toujours traduire les pages en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Activité de navigation Web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_fr.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_fr.xtb index 9810a3e323a..334bf0344fd 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_fr.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_fr.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="fr"> +<translation id="1068672505746868501">Ne jamais traduire les pages en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Plus de langues</translation> +<translation id="1285320974508926690">Ne jamais traduire ce site</translation> +<translation id="290376772003165898">La page n'est pas en <ph name="LANGUAGE" /> ?</translation> +<translation id="5684874026226664614">Petit problème… Impossible de traduire cette page.</translation> +<translation id="6040143037577758943">Fermer</translation> +<translation id="6831043979455480757">Traduire</translation> +<translation id="7243308994586599757">Options disponibles au bas de l'écran</translation> +<translation id="773466115871691567">Toujours traduire les pages en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Activité de navigation sur le Web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_gl.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_gl.xtb index c315bcbcd56..3f598e52c3b 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_gl.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_gl.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="gl"> +<translation id="1068672505746868501">Non traducir nunca páxinas en <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Máis idiomas</translation> +<translation id="1285320974508926690">Non traducir nunca este sitio</translation> +<translation id="290376772003165898">A páxina non está en <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Non se puido traducir esta páxina.</translation> +<translation id="6040143037577758943">Pechar</translation> +<translation id="6831043979455480757">Traducir</translation> +<translation id="7243308994586599757">Opcións dispoñibles na parte inferior da pantalla</translation> +<translation id="773466115871691567">Traducir sempre páxinas en <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Actividade de navegación pola Web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_gu.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_gu.xtb index 9e2ee22696d..957cc2993a6 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_gu.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_gu.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="gu"> +<translation id="1068672505746868501">પેજનો ક્યારેય પણ <ph name="SOURCE_LANGUAGE" />માં અનુવાદ કરશો નહીં</translation> +<translation id="124116460088058876">વધુ ભાષાઓ</translation> +<translation id="1285320974508926690">આ સાઇટનું ક્યારેય ભાષાંતર કરશો નહીં</translation> +<translation id="290376772003165898">પેજ <ph name="LANGUAGE" />માં નથી?</translation> +<translation id="5684874026226664614">અરેરે. આ પૃષ્ઠનો અનુવાદ કરી શકાયો નથી.</translation> +<translation id="6040143037577758943">બંધ કરો</translation> +<translation id="6831043979455480757">અનુવાદ કરો</translation> +<translation id="7243308994586599757">સ્ક્રીનના તળિયા નજીક વિકલ્પો ઉપલબ્ધ છે</translation> +<translation id="773466115871691567">હંમેશાં પેજનો <ph name="SOURCE_LANGUAGE" />માં અનુવાદ કરો</translation> <translation id="8298278839890148234">વેબ બ્રાઉઝરની પ્રવૃત્તિ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_hi.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_hi.xtb index a9ae0e70db1..6fe2057217c 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_hi.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_hi.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="hi"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> भाषा के पेज का कभी भी अनुवाद न करें</translation> +<translation id="124116460088058876">ज़्यादा भाषाएं</translation> +<translation id="1285320974508926690">कभी भी इस साइट का अनुवाद न करें</translation> +<translation id="290376772003165898">क्या पेज <ph name="LANGUAGE" /> भाषा में नहीं है?</translation> +<translation id="5684874026226664614">ओह. इस पेज का अनुवाद नहीं किया जा सका.</translation> +<translation id="6040143037577758943">बंद करें</translation> +<translation id="6831043979455480757">अनुवाद करें</translation> +<translation id="7243308994586599757">विकल्प स्क्रीन के नीचे उपलब्ध हैं</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> भाषा के पेज का हमेशा अनुवाद करें</translation> <translation id="8298278839890148234">वेब ब्राउज़र की गतिविधि</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_hr.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_hr.xtb index bc0817b9c82..d9fc4ee6cec 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_hr.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_hr.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="hr"> +<translation id="1068672505746868501">Nikad ne prevodi <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Više jezika</translation> +<translation id="1285320974508926690">Nikad nemoj prevoditi ovu web-lokaciju</translation> +<translation id="290376772003165898">Ovo nije <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups. Stranicu nije bilo moguće prevesti.</translation> +<translation id="6040143037577758943">Zatvori</translation> +<translation id="6831043979455480757">Prevedi</translation> +<translation id="7243308994586599757">Opcije dostupne pri dnu zaslona</translation> +<translation id="773466115871691567">Uvijek prevodi <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktivnost u web-pregledniku</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_hu.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_hu.xtb index 587b17fb1f5..498d74cc4c9 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_hu.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_hu.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="hu"> +<translation id="1068672505746868501">Soha ne fordítsa le a(z) <ph name="SOURCE_LANGUAGE" /> nyelvű oldalakat</translation> +<translation id="124116460088058876">További nyelvek…</translation> +<translation id="1285320974508926690">Ezt a webhelyet soha ne fordítsa le</translation> +<translation id="290376772003165898">Az oldal nem <ph name="LANGUAGE" /> nyelvű?</translation> +<translation id="5684874026226664614">Hoppá! Az oldalt nem sikerült lefordítani.</translation> +<translation id="6040143037577758943">Bezárás</translation> +<translation id="6831043979455480757">Fordítás</translation> +<translation id="7243308994586599757">A beállítások a képernyő alsó részén találhatók</translation> +<translation id="773466115871691567">Mindig fordítsa le a(z) <ph name="SOURCE_LANGUAGE" /> nyelvű oldalakat</translation> <translation id="8298278839890148234">Böngészős tevékenységek</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_hy.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_hy.xtb index c5676da7cb3..e3c1efcec5e 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_hy.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_hy.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="hy"> +<translation id="1068672505746868501">Երբեք չթարգմանել <ph name="SOURCE_LANGUAGE" /> էջերը</translation> +<translation id="124116460088058876">Այլ լեզուներ</translation> +<translation id="1285320974508926690">Երբեք չթարգմանել այս կայքը</translation> +<translation id="290376772003165898">Էջը <ph name="LANGUAGE" /> չէ՞</translation> +<translation id="5684874026226664614">Չհաջողվեց թարգմանել այս էջը:</translation> +<translation id="6040143037577758943">Փակել</translation> +<translation id="6831043979455480757">Թարգմանել</translation> +<translation id="7243308994586599757">Ընտրանքները հասանելի են էկրանի ստորին հատվածում</translation> +<translation id="773466115871691567">Միշտ թարգմանել <ph name="SOURCE_LANGUAGE" /> էջերը</translation> <translation id="8298278839890148234">Գործողություններ դիտարկիչում</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_id.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_id.xtb index 31b5fe5d797..673f32be14c 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_id.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_id.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="id"> +<translation id="1068672505746868501">Jangan pernah menerjemahkan halaman dalam <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Bahasa lainnya</translation> +<translation id="1285320974508926690">Jangan pernah terjemahkan situs ini</translation> +<translation id="290376772003165898">Halaman tidak dalam bahasa <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups. Halaman ini tidak dapat diterjemahkan.</translation> +<translation id="6040143037577758943">Tutup</translation> +<translation id="6831043979455480757">Terjemahkan</translation> +<translation id="7243308994586599757">Opsi terdapat di dekat bagian bawah layar</translation> +<translation id="773466115871691567">Selalu terjemahkan halaman dalam bahasa <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktivitas penjelajahan web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_is.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_is.xtb index 923602bb298..2f77ab69860 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_is.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_is.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="is"> +<translation id="1068672505746868501">Þýða aldrei síður á þessu tungumáli: <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Fleiri tungumál</translation> +<translation id="1285320974508926690">Aldrei þýða þetta vefsvæði</translation> +<translation id="290376772003165898">Er tungumál síðunnar ekki <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Úbbs. Þessa síðu er ekki hægt að þýða.</translation> +<translation id="6040143037577758943">Loka</translation> +<translation id="6831043979455480757">Þýða</translation> +<translation id="7243308994586599757">Valkostir eru neðst á skjánum</translation> +<translation id="773466115871691567">Þýða alltaf síður á þessu tungumáli: <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Vafranotkun</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_it.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_it.xtb index c4dc6e6ca5d..1f19226503d 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_it.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_it.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="it"> +<translation id="1068672505746868501">Non tradurre mai le pagine in <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Altre lingue</translation> +<translation id="1285320974508926690">Non tradurre mai questo sito</translation> +<translation id="290376772003165898">La pagina non è in <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Spiacenti. Impossibile tradurre questa pagina.</translation> +<translation id="6040143037577758943">Chiudi</translation> +<translation id="6831043979455480757">Traduci</translation> +<translation id="7243308994586599757">Opzioni disponibili nella parte inferiore dello schermo</translation> +<translation id="773466115871691567">Traduci sempre le pagine in <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Attività del browser web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_iw.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_iw.xtb index bea498feca7..61e44631e2e 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_iw.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_iw.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="iw"> +<translation id="1068672505746868501">אף פעם אל תתרגם דפים ב<ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">שפות נוספות</translation> +<translation id="1285320974508926690">איני רוצה לקבל תרגום של אתר זה</translation> +<translation id="290376772003165898">הדף לא ב<ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">אופס. לא ניתן היה לתרגם את הדף הזה.</translation> +<translation id="6040143037577758943">סגור</translation> +<translation id="6831043979455480757">תרגום</translation> +<translation id="7243308994586599757">אפשרויות הזמינות באזור החלק התחתון של המסך</translation> +<translation id="773466115871691567">ברצוני לקבל תרגום תמיד דפים ב<ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">פעילות דפדפן אינטרנט</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ja.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ja.xtb index 091fdcc87ff..cf3a9d91955 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ja.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ja.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ja"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" />のページを翻訳しない</translation> +<translation id="124116460088058876">その他の言語</translation> +<translation id="1285320974508926690">このサイトは翻訳しない</translation> +<translation id="290376772003165898"><ph name="LANGUAGE" />のページではない場合</translation> +<translation id="5684874026226664614">このページを翻訳できませんでした。</translation> +<translation id="6040143037577758943">閉じる</translation> +<translation id="6831043979455480757">翻訳</translation> +<translation id="7243308994586599757">画面の下の方にオプションがあります</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" />のページを常に翻訳する</translation> <translation id="8298278839890148234">ウェブブラウザのアクティビティ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ka.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ka.xtb index 57bc785af7f..2aa6a13f2ff 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ka.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ka.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ka"> +<translation id="1068672505746868501">არასოდეს ითარგმნოს <ph name="SOURCE_LANGUAGE" /> გვერდები</translation> +<translation id="124116460088058876">სხვა ენები</translation> +<translation id="1285320974508926690">არასდროს გადათარგმნო ეს საიტი</translation> +<translation id="290376772003165898">გვერდის ტექსტი არ არის <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">ამ გვერდის გადათარგმნა ვერ მოხერხდა.</translation> +<translation id="6040143037577758943">დახურვა</translation> +<translation id="6831043979455480757">თარგმნა</translation> +<translation id="7243308994586599757">ვარიანტები ხელმისაწვდომია ეკრანის ქვედა ნაწილთან</translation> +<translation id="773466115871691567">ყოველთვის ითარგმნოს <ph name="SOURCE_LANGUAGE" /> გვერდები</translation> <translation id="8298278839890148234">ვებ-ბრაუზერის აქტივობა</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_kk.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_kk.xtb index d18117e9c7f..2e23049928a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_kk.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_kk.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="kk"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> тіліндегі беттер ешқашан аударылмасын</translation> +<translation id="124116460088058876">Қосымша тілдер</translation> +<translation id="1285320974508926690">Бұл сайтты ешқашан аудармау</translation> +<translation id="290376772003165898">Бет <ph name="LANGUAGE" /> тілінде емес пе?</translation> +<translation id="5684874026226664614">Бұл бетті аудару мүмкін емес.</translation> +<translation id="6040143037577758943">Жабу</translation> +<translation id="6831043979455480757">Аудару</translation> +<translation id="7243308994586599757">Опциялар экранның төменгі жағында тұрады</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> тіліндегі беттер әрқашан аударылсын</translation> <translation id="8298278839890148234">Браузерді қолдану мәліметі</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_km.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_km.xtb index 312955fb520..79e7f51cb82 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_km.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_km.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="km"> +<translation id="1068672505746868501">កុំបកប្រែទំព័រជាភាសា <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">ភាសាច្រើនទៀត</translation> +<translation id="1285320974508926690">មិនបកប្រែគេហទំព័រនេះទៀតឡើយ</translation> +<translation id="290376772003165898">ទំព័រមិនមានជាភាសា <ph name="LANGUAGE" /> ទេ?</translation> +<translation id="5684874026226664614">អូ។ ទំព័រនេះមិនអាចបកប្រែទេ។</translation> +<translation id="6040143037577758943">បិទ</translation> +<translation id="6831043979455480757">បកប្រែ</translation> +<translation id="7243308994586599757">មានជម្រើសនៅក្បែរផ្នែកខាងក្រោមអេក្រង់</translation> +<translation id="773466115871691567">បកប្រែទំព័រជាភាសា <ph name="SOURCE_LANGUAGE" /> ជានិច្ច</translation> <translation id="8298278839890148234">សកម្មភាពកម្មវិធីរុករកតាមអ៊ីនធឺណិត</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_kn.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_kn.xtb index 91524cd5c0f..cabf85f2d68 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_kn.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_kn.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="kn"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> ನಲ್ಲಿನ ಪುಟಗಳನ್ನು ಎಂದಿಗೂ ಅನುವಾದ ಮಾಡಬೇಡಿ</translation> +<translation id="124116460088058876">ಹೆಚ್ಚಿನ ಭಾಷೆಗಳು</translation> +<translation id="1285320974508926690">ಈ ಸೈಟ್ ಅನ್ನು ಎಂದಿಗೂ ಭಾಷಾಂತರಿಸದಿರಿ</translation> +<translation id="290376772003165898"><ph name="LANGUAGE" /> ನಲ್ಲಿನ ಪುಟ ಇಲ್ಲವೇ?</translation> +<translation id="5684874026226664614">ಓಹ್. ಈ ಪುಟವನ್ನು ಅನುವಾದಿಸಲಾಗುವುದಿಲ್ಲ.</translation> +<translation id="6040143037577758943">ಮುಚ್ಚಿರಿ</translation> +<translation id="6831043979455480757">ಅನುವಾದಿಸು</translation> +<translation id="7243308994586599757">ಪರದೆಯ ಕೆಳಗೆ ಲಭ್ಯವಿರುವ ಆಯ್ಕೆಗಳು</translation> +<translation id="773466115871691567">ಯಾವಾಗಲು <ph name="SOURCE_LANGUAGE" /> ನಲ್ಲಿನ ಪುಟಗಳನ್ನು ಅನುವಾದ ಮಾಡಿ</translation> <translation id="8298278839890148234">ವೆಬ್ ಬ್ರೌಸಿಂಗ್ ಚಟುವಟಿಕೆ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ko.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ko.xtb index 742a5338444..de20f5dc367 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ko.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ko.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ko"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" />로 된 페이지는 번역하지 않음</translation> +<translation id="124116460088058876">다른 언어</translation> +<translation id="1285320974508926690">이 사이트 번역 안함</translation> +<translation id="290376772003165898">페이지 언어가 <ph name="LANGUAGE" />가 아닌가요?</translation> +<translation id="5684874026226664614">죄송합니다. 이 페이지를 번역할 수 없습니다.</translation> +<translation id="6040143037577758943">닫기</translation> +<translation id="6831043979455480757">번역</translation> +<translation id="7243308994586599757">화면 하단에서 옵션 선택 가능</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" />로 된 페이지를 항상 번역</translation> <translation id="8298278839890148234">웹브라우저 활동</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ky.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ky.xtb index 2d0f4b479b2..717031affb9 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ky.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ky.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ky"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> тилиндеги барактар эч качан которулбасын</translation> +<translation id="124116460088058876">Дагы тилдер</translation> +<translation id="1285320974508926690">Бул сайт эч качан которулбасын</translation> +<translation id="290376772003165898">Барак <ph name="LANGUAGE" /> тилинде эмес бекен?</translation> +<translation id="5684874026226664614">Ой, бул бет которулган жок.</translation> +<translation id="6040143037577758943">Жабуу</translation> +<translation id="6831043979455480757">Которуу</translation> +<translation id="7243308994586599757">Параметрлер экрандын түбүндө берилген</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> тилиндеги барактар дайыма которулсун</translation> <translation id="8298278839890148234">Көрүлгөн вебсайттар</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_lo.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_lo.xtb index fba8f18695e..7ab378b6803 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_lo.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_lo.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="lo"> +<translation id="1068672505746868501">ຢ່າແປໜ້າຕ່າງໆໃນ <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">ພາສາເພີ່ມເຕີມ</translation> +<translation id="1285320974508926690">ຢ່າແປເວັບໄຊທ໌ນີ້</translation> +<translation id="290376772003165898">ໜ້າບໍ່ຢູ່ໃນ <ph name="LANGUAGE" /> ບໍ?</translation> +<translation id="5684874026226664614">ອຸ້ຍ. ບໍ່ສາມາດແປໜ້ານີ້ໄດ້.</translation> +<translation id="6040143037577758943">ປິດ</translation> +<translation id="6831043979455480757">ແປພາສາ</translation> +<translation id="7243308994586599757">ທາງເລືອກມີໃຫ້ໃກ້ປຸ່ມຂອງໜ້າຈໍ</translation> +<translation id="773466115871691567">ແປໜ້າຕ່າງໆສະເໝີໃນ <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">ການເຄື່ອນໄຫວໃນໂປຣແກຣມທ່ອງເວັບ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_lt.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_lt.xtb index a1a90e6ff7c..05d3d0706ff 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_lt.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_lt.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="lt"> +<translation id="1068672505746868501">Niekada neversti puslapių, parašytų <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Daugiau kalbų</translation> +<translation id="1285320974508926690">Niekada neversti šios svetainės</translation> +<translation id="290376772003165898">Puslapis parašytas ne <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Oi, nepavyko išversti šio puslapio.</translation> +<translation id="6040143037577758943">Uždaryti</translation> +<translation id="6831043979455480757">Vertėjas</translation> +<translation id="7243308994586599757">Parinktys pasiekiamos netoli ekrano apačios</translation> +<translation id="773466115871691567">Visada versti puslapius, parašytus <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Žiniatinklio naršyklės veikla</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_lv.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_lv.xtb index 5d965478866..d53de48768c 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_lv.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_lv.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="lv"> +<translation id="1068672505746868501">Nekad netulkot lapas šādā valodā: <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Citas valodas…</translation> +<translation id="1285320974508926690">Nekad netulkot šo vietni</translation> +<translation id="290376772003165898">Vai lapa nav šajā valodā: <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Hmm... Šo lapu nevarēja iztulkot.</translation> +<translation id="6040143037577758943">Aizvērt</translation> +<translation id="6831043979455480757">Tulkot</translation> +<translation id="7243308994586599757">Opcijas, kas pieejamas ekrāna apakšējā daļā</translation> +<translation id="773466115871691567">Vienmēr tulkot lapas šādā valodā: <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Pārlūkošanas darbības</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_mk.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_mk.xtb index 2f2d3c2b91b..82359fc6539 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_mk.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_mk.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="mk"> +<translation id="1068672505746868501">Никогаш не преведувај страници на <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Повеќе јазици</translation> +<translation id="1285320974508926690">Никогаш не преведувај ја оваа локација</translation> +<translation id="290376772003165898">Страницата не е на <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Оваа страница не може да се преведе.</translation> +<translation id="6040143037577758943">Затвори</translation> +<translation id="6831043979455480757">Преведи</translation> +<translation id="7243308994586599757">Достапни се опции на дното на екранот</translation> +<translation id="773466115871691567">Секогаш преведувај ги страниците на <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Активност на прелистувачот</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ml.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ml.xtb index d4a01802579..8d51f41f713 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ml.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ml.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ml"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> ഭാഷയിലുള്ള പേജുകൾ ഒരിക്കലും വിവർത്തനം ചെയ്യരുത്</translation> +<translation id="124116460088058876">കൂടുതൽ ഭാഷകൾ</translation> +<translation id="1285320974508926690">ഈ സൈറ്റ് ഒരിക്കലും വിവര്ത്തനം ചെയ്യരുത്</translation> +<translation id="290376772003165898">പേജ് <ph name="LANGUAGE" /> ഭാഷയിലല്ലേ?</translation> +<translation id="5684874026226664614">ക്ഷമിക്കണം. ഈ പേജ് വിവർത്തനം ചെയ്യാനായില്ല.</translation> +<translation id="6040143037577758943">അടയ്ക്കുക</translation> +<translation id="6831043979455480757">വിവർത്തനം ചെയ്യുക</translation> +<translation id="7243308994586599757">സ്ക്രീനിന്റെ ചുവടെ ഓപ്ഷനുകൾ ലഭ്യമാണ്</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> ഭാഷയിലുള്ള പേജുകൾ എപ്പോഴും വിവർത്തനം ചെയ്യുക</translation> <translation id="8298278839890148234">വെബ് ബ്രൗസർ ആക്റ്റിവിറ്റി</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_mn.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_mn.xtb index 79d5dac7607..2f721cf0b56 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_mn.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_mn.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="mn"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> хэл дээрх хуудсыг хэзээ ч орчуулахгүй</translation> +<translation id="124116460088058876">Бусад хэл</translation> +<translation id="1285320974508926690">Энэ сайтыг хэзээ ч бүү хөрвүүл</translation> +<translation id="290376772003165898">Хуудас <ph name="LANGUAGE" /> хэл дээр биш байна уу?</translation> +<translation id="5684874026226664614">Өө. Энэ хуудсыг хөрвүүлж чадсангүй.</translation> +<translation id="6040143037577758943">Хаах</translation> +<translation id="6831043979455480757">Хөрвүүлэх</translation> +<translation id="7243308994586599757">Дэлгэцийн доод хэсэгт сонголт боломжтой</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> хэл дээрх хуудсыг байнга орчуулна</translation> <translation id="8298278839890148234">Хөтчийн үйл ажиллагаа</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_mr.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_mr.xtb index 5a3e6820752..66b36082e3d 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_mr.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_mr.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="mr"> +<translation id="1068672505746868501">पेज <ph name="SOURCE_LANGUAGE" /> मध्ये कधीही भाषांतरित करू नका</translation> +<translation id="124116460088058876">आणखी भाषा...</translation> +<translation id="1285320974508926690">या साइटचा कधीही भाषांतर करु नका</translation> +<translation id="290376772003165898">पेज <ph name="LANGUAGE" />मध्ये नाही?</translation> +<translation id="5684874026226664614">अरेरे. हे पृष्ठ भाषांतरित केले जाऊ शकले नाही.</translation> +<translation id="6040143037577758943">बंद करा</translation> +<translation id="6831043979455480757">भाषांतर करा</translation> +<translation id="7243308994586599757">स्क्रीनच्या तळाशी पर्याय उपलब्ध आहेत</translation> +<translation id="773466115871691567">नेहमी पेज <ph name="SOURCE_LANGUAGE" />मध्ये भाषांतरित करा</translation> <translation id="8298278839890148234">वेब ब्राउझर अॅक्टिव्हिटी</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ms.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ms.xtb index e105952bce6..9bca4a8918a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ms.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ms.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ms"> +<translation id="1068672505746868501">Jangan sekali-kali terjemahkan halaman dalam <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Lagi bahasa</translation> +<translation id="1285320974508926690">Jangan sekali-kali menterjemahkan tapak ini</translation> +<translation id="290376772003165898">Halaman bukan dalam <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Op. Halaman ini tidak dapat diterjemahkan.</translation> +<translation id="6040143037577758943">Tutup</translation> +<translation id="6831043979455480757">Terjemah</translation> +<translation id="7243308994586599757">Pilihan tersedia berhampiran bahagian bawah skrin</translation> +<translation id="773466115871691567">Sentiasa terjemahkan halaman dalam <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktiviti penyemakan imbas</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_my.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_my.xtb index e8bf710e16c..ed292bfc27e 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_my.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_my.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="my"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> ဘာသာ စာမျက်နှာများကို ဘယ်သောအခါမျှ ဘာသာမပြန်ရန်</translation> +<translation id="124116460088058876">နောက်ထပ် ဘာသာစကားများ</translation> +<translation id="1285320974508926690">ဒီဆိုက်ကို ဘယ်တော့မှ ဘာသာမပြန်ပါနှင့်</translation> +<translation id="290376772003165898">စာမျက်နှာသည် <ph name="LANGUAGE" /> ဖြင့် ဟုတ်ပါသလား။</translation> +<translation id="5684874026226664614">အူးပ်စ်။ ဒီစာမျက်နှာကို ဘာသာပြန် မရနိုင်ခဲ့ပါ။</translation> +<translation id="6040143037577758943">ပိတ်ရန်</translation> +<translation id="6831043979455480757">ဘာသာပြန်ရန်</translation> +<translation id="7243308994586599757">ရွေးစရာများမှာ မျက်နှာပြင်၏ အောက်ခြေပိုင်းနားမှာ ရှိကြသည်</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> ဘာသာဖြင့် စာမျက်နှာများအားလုံးကို အမြဲဘာသာပြန်ရန်</translation> <translation id="8298278839890148234">ဝဘ်ဘရောင်ဇာ လုပ်ဆောင်ချက်</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ne.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ne.xtb index 06ec47e44f4..11e137141d1 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ne.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ne.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ne"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> मा भएका पृष्ठहरूलाई कहिल्यै अनुवाद नगर्नुहोस्</translation> +<translation id="124116460088058876">थप भाषाहरू</translation> +<translation id="1285320974508926690">यो साइट कहिले पनि अनुवाद नगर्नुहोस्</translation> +<translation id="290376772003165898"><ph name="LANGUAGE" /> भाषामा पृष्ठ छैन?</translation> +<translation id="5684874026226664614">ओहो। यो पृष्ठ अनुवादन गर्न सकिएन।</translation> +<translation id="6040143037577758943">बन्द गर्नुहोस्</translation> +<translation id="6831043979455480757">अनुवाद गर्नुहोस्</translation> +<translation id="7243308994586599757">विकल्पहरू स्क्रिनको तल नजिकै उपलब्ध छ</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> मा रहेका पृष्ठहरूलाई सधैँ अनुवाद गर्नुहोस्</translation> <translation id="8298278839890148234">ब्राउजर प्रयोग गरी गरिएको क्रियाकलाप</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_nl.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_nl.xtb index 759b04e50cc..cd20f14e5ca 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_nl.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_nl.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="nl"> +<translation id="1068672505746868501">Pagina's in het <ph name="SOURCE_LANGUAGE" /> nooit vertalen</translation> +<translation id="124116460088058876">Meer talen</translation> +<translation id="1285320974508926690">Deze site nooit vertalen</translation> +<translation id="290376772003165898">Is deze pagina niet in het <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Deze pagina kan niet worden vertaald.</translation> +<translation id="6040143037577758943">Sluiten</translation> +<translation id="6831043979455480757">Vertalen</translation> +<translation id="7243308994586599757">Opties beschikbaar onder aan het scherm</translation> +<translation id="773466115871691567">Pagina's in het <ph name="SOURCE_LANGUAGE" /> altijd vertalen</translation> <translation id="8298278839890148234">Webbrowseractivititeit</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_no.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_no.xtb index 409a800ee70..0a02095d40b 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_no.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_no.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="no"> +<translation id="1068672505746868501">Oversett aldri sider på <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Flere språk</translation> +<translation id="1285320974508926690">Oversett aldri dette nettstedet</translation> +<translation id="290376772003165898">Er ikke siden på <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Beklager. Denne siden kunne ikke oversettes.</translation> +<translation id="6040143037577758943">Lukk</translation> +<translation id="6831043979455480757">Oversett</translation> +<translation id="7243308994586599757">Du finner alternativer ved bunnen av skjermen</translation> +<translation id="773466115871691567">Oversett alltid sider på <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Nettleseraktivitet</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_or.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_or.xtb index f88f14b33c3..8d046c770c1 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_or.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_or.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="or"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" />ରେ ପୃଷ୍ଠାଗୁଡ଼ିକୁ ଅନୁବାଦ କରନ୍ତୁ ନାହିଁ</translation> +<translation id="124116460088058876">ଅନେକ ଭାଷା</translation> +<translation id="1285320974508926690">ଏହି ସାଇଟ୍କୁ କଦାପି ଅନୁବାଦ କରନ୍ତୁ ନାହିଁ</translation> +<translation id="290376772003165898">ପୃଷ୍ଠାଟି <ph name="LANGUAGE" />ରେ ନାହିଁ?</translation> +<translation id="5684874026226664614">ଓହୋଃ! ଏହି ପୃଷ୍ଠା ଅନୁବାଦ କରାଯାଇପାରିଲା ନାହିଁ।</translation> +<translation id="6040143037577758943">ବନ୍ଦ</translation> +<translation id="6831043979455480757">ଅନୁବାଦ କରନ୍ତୁ</translation> +<translation id="7243308994586599757">ସ୍କ୍ରିନ୍ର ନିମ୍ନରେ ବିକଳ୍ପଗୁଡ଼ିକ ଉପଲବ୍ଧ ଅଛି</translation> +<translation id="773466115871691567">ସର୍ବଦା <ph name="SOURCE_LANGUAGE" />ରେ ପୃଷ୍ଠାଗୁଡ଼ିକୁ ଅନୁବାଦ କରନ୍ତୁ</translation> <translation id="8298278839890148234">ୱେବ୍ ବ୍ରାଉଜର୍ କାର୍ଯ୍ୟକଳାପ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_pa.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_pa.xtb index 0d4c8c78fba..6394a67f909 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_pa.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_pa.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="pa"> +<translation id="1068672505746868501">ਕਦੇ ਵੀ ਪੰਨਿਆਂ ਦਾ ਅਨੁਵਾਦ <ph name="SOURCE_LANGUAGE" /> ਵਿੱਚ ਨਾ ਕਰੋ</translation> +<translation id="124116460088058876">ਹੋਰ ਭਾਸ਼ਾਵਾਂ</translation> +<translation id="1285320974508926690">ਕਦੇ ਵੀ ਇਸ ਸਾਈਟ ਦਾ ਅਨੁਵਾਦ ਨਾ ਕਰੋ</translation> +<translation id="290376772003165898">ਕੀ ਪੰਨਾ <ph name="LANGUAGE" /> ਵਿੱਚ ਨਹੀਂ ਹੈ?</translation> +<translation id="5684874026226664614">ਓਹੋ। ਇਸ ਪੰਨੇ ਦਾ ਅਨੁਵਾਦ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</translation> +<translation id="6040143037577758943">ਬੰਦ ਕਰੋ</translation> +<translation id="6831043979455480757">ਅਨੁਵਾਦ ਕਰੋ</translation> +<translation id="7243308994586599757">ਵਿਕਲਪ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਪਾਸੇ ਦੇ ਕੋਲ ਉਪਲਬਧ ਹਨ</translation> +<translation id="773466115871691567">ਪੰਨਿਆਂ ਦਾ ਅਨੁਵਾਦ ਹਮੇਸ਼ਾਂ <ph name="SOURCE_LANGUAGE" /> ਭਾਸ਼ਾ ਵਿੱਚ ਕਰੋ</translation> <translation id="8298278839890148234">ਵੈੱਬ ਬ੍ਰਾਊਜ਼ਰ ਸਰਗਰਮੀ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_pl.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_pl.xtb index 52a394f3e02..2185aeb5d3d 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_pl.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_pl.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="pl"> +<translation id="1068672505746868501">Nigdy nie tłumacz stron, których językiem jest <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Więcej języków</translation> +<translation id="1285320974508926690">Nigdy nie tłumacz tej witryny</translation> +<translation id="290376772003165898">Język tej strony to nie <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Nie można przetłumaczyć tej strony.</translation> +<translation id="6040143037577758943">Zamknij</translation> +<translation id="6831043979455480757">Tłumacz</translation> +<translation id="7243308994586599757">Opcje dostępne u dołu ekranu</translation> +<translation id="773466115871691567">Zawsze tłumacz strony, których językiem jest <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktywność w przeglądarce</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_pt-BR.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_pt-BR.xtb index dd6972f79e3..45880dbbefa 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_pt-BR.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_pt-BR.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="pt-BR"> +<translation id="1068672505746868501">Nunca traduzir páginas em <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Mais idiomas</translation> +<translation id="1285320974508926690">Nunca traduzir este site</translation> +<translation id="290376772003165898">A página não está em <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Não foi possível traduzir esta página.</translation> +<translation id="6040143037577758943">Fechar</translation> +<translation id="6831043979455480757">Traduzir</translation> +<translation id="7243308994586599757">Opções disponíveis perto da parte inferior da tela</translation> +<translation id="773466115871691567">Sempre traduzir páginas em <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Atividade do navegador da Web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_pt-PT.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_pt-PT.xtb index 76966566f3d..0b06168100d 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_pt-PT.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_pt-PT.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="pt-PT"> +<translation id="1068672505746868501">Nunca traduzir páginas em <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Mais idiomas</translation> +<translation id="1285320974508926690">Nunca traduzir este site</translation> +<translation id="290376772003165898">A página não está em <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups! Não foi possível traduzir esta página.</translation> +<translation id="6040143037577758943">Fechar</translation> +<translation id="6831043979455480757">Traduzir</translation> +<translation id="7243308994586599757">Opções disponíveis junto à parte inferior do ecrã</translation> +<translation id="773466115871691567">Traduza sempre páginas em <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Atividade do navegador de Internet</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ro.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ro.xtb index 12f45d28945..87a6bce7c07 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ro.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ro.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ro"> +<translation id="1068672505746868501">Nu traduce niciodată paginile în <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Mai multe limbi</translation> +<translation id="1285320974508926690">Nu traduce niciodată acest site</translation> +<translation id="290376772003165898">Pagina nu este în <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Hopa. Această pagină nu a putut fi tradusă.</translation> +<translation id="6040143037577758943">Închide</translation> +<translation id="6831043979455480757">Tradu</translation> +<translation id="7243308994586599757">Opțiuni disponibile în partea de jos a ecranului</translation> +<translation id="773466115871691567">Tradu întotdeauna paginile în <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Activitatea în browserul web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ru.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ru.xtb index 401647df65a..733f199d730 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ru.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ru.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ru"> +<translation id="1068672505746868501">Не переводить страницы на этом языке: <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Другие языки</translation> +<translation id="1285320974508926690">Никогда не переводить этот сайт</translation> +<translation id="290376772003165898">Язык страницы не <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Не удалось перевести страницу</translation> +<translation id="6040143037577758943">Закрыть</translation> +<translation id="6831043979455480757">Перевести</translation> +<translation id="7243308994586599757">Доступные параметры указаны в нижней части экрана</translation> +<translation id="773466115871691567">Переводить страницы на этом языке: <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Действия в веб-браузере</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_si.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_si.xtb index 1e95ef55536..90b6f032c71 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_si.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_si.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="si"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> හි පිටු කිසිදාක පරිවර්තනය කරන්න එපා</translation> +<translation id="124116460088058876">තවත් භාෂා</translation> +<translation id="1285320974508926690">මෙම අඩවිය කිසිවිට පරිවර්තනය නොකරන්න</translation> +<translation id="290376772003165898">පිටුව <ph name="LANGUAGE" /> බසින් නොවේ ද?</translation> +<translation id="5684874026226664614">අපොයි. මෙම පිටුව පැටවිය නොහැකි විය.</translation> +<translation id="6040143037577758943">වසන්න</translation> +<translation id="6831043979455480757">පරිවර්තනය කරන්න</translation> +<translation id="7243308994586599757">තිරයේ පහළට ආසන්නව විකල්ප ලබා ගත හැකිය</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> හි පිටු සැමවිටම පරිවර්තනය කරන්න</translation> <translation id="8298278839890148234">වෙබ් බ්රවුසර ක්රියාකාරකම</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sk.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sk.xtb index 53b2d4ec263..fe4bb0c1612 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sk.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sk.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sk"> +<translation id="1068672505746868501">Nikdy neprekladať stránky v jazyku <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Ďalšie jazyky</translation> +<translation id="1285320974508926690">Nikdy neprekladať tieto webové stránky</translation> +<translation id="290376772003165898">Stránka nie je v jazyku <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Hops. Túto stránku nebolo možné preložiť.</translation> +<translation id="6040143037577758943">Zavrieť</translation> +<translation id="6831043979455480757">Preložiť</translation> +<translation id="7243308994586599757">Možnosti sú k dispozícii v dolnej časti obrazovky</translation> +<translation id="773466115871691567">Vždy prekladať stránky v jazyku <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktivita webového prehliadača</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sl.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sl.xtb index 152968ea560..bc91c54ae43 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sl.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sl.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sl"> +<translation id="1068672505746868501">Nikoli ne prevedi strani v jeziku <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Več jezikov</translation> +<translation id="1285320974508926690">Nikoli ne prevedi tega spletnega mesta</translation> +<translation id="290376772003165898">Stran ni v jeziku <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ojoj, te strani bi bilo mogoče prevesti.</translation> +<translation id="6040143037577758943">Zapri</translation> +<translation id="6831043979455480757">Prevedi</translation> +<translation id="7243308994586599757">Možnosti so na voljo pri dnu zaslona</translation> +<translation id="773466115871691567">Vedno prevedi strani v jeziku <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Dejavnost brskalnika</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sq.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sq.xtb index 2e0a6950797..c88ab6c0cfc 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sq.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sq.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sq"> +<translation id="1068672505746868501">Mos përkthe asnjëherë faqet që janë në <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Gjuhë të tjera</translation> +<translation id="1285320974508926690">Asnjëherë mos e përkthe këtë sajt</translation> +<translation id="290376772003165898">Faqja nuk është në <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Mos! Kjo faqe nuk mund të përkthehej.</translation> +<translation id="6040143037577758943">Mbyll</translation> +<translation id="6831043979455480757">Përkthe</translation> +<translation id="7243308994586599757">Opsionet janë të disponueshme pranë fundit të ekranit</translation> +<translation id="773466115871691567">Përkthe gjithmonë faqet që janë në <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktiviteti i shfletuesit të uebit</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sr-Latn.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sr-Latn.xtb index 24f260082a7..d66789dacf8 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sr-Latn.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sr-Latn.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sr-Latn"> +<translation id="1068672505746868501">Nikad ne prevodi stranice na jeziku <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Još jezika</translation> +<translation id="1285320974508926690">Nikad ne prevodi ovaj sajt</translation> +<translation id="290376772003165898">Ova stranica nije na jeziku <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Ups, prevođenje ove stranice nije uspelo.</translation> +<translation id="6040143037577758943">Zatvori</translation> +<translation id="6831043979455480757">Prevedi</translation> +<translation id="7243308994586599757">Opcije su dostupne u dnu ekrana</translation> +<translation id="773466115871691567">Uvek predvodi stranice na jeziku <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Aktivnosti u veb-pregledaču</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sr.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sr.xtb index 9008032cbef..d5eb9aa0fef 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sr.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sr.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sr"> +<translation id="1068672505746868501">Никад не преводи странице на језику <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Још језика</translation> +<translation id="1285320974508926690">Никад не преводи овај сајт</translation> +<translation id="290376772003165898">Ова страница није на језику <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Упс, превођење ове странице није успело.</translation> +<translation id="6040143037577758943">Затвори</translation> +<translation id="6831043979455480757">Преведи</translation> +<translation id="7243308994586599757">Опције су доступне у дну екрана</translation> +<translation id="773466115871691567">Увек предводи странице на језику <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Активности у веб-прегледачу</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sv.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sv.xtb index 610cb47e53f..532dc6eb359 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sv.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sv.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sv"> +<translation id="1068672505746868501">Översätt aldrig sidor på <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Fler språk</translation> +<translation id="1285320974508926690">Översätt aldrig den här webbplatsen</translation> +<translation id="290376772003165898">Är sidan inte på <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Det gick inte att översätta sidan.</translation> +<translation id="6040143037577758943">Stäng</translation> +<translation id="6831043979455480757">Översätt</translation> +<translation id="7243308994586599757">Alternativ visas nära skärmens nedre kant</translation> +<translation id="773466115871691567">Översätt alltid sidor på <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Webbläsaraktivitet</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_sw.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_sw.xtb index cf0a4b711da..2854041492f 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_sw.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_sw.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="sw"> +<translation id="1068672505746868501">Usiwahi kutafsiri kurasa katika lugha ya <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Lugha zaidi</translation> +<translation id="1285320974508926690">Kamwe usitafsiri tovuti hii</translation> +<translation id="290376772003165898">Je, ukurasa huu haupo katika <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Lo! Ukurasa huu haukuweza kutafsiriwa.</translation> +<translation id="6040143037577758943">Funga</translation> +<translation id="6831043979455480757">Tafsiri</translation> +<translation id="7243308994586599757">Chaguo zinapatikana karibu na sehemu ya chini ya skrini</translation> +<translation id="773466115871691567">Zitafsiri kurasa katika <ph name="SOURCE_LANGUAGE" /> wakati wote</translation> <translation id="8298278839890148234">Shughuli za kivinjari</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ta.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ta.xtb index 8a47545a26a..c7f7130e4c5 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ta.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ta.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ta"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> மொழியில் உள்ள பக்கங்களை ஒருபோதும் மொழிபெயர்க்காதே</translation> +<translation id="124116460088058876">மேலும் மொழிகள்</translation> +<translation id="1285320974508926690">இந்த தளத்தை எப்போதும் மொழிபெயர்க்க வேண்டாம்</translation> +<translation id="290376772003165898"><ph name="LANGUAGE" /> மொழியில் பக்கம் இல்லையா?</translation> +<translation id="5684874026226664614">அச்சச்சோ. இந்தப் பக்கத்தை மொழிபெயர்க்க முடியாது.</translation> +<translation id="6040143037577758943">மூடு</translation> +<translation id="6831043979455480757">மொழிபெயர்</translation> +<translation id="7243308994586599757">திரையின் கீழ்ப்பகுதிக்கு அருகில் கிடைக்கும் விருப்பங்கள்</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> மொழியில் உள்ள பக்கங்களை எப்போதும் மொழிபெயர்</translation> <translation id="8298278839890148234">இணைய உலாவியின் செயல்பாடு</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_te.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_te.xtb index 3894c532d87..53279940602 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_te.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_te.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="te"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" />లో ఉన్న పేజీలను ఎప్పుడూ అనువదించవద్దు</translation> +<translation id="124116460088058876">మరిన్ని భాషలు</translation> +<translation id="1285320974508926690">ఈ సైట్ను ఎప్పటికీ అనువదించవద్దు</translation> +<translation id="290376772003165898">పేజీ <ph name="LANGUAGE" />లో లేదా?</translation> +<translation id="5684874026226664614">అయ్యో. ఈ పేజీని అనువదించడం సాధ్యపడలేదు.</translation> +<translation id="6040143037577758943">మూసివేయి</translation> +<translation id="6831043979455480757">అనువదించు</translation> +<translation id="7243308994586599757">స్క్రీన్ దిగువభాగం సమీపంలో ఎంపికలు అందుబాటులో ఉంటాయి</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" />లో ఉన్న పేజీలను ఎల్లప్పుడూ అనువదించు</translation> <translation id="8298278839890148234">బ్రౌజింగ్ యాక్టివిటీ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_th.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_th.xtb index 1c8e14c9ca0..024e4b547a9 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_th.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_th.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="th"> +<translation id="1068672505746868501">ไม่ต้องแปลหน้าเว็บภาษา<ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">ภาษาเพิ่มเติม</translation> +<translation id="1285320974508926690">ไม่ต้องแปลเว็บไซต์นี้</translation> +<translation id="290376772003165898">หน้านี้ไม่ใช่ภาษา<ph name="LANGUAGE" />ใช่ไหม</translation> +<translation id="5684874026226664614">อ๊ะ หน้านี้ไม่สามารถแปลได้</translation> +<translation id="6040143037577758943">ปิด</translation> +<translation id="6831043979455480757">แปลภาษา</translation> +<translation id="7243308994586599757">มีตัวเลือกอยู่ทางด้านล่างของหน้าจอ</translation> +<translation id="773466115871691567">แปลหน้าเว็บภาษา<ph name="SOURCE_LANGUAGE" />ทุกครั้ง</translation> <translation id="8298278839890148234">กิจกรรมของเว็บเบราว์เซอร์</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_tr.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_tr.xtb index 11d4ab4aeef..2f9a035ea1b 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_tr.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_tr.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="tr"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> dilindeki sayfaları asla çevirme</translation> +<translation id="124116460088058876">Diğer diller</translation> +<translation id="1285320974508926690">Bu siteyi hiçbir zaman çevirme</translation> +<translation id="290376772003165898">Sayfa <ph name="LANGUAGE" /> dilinde değil mi?</translation> +<translation id="5684874026226664614">Hata! Bu sayfa çevrilemedi.</translation> +<translation id="6040143037577758943">Kapat</translation> +<translation id="6831043979455480757">Çevir</translation> +<translation id="7243308994586599757">Sayfanın altına yakın bir yerde kullanılabilen seçenekler</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> dilindeki sayfaları her zaman çevir</translation> <translation id="8298278839890148234">Web tarayıcısı etkinliği</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_uk.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_uk.xtb index f8630458317..4a29de8edb5 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_uk.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_uk.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="uk"> +<translation id="1068672505746868501">Ніколи не перекладати сторінки такою мовою: <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Інші мови</translation> +<translation id="1285320974508926690">Ніколи не перекладати цей сайт</translation> +<translation id="290376772003165898">Ця сторінка відображається не такою мовою: <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">На жаль, цю сторінку неможливо перекласти.</translation> +<translation id="6040143037577758943">Закрити</translation> +<translation id="6831043979455480757">Перекласти</translation> +<translation id="7243308994586599757">Опції можна знайти внизу екрана</translation> +<translation id="773466115871691567">Завжди перекладати сторінки такою мовою: <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Дії у веб-переглядачі</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_ur.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_ur.xtb index 57e46884972..4113ad7cdaa 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_ur.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_ur.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="ur"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> کے صفحات کا کبھی ترجمہ نہ کریں</translation> +<translation id="124116460088058876">مزید زبانیں</translation> +<translation id="1285320974508926690">اس سائٹ کا ترجمہ کبھی نہ کریں</translation> +<translation id="290376772003165898">صفحہ <ph name="LANGUAGE" /> میں نہیں ہے؟</translation> +<translation id="5684874026226664614">افوہ۔ اس صفحہ کا ترجمہ نہیں کیا جا سکا۔</translation> +<translation id="6040143037577758943">بند کریں</translation> +<translation id="6831043979455480757">ترجمہ کریں</translation> +<translation id="7243308994586599757">اسکرین کے نچلے حصہ کے قریب اختیارات دستیاب ہیں</translation> +<translation id="773466115871691567">ہمیشہ <ph name="SOURCE_LANGUAGE" /> کے صفحات کا ترجمہ کریں</translation> <translation id="8298278839890148234">ویب براؤزر کی سرگرمی</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_uz.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_uz.xtb index 92655c71edf..f0af58d90cd 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_uz.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_uz.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="uz"> +<translation id="1068672505746868501"><ph name="SOURCE_LANGUAGE" /> tilidagi sahifalar hech qachon tarjima qilinmasin</translation> +<translation id="124116460088058876">Boshqa tillar</translation> +<translation id="1285320974508926690">Bu sayt hech qachon tarjima qilinmasin</translation> +<translation id="290376772003165898">Sahifa <ph name="LANGUAGE" /> tilida emasmi?</translation> +<translation id="5684874026226664614">Bu sahifani tarjima qilib bo‘lmadi.</translation> +<translation id="6040143037577758943">Yopish</translation> +<translation id="6831043979455480757">Tarjima</translation> +<translation id="7243308994586599757">Parametrlar ekranning quyi qismiga yaqinroq joyda</translation> +<translation id="773466115871691567"><ph name="SOURCE_LANGUAGE" /> tilidagi sahifalar doim tarjima qilinsin</translation> <translation id="8298278839890148234">Veb-brauzerdagi faoliyat</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_vi.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_vi.xtb index 2f57bd60947..2744201fd71 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_vi.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_vi.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="vi"> +<translation id="1068672505746868501">Không bao giờ dịch các trang viết bằng <ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Ngôn ngữ khác</translation> +<translation id="1285320974508926690">Không bao giờ dịch trang web này</translation> +<translation id="290376772003165898">Trang này không được viết bằng <ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Rất tiếc. Không thể dịch trang này.</translation> +<translation id="6040143037577758943">Đóng</translation> +<translation id="6831043979455480757">Dịch</translation> +<translation id="7243308994586599757">Có các tùy chọn ở gần cuối màn hình</translation> +<translation id="773466115871691567">Luôn dịch các trang viết bằng <ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Hoạt động duyệt web</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-CN.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-CN.xtb index 84075ec9807..0c3183bb774 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-CN.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-CN.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="zh-CN"> +<translation id="1068672505746868501">一律不翻译<ph name="SOURCE_LANGUAGE" />网页</translation> +<translation id="124116460088058876">更多语言</translation> +<translation id="1285320974508926690">一律不翻译此网站</translation> +<translation id="290376772003165898">网页的源语言不是<ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">糟糕,此网页内容无法翻译。</translation> +<translation id="6040143037577758943">关闭</translation> +<translation id="6831043979455480757">翻译</translation> +<translation id="7243308994586599757">选项在靠近屏幕底部的位置</translation> +<translation id="773466115871691567">一律翻译<ph name="SOURCE_LANGUAGE" />网页</translation> <translation id="8298278839890148234">网络浏览器活动</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-HK.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-HK.xtb index abd42fba040..3b6aab868dd 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-HK.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-HK.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="zh-HK"> +<translation id="1068672505746868501">永不翻譯來源語言為<ph name="SOURCE_LANGUAGE" />的網頁</translation> +<translation id="124116460088058876">更多語言</translation> +<translation id="1285320974508926690">永不翻譯此網站</translation> +<translation id="290376772003165898">網頁的來源語言不是<ph name="LANGUAGE" />嗎?</translation> +<translation id="5684874026226664614">糟糕!系統無法翻譯這個網頁的內容。</translation> +<translation id="6040143037577758943">關閉</translation> +<translation id="6831043979455480757">翻譯</translation> +<translation id="7243308994586599757">您可在畫面底部附近找到選項</translation> +<translation id="773466115871691567">一律翻譯來源語言為<ph name="SOURCE_LANGUAGE" />的網頁</translation> <translation id="8298278839890148234">網絡瀏覽器活動</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-TW.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-TW.xtb index ecea64ea7f4..d338d98229a 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_zh-TW.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_zh-TW.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="zh-TW"> +<translation id="1068672505746868501">一律不翻譯<ph name="SOURCE_LANGUAGE" />網頁</translation> +<translation id="124116460088058876">更多語言</translation> +<translation id="1285320974508926690">一律不翻譯此網站</translation> +<translation id="290376772003165898">不是<ph name="LANGUAGE" />網頁嗎?</translation> +<translation id="5684874026226664614">糟糕!系統無法翻譯這個網頁的內容。</translation> +<translation id="6040143037577758943">關閉</translation> +<translation id="6831043979455480757">翻譯</translation> +<translation id="7243308994586599757">選項在接近畫面底部的位置</translation> +<translation id="773466115871691567">一律翻譯<ph name="SOURCE_LANGUAGE" />網頁</translation> <translation id="8298278839890148234">網路瀏覽器活動</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/translations/weblayer_strings_zu.xtb b/chromium/weblayer/browser/java/translations/weblayer_strings_zu.xtb index 1da5d91a768..9e01afbe908 100644 --- a/chromium/weblayer/browser/java/translations/weblayer_strings_zu.xtb +++ b/chromium/weblayer/browser/java/translations/weblayer_strings_zu.xtb @@ -1,5 +1,14 @@ <?xml version="1.0" ?> <!DOCTYPE translationbundle> <translationbundle lang="zu"> +<translation id="1068672505746868501">Ungahumushi amakhasi ngesi-<ph name="SOURCE_LANGUAGE" /></translation> +<translation id="124116460088058876">Izilimi eziningi</translation> +<translation id="1285320974508926690">Ungalokothi uhumushe leli sayithi</translation> +<translation id="290376772003165898">Ikhasi alikho ngesi-<ph name="LANGUAGE" />?</translation> +<translation id="5684874026226664614">Eshu. Leli khasi alikwazi ukuhunyushwa.</translation> +<translation id="6040143037577758943">Vala</translation> +<translation id="6831043979455480757">Humusha</translation> +<translation id="7243308994586599757">Izinketho ziyatholakala eduze kwangaphansi kwesikrini</translation> +<translation id="773466115871691567">Njalo humusha amakhasi ngesi-<ph name="SOURCE_LANGUAGE" /></translation> <translation id="8298278839890148234">Umsebenzi wesiphequluli sewebhu</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/weblayer/browser/java/weblayer_strings.grd b/chromium/weblayer/browser/java/weblayer_strings.grd index cd1e72cc645..79a4dcddd27 100644 --- a/chromium/weblayer/browser/java/weblayer_strings.grd +++ b/chromium/weblayer/browser/java/weblayer_strings.grd @@ -172,6 +172,34 @@ <message name="IDS_WEBLAYER_NOTIFICATION_CHANNEL_GROUP_NAME" desc="The user-facing label for the notification channel group used for notifications arising from web browsing activity."> Web browser activity </message> + <message name="IDS_WEBLAYER_BOTTOM_BAR_SCREEN_POSITION" desc="Accessibility label to inform users about the InfoBar location"> + Options available near bottom of the screen + </message> + <message name="IDS_WEBLAYER_INFOBAR_CLOSE" desc="Accessibility label for the dismiss infobar Button"> + Close + </message> + <!-- TranslateInfoBar --> + <message name="IDS_TRANSLATE_INFOBAR_ERROR"> + Oops. This page could not be translated. + </message> + <message name="IDS_TRANSLATE_BUTTON" desc="Possible texts to display on the translate infobar buttons. [CHAR-LIMIT=24]"> + Translate + </message> + <message name="IDS_TRANSLATE_NEVER_TRANSLATE_SITE" desc="Text to display on the never translate site (like www.google.com) button. [CHAR-LIMIT=64]"> + Never translate this site + </message> + <message name="IDS_TRANSLATE_OPTION_ALWAYS_TRANSLATE" desc="Option in the Chrome menu. User can click the 'Always Translate' option to indicate that they want Chrome to translate pages in this language automatically. Imperative."> + Always translate pages in <ph name="SOURCE_LANGUAGE">%1$s<ex>French</ex></ph> + </message> + <message name="IDS_TRANSLATE_OPTION_NEVER_TRANSLATE" desc="Option in the Chrome menu. User can click the 'Never Translate' option to indicate that they never want Chrome to translate pages in this language. The variable SOURCE_LANGUAGE could be any of 50+ languages supported by Google Translate, like French, Spanish, German, Italian, Japanese, Korean, etc. Imperative."> + Never translate pages in <ph name="SOURCE_LANGUAGE">%1$s<ex>French</ex></ph> + </message> + <message name="IDS_TRANSLATE_OPTION_MORE_LANGUAGE" desc="Option in the Chrome menu. Lets the user open a dialog to choose other target languages for translation, from a list of available languages. [CHAR-LIMIT=64]"> + More languages + </message> + <message name="IDS_TRANSLATE_OPTION_NOT_SOURCE_LANGUAGE" desc="Option in the Chrome menu. Sometimes a web page's source language is not correctly identified by Google Translate, and this menu option lets the user open a submenu to select another language as the source language to translate. Phrased as a question as if to query the user, 'Is this page not in [source language identified]? If so, click here.' [CHAR-LIMIT=64]"> + Page is not in <ph name="LANGUAGE">%1$s<ex>French</ex></ph>? + </message> </messages> </release> </grit> diff --git a/chromium/weblayer/browser/js_communication/web_message_browsertest.cc b/chromium/weblayer/browser/js_communication/web_message_browsertest.cc new file mode 100644 index 00000000000..8d147c10be7 --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_browsertest.cc @@ -0,0 +1,112 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/public/js_communication/web_message.h" + +#include "base/callback.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "weblayer/public/js_communication/web_message.h" +#include "weblayer/public/js_communication/web_message_host.h" +#include "weblayer/public/js_communication/web_message_host_factory.h" +#include "weblayer/public/js_communication/web_message_reply_proxy.h" +#include "weblayer/public/navigation.h" +#include "weblayer/public/navigation_controller.h" +#include "weblayer/public/tab.h" +#include "weblayer/shell/browser/shell.h" +#include "weblayer/test/weblayer_browser_test.h" +#include "weblayer/test/weblayer_browser_test_utils.h" + +namespace weblayer { + +namespace { + +class WebMessageHostImpl; +WebMessageHostImpl* current_connection = nullptr; + +// WebMessageHost implementation that records contents of OnPostMessage(). +class WebMessageHostImpl : public WebMessageHost { + public: + WebMessageHostImpl(base::RepeatingClosure quit_closure, + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* proxy) + : quit_closure_(quit_closure), proxy_(proxy) { + current_connection = this; + } + ~WebMessageHostImpl() override { + if (current_connection == this) + current_connection = nullptr; + } + + std::vector<base::string16>& messages() { return messages_; } + + // WebMessageHost: + void OnPostMessage(std::unique_ptr<WebMessage> message) override { + messages_.push_back(std::move(message->message)); + if (++call_count_ == 1) { + // First time called, send a message to the page. + std::unique_ptr<WebMessage> m2 = std::make_unique<WebMessage>(); + m2->message = base::ASCIIToUTF16("from c++"); + proxy_->PostMessage(std::move(m2)); + } else { + // On subsequent calls quit. + quit_closure_.Run(); + } + } + + private: + int call_count_ = 0; + base::RepeatingClosure quit_closure_; + WebMessageReplyProxy* proxy_; + std::vector<base::string16> messages_; +}; + +// WebMessageHostFactory implementation that creates WebMessageHostImp. +class WebMessageHostFactoryImpl : public WebMessageHostFactory { + public: + explicit WebMessageHostFactoryImpl(base::RepeatingClosure quit_closure) + : quit_closure_(quit_closure) {} + ~WebMessageHostFactoryImpl() override = default; + + // WebMessageHostFactory: + std::unique_ptr<WebMessageHost> CreateHost( + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* proxy) override { + return std::make_unique<WebMessageHostImpl>(quit_closure_, origin_string, + is_main_frame, proxy); + } + + private: + base::RepeatingClosure quit_closure_; +}; + +} // namespace + +using WebMessageTest = WebLayerBrowserTest; + +IN_PROC_BROWSER_TEST_F(WebMessageTest, SendAndReceive) { + EXPECT_TRUE(embedded_test_server()->Start()); + + base::RunLoop run_loop; + shell()->tab()->AddWebMessageHostFactory( + std::make_unique<WebMessageHostFactoryImpl>(run_loop.QuitClosure()), + base::ASCIIToUTF16("x"), {"*"}); + + // web_message_test.html posts a message immediately. + shell()->tab()->GetNavigationController()->Navigate( + embedded_test_server()->GetURL("/web_message_test.html")); + run_loop.Run(); + + // There should be two messages. The one from the page, and the ack triggered + // when WebMessageHostImpl calls PostMessage(). + ASSERT_TRUE(current_connection); + ASSERT_EQ(2u, current_connection->messages().size()); + EXPECT_EQ(base::ASCIIToUTF16("from page"), current_connection->messages()[0]); + EXPECT_EQ(base::ASCIIToUTF16("bouncing from c++"), + current_connection->messages()[1]); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.cc b/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.cc new file mode 100644 index 00000000000..e634cd95d78 --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.cc @@ -0,0 +1,25 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/js_communication/web_message_host_factory_proxy.h" + +#include "weblayer/browser/js_communication/web_message_reply_proxy_impl.h" + +namespace weblayer { + +WebMessageHostFactoryProxy::WebMessageHostFactoryProxy( + const base::android::JavaParamRef<jobject>& client) + : client_(client) {} + +WebMessageHostFactoryProxy::~WebMessageHostFactoryProxy() = default; + +std::unique_ptr<WebMessageHost> WebMessageHostFactoryProxy::CreateHost( + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* proxy) { + return std::make_unique<WebMessageReplyProxyImpl>( + ++next_id_, client_, origin_string, is_main_frame, proxy); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.h b/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.h new file mode 100644 index 00000000000..93e5d55d71e --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_host_factory_proxy.h @@ -0,0 +1,38 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_PROXY_H_ +#define WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_PROXY_H_ + +#include "base/android/scoped_java_ref.h" +#include "weblayer/public/js_communication/web_message_host_factory.h" + +namespace weblayer { + +// TabImpl, on android, creates a WebMessageHostFactoryProxy for every call +// to RegisterWebMessageCallback(). This is used to delegate the calls back to +// the Java side. +class WebMessageHostFactoryProxy : public WebMessageHostFactory { + public: + explicit WebMessageHostFactoryProxy( + const base::android::JavaParamRef<jobject>& client); + WebMessageHostFactoryProxy(const WebMessageHostFactoryProxy&) = delete; + WebMessageHostFactoryProxy& operator=(const WebMessageHostFactoryProxy&) = + delete; + ~WebMessageHostFactoryProxy() override; + + // WebMessageHostFactory: + std::unique_ptr<WebMessageHost> CreateHost( + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* proxy) override; + + private: + base::android::ScopedJavaGlobalRef<jobject> client_; + int next_id_ = 0; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_PROXY_H_ diff --git a/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc b/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc new file mode 100644 index 00000000000..7a6088f540b --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc @@ -0,0 +1,70 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/js_communication/web_message_host_factory_wrapper.h" + +#include "components/js_injection/browser/web_message.h" +#include "components/js_injection/browser/web_message_host.h" +#include "components/js_injection/browser/web_message_reply_proxy.h" +#include "weblayer/public/js_communication/web_message.h" +#include "weblayer/public/js_communication/web_message_host.h" +#include "weblayer/public/js_communication/web_message_host_factory.h" +#include "weblayer/public/js_communication/web_message_reply_proxy.h" + +namespace weblayer { +namespace { + +// An implementation of js_injection::WebMessageHost that delegates to the +// corresponding WebLayer type. This also serves as the WebMessageReplyProxy +// implementation, which forwards to the js_injection implementation. +class WebMessageHostWrapper : public js_injection::WebMessageHost, + public WebMessageReplyProxy { + public: + WebMessageHostWrapper(weblayer::WebMessageHostFactory* factory, + const std::string& origin_string, + bool is_main_frame, + js_injection::WebMessageReplyProxy* proxy) + : proxy_(proxy), + connection_(factory->CreateHost(origin_string, is_main_frame, this)) {} + + // js_injection::WebMessageHost: + void OnPostMessage( + std::unique_ptr<js_injection::WebMessage> message) override { + std::unique_ptr<WebMessage> m = std::make_unique<WebMessage>(); + m->message = message->message; + connection_->OnPostMessage(std::move(m)); + } + + // WebMessageReplyProxy: + void PostMessage(std::unique_ptr<WebMessage> message) override { + std::unique_ptr<js_injection::WebMessage> w = + std::make_unique<js_injection::WebMessage>(); + w->message = std::move(message->message); + proxy_->PostMessage(std::move(w)); + } + + private: + js_injection::WebMessageReplyProxy* proxy_; + std::unique_ptr<weblayer::WebMessageHost> connection_; +}; + +} // namespace + +WebMessageHostFactoryWrapper::WebMessageHostFactoryWrapper( + std::unique_ptr<weblayer::WebMessageHostFactory> factory) + : factory_(std::move(factory)) {} + +WebMessageHostFactoryWrapper::~WebMessageHostFactoryWrapper() = default; + +std::unique_ptr<js_injection::WebMessageHost> +WebMessageHostFactoryWrapper::CreateHost( + const std::string& origin_string, + bool is_main_frame, + js_injection::WebMessageReplyProxy* proxy) { + auto wrapper = std::make_unique<WebMessageHostWrapper>( + factory_.get(), origin_string, is_main_frame, proxy); + return wrapper; +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.h b/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.h new file mode 100644 index 00000000000..1c32d02bfc2 --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_host_factory_wrapper.h @@ -0,0 +1,38 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_WRAPPER_H_ +#define WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_WRAPPER_H_ + +#include "components/js_injection/browser/web_message_host_factory.h" + +namespace weblayer { + +class WebMessageHostFactory; + +// Provides an implementation of js_injection::WebMessageHostFactory that +// wraps the corresponding WebLayer type. +class WebMessageHostFactoryWrapper + : public js_injection::WebMessageHostFactory { + public: + explicit WebMessageHostFactoryWrapper( + std::unique_ptr<weblayer::WebMessageHostFactory> factory); + WebMessageHostFactoryWrapper(const WebMessageHostFactoryWrapper&) = delete; + WebMessageHostFactoryWrapper& operator=(const WebMessageHostFactoryWrapper&) = + delete; + ~WebMessageHostFactoryWrapper() override; + + // js_injection::WebMessageHostFactory: + std::unique_ptr<js_injection::WebMessageHost> CreateHost( + const std::string& origin_string, + bool is_main_frame, + js_injection::WebMessageReplyProxy* proxy) override; + + private: + std::unique_ptr<weblayer::WebMessageHostFactory> factory_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_HOST_FACTORY_WRAPPER_H_ diff --git a/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.cc b/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.cc new file mode 100644 index 00000000000..8172852ecec --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.cc @@ -0,0 +1,52 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/js_communication/web_message_reply_proxy_impl.h" + +#include <memory> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "weblayer/browser/java/jni/WebMessageReplyProxyImpl_jni.h" +#include "weblayer/public/js_communication/web_message.h" +#include "weblayer/public/js_communication/web_message_reply_proxy.h" + +namespace weblayer { + +WebMessageReplyProxyImpl::WebMessageReplyProxyImpl( + int id, + base::android::ScopedJavaGlobalRef<jobject> client, + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* reply_proxy) + : reply_proxy_(reply_proxy) { + auto* env = base::android::AttachCurrentThread(); + java_object_ = Java_WebMessageReplyProxyImpl_create( + env, reinterpret_cast<intptr_t>(this), id, client, is_main_frame, + base::android::ConvertUTF8ToJavaString(env, origin_string)); +} + +WebMessageReplyProxyImpl::~WebMessageReplyProxyImpl() { + Java_WebMessageReplyProxyImpl_onNativeDestroyed( + base::android::AttachCurrentThread(), java_object_); +} + +void WebMessageReplyProxyImpl::PostMessage( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& message_contents) { + auto message = std::make_unique<WebMessage>(); + base::android::ConvertJavaStringToUTF16(env, message_contents, + &(message->message)); + reply_proxy_->PostMessage(std::move(message)); +} + +void WebMessageReplyProxyImpl::OnPostMessage( + std::unique_ptr<WebMessage> message) { + auto* env = base::android::AttachCurrentThread(); + Java_WebMessageReplyProxyImpl_onPostMessage( + env, java_object_, + base::android::ConvertUTF16ToJavaString(env, message->message)); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.h b/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.h new file mode 100644 index 00000000000..dbcfe016f06 --- /dev/null +++ b/chromium/weblayer/browser/js_communication/web_message_reply_proxy_impl.h @@ -0,0 +1,47 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_REPLY_PROXY_IMPL_H_ +#define WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_REPLY_PROXY_IMPL_H_ + +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "components/js_injection/browser/web_message_host.h" +#include "weblayer/public/js_communication/web_message_host.h" + +namespace weblayer { + +class WebMessageReplyProxy; + +// Created only on the Android side to support post-message. +// WebMessageReplyProxyImpl creates the Java WebMessageReplyProxy that is then +// sent over to the client side for communication with the page. +class WebMessageReplyProxyImpl : public WebMessageHost { + public: + WebMessageReplyProxyImpl(int id, + base::android::ScopedJavaGlobalRef<jobject> client, + const std::string& origin_string, + bool is_main_frame, + WebMessageReplyProxy* reply_proxy); + WebMessageReplyProxyImpl(const WebMessageReplyProxyImpl&) = delete; + WebMessageReplyProxyImpl& operator=(const WebMessageReplyProxyImpl&) = delete; + ~WebMessageReplyProxyImpl() override; + + void PostMessage( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& message_contents); + // WebMessageHost: + void OnPostMessage(std::unique_ptr<WebMessage> message) override; + + private: + WebMessageReplyProxy* reply_proxy_; + + // The Java WebMessageReplyProxy. + base::android::ScopedJavaGlobalRef<jobject> java_object_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_JS_COMMUNICATION_WEB_MESSAGE_REPLY_PROXY_IMPL_H_ diff --git a/chromium/weblayer/browser/navigation_browsertest.cc b/chromium/weblayer/browser/navigation_browsertest.cc index 73773c8b092..c573269cc60 100644 --- a/chromium/weblayer/browser/navigation_browsertest.cc +++ b/chromium/weblayer/browser/navigation_browsertest.cc @@ -361,6 +361,28 @@ IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeader) { EXPECT_EQ(header_value, response_2.http_request()->headers.at(header_name)); } +// Verifies setting the 'referer' via SetRequestHeader() works as expected. +IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderWithReferer) { + net::test_server::ControllableHttpResponse response(embedded_test_server(), + "", true); + ASSERT_TRUE(embedded_test_server()->Start()); + + const std::string header_name = "Referer"; + const std::string header_value = "http://request.com"; + NavigationObserverImpl observer(GetNavigationController()); + observer.SetStartedCallback( + base::BindLambdaForTesting([&](Navigation* navigation) { + navigation->SetRequestHeader(header_name, header_value); + })); + + shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html")); + response.WaitForRequest(); + + // Verify 'referer' matches expected value. + EXPECT_EQ(GURL(header_value), + GURL(response.http_request()->headers.at(header_name))); +} + IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderInRedirect) { net::test_server::ControllableHttpResponse response_1(embedded_test_server(), "", true); diff --git a/chromium/weblayer/browser/navigation_controller_impl.cc b/chromium/weblayer/browser/navigation_controller_impl.cc index 6b9b38abadd..cabbd2cca24 100644 --- a/chromium/weblayer/browser/navigation_controller_impl.cc +++ b/chromium/weblayer/browser/navigation_controller_impl.cc @@ -4,6 +4,8 @@ #include "weblayer/browser/navigation_controller_impl.h" +#include <utility> + #include "base/auto_reset.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" @@ -145,6 +147,11 @@ ScopedJavaLocalRef<jstring> NavigationControllerImpl::GetNavigationEntryTitle( return ScopedJavaLocalRef<jstring>(base::android::ConvertUTF8ToJavaString( env, GetNavigationEntryTitle(index))); } + +bool NavigationControllerImpl::IsNavigationEntrySkippable(JNIEnv* env, + int index) { + return IsNavigationEntrySkippable(index); +} #endif void NavigationControllerImpl::WillRedirectRequest( @@ -257,6 +264,10 @@ std::string NavigationControllerImpl::GetNavigationEntryTitle(int index) { return base::UTF16ToUTF8(entry->GetTitle()); } +bool NavigationControllerImpl::IsNavigationEntrySkippable(int index) { + return web_contents()->GetController().IsEntryMarkedToBeSkipped(index); +} + void NavigationControllerImpl::DidStartNavigation( content::NavigationHandle* navigation_handle) { if (!navigation_handle->IsInMainFrame()) @@ -353,6 +364,14 @@ void NavigationControllerImpl::DidFinishNavigation( observer.NavigationFailed(navigation); } + // Note InsertVisualStateCallback currently does not take into account + // any delays from surface sync, ie a frame submitted by renderer may not + // be displayed immediately. Such situations should be rare however, so + // this should be good enough for the purposes needed. + web_contents()->GetMainFrame()->InsertVisualStateCallback(base::BindOnce( + &NavigationControllerImpl::OldPageNoLongerRendered, + weak_ptr_factory_.GetWeakPtr(), navigation_handle->GetURL())); + navigation_map_.erase(navigation_map_.find(navigation_handle)); } @@ -389,6 +408,20 @@ void NavigationControllerImpl::DidFirstVisuallyNonEmptyPaint() { observer.OnFirstContentfulPaint(); } +void NavigationControllerImpl::OldPageNoLongerRendered(const GURL& url, + bool success) { +#if defined(OS_ANDROID) + TRACE_EVENT0("weblayer", + "Java_NavigationControllerImpl_onOldPageNoLongerRendered"); + JNIEnv* env = AttachCurrentThread(); + Java_NavigationControllerImpl_onOldPageNoLongerRendered( + env, java_controller_, + base::android::ConvertUTF8ToJavaString(env, url.spec())); +#endif + for (auto& observer : observers_) + observer.OnOldPageNoLongerRendered(url); +} + void NavigationControllerImpl::NotifyLoadStateChanged() { #if defined(OS_ANDROID) if (java_controller_) { diff --git a/chromium/weblayer/browser/navigation_controller_impl.h b/chromium/weblayer/browser/navigation_controller_impl.h index 52dd02735cb..ce96e147504 100644 --- a/chromium/weblayer/browser/navigation_controller_impl.h +++ b/chromium/weblayer/browser/navigation_controller_impl.h @@ -9,6 +9,7 @@ #include <memory> #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "build/build_config.h" #include "content/public/browser/navigation_controller.h" @@ -64,6 +65,7 @@ class NavigationControllerImpl : public NavigationController, base::android::ScopedJavaLocalRef<jstring> GetNavigationEntryTitle( JNIEnv* env, int index); + bool IsNavigationEntrySkippable(JNIEnv* env, int index); #endif private: @@ -90,6 +92,7 @@ class NavigationControllerImpl : public NavigationController, int GetNavigationListCurrentIndex() override; GURL GetNavigationEntryDisplayURL(int index) override; std::string GetNavigationEntryTitle(int index) override; + bool IsNavigationEntrySkippable(int index) override; // content::WebContentsObserver implementation: void DidStartNavigation( @@ -105,6 +108,7 @@ class NavigationControllerImpl : public NavigationController, void LoadProgressChanged(double progress) override; void DidFirstVisuallyNonEmptyPaint() override; + void OldPageNoLongerRendered(const GURL& url, bool success); void NotifyLoadStateChanged(); void DoNavigate( @@ -125,6 +129,8 @@ class NavigationControllerImpl : public NavigationController, base::android::ScopedJavaGlobalRef<jobject> java_controller_; #endif + base::WeakPtrFactory<NavigationControllerImpl> weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl); }; diff --git a/chromium/weblayer/browser/navigation_impl.cc b/chromium/weblayer/browser/navigation_impl.cc index 211f2f0e877..e160069f225 100644 --- a/chromium/weblayer/browser/navigation_impl.cc +++ b/chromium/weblayer/browser/navigation_impl.cc @@ -9,6 +9,7 @@ #include "net/base/net_errors.h" #include "net/http/http_util.h" #include "third_party/blink/public/common/user_agent/user_agent_metadata.h" +#include "third_party/blink/public/mojom/referrer.mojom.h" #if defined(OS_ANDROID) #include "base/android/jni_array.h" @@ -150,8 +151,17 @@ Navigation::LoadError NavigationImpl::GetLoadError() { void NavigationImpl::SetRequestHeader(const std::string& name, const std::string& value) { - // Any headers coming from the client should be exempt from CORS checks. - navigation_handle_->SetCorsExemptRequestHeader(name, value); + if (base::ToLowerASCII(name) == "referer") { + // The referrer needs to be special cased as content maintains it + // separately. + auto referrer = blink::mojom::Referrer::New(); + referrer->url = GURL(value); + referrer->policy = network::mojom::ReferrerPolicy::kDefault; + navigation_handle_->SetReferrer(std::move(referrer)); + } else { + // Any headers coming from the client should be exempt from CORS checks. + navigation_handle_->SetCorsExemptRequestHeader(name, value); + } } void NavigationImpl::SetUserAgentString(const std::string& value) { diff --git a/chromium/weblayer/browser/new_tab_callback_proxy.cc b/chromium/weblayer/browser/new_tab_callback_proxy.cc index 8e466430c06..b991a8abfe0 100644 --- a/chromium/weblayer/browser/new_tab_callback_proxy.cc +++ b/chromium/weblayer/browser/new_tab_callback_proxy.cc @@ -23,12 +23,12 @@ NewTabCallbackProxy::~NewTabCallbackProxy() { tab_->SetNewTabDelegate(nullptr); } -void NewTabCallbackProxy::OnNewTab(std::unique_ptr<Tab> tab, NewTabType type) { +void NewTabCallbackProxy::OnNewTab(Tab* tab, NewTabType type) { JNIEnv* env = AttachCurrentThread(); // The Java side takes ownership of Tab. TRACE_EVENT0("weblayer", "Java_NewTabCallbackProxy_onNewTab"); Java_NewTabCallbackProxy_onNewTab(env, java_impl_, - reinterpret_cast<jlong>(tab.release()), + static_cast<TabImpl*>(tab)->GetJavaTab(), static_cast<int>(type)); } diff --git a/chromium/weblayer/browser/new_tab_callback_proxy.h b/chromium/weblayer/browser/new_tab_callback_proxy.h index 500d2f81894..02667f48607 100644 --- a/chromium/weblayer/browser/new_tab_callback_proxy.h +++ b/chromium/weblayer/browser/new_tab_callback_proxy.h @@ -23,7 +23,7 @@ class NewTabCallbackProxy : public NewTabDelegate { ~NewTabCallbackProxy() override; // NewTabDelegate: - void OnNewTab(std::unique_ptr<Tab> tab, NewTabType type) override; + void OnNewTab(Tab* tab, NewTabType type) override; void CloseTab() override; private: diff --git a/chromium/weblayer/browser/password_manager_driver_factory.cc b/chromium/weblayer/browser/password_manager_driver_factory.cc new file mode 100644 index 00000000000..8bdf396a44e --- /dev/null +++ b/chromium/weblayer/browser/password_manager_driver_factory.cc @@ -0,0 +1,132 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/password_manager_driver_factory.h" + +#include "components/password_manager/content/browser/bad_message.h" +#include "components/site_isolation/site_isolation_policy.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "mojo/public/cpp/bindings/associated_receiver.h" + +namespace weblayer { + +// A minimal implementation of autofill::mojom::PasswordManagerDriver which just +// listens for the user to type into a password field. +class PasswordManagerDriverFactory::PasswordManagerDriver + : public autofill::mojom::PasswordManagerDriver { + public: + explicit PasswordManagerDriver(content::RenderFrameHost* render_frame_host) + : render_frame_host_(render_frame_host) {} + + void BindPendingReceiver( + mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver> + pending_receiver) { + password_manager_receiver_.Bind(std::move(pending_receiver)); + } + + private: + // autofill::mojom::PasswordManagerDriver: + // Note that these messages received from a potentially compromised renderer. + // For that reason, any access to form data should be validated via + // bad_message::CheckChildProcessSecurityPolicy. + void PasswordFormsParsed( + const std::vector<autofill::FormData>& forms_data) override {} + void PasswordFormsRendered( + const std::vector<autofill::FormData>& visible_forms_data, + bool did_stop_loading) override {} + void PasswordFormSubmitted(const autofill::FormData& form_data) override {} + void ShowManualFallbackForSaving( + const autofill::FormData& form_data) override { + if (!password_manager::bad_message::CheckChildProcessSecurityPolicyForURL( + render_frame_host_, form_data.url, + password_manager::BadMessageReason:: + CPMD_BAD_ORIGIN_SHOW_FALLBACK_FOR_SAVING)) { + return; + } + + if (site_isolation::SiteIsolationPolicy:: + IsIsolationForPasswordSitesEnabled()) { + // This function signals that a password field has been filled (whether by + // the user, JS, autofill, or some other means) or a password form has + // been submitted. Use this as a heuristic to start site-isolating the + // form's site. This is intended to be used primarily when full site + // isolation is not used, such as on Android. + content::SiteInstance::StartIsolatingSite( + render_frame_host_->GetSiteInstance()->GetBrowserContext(), + form_data.url); + } + } + void HideManualFallbackForSaving() override {} + void SameDocumentNavigation(autofill::mojom::SubmissionIndicatorEvent + submission_indication_event) override {} + void RecordSavePasswordProgress(const std::string& log) override {} + void UserModifiedPasswordField() override {} + void UserModifiedNonPasswordField(autofill::FieldRendererId renderer_id, + const base::string16& value) override {} + void ShowPasswordSuggestions(base::i18n::TextDirection text_direction, + const base::string16& typed_username, + int options, + const gfx::RectF& bounds) override {} + void ShowTouchToFill() override {} + void CheckSafeBrowsingReputation(const GURL& form_action, + const GURL& frame_url) override {} + void FocusedInputChanged( + autofill::mojom::FocusedFieldType focused_field_type) override {} + void LogFirstFillingResult(autofill::FormRendererId form_renderer_id, + int32_t result) override {} + + mojo::AssociatedReceiver<autofill::mojom::PasswordManagerDriver> + password_manager_receiver_{this}; + content::RenderFrameHost* render_frame_host_; +}; + +PasswordManagerDriverFactory::PasswordManagerDriverFactory( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) {} + +PasswordManagerDriverFactory::~PasswordManagerDriverFactory() = default; + +// static +void PasswordManagerDriverFactory::BindPasswordManagerDriver( + mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver> + pending_receiver, + content::RenderFrameHost* render_frame_host) { + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + if (!web_contents) + return; + + PasswordManagerDriverFactory* factory = + PasswordManagerDriverFactory::FromWebContents(web_contents); + if (!factory) + return; + + factory->GetDriverForFrame(render_frame_host) + ->BindPendingReceiver(std::move(pending_receiver)); +} + +PasswordManagerDriverFactory::PasswordManagerDriver* +PasswordManagerDriverFactory::GetDriverForFrame( + content::RenderFrameHost* render_frame_host) { + DCHECK_EQ(web_contents(), + content::WebContents::FromRenderFrameHost(render_frame_host)); + DCHECK(render_frame_host->IsRenderFrameCreated()); + + // TryEmplace() will return an iterator to the driver corresponding to + // `render_frame_host`. It creates a new one if required. + return &base::TryEmplace(frame_driver_map_, render_frame_host, + render_frame_host) + .first->second; +} + +void PasswordManagerDriverFactory::RenderFrameDeleted( + content::RenderFrameHost* render_frame_host) { + frame_driver_map_.erase(render_frame_host); +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(PasswordManagerDriverFactory) + +} // namespace weblayer diff --git a/chromium/weblayer/browser/password_manager_driver_factory.h b/chromium/weblayer/browser/password_manager_driver_factory.h new file mode 100644 index 00000000000..b29d46960c4 --- /dev/null +++ b/chromium/weblayer/browser/password_manager_driver_factory.h @@ -0,0 +1,61 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_PASSWORD_MANAGER_DRIVER_FACTORY_H_ +#define WEBLAYER_BROWSER_PASSWORD_MANAGER_DRIVER_FACTORY_H_ + +#include <map> + +#include "base/supports_user_data.h" +#include "components/autofill/content/common/mojom/autofill_driver.mojom.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" +#include "mojo/public/cpp/bindings/pending_associated_receiver.h" + +namespace content { +class WebContents; +} + +namespace weblayer { + +// WebLayer uses the system autofill and not the autofill used by Chrome. This +// factory and the corresponding driver are only used to listen for the +// notification that a password was typed into a form, since this is used as a +// signal to start isolating that site. +// TODO(crbug.com/1088446): Find a way to easily share this with Chrome. +class PasswordManagerDriverFactory + : public content::WebContentsObserver, + public content::WebContentsUserData<PasswordManagerDriverFactory> { + public: + ~PasswordManagerDriverFactory() override; + + PasswordManagerDriverFactory(const PasswordManagerDriverFactory&) = delete; + PasswordManagerDriverFactory& operator=(const PasswordManagerDriverFactory&) = + delete; + + static void BindPasswordManagerDriver( + mojo::PendingAssociatedReceiver<autofill::mojom::PasswordManagerDriver> + pending_receiver, + content::RenderFrameHost* render_frame_host); + + private: + class PasswordManagerDriver; + friend class content::WebContentsUserData<PasswordManagerDriverFactory>; + + explicit PasswordManagerDriverFactory(content::WebContents* web_contents); + + // content::WebContentsObserver: + void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; + + PasswordManagerDriver* GetDriverForFrame( + content::RenderFrameHost* render_frame_host); + + std::map<content::RenderFrameHost*, PasswordManagerDriver> frame_driver_map_; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_PASSWORD_MANAGER_DRIVER_FACTORY_H_ diff --git a/chromium/weblayer/browser/permissions/weblayer_permissions_client.cc b/chromium/weblayer/browser/permissions/weblayer_permissions_client.cc index 9b27ab1d974..e2fb5ff077e 100644 --- a/chromium/weblayer/browser/permissions/weblayer_permissions_client.cc +++ b/chromium/weblayer/browser/permissions/weblayer_permissions_client.cc @@ -4,6 +4,8 @@ #include "weblayer/browser/permissions/weblayer_permissions_client.h" +#include "components/content_settings/core/browser/cookie_settings.h" +#include "weblayer/browser/cookie_settings_factory.h" #include "weblayer/browser/host_content_settings_map_factory.h" #include "weblayer/browser/permissions/permission_decision_auto_blocker_factory.h" #include "weblayer/browser/permissions/permission_manager_factory.h" @@ -26,6 +28,12 @@ HostContentSettingsMap* WebLayerPermissionsClient::GetSettingsMap( return HostContentSettingsMapFactory::GetForBrowserContext(browser_context); } +scoped_refptr<content_settings::CookieSettings> +WebLayerPermissionsClient::GetCookieSettings( + content::BrowserContext* browser_context) { + return CookieSettingsFactory::GetForBrowserContext(browser_context); +} + permissions::PermissionDecisionAutoBlocker* WebLayerPermissionsClient::GetPermissionDecisionAutoBlocker( content::BrowserContext* browser_context) { diff --git a/chromium/weblayer/browser/permissions/weblayer_permissions_client.h b/chromium/weblayer/browser/permissions/weblayer_permissions_client.h index 1d7f899a89a..8bcc5dbc215 100644 --- a/chromium/weblayer/browser/permissions/weblayer_permissions_client.h +++ b/chromium/weblayer/browser/permissions/weblayer_permissions_client.h @@ -22,6 +22,8 @@ class WebLayerPermissionsClient : public permissions::PermissionsClient { // PermissionsClient: HostContentSettingsMap* GetSettingsMap( content::BrowserContext* browser_context) override; + scoped_refptr<content_settings::CookieSettings> GetCookieSettings( + content::BrowserContext* browser_context) override; permissions::PermissionDecisionAutoBlocker* GetPermissionDecisionAutoBlocker( content::BrowserContext* browser_context) override; permissions::PermissionManager* GetPermissionManager( diff --git a/chromium/weblayer/browser/persistence/browser_persistence_common.cc b/chromium/weblayer/browser/persistence/browser_persistence_common.cc index 76e2d4ce630..9ca03a353d2 100644 --- a/chromium/weblayer/browser/persistence/browser_persistence_common.cc +++ b/chromium/weblayer/browser/persistence/browser_persistence_common.cc @@ -9,11 +9,11 @@ #include "components/sessions/core/session_command.h" #include "components/sessions/core/session_service_commands.h" #include "components/sessions/core/session_types.h" -#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_url_handler.h" #include "content/public/browser/dom_storage_context.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/storage_partition.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" @@ -84,6 +84,7 @@ void ProcessRestoreCommands( DCHECK(entries.empty()); TabImpl* tab = browser->CreateTabForSessionRestore(std::move(web_contents), session_tab.guid); + tab->SetData(session_tab.data); if (!had_tabs && i == (windows[0])->selected_tab_index) browser->SetActiveTab(tab); @@ -142,6 +143,8 @@ BuildCommandsForTabConfiguration(const SessionID& browser_session_id, result.push_back(sessions::CreateSetTabGuidCommand(tab_id, tab->GetGuid())); + result.push_back(sessions::CreateSetTabDataCommand(tab_id, tab->GetData())); + return result; } diff --git a/chromium/weblayer/browser/persistence/browser_persister.cc b/chromium/weblayer/browser/persistence/browser_persister.cc index c7fe1a5c325..fac791261ab 100644 --- a/chromium/weblayer/browser/persistence/browser_persister.cc +++ b/chromium/weblayer/browser/persistence/browser_persister.cc @@ -18,13 +18,13 @@ #include "components/sessions/core/session_constants.h" #include "components/sessions/core/session_id.h" #include "components/sessions/core/session_types.h" -#include "content/public/browser/browser_context.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/restore_type.h" #include "content/public/browser/session_storage_namespace.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/persistence/browser_persistence_common.h" #include "weblayer/browser/profile_impl.h" @@ -105,8 +105,9 @@ void BrowserPersister::OnGeneratedNewCryptoKey( } void BrowserPersister::OnTabAdded(Tab* tab) { - content::WebContents* web_contents = - static_cast<TabImpl*>(tab)->web_contents(); + auto* tab_impl = static_cast<TabImpl*>(tab); + data_observer_.Add(tab_impl); + content::WebContents* web_contents = tab_impl->web_contents(); auto* tab_helper = sessions::SessionTabHelper::FromWebContents(web_contents); DCHECK(tab_helper); tab_helper->SetWindowID(browser_session_id_); @@ -130,10 +131,11 @@ void BrowserPersister::OnTabAdded(Tab* tab) { } void BrowserPersister::OnTabRemoved(Tab* tab, bool active_tab_changed) { + auto* tab_impl = static_cast<TabImpl*>(tab); + data_observer_.Remove(tab_impl); // Allow the associated sessionStorage to get deleted; it won't be needed // in the session restore. - content::WebContents* web_contents = - static_cast<TabImpl*>(tab)->web_contents(); + content::WebContents* web_contents = tab_impl->web_contents(); content::SessionStorageNamespace* session_storage_namespace = web_contents->GetController().GetDefaultSessionStorageNamespace(); session_storage_namespace->SetShouldPersist(false); @@ -161,6 +163,16 @@ void BrowserPersister::OnActiveTabChanged(Tab* tab) { browser_session_id_, index)); } +void BrowserPersister::OnDataChanged( + TabImpl* tab, + const std::map<std::string, std::string>& data) { + if (rebuild_on_next_save_) + return; + + ScheduleCommand( + sessions::CreateSetTabDataCommand(GetSessionIDForTab(tab), data)); +} + void BrowserPersister::SetTabUserAgentOverride( const SessionID& window_id, const SessionID& tab_id, diff --git a/chromium/weblayer/browser/persistence/browser_persister.h b/chromium/weblayer/browser/persistence/browser_persister.h index c157abec93e..a956914ad59 100644 --- a/chromium/weblayer/browser/persistence/browser_persister.h +++ b/chromium/weblayer/browser/persistence/browser_persister.h @@ -14,10 +14,12 @@ #include <vector> #include "base/macros.h" +#include "base/scoped_observer.h" #include "base/task/cancelable_task_tracker.h" #include "components/sessions/content/session_tab_helper_delegate.h" #include "components/sessions/core/command_storage_manager_delegate.h" #include "components/sessions/core/session_service_commands.h" +#include "weblayer/browser/tab_impl.h" #include "weblayer/public/browser_observer.h" class SessionID; @@ -29,7 +31,6 @@ class SessionCommand; namespace weblayer { class BrowserImpl; -class TabImpl; // BrowserPersister is responsible for maintaining the state of tabs in a // single Browser so that they can be restored at a later date. The state is @@ -40,7 +41,8 @@ class TabImpl; // current state. class BrowserPersister : public sessions::CommandStorageManagerDelegate, public sessions::SessionTabHelperDelegate, - public BrowserObserver { + public BrowserObserver, + public TabImpl::DataObserver { public: BrowserPersister(const base::FilePath& path, BrowserImpl* browser, @@ -72,6 +74,10 @@ class BrowserPersister : public sessions::CommandStorageManagerDelegate, void OnTabRemoved(Tab* tab, bool active_tab_changed) override; void OnActiveTabChanged(Tab* tab) override; + // TabImpl::DataObserver: + void OnDataChanged(TabImpl* tab, + const std::map<std::string, std::string>& data) override; + // sessions::SessionTabHelperDelegate: void SetTabUserAgentOverride(const SessionID& window_id, const SessionID& tab_id, @@ -127,6 +133,12 @@ class BrowserPersister : public sessions::CommandStorageManagerDelegate, std::vector<uint8_t> crypto_key_; + ScopedObserver<TabImpl, + TabImpl::DataObserver, + &TabImpl::AddDataObserver, + &TabImpl::RemoveDataObserver> + data_observer_{this}; + base::CancelableTaskTracker cancelable_task_tracker_; }; diff --git a/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc b/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc index 57933cd8cbe..23e30e3217c 100644 --- a/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc +++ b/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc @@ -6,16 +6,21 @@ #include "base/bind_helpers.h" #include "base/files/file_path.h" +#include "base/files/file_util.h" #include "base/guid.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/test/bind_test_util.h" +#include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/sessions/core/command_storage_manager_test_helper.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "net/base/filename_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" #include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/common/weblayer_paths.h" @@ -40,6 +45,7 @@ class BrowserPersisterTestHelper { }; namespace { +using testing::UnorderedElementsAre; class OneShotNavigationObserver : public NavigationObserver { public: @@ -183,7 +189,7 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, SingleTab) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab = browser->CreateTab(); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); ShutdownBrowserPersisterAndWait(browser.get()); @@ -208,7 +214,7 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresGuid) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab = browser->CreateTab(); const std::string original_guid = tab->GetGuid(); EXPECT_FALSE(original_guid.empty()); EXPECT_TRUE(base::IsValidGUID(original_guid)); @@ -230,15 +236,72 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresGuid) { EXPECT_EQ(original_guid, browser->GetTabs()[0]->GetGuid()); } +IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresData) { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); + Tab* tab = browser->CreateTab(); + tab->SetData({{"abc", "efg"}}); + const GURL url = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url, tab); + ShutdownBrowserPersisterAndWait(browser.get()); + tab = nullptr; + browser.reset(); + + browser = CreateBrowser(GetProfile(), "x"); + // Should be no tabs while waiting for restore. + EXPECT_TRUE(browser->GetTabs().empty()); + // Wait for the restore and navigation to complete. + BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( + browser.get(), url); + + ASSERT_EQ(1u, browser->GetTabs().size()); + EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); + EXPECT_THAT(browser->GetTabs()[0]->GetData(), + UnorderedElementsAre(std::make_pair("abc", "efg"))); +} + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresMostRecentData) { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); + Tab* tab = browser->CreateTab(); + tab->SetData({{"xxx", "xxx"}}); + const GURL url = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url, tab); + + // Make sure the data has been saved, then set different data on the tab. + BrowserPersisterTestHelper::GetCommandStorageManager( + browser->browser_persister()) + ->Save(); + tab->SetData({{"abc", "efg"}}); + + ShutdownBrowserPersisterAndWait(browser.get()); + tab = nullptr; + browser.reset(); + + browser = CreateBrowser(GetProfile(), "x"); + // Should be no tabs while waiting for restore. + EXPECT_TRUE(browser->GetTabs().empty()); + // Wait for the restore and navigation to complete. + BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( + browser.get(), url); + + ASSERT_EQ(1u, browser->GetTabs().size()); + EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); + EXPECT_THAT(browser->GetTabs()[0]->GetData(), + UnorderedElementsAre(std::make_pair("abc", "efg"))); +} + IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, TwoTabs) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab1 = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab1 = browser->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); - Tab* tab2 = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser->SetActiveTab(tab2); @@ -282,23 +345,23 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, MoveBetweenBrowsers) { // Create a browser with two tabs. std::unique_ptr<BrowserImpl> browser1 = CreateBrowser(GetProfile(), "x"); - Tab* tab1 = browser1->AddTab(Tab::Create(GetProfile())); + Tab* tab1 = browser1->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); - Tab* tab2 = browser1->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser1->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser1->SetActiveTab(tab2); // Create another browser with a single tab. std::unique_ptr<BrowserImpl> browser2 = CreateBrowser(GetProfile(), "y"); - Tab* tab3 = browser2->AddTab(Tab::Create(GetProfile())); + Tab* tab3 = browser2->CreateTab(); const GURL url3 = embedded_test_server()->GetURL("/simple_page3.html"); NavigateAndWaitForCompletion(url3, tab3); // Move |tab2| to |browser2|. - browser2->AddTab(browser1->RemoveTab(tab2)); + browser2->AddTab(tab2); browser2->SetActiveTab(tab2); ShutdownBrowserPersisterAndWait(browser1.get()); @@ -333,4 +396,81 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, MoveBetweenBrowsers) { content::WaitForLoadStop(restored_tab_3->web_contents()); } +class BrowserPersisterTestWithTwoPersistedIds : public WebLayerBrowserTest { + public: + // WebLayerBrowserTest: + void SetUpOnMainThread() override { + WebLayerBrowserTest::SetUpOnMainThread(); + // Configure two browsers with ids 'x' and 'y'. + ASSERT_TRUE(embedded_test_server()->Start()); + std::unique_ptr<BrowserImpl> browser1 = CreateBrowser(GetProfile(), "x"); + const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url1, browser1->CreateTab()); + + std::unique_ptr<BrowserImpl> browser2 = CreateBrowser(GetProfile(), "y"); + const GURL url2 = embedded_test_server()->GetURL("/simple_page3.html"); + NavigateAndWaitForCompletion(url2, browser2->CreateTab()); + + // Shut down the browsers. + ShutdownBrowserPersisterAndWait(browser1.get()); + browser1.reset(); + ShutdownBrowserPersisterAndWait(browser2.get()); + browser2.reset(); + } +}; + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, + GetBrowserPersistenceIds) { + { + // Create a file that has the name of a valid persistence file, but has + // invalid contents. + base::ScopedAllowBlockingForTesting allow_blocking; + base::WriteFile(BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "z"), + "a bogus persistence file"); + } + + base::RunLoop run_loop; + base::flat_set<std::string> persistence_ids; + GetProfile()->GetBrowserPersistenceIds( + base::BindLambdaForTesting([&](base::flat_set<std::string> ids) { + persistence_ids = std::move(ids); + run_loop.Quit(); + })); + run_loop.Run(); + ASSERT_EQ(2u, persistence_ids.size()); + EXPECT_TRUE(persistence_ids.contains("x")); + EXPECT_TRUE(persistence_ids.contains("y")); +} + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, + RemoveBrowserPersistenceStorage) { + base::FilePath file_path1 = BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "x"); + base::FilePath file_path2 = BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "y"); + + { + base::ScopedAllowBlockingForTesting allow_blocking; + ASSERT_TRUE(base::PathExists(file_path1)); + ASSERT_TRUE(base::PathExists(file_path2)); + } + base::RunLoop run_loop; + base::flat_set<std::string> persistence_ids; + persistence_ids.insert("x"); + persistence_ids.insert("y"); + GetProfile()->RemoveBrowserPersistenceStorage( + base::BindLambdaForTesting([&](bool result) { + EXPECT_TRUE(result); + run_loop.Quit(); + }), + std::move(persistence_ids)); + run_loop.Run(); + { + base::ScopedAllowBlockingForTesting allow_blocking; + EXPECT_FALSE(base::PathExists(file_path1)); + EXPECT_FALSE(base::PathExists(file_path2)); + } +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc b/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc new file mode 100644 index 00000000000..ead41f214f9 --- /dev/null +++ b/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc @@ -0,0 +1,91 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/persistence/browser_persister_file_utils.h" + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/stl_util.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "components/base32/base32.h" +#include "components/sessions/core/command_storage_backend.h" +#include "content/public/browser/browser_thread.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/browser_list.h" +#include "weblayer/browser/profile_impl.h" + +namespace weblayer { +namespace { + +bool RemoveBrowserPersistenceStorageOnBackgroundThread( + const base::FilePath& path, + base::flat_set<std::string> ids) { + DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + bool all_succeeded = true; + for (const std::string& id : ids) { + DCHECK(!id.empty()); + base::FilePath persistence_path = BuildPathForBrowserPersister(path, id); + if (!base::DeleteFile(persistence_path, /* recurse */ false)) + all_succeeded = false; + } + return all_succeeded; +} + +} // namespace + +base::flat_set<std::string> GetBrowserPersistenceIdsOnBackgroundThread( + const base::FilePath& path) { + DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + base::flat_set<std::string> ids; + base::FilePath matching_path = base::FilePath().AppendASCII( + std::string(BrowserImpl::kPersistenceFilePrefix) + std::string("*")); + base::FileEnumerator iter(path, /* recursive */ false, + base::FileEnumerator::FILES, matching_path.value()); + for (base::FilePath name = iter.Next(); !name.empty(); name = iter.Next()) { + // The name is base32 encoded, which is ascii. + const std::string base_name = iter.GetInfo().GetName().MaybeAsASCII(); + if (base_name.size() <= base::size(BrowserImpl::kPersistenceFilePrefix)) + continue; + + const std::string encoded_id = + base_name.substr(base::size(BrowserImpl::kPersistenceFilePrefix) - 1); + const std::string decoded_id = base32::Base32Decode(encoded_id); + if (!decoded_id.empty() && + sessions::CommandStorageBackend::IsValidFile(name)) { + ids.insert(decoded_id); + } + } + return ids; +} + +base::FilePath BuildPathForBrowserPersister(const base::FilePath& base_path, + const std::string& browser_id) { + DCHECK(!browser_id.empty()); + const std::string encoded_name = base32::Base32Encode(browser_id); + return base_path.AppendASCII(BrowserImpl::kPersistenceFilePrefix + + encoded_name); +} + +void RemoveBrowserPersistenceStorageImpl( + ProfileImpl* profile, + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids) { + // Remove any ids that are actively in use. + for (BrowserImpl* browser : BrowserList::GetInstance()->browsers()) { + if (browser->profile() == profile) + ids.erase(browser->GetPersistenceId()); + } + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::BindOnce(&RemoveBrowserPersistenceStorageOnBackgroundThread, + profile->GetBrowserPersisterDataBaseDir(), std::move(ids)), + std::move(done_callback)); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/persistence/browser_persister_file_utils.h b/chromium/weblayer/browser/persistence/browser_persister_file_utils.h new file mode 100644 index 00000000000..66cb58addc6 --- /dev/null +++ b/chromium/weblayer/browser/persistence/browser_persister_file_utils.h @@ -0,0 +1,39 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ +#define WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/containers/flat_set.h" + +namespace base { +class FilePath; +} + +namespace weblayer { + +class ProfileImpl; + +// Returns the set of known persistence ids for the profile at |path|. +base::flat_set<std::string> GetBrowserPersistenceIdsOnBackgroundThread( + const base::FilePath& path); + +// Returns the path to save persistence information. |base_path| is the base +// path of the profile, and |browser_id| the persistence id. +base::FilePath BuildPathForBrowserPersister(const base::FilePath& base_path, + const std::string& browser_id); + +// Implementation of RemoveBrowserPersistenceStorage(). Tries to remove all +// the persistence files for the set of browser persistence ids. +void RemoveBrowserPersistenceStorageImpl( + ProfileImpl* profile, + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids); + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ diff --git a/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc b/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc index 093fb4cd4f7..2d9ab733610 100644 --- a/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc +++ b/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc @@ -31,7 +31,7 @@ class MinimalBrowserPersisterTest : public WebLayerBrowserTest { WebLayerBrowserTest::SetUpOnMainThread(); ASSERT_TRUE(embedded_test_server()->Start()); browser_ = Browser::Create(GetProfile(), nullptr); - tab_ = static_cast<TabImpl*>(browser_->AddTab(Tab::Create(GetProfile()))); + tab_ = static_cast<TabImpl*>(browser_->CreateTab()); browser_->SetActiveTab(tab_); } void PostRunTestOnMainThread() override { @@ -83,7 +83,7 @@ IN_PROC_BROWSER_TEST_F(MinimalBrowserPersisterTest, SingleTab) { IN_PROC_BROWSER_TEST_F(MinimalBrowserPersisterTest, TwoTabs) { NavigateAndWaitForCompletion(url1(), tab_); - Tab* tab2 = browser_->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser_->CreateTab(); NavigateAndWaitForCompletion(url2(), tab2); browser_->SetActiveTab(tab2); diff --git a/chromium/weblayer/browser/popup_blocker_browsertest.cc b/chromium/weblayer/browser/popup_blocker_browsertest.cc new file mode 100644 index 00000000000..bd53f10dda5 --- /dev/null +++ b/chromium/weblayer/browser/popup_blocker_browsertest.cc @@ -0,0 +1,265 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" +#include "components/blocked_content/popup_blocker_tab_helper.h" +#include "ui/base/page_transition_types.h" +#include "ui/base/window_open_disposition.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/host_content_settings_map_factory.h" +#include "weblayer/browser/profile_impl.h" +#include "weblayer/browser/tab_impl.h" +#include "weblayer/public/browser.h" +#include "weblayer/public/browser_observer.h" +#include "weblayer/public/navigation_controller.h" +#include "weblayer/public/new_tab_delegate.h" +#include "weblayer/shell/browser/shell.h" +#include "weblayer/test/test_navigation_observer.h" +#include "weblayer/test/weblayer_browser_test.h" +#include "weblayer/test/weblayer_browser_test_utils.h" + +namespace weblayer { + +class PopupBlockerBrowserTest : public WebLayerBrowserTest, + public NewTabDelegate, + public BrowserObserver { + public: + // WebLayerBrowserTest: + void SetUpOnMainThread() override { + ASSERT_TRUE(embedded_test_server()->Start()); + original_tab_ = shell()->tab(); +#if !defined(OS_ANDROID) + // Android does this in Java. + original_tab_->SetNewTabDelegate(this); +#endif + shell()->browser()->AddObserver(this); + + NavigateAndWaitForCompletion(embedded_test_server()->GetURL("/echo"), + original_tab_); + } + void TearDownOnMainThread() override { + shell()->browser()->RemoveObserver(this); + } + + // NewTabDelegate: + void OnNewTab(Tab* new_tab, NewTabType type) override {} + void CloseTab() override {} + + // BrowserObserver: + void OnTabAdded(Tab* tab) override { + new_tab_ = tab; + if (new_tab_run_loop_) + new_tab_run_loop_->Quit(); + } + void OnTabRemoved(Tab* tab, bool active_tab_changed) override { + ASSERT_EQ(tab, new_tab_); + new_tab_ = nullptr; + if (close_tab_run_loop_) + close_tab_run_loop_->Quit(); + } + + size_t GetBlockedPopupCount() { + return blocked_content::PopupBlockerTabHelper::FromWebContents( + GetWebContents(original_tab_)) + ->GetBlockedPopupsCount(); + } + + content::WebContents* GetWebContents(Tab* tab) { + return static_cast<TabImpl*>(tab)->web_contents(); + } + + Tab* WaitForNewTab() { + if (!new_tab_) { + new_tab_run_loop_ = std::make_unique<base::RunLoop>(); + new_tab_run_loop_->Run(); + new_tab_run_loop_ = nullptr; + } + return new_tab_; + } + + void WaitForCloseTab() { + if (new_tab_) { + close_tab_run_loop_ = std::make_unique<base::RunLoop>(); + close_tab_run_loop_->Run(); + close_tab_run_loop_ = nullptr; + } + ASSERT_FALSE(new_tab_); + } + + void ExpectTabURL(Tab* tab, const GURL& url) { + if (tab->GetNavigationController()->GetNavigationListSize() > 0) { + EXPECT_EQ(tab->GetNavigationController()->GetNavigationEntryDisplayURL(0), + url); + } else { + TestNavigationObserver( + url, TestNavigationObserver::NavigationEvent::kCompletion, tab) + .Wait(); + } + } + + Tab* ShowPopup(const GURL& url) { + auto* popup_blocker = + blocked_content::PopupBlockerTabHelper::FromWebContents( + GetWebContents(original_tab_)); + popup_blocker->ShowBlockedPopup( + popup_blocker->GetBlockedPopupRequests().begin()->first, + WindowOpenDisposition::NEW_FOREGROUND_TAB); + Tab* new_tab = WaitForNewTab(); + ExpectTabURL(new_tab, url); + EXPECT_EQ(GetBlockedPopupCount(), 0u); + return new_tab; + } + + Tab* original_tab() { return original_tab_; } + + private: + std::unique_ptr<base::RunLoop> new_tab_run_loop_; + std::unique_ptr<base::RunLoop> close_tab_run_loop_; + + Tab* original_tab_ = nullptr; + Tab* new_tab_ = nullptr; +}; + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, BlocksPopup) { + ExecuteScript(original_tab(), "window.open('https://google.com')", true); + EXPECT_EQ(GetBlockedPopupCount(), 1u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, BlocksMultiplePopups) { + ExecuteScript(original_tab(), "window.open('https://google.com')", true); + ExecuteScript(original_tab(), "window.open('https://google.com')", true); + EXPECT_EQ(GetBlockedPopupCount(), 2u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, DoesNotBlockUserGesture) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + ExecuteScriptWithUserGesture( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str())); + + Tab* new_tab = WaitForNewTab(); + ExpectTabURL(new_tab, popup_url); + EXPECT_EQ(GetBlockedPopupCount(), 0u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, OpensBlockedPopup) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + ExecuteScript( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str()), true); + EXPECT_EQ(GetBlockedPopupCount(), 1u); + + Tab* new_tab = ShowPopup(popup_url); + + // Blocked popups should no longer have the opener set to match Chrome + // behavior. + EXPECT_FALSE(GetWebContents(new_tab)->HasOpener()); + // Make sure we can cleanly close the popup, and there's no crash. + ExecuteScriptWithUserGesture(new_tab, "window.close()"); + WaitForCloseTab(); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + AllowsPopupThroughContentSettingException) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + HostContentSettingsMapFactory::GetForBrowserContext( + GetWebContents(original_tab())->GetBrowserContext()) + ->SetContentSettingDefaultScope(popup_url, GURL(), + ContentSettingsType::POPUPS, + std::string(), CONTENT_SETTING_ALLOW); + ExecuteScript( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str()), true); + Tab* new_tab = WaitForNewTab(); + ExpectTabURL(new_tab, popup_url); + EXPECT_EQ(GetBlockedPopupCount(), 0u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + AllowsPopupThroughContentSettingDefaultValue) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + HostContentSettingsMapFactory::GetForBrowserContext( + GetWebContents(original_tab())->GetBrowserContext()) + ->SetDefaultContentSetting(ContentSettingsType::POPUPS, + CONTENT_SETTING_ALLOW); + ExecuteScript( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str()), true); + Tab* new_tab = WaitForNewTab(); + ExpectTabURL(new_tab, popup_url); + EXPECT_EQ(GetBlockedPopupCount(), 0u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + BlockPopupThroughContentSettingException) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + HostContentSettingsMapFactory::GetForBrowserContext( + GetWebContents(original_tab())->GetBrowserContext()) + ->SetDefaultContentSetting(ContentSettingsType::POPUPS, + CONTENT_SETTING_ALLOW); + HostContentSettingsMapFactory::GetForBrowserContext( + GetWebContents(original_tab())->GetBrowserContext()) + ->SetContentSettingDefaultScope(popup_url, GURL(), + ContentSettingsType::POPUPS, + std::string(), CONTENT_SETTING_BLOCK); + ExecuteScript( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str()), true); + EXPECT_EQ(GetBlockedPopupCount(), 1u); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + BlocksAndOpensPopupFromOpenURL) { + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + content::OpenURLParams params(popup_url, content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, true); + params.initiator_origin = url::Origin::Create(popup_url); + GetWebContents(original_tab())->OpenURL(params); + EXPECT_EQ(GetBlockedPopupCount(), 1u); + + ShowPopup(popup_url); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + DoesNotOpenPopupWithoutNewTabDelegate) { + NewTabDelegate* old_delegate = + static_cast<TabImpl*>(original_tab())->new_tab_delegate(); + original_tab()->SetNewTabDelegate(nullptr); + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + ExecuteScriptWithUserGesture( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str())); + EXPECT_EQ(GetBlockedPopupCount(), 0u); + + // Navigate the original tab, then make sure we still only have a single tab. + NavigateAndWaitForCompletion(embedded_test_server()->GetURL("/echo"), + original_tab()); + EXPECT_EQ(shell()->browser()->GetTabs().size(), 1u); + + // Restore the old delegate to make sure it is cleaned up on Android. + original_tab()->SetNewTabDelegate(old_delegate); +} + +IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, + DoesNotOpenBlockedPopupWithoutNewTabDelegate) { + NewTabDelegate* old_delegate = + static_cast<TabImpl*>(original_tab())->new_tab_delegate(); + original_tab()->SetNewTabDelegate(nullptr); + GURL popup_url = embedded_test_server()->GetURL("/echo?popup"); + ExecuteScript( + original_tab(), + base::StringPrintf("window.open('%s')", popup_url.spec().c_str()), true); + EXPECT_EQ(GetBlockedPopupCount(), 0u); + + // Navigate the original tab, then make sure we still only have a single tab. + NavigateAndWaitForCompletion(embedded_test_server()->GetURL("/echo"), + original_tab()); + EXPECT_EQ(shell()->browser()->GetTabs().size(), 1u); + + // Restore the old delegate to make sure it is cleaned up on Android. + original_tab()->SetNewTabDelegate(old_delegate); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/popup_navigation_delegate_impl.cc b/chromium/weblayer/browser/popup_navigation_delegate_impl.cc new file mode 100644 index 00000000000..01a917647cd --- /dev/null +++ b/chromium/weblayer/browser/popup_navigation_delegate_impl.cc @@ -0,0 +1,69 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/popup_navigation_delegate_impl.h" + +#include "build/build_config.h" +#include "content/public/browser/web_contents.h" +#include "weblayer/browser/host_content_settings_map_factory.h" +#include "weblayer/browser/infobar_service.h" + +#if defined(OS_ANDROID) +#include "components/blocked_content/android/popup_blocked_infobar_delegate.h" +#endif + +namespace weblayer { + +PopupNavigationDelegateImpl::PopupNavigationDelegateImpl( + const content::OpenURLParams& params, + content::WebContents* source_contents, + content::RenderFrameHost* opener) + : params_(params), + source_contents_(source_contents), + opener_(opener), + original_user_gesture_(params_.user_gesture) {} + +content::RenderFrameHost* PopupNavigationDelegateImpl::GetOpener() { + return opener_; +} + +bool PopupNavigationDelegateImpl::GetOriginalUserGesture() { + return original_user_gesture_; +} + +const GURL& PopupNavigationDelegateImpl::GetURL() { + return params_.url; +} + +blocked_content::PopupNavigationDelegate::NavigateResult +PopupNavigationDelegateImpl::NavigateWithGesture( + const blink::mojom::WindowFeatures& window_features, + base::Optional<WindowOpenDisposition> updated_disposition) { + // It's safe to mutate |params_| here because NavigateWithGesture() will only + // be called once, and the user gesture value has already been saved in + // |original_user_gesture_|. + params_.user_gesture = true; + if (updated_disposition) + params_.disposition = updated_disposition.value(); + content::WebContents* new_contents = source_contents_->OpenURL(params_); + return NavigateResult{ + new_contents, + params_.disposition, + }; +} + +void PopupNavigationDelegateImpl::OnPopupBlocked( + content::WebContents* web_contents, + int total_popups_blocked_on_page) { +#if defined(OS_ANDROID) + blocked_content::PopupBlockedInfoBarDelegate::Create( + InfoBarService::FromWebContents(web_contents), + total_popups_blocked_on_page, + HostContentSettingsMapFactory::GetForBrowserContext( + web_contents->GetBrowserContext()), + base::NullCallback()); +#endif +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/popup_navigation_delegate_impl.h b/chromium/weblayer/browser/popup_navigation_delegate_impl.h new file mode 100644 index 00000000000..7e049647513 --- /dev/null +++ b/chromium/weblayer/browser/popup_navigation_delegate_impl.h @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_POPUP_NAVIGATION_DELEGATE_IMPL_H_ +#define WEBLAYER_BROWSER_POPUP_NAVIGATION_DELEGATE_IMPL_H_ + +#include "components/blocked_content/popup_navigation_delegate.h" +#include "content/public/browser/page_navigator.h" +#include "content/public/browser/render_frame_host.h" + +namespace weblayer { + +class PopupNavigationDelegateImpl + : public blocked_content::PopupNavigationDelegate { + public: + PopupNavigationDelegateImpl(const content::OpenURLParams& params, + content::WebContents* source_contents, + content::RenderFrameHost* opener); + + // blocked_content::PopupNavigationDelegate: + content::RenderFrameHost* GetOpener() override; + bool GetOriginalUserGesture() override; + const GURL& GetURL() override; + NavigateResult NavigateWithGesture( + const blink::mojom::WindowFeatures& window_features, + base::Optional<WindowOpenDisposition> updated_disposition) override; + void OnPopupBlocked(content::WebContents* web_contents, + int total_popups_blocked_on_page) override; + + const content::OpenURLParams& params() const { return params_; } + + private: + content::OpenURLParams params_; + content::WebContents* source_contents_; + content::RenderFrameHost* opener_; + const bool original_user_gesture_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_POPUP_NAVIGATION_DELEGATE_IMPL_H_ diff --git a/chromium/weblayer/browser/prefetch_browsertest.cc b/chromium/weblayer/browser/prefetch_browsertest.cc new file mode 100644 index 00000000000..796d8905d73 --- /dev/null +++ b/chromium/weblayer/browser/prefetch_browsertest.cc @@ -0,0 +1,125 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_path.h" +#include "base/test/bind_test_util.h" +#include "components/network_session_configurator/common/network_switches.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test_utils.h" +#include "net/base/network_change_notifier.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" +#include "weblayer/browser/tab_impl.h" +#include "weblayer/shell/browser/shell.h" +#include "weblayer/test/weblayer_browser_test.h" +#include "weblayer/test/weblayer_browser_test_utils.h" + +namespace weblayer { +namespace { +const char kPrefetchPage[] = "/simple_prefetch.html"; +const char kRedirectPrefetchPage[] = "/redirect_prefetch.html"; +const char kRedirectPrefetchUrl[] = "/redirect"; +const char kRedirectedPrefetchUrl[] = "/redirected"; +const char kPrefetchTarget[] = "/prefetch_target.lnk"; +} // namespace + +class PrefetchBrowserTest : public WebLayerBrowserTest { + public: + void SetUpOnMainThread() override { + // The test makes requests to google.com which we want to redirect to the + // test server. + host_resolver()->AddRule("*", "127.0.0.1"); + + embedded_test_server()->RegisterRequestMonitor(base::BindRepeating( + &PrefetchBrowserTest::MonitorRequest, base::Unretained(this))); + ASSERT_TRUE(embedded_test_server()->Start()); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + // Set a dummy variation ID to send X-Client-Data header to Google hosts + // in RedirectedPrefetch test. + command_line->AppendSwitchASCII("force-variation-ids", "42"); + // Need to ignore cert errors to use a HTTPS server for the test domains. + command_line->AppendSwitch(switches::kIgnoreCertificateErrors); + } + + bool RunPrefetchExperiment(GURL url, const base::string16 expected_title) { + content::TitleWatcher title_watcher( + static_cast<TabImpl*>(shell()->tab())->web_contents(), expected_title); + NavigateAndWaitForCompletion(url, shell()); + return expected_title == title_watcher.WaitAndGetTitle(); + } + + protected: + bool prefetch_target_request_seen_ = false; + + private: + void MonitorRequest(const net::test_server::HttpRequest& request) { + if (request.relative_url == std::string(kPrefetchTarget)) { + prefetch_target_request_seen_ = true; + } + } +}; + +IN_PROC_BROWSER_TEST_F(PrefetchBrowserTest, PrefetchWorks) { + // Set real NetworkChangeNotifier singleton aside. + std::unique_ptr<net::NetworkChangeNotifier::DisableForTest> disable_for_test( + new net::NetworkChangeNotifier::DisableForTest); + ASSERT_FALSE(prefetch_target_request_seen_); + EXPECT_TRUE( + RunPrefetchExperiment(embedded_test_server()->GetURL(kPrefetchPage), + base::ASCIIToUTF16("link onload"))); + EXPECT_TRUE(prefetch_target_request_seen_); +} + +// https://crbug.com/922362: When the prefetched request is redirected, DCHECKs +// in PrefetchURLLoader::FollowRedirect() failed due to "X-Client-Data" in +// removed_headers. Verify that it no longer does. +IN_PROC_BROWSER_TEST_F(PrefetchBrowserTest, RedirectedPrefetch) { + std::vector<net::test_server::HttpRequest> requests; + net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); + https_server.RegisterRequestHandler(base::BindLambdaForTesting( + [&requests](const net::test_server::HttpRequest& request) + -> std::unique_ptr<net::test_server::HttpResponse> { + auto response = std::make_unique<net::test_server::BasicHttpResponse>(); + if (request.relative_url == std::string(kRedirectPrefetchPage)) { + requests.push_back(request); + response->set_content_type("text/html"); + response->set_content( + base::StringPrintf("<link rel=\"prefetch\" href=\"%s\" " + "onload=\"document.title='done'\">", + kRedirectPrefetchUrl)); + return response; + } else if (request.relative_url == std::string(kRedirectPrefetchUrl)) { + requests.push_back(request); + response->set_code(net::HTTP_MOVED_PERMANENTLY); + response->AddCustomHeader( + "Location", base::StringPrintf("https://example.com:%s%s", + request.GetURL().port().c_str(), + kRedirectedPrefetchUrl)); + return response; + } else if (request.relative_url == + std::string(kRedirectedPrefetchUrl)) { + requests.push_back(request); + return response; + } + return nullptr; + })); + + https_server.ServeFilesFromSourceDirectory( + base::FilePath(FILE_PATH_LITERAL("weblayer/test/data"))); + ASSERT_TRUE(https_server.Start()); + + GURL url = https_server.GetURL("www.google.com", kRedirectPrefetchPage); + EXPECT_TRUE(RunPrefetchExperiment(url, base::ASCIIToUTF16("done"))); + ASSERT_EQ(3U, requests.size()); + + EXPECT_EQ(base::StringPrintf("www.google.com:%u", https_server.port()), + requests[0].headers["Host"]); + EXPECT_EQ(kRedirectPrefetchPage, requests[0].relative_url); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/profile_browsertest.cc b/chromium/weblayer/browser/profile_browsertest.cc new file mode 100644 index 00000000000..3b8bbf3983b --- /dev/null +++ b/chromium/weblayer/browser/profile_browsertest.cc @@ -0,0 +1,27 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" +#include "weblayer/browser/profile_impl.h" +#include "weblayer/test/weblayer_browser_test.h" + +namespace weblayer { + +using ProfileBrowsertest = WebLayerBrowserTest; + +// TODO(crbug.com/654704): Android does not support PRE_ tests. +#if !defined(OS_ANDROID) + +// UKM enabling via Profile persists across restarts. +IN_PROC_BROWSER_TEST_F(ProfileBrowsertest, PRE_PersistUKM) { + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); +} + +IN_PROC_BROWSER_TEST_F(ProfileBrowsertest, PersistUKM) { + ASSERT_TRUE(GetProfile()->GetBooleanSetting(SettingType::UKM_ENABLED)); +} + +#endif // !defined(OS_ANDROID) + +} // namespace weblayer diff --git a/chromium/weblayer/browser/profile_disk_operations.cc b/chromium/weblayer/browser/profile_disk_operations.cc index 3b1a58d3f21..736c102a8a3 100644 --- a/chromium/weblayer/browser/profile_disk_operations.cc +++ b/chromium/weblayer/browser/profile_disk_operations.cc @@ -6,6 +6,7 @@ #include "base/files/file_enumerator.h" #include "base/files/file_util.h" +#include "base/logging.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" diff --git a/chromium/weblayer/browser/profile_disk_operations_unittests.cc b/chromium/weblayer/browser/profile_disk_operations_unittests.cc index dd1f381be42..801587d463b 100644 --- a/chromium/weblayer/browser/profile_disk_operations_unittests.cc +++ b/chromium/weblayer/browser/profile_disk_operations_unittests.cc @@ -6,11 +6,11 @@ #include <string> #include <vector> +#include "base/base_paths.h" #include "base/check.h" #include "base/files/file_util.h" -#include "base/files/scoped_temp_dir.h" -#include "base/path_service.h" #include "base/strings/string_number_conversions.h" +#include "base/test/scoped_path_override.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "weblayer/browser/profile_disk_operations.h" @@ -19,20 +19,10 @@ namespace weblayer { class ProfileDiskOperationsTest : public testing::Test { - public: - void SetUp() override { - CHECK(data_temp_dir_.CreateUniqueTempDir()); - base::PathService::Override(DIR_USER_DATA, data_temp_dir_.GetPath()); -#if defined(OS_POSIX) - CHECK(cache_temp_dir_.CreateUniqueTempDir()); - base::PathService::Override(base::DIR_CACHE, cache_temp_dir_.GetPath()); -#endif - } - protected: - base::ScopedTempDir data_temp_dir_; + base::ScopedPathOverride data_dir_override_{DIR_USER_DATA}; #if defined(OS_POSIX) - base::ScopedTempDir cache_temp_dir_; + base::ScopedPathOverride cache_dir_override_{base::DIR_CACHE}; #endif }; diff --git a/chromium/weblayer/browser/profile_impl.cc b/chromium/weblayer/browser/profile_impl.cc index 8e1c744f106..3cc8cc635af 100644 --- a/chromium/weblayer/browser/profile_impl.cc +++ b/chromium/weblayer/browser/profile_impl.cc @@ -4,6 +4,7 @@ #include "weblayer/browser/profile_impl.h" +#include <algorithm> #include <memory> #include <string> #include <utility> @@ -11,7 +12,8 @@ #include "base/bind.h" #include "base/callback_forward.h" -#include "base/task/post_task.h" +#include "base/no_destructor.h" +#include "base/observer_list.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/threading/thread_restrictions.h" @@ -23,10 +25,16 @@ #include "content/public/browser/browsing_data_remover.h" #include "content/public/browser/device_service.h" #include "content/public/browser/download_manager.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/storage_partition.h" #include "services/network/public/mojom/network_context.mojom.h" +#include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h" #include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/browser_list.h" +#include "weblayer/browser/browsing_data_remover_delegate.h" #include "weblayer/browser/cookie_manager_impl.h" +#include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/tab_impl.h" #if defined(OS_ANDROID) @@ -34,6 +42,8 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/android/scoped_java_ref.h" +#include "components/safe_browsing/core/common/safe_browsing_prefs.h" +#include "components/unified_consent/pref_names.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/java/jni/ProfileImpl_jni.h" #include "weblayer/browser/safe_browsing/safe_browsing_service.h" @@ -63,6 +73,18 @@ base::SequencedTaskRunner* GetBackgroundDiskOperationTaskRunner() { return task_runner.get()->get(); } +std::set<ProfileImpl*>& GetProfiles() { + static base::NoDestructor<std::set<ProfileImpl*>> s_all_profiles; + return *s_all_profiles; +} + +base::ObserverList<ProfileImpl::ProfileObserver>::Unchecked& GetObservers() { + static base::NoDestructor< + base::ObserverList<ProfileImpl::ProfileObserver>::Unchecked> + s_observers; + return *s_observers; +} + #if defined(OS_ANDROID) void PassFilePathsToJavaCallback( const base::android::ScopedJavaGlobalRef<jobject>& callback, @@ -71,6 +93,24 @@ void PassFilePathsToJavaCallback( callback, base::android::ToJavaArrayOfStrings( base::android::AttachCurrentThread(), file_paths)); } + +void OnGotBrowserPersistenceIds( + const base::android::ScopedJavaGlobalRef<jobject>& callback, + base::flat_set<std::string> ids) { + std::vector<std::string> as_vector; + for (const std::string& id : ids) + as_vector.push_back(id); + base::android::RunObjectCallbackAndroid( + callback, + base::android::ToJavaArrayOfStrings(AttachCurrentThread(), as_vector)); +} + +void OnDidRemoveBrowserPersistenceStorage( + const base::android::ScopedJavaGlobalRef<jobject>& callback, + bool result) { + base::android::RunBooleanCallbackAndroid(callback, result); +} + #endif // OS_ANDROID } // namespace @@ -87,8 +127,8 @@ class ProfileImpl::DataClearer : public content::BrowsingDataRemover::Observer { ~DataClearer() override { remover_->RemoveObserver(this); } - void ClearData(int mask, base::Time from_time, base::Time to_time) { - int origin_types = + void ClearData(uint64_t mask, base::Time from_time, base::Time to_time) { + uint64_t origin_types = content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB | content::BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB; remover_->RemoveAndReply(from_time, to_time, mask, origin_types, this); @@ -118,6 +158,10 @@ ProfileImpl::ProfileImpl(const std::string& name) info_ = CreateProfileInfo(name); } + GetProfiles().insert(this); + for (auto& observer : GetObservers()) + observer.ProfileCreated(this); + if (!g_first_profile_created) { g_first_profile_created = true; GetBackgroundDiskOperationTaskRunner()->PostTask( @@ -127,12 +171,18 @@ ProfileImpl::ProfileImpl(const std::string& name) // Ensure WebCacheManager is created so that it starts observing // OnRenderProcessHostCreated events. web_cache::WebCacheManager::GetInstance(); + +#if defined(OS_ANDROID) + WebLayerMetricsServiceClient::GetInstance()->UpdateUkm(false); +#endif } ProfileImpl::~ProfileImpl() { - DCHECK_EQ(num_browser_impl_, 0u); if (browser_context_) browser_context_->ShutdownStoragePartitions(); + GetProfiles().erase(this); + for (auto& observer : GetObservers()) + observer.ProfileDestroyed(this); } ProfileImpl* ProfileImpl::FromBrowserContext( @@ -140,7 +190,19 @@ ProfileImpl* ProfileImpl::FromBrowserContext( return static_cast<BrowserContextImpl*>(browser_context)->profile_impl(); } -content::BrowserContext* ProfileImpl::GetBrowserContext() { +std::set<ProfileImpl*> ProfileImpl::GetAllProfiles() { + return GetProfiles(); +} + +void ProfileImpl::AddProfileObserver(ProfileObserver* observer) { + GetObservers().AddObserver(observer); +} + +void ProfileImpl::RemoveProfileObserver(ProfileObserver* observer) { + GetObservers().RemoveObserver(observer); +} + +BrowserContextImpl* ProfileImpl::GetBrowserContext() { if (browser_context_) return browser_context_.get(); @@ -170,7 +232,7 @@ void ProfileImpl::ClearBrowsingData( // browser_context_ and then BrowsingDataRemover, which in turn would call // OnBrowsingDataRemoverDone(), even though the clearing hasn't been finished. - int remove_mask = 0; + uint64_t remove_mask = 0; // This follows what Chrome does: see browsing_data_bridge.cc. for (auto data_type : data_types) { switch (data_type) { @@ -178,6 +240,7 @@ void ProfileImpl::ClearBrowsingData( remove_mask |= content::BrowsingDataRemover::DATA_TYPE_COOKIES; remove_mask |= content::BrowsingDataRemover::DATA_TYPE_DOM_STORAGE; remove_mask |= content::BrowsingDataRemover::DATA_TYPE_MEDIA_LICENSES; + remove_mask |= BrowsingDataRemoverDelegate::DATA_TYPE_ISOLATED_ORIGINS; break; case BrowsingDataType::CACHE: remove_mask |= content::BrowsingDataRemover::DATA_TYPE_CACHE; @@ -204,13 +267,33 @@ CookieManager* ProfileImpl::GetCookieManager() { return cookie_manager_.get(); } +void ProfileImpl::GetBrowserPersistenceIds( + base::OnceCallback<void(base::flat_set<std::string>)> callback) { + DCHECK(!browser_context_->IsOffTheRecord()); + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::BindOnce(&GetBrowserPersistenceIdsOnBackgroundThread, + GetBrowserPersisterDataBaseDir()), + std::move(callback)); +} + +void ProfileImpl::RemoveBrowserPersistenceStorage( + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids) { + DCHECK(!browser_context_->IsOffTheRecord()); + RemoveBrowserPersistenceStorageImpl(this, std::move(done_callback), + std::move(ids)); +} + // static void ProfileImpl::NukeDataAfterRemovingData( std::unique_ptr<ProfileImpl> profile, base::OnceClosure done_callback) { // Need PostTask to avoid reentrancy for deleting |browser_context_|. - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&ProfileImpl::DoNukeData, std::move(profile), + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ProfileImpl::DoNukeData, std::move(profile), std::move(done_callback))); } @@ -268,7 +351,7 @@ std::unique_ptr<Profile> Profile::DestroyAndDeleteDataFromDisk( std::unique_ptr<ProfileImpl> ProfileImpl::DestroyAndDeleteDataFromDisk( std::unique_ptr<ProfileImpl> profile, base::OnceClosure done_callback) { - if (profile->num_browser_impl_ > 0) + if (profile->GetNumberOfBrowsers() > 0) return profile; GetBackgroundDiskOperationTaskRunner()->PostTaskAndReply( @@ -282,9 +365,7 @@ std::unique_ptr<ProfileImpl> ProfileImpl::DestroyAndDeleteDataFromDisk( void ProfileImpl::OnProfileMarked(std::unique_ptr<ProfileImpl> profile, base::OnceClosure done_callback) { // Try to finish all writes and remove all data before nuking the profile. - static_cast<BrowserContextImpl*>(profile->GetBrowserContext()) - ->pref_service() - ->CommitPendingWrite(); + profile->GetBrowserContext()->pref_service()->CommitPendingWrite(); // Unretained is safe here because DataClearer is owned by // BrowserContextImpl which is owned by this. @@ -292,7 +373,7 @@ void ProfileImpl::OnProfileMarked(std::unique_ptr<ProfileImpl> profile, profile->GetBrowserContext(), base::BindOnce(&ProfileImpl::NukeDataAfterRemovingData, std::move(profile), std::move(done_callback))); - int remove_all_mask = 0x8fffffff; + uint64_t remove_all_mask = 0xffffffffffffffffull; clearer->ClearData(remove_all_mask, base::Time::Min(), base::Time::Max()); } @@ -329,7 +410,7 @@ static void JNI_ProfileImpl_EnumerateAllProfileNames( } jint ProfileImpl::GetNumBrowserImpl(JNIEnv* env) { - return num_browser_impl_; + return GetNumberOfBrowsers(); } jlong ProfileImpl::GetBrowserContext(JNIEnv* env) { @@ -397,39 +478,107 @@ jboolean ProfileImpl::GetBooleanSetting(JNIEnv* env, jint j_type) { return GetBooleanSetting(static_cast<SettingType>(j_type)); } -#endif // OS_ANDROID +void ProfileImpl::GetBrowserPersistenceIds( + JNIEnv* env, + const base::android::JavaRef<jobject>& j_callback) { + GetBrowserPersistenceIds( + base::BindOnce(&OnGotBrowserPersistenceIds, + base::android::ScopedJavaGlobalRef<jobject>(j_callback))); +} -void ProfileImpl::IncrementBrowserImplCount() { - num_browser_impl_++; +void ProfileImpl::RemoveBrowserPersistenceStorage( + JNIEnv* env, + const base::android::JavaRef<jobjectArray>& j_ids, + const base::android::JavaRef<jobject>& j_callback) { + std::vector<std::string> ids; + base::android::AppendJavaStringArrayToStringVector(env, j_ids, &ids); + RemoveBrowserPersistenceStorage( + base::BindOnce(&OnDidRemoveBrowserPersistenceStorage, + base::android::ScopedJavaGlobalRef<jobject>(j_callback)), + base::flat_set<std::string>(ids.begin(), ids.end())); } -void ProfileImpl::DecrementBrowserImplCount() { - DCHECK_GT(num_browser_impl_, 0u); - num_browser_impl_--; +void ProfileImpl::PrepareForPossibleCrossOriginNavigation(JNIEnv* env) { + PrepareForPossibleCrossOriginNavigation(); } +#endif // OS_ANDROID + base::FilePath ProfileImpl::GetBrowserPersisterDataBaseDir() const { return ComputeBrowserPersisterDataBaseDir(info_); } void ProfileImpl::SetBooleanSetting(SettingType type, bool value) { + auto* pref_service = GetBrowserContext()->pref_service(); switch (type) { case SettingType::BASIC_SAFE_BROWSING_ENABLED: - basic_safe_browsing_enabled_ = value; #if defined(OS_ANDROID) - BrowserProcess::GetInstance() - ->GetSafeBrowsingService(weblayer::GetUserAgent()) - ->SetSafeBrowsingDisabled(!basic_safe_browsing_enabled_); + pref_service->SetBoolean(::prefs::kSafeBrowsingEnabled, value); + pref_service->SetBoolean(::prefs::kSafeBrowsingEnhanced, false); +#endif + break; + case SettingType::UKM_ENABLED: { +#if defined(OS_ANDROID) + bool old_value = pref_service->GetBoolean(prefs::kUkmEnabled); #endif + pref_service->SetBoolean(prefs::kUkmEnabled, value); +#if defined(OS_ANDROID) + // Trigger a purge if the current state no longer allows UKM. + bool must_purge = old_value && !value; + WebLayerMetricsServiceClient::GetInstance()->UpdateUkm(must_purge); +#endif + break; + } + case SettingType::EXTENDED_REPORTING_SAFE_BROWSING_ENABLED: +#if defined(OS_ANDROID) + pref_service->SetBoolean(::prefs::kSafeBrowsingScoutReportingEnabled, + value); +#endif + break; + case SettingType::REAL_TIME_SAFE_BROWSING_ENABLED: +#if defined(OS_ANDROID) + pref_service->SetBoolean( + unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + value); +#endif + break; } } bool ProfileImpl::GetBooleanSetting(SettingType type) { + auto* pref_service = GetBrowserContext()->pref_service(); switch (type) { case SettingType::BASIC_SAFE_BROWSING_ENABLED: - return basic_safe_browsing_enabled_; +#if defined(OS_ANDROID) + return safe_browsing::IsSafeBrowsingEnabled(*pref_service); +#endif + return false; + case SettingType::UKM_ENABLED: + return pref_service->GetBoolean(prefs::kUkmEnabled); + case SettingType::EXTENDED_REPORTING_SAFE_BROWSING_ENABLED: +#if defined(OS_ANDROID) + return pref_service->GetBoolean( + ::prefs::kSafeBrowsingScoutReportingEnabled); +#endif + return false; + case SettingType::REAL_TIME_SAFE_BROWSING_ENABLED: +#if defined(OS_ANDROID) + return pref_service->GetBoolean( + unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled); +#endif + return false; } NOTREACHED(); } +void ProfileImpl::PrepareForPossibleCrossOriginNavigation() { + content::RenderProcessHost::WarmupSpareRenderProcessHost(GetBrowserContext()); +} + +int ProfileImpl::GetNumberOfBrowsers() { + const auto& browsers = BrowserList::GetInstance()->browsers(); + return std::count_if(browsers.begin(), browsers.end(), + [this](BrowserImpl* b) { return b->profile() == this; }); +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/profile_impl.h b/chromium/weblayer/browser/profile_impl.h index fbd30a82c8a..d845d6bc646 100644 --- a/chromium/weblayer/browser/profile_impl.h +++ b/chromium/weblayer/browser/profile_impl.h @@ -5,6 +5,8 @@ #ifndef WEBLAYER_BROWSER_PROFILE_IMPL_H_ #define WEBLAYER_BROWSER_PROFILE_IMPL_H_ +#include <set> + #include "base/files/file_path.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" @@ -46,7 +48,22 @@ class ProfileImpl : public Profile { static ProfileImpl* FromBrowserContext( content::BrowserContext* browser_context); - content::BrowserContext* GetBrowserContext(); + static std::set<ProfileImpl*> GetAllProfiles(); + + // Allows getting notified when profiles are created or destroyed. + class ProfileObserver { + public: + virtual void ProfileCreated(ProfileImpl* profile) {} + virtual void ProfileDestroyed(ProfileImpl* profile) {} + + protected: + virtual ~ProfileObserver() = default; + }; + + static void AddProfileObserver(ProfileObserver* observer); + static void RemoveProfileObserver(ProfileObserver* observer); + + BrowserContextImpl* GetBrowserContext(); // Called when the download subsystem has finished initializing. By this point // information about downloads that were interrupted by a previous crash would @@ -55,6 +72,7 @@ class ProfileImpl : public Profile { // Path data is stored at, empty if off-the-record. const base::FilePath& data_path() const { return info_.data_path; } + const std::string& name() const { return info_.name; } DownloadDelegate* download_delegate() { return download_delegate_; } // Profile implementation: @@ -65,8 +83,14 @@ class ProfileImpl : public Profile { void SetDownloadDirectory(const base::FilePath& directory) override; void SetDownloadDelegate(DownloadDelegate* delegate) override; CookieManager* GetCookieManager() override; + void GetBrowserPersistenceIds( + base::OnceCallback<void(base::flat_set<std::string>)> callback) override; + void RemoveBrowserPersistenceStorage( + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids) override; void SetBooleanSetting(SettingType type, bool value) override; bool GetBooleanSetting(SettingType type) override; + void PrepareForPossibleCrossOriginNavigation() override; #if defined(OS_ANDROID) ProfileImpl(JNIEnv* env, @@ -91,10 +115,16 @@ class ProfileImpl : public Profile { void EnsureBrowserContextInitialized(JNIEnv* env); void SetBooleanSetting(JNIEnv* env, jint j_type, jboolean j_value); jboolean GetBooleanSetting(JNIEnv* env, jint j_type); + void GetBrowserPersistenceIds( + JNIEnv* env, + const base::android::JavaRef<jobject>& j_callback); + void RemoveBrowserPersistenceStorage( + JNIEnv* env, + const base::android::JavaRef<jobjectArray>& j_ids, + const base::android::JavaRef<jobject>& j_callback); + void PrepareForPossibleCrossOriginNavigation(JNIEnv* env); #endif - void IncrementBrowserImplCount(); - void DecrementBrowserImplCount(); const base::FilePath& download_directory() { return download_directory_; } // Get the directory where BrowserPersister stores tab state data. This will @@ -115,6 +145,9 @@ class ProfileImpl : public Profile { // Callback when the system locale has been updated. void OnLocaleChanged(); + // Returns the number of Browsers with this profile. + int GetNumberOfBrowsers(); + ProfileInfo info_; std::unique_ptr<BrowserContextImpl> browser_context_; @@ -127,10 +160,6 @@ class ProfileImpl : public Profile { std::unique_ptr<CookieManagerImpl> cookie_manager_; - size_t num_browser_impl_ = 0u; - - bool basic_safe_browsing_enabled_ = true; - #if defined(OS_ANDROID) base::android::ScopedJavaGlobalRef<jobject> java_profile_; #endif diff --git a/chromium/weblayer/browser/safe_browsing/BUILD.gn b/chromium/weblayer/browser/safe_browsing/BUILD.gn deleted file mode 100644 index 001f036d3e8..00000000000 --- a/chromium/weblayer/browser/safe_browsing/BUILD.gn +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//build/config/android/config.gni") - -assert(is_android) - -source_set("safe_browsing") { - sources = [ - "safe_browsing_blocking_page.cc", - "safe_browsing_blocking_page.h", - "safe_browsing_navigation_throttle.cc", - "safe_browsing_navigation_throttle.h", - "safe_browsing_service.cc", - "safe_browsing_service.h", - "safe_browsing_subresource_helper.cc", - "safe_browsing_subresource_helper.h", - "safe_browsing_ui_manager.cc", - "safe_browsing_ui_manager.h", - "url_checker_delegate_impl.cc", - "url_checker_delegate_impl.h", - ] - deps = [ - "//components/safe_browsing/android:remote_database_manager", - "//components/safe_browsing/android:safe_browsing_api_handler", - "//components/safe_browsing/content", - "//components/safe_browsing/content/browser", - "//components/safe_browsing/content/renderer:throttles", - "//components/safe_browsing/core/browser", - "//components/safe_browsing/core/browser:network_context", - "//components/safe_browsing/core/common", - "//components/safe_browsing/core/db:database_manager", - "//components/security_interstitials/content:security_interstitial_page", - "//components/security_interstitials/core:unsafe_resource", - "//components/security_interstitials/core/", - "//content/public/browser", - "//skia", - "//third_party/blink/public/common", - ] -} diff --git a/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.cc b/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.cc new file mode 100644 index 00000000000..d3bee53a080 --- /dev/null +++ b/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.cc @@ -0,0 +1,60 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/safe_browsing/core/common/utils.h" +#include "components/safe_browsing/core/realtime/url_lookup_service.h" +#include "content/public/browser/browser_context.h" +#include "services/network/public/cpp/cross_thread_pending_shared_url_loader_factory.h" +#include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/browser_process.h" +#include "weblayer/browser/feature_list_creator.h" +#include "weblayer/browser/safe_browsing/safe_browsing_service.h" +#include "weblayer/browser/user_agent.h" +#include "weblayer/browser/verdict_cache_manager_factory.h" + +namespace weblayer { + +// static +safe_browsing::RealTimeUrlLookupService* +RealTimeUrlLookupServiceFactory::GetForBrowserContext( + content::BrowserContext* browser_context) { + return static_cast<safe_browsing::RealTimeUrlLookupService*>( + GetInstance()->GetServiceForBrowserContext(browser_context, + /* create= */ true)); +} + +// static +RealTimeUrlLookupServiceFactory* +RealTimeUrlLookupServiceFactory::GetInstance() { + return base::Singleton<RealTimeUrlLookupServiceFactory>::get(); +} + +RealTimeUrlLookupServiceFactory::RealTimeUrlLookupServiceFactory() + : BrowserContextKeyedServiceFactory( + "RealTimeUrlLookupService", + BrowserContextDependencyManager::GetInstance()) {} + +KeyedService* RealTimeUrlLookupServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + auto url_loader_factory = + std::make_unique<network::CrossThreadPendingSharedURLLoaderFactory>( + BrowserProcess::GetInstance() + ->GetSafeBrowsingService(weblayer::GetUserAgent()) + ->GetURLLoaderFactory()); + + return new safe_browsing::RealTimeUrlLookupService( + network::SharedURLLoaderFactory::Create(std::move(url_loader_factory)), + VerdictCacheManagerFactory::GetForBrowserContext(context), + nullptr /* identity manager */, nullptr /* profile sync service */, + static_cast<BrowserContextImpl*>(context)->pref_service(), + safe_browsing::GetProfileManagementStatus(nullptr), + false /* is_under_advanced_protection */, + static_cast<BrowserContextImpl*>(context)->IsOffTheRecord(), + FeatureListCreator::GetInstance()->variations_service()); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.h b/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.h new file mode 100644 index 00000000000..1d9e6780a26 --- /dev/null +++ b/chromium/weblayer/browser/safe_browsing/real_time_url_lookup_service_factory.h @@ -0,0 +1,53 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_SAFE_BROWSING_REAL_TIME_URL_LOOKUP_SERVICE_FACTORY_H_ +#define WEBLAYER_BROWSER_SAFE_BROWSING_REAL_TIME_URL_LOOKUP_SERVICE_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +class KeyedService; + +namespace content { +class BrowserContext; +} + +namespace safe_browsing { +class RealTimeUrlLookupService; +} // namespace safe_browsing + +namespace weblayer { + +// Singleton that owns RealTimeUrlLookupService objects and associates them +// them with BrowserContextImpl instances. +class RealTimeUrlLookupServiceFactory + : public BrowserContextKeyedServiceFactory { + public: + // Creates the service if it doesn't exist already for the given + // |browser_context|. If the service already exists, return its pointer. + static safe_browsing::RealTimeUrlLookupService* GetForBrowserContext( + content::BrowserContext* browser_context); + + // Get the singleton instance. + static RealTimeUrlLookupServiceFactory* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits<RealTimeUrlLookupServiceFactory>; + + RealTimeUrlLookupServiceFactory(); + ~RealTimeUrlLookupServiceFactory() override = default; + RealTimeUrlLookupServiceFactory(const RealTimeUrlLookupServiceFactory&) = + delete; + RealTimeUrlLookupServiceFactory& operator=( + const RealTimeUrlLookupServiceFactory&) = delete; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_SAFE_BROWSING_REAL_TIME_URL_LOOKUP_SERVICE_FACTORY_H_ diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_blocking_page.cc b/chromium/weblayer/browser/safe_browsing/safe_browsing_blocking_page.cc index 34cf6cb561d..fd6c603a40f 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_blocking_page.cc +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_blocking_page.cc @@ -6,7 +6,9 @@ #include "components/security_interstitials/content/security_interstitial_controller_client.h" #include "components/security_interstitials/content/unsafe_resource_util.h" +#include "components/security_interstitials/core/base_safe_browsing_error_ui.h" #include "content/public/browser/navigation_entry.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/safe_browsing/safe_browsing_ui_manager.h" namespace weblayer { @@ -43,11 +45,23 @@ SafeBrowsingBlockingPage* SafeBrowsingBlockingPage::CreateBlockingPage( GURL url = (main_frame_url.is_empty() && entry) ? entry->GetURL() : main_frame_url; + BrowserContextImpl* browser_context = + static_cast<BrowserContextImpl*>(web_contents->GetBrowserContext()); + security_interstitials::BaseSafeBrowsingErrorUI::SBErrorDisplayOptions + display_options = + BaseBlockingPage::CreateDefaultDisplayOptions(unsafe_resources); + display_options.is_extended_reporting_opt_in_allowed = + safe_browsing::IsExtendedReportingOptInAllowed( + *(browser_context->pref_service())); + display_options.is_extended_reporting_enabled = + safe_browsing::IsExtendedReportingEnabled( + *(browser_context->pref_service())); + return new SafeBrowsingBlockingPage( ui_manager, web_contents, url, unsafe_resources, CreateControllerClient(web_contents, unsafe_resources, ui_manager, - nullptr /*pref_service*/), - BaseBlockingPage::CreateDefaultDisplayOptions(unsafe_resources)); + browser_context->pref_service()), + display_options); } security_interstitials::SecurityInterstitialPage::TypeID diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc b/chromium/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc index 51bd76179c4..77975384186 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc @@ -4,21 +4,28 @@ #include <map> -#include "base/task/post_task.h" +#include "components/prefs/pref_service.h" #include "components/safe_browsing/android/safe_browsing_api_handler.h" #include "components/safe_browsing/content/base_blocking_page.h" #include "components/safe_browsing/core/db/v4_protocol_manager_util.h" #include "components/security_interstitials/content/security_interstitial_page.h" #include "components/security_interstitials/content/security_interstitial_tab_helper.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_utils.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/profile_impl.h" #include "weblayer/browser/safe_browsing/safe_browsing_blocking_page.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/public/navigation.h" #include "weblayer/public/navigation_controller.h" +#include "weblayer/public/profile.h" #include "weblayer/public/tab.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/load_completion_observer.h" @@ -34,8 +41,8 @@ void RunCallbackOnIOThread( callback, safe_browsing::SBThreatType threat_type, const safe_browsing::ThreatMetadata& metadata) { - base::PostTask(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(std::move(*callback), threat_type, metadata)); + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(*callback), threat_type, metadata)); } } // namespace @@ -79,14 +86,29 @@ class SafeBrowsingBrowserTest : public WebLayerBrowserTest { SafeBrowsingBrowserTest() : fake_handler_(new FakeSafeBrowsingApiHandler()) {} ~SafeBrowsingBrowserTest() override = default; - // WebLayerBrowserTest: void SetUpOnMainThread() override { + InitializeOnMainThread(); + // Safe Browsing is enabled by default + ASSERT_TRUE(GetSafeBrowsingEnabled()); + } + + void InitializeOnMainThread() { NavigateAndWaitForCompletion(GURL("about:blank"), shell()); safe_browsing::SafeBrowsingApiHandler::SetInstance(fake_handler_.get()); ASSERT_TRUE(embedded_test_server()->Start()); url_ = embedded_test_server()->GetURL("/simple_page.html"); } + void SetSafeBrowsingEnabled(bool value) { + GetProfile()->SetBooleanSetting(SettingType::BASIC_SAFE_BROWSING_ENABLED, + value); + } + + bool GetSafeBrowsingEnabled() { + return GetProfile()->GetBooleanSetting( + SettingType::BASIC_SAFE_BROWSING_ENABLED); + } + void NavigateWithThreatType(const safe_browsing::SBThreatType& threatType, bool expect_interstitial) { fake_handler_->AddRestriction(url_, threatType); @@ -106,6 +128,16 @@ class SafeBrowsingBrowserTest : public WebLayerBrowserTest { } } + void NavigateWithSubResourceAndThreatType( + const safe_browsing::SBThreatType& threat_type, + bool expect_interstitial) { + GURL page_with_script_url = + embedded_test_server()->GetURL("/simple_page_with_script.html"); + GURL script_url = embedded_test_server()->GetURL("/script.js"); + fake_handler_->AddRestriction(script_url, threat_type); + Navigate(page_with_script_url, expect_interstitial); + } + protected: content::WebContents* GetWebContents() { Tab* tab = shell()->tab(); @@ -126,6 +158,19 @@ class SafeBrowsingBrowserTest : public WebLayerBrowserTest { bool HasInterstitial() { return GetSecurityInterstitialPage() != nullptr; } + void KillRenderer() { + content::RenderProcessHost* child_process = + static_cast<TabImpl*>(shell()->tab()) + ->web_contents() + ->GetMainFrame() + ->GetProcess(); + content::RenderProcessHostWatcher crash_observer( + child_process, + content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + child_process->Shutdown(0); + crash_observer.Wait(); + } + std::unique_ptr<FakeSafeBrowsingApiHandler> fake_handler_; GURL url_; @@ -133,6 +178,21 @@ class SafeBrowsingBrowserTest : public WebLayerBrowserTest { DISALLOW_COPY_AND_ASSIGN(SafeBrowsingBrowserTest); }; +class SafeBrowsingDisabledBrowserTest : public SafeBrowsingBrowserTest { + public: + SafeBrowsingDisabledBrowserTest() {} + ~SafeBrowsingDisabledBrowserTest() override = default; + + void SetUpOnMainThread() override { + SetSafeBrowsingEnabled(false); + SafeBrowsingBrowserTest::InitializeOnMainThread(); + ASSERT_FALSE(GetSafeBrowsingEnabled()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SafeBrowsingDisabledBrowserTest); +}; + IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, DoesNotShowInterstitial_NoRestriction) { Navigate(url_, false); @@ -160,12 +220,70 @@ IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Billing) { IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Malware_Subresource) { - GURL page_with_script_url = - embedded_test_server()->GetURL("/simple_page_with_script.html"); - GURL script_url = embedded_test_server()->GetURL("/script.js"); - fake_handler_->AddRestriction(script_url, - safe_browsing::SB_THREAT_TYPE_URL_MALWARE); - Navigate(page_with_script_url, true); + NavigateWithSubResourceAndThreatType( + safe_browsing::SB_THREAT_TYPE_URL_MALWARE, true); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, + DoesNotShowInterstitial_Phishing_disableSB) { + // Test that the browser checks the safe browsing setting for new navigations. + SetSafeBrowsingEnabled(false); + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_PHISHING, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, + DoesNotShowInterstitial_Malware_Subresource_disableSB) { + // Test that new renderer checks the safe browsing setting. + SetSafeBrowsingEnabled(false); + KillRenderer(); + NavigateWithSubResourceAndThreatType( + safe_browsing::SB_THREAT_TYPE_URL_MALWARE, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, CheckSetsPrefs) { + // Check that changing safe browsing setting sets corresponding pref, + // which is persistent. + PrefService* prefs = GetProfile()->GetBrowserContext()->pref_service(); + SetSafeBrowsingEnabled(true); + EXPECT_TRUE(prefs->GetBoolean(::prefs::kSafeBrowsingEnabled)); + SetSafeBrowsingEnabled(false); + EXPECT_FALSE(prefs->GetBoolean(::prefs::kSafeBrowsingEnabled)); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_NoRestriction) { + Navigate(url_, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Safe) { + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_SAFE, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Malware) { + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_MALWARE, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Phishing) { + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_PHISHING, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Unwanted) { + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_UNWANTED, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Billing) { + NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_BILLING, false); +} + +IN_PROC_BROWSER_TEST_F(SafeBrowsingDisabledBrowserTest, + DoesNotShowInterstitial_Malware_Subresource) { + NavigateWithSubResourceAndThreatType( + safe_browsing::SB_THREAT_TYPE_URL_MALWARE, false); } -} // namespace weblayer +} // namespace weblayer
\ No newline at end of file diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_service.cc b/chromium/weblayer/browser/safe_browsing/safe_browsing_service.cc index 915e9a26f89..fca167feed4 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_service.cc +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_service.cc @@ -6,19 +6,24 @@ #include "base/bind.h" #include "base/path_service.h" -#include "base/task/post_task.h" +#include "components/prefs/pref_service.h" #include "components/safe_browsing/android/remote_database_manager.h" #include "components/safe_browsing/android/safe_browsing_api_handler_bridge.h" #include "components/safe_browsing/content/browser/browser_url_loader_throttle.h" #include "components/safe_browsing/content/browser/mojo_safe_browsing_impl.h" #include "components/safe_browsing/core/browser/safe_browsing_network_context.h" +#include "components/safe_browsing/core/common/safe_browsing_prefs.h" +#include "components/safe_browsing/core/realtime/url_lookup_service.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/network_service_instance.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/resource_context.h" +#include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" #include "third_party/blink/public/common/loader/url_loader_throttle.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/safe_browsing/safe_browsing_navigation_throttle.h" #include "weblayer/browser/safe_browsing/url_checker_delegate_impl.h" @@ -30,12 +35,15 @@ network::mojom::NetworkContextParamsPtr CreateDefaultNetworkContextParams( const std::string& user_agent) { network::mojom::NetworkContextParamsPtr network_context_params = network::mojom::NetworkContextParams::New(); + network_context_params->cert_verifier_params = content::GetCertVerifierParams( + network::mojom::CertVerifierCreationParams::New()); network_context_params->user_agent = user_agent; return network_context_params; } -// Helper method that checks the RenderProcessHost is still alive before hopping -// over to the IO thread. +// Helper method that checks the RenderProcessHost is still alive and checks the +// latest Safe Browsing pref value on the UI thread before hopping over to the +// IO thread. void MaybeCreateSafeBrowsing( int rph_id, content::ResourceContext* resource_context, @@ -49,8 +57,16 @@ void MaybeCreateSafeBrowsing( if (!render_process_host) return; - base::PostTask( - FROM_HERE, {content::BrowserThread::IO}, + bool is_safe_browsing_enabled = safe_browsing::IsSafeBrowsingEnabled( + *static_cast<BrowserContextImpl*>( + render_process_host->GetBrowserContext()) + ->pref_service()); + + if (!is_safe_browsing_enabled) + return; + + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&safe_browsing::MojoSafeBrowsingImpl::MaybeCreate, rph_id, resource_context, std::move(get_checker_delegate), std::move(receiver))); @@ -59,7 +75,7 @@ void MaybeCreateSafeBrowsing( } // namespace SafeBrowsingService::SafeBrowsingService(const std::string& user_agent) - : user_agent_(user_agent), safe_browsing_disabled_(false) {} + : user_agent_(user_agent) {} SafeBrowsingService::~SafeBrowsingService() = default; @@ -93,7 +109,8 @@ void SafeBrowsingService::Initialize() { std::unique_ptr<blink::URLLoaderThrottle> SafeBrowsingService::CreateURLLoaderThrottle( const base::RepeatingCallback<content::WebContents*()>& wc_getter, - int frame_tree_node_id) { + int frame_tree_node_id, + safe_browsing::RealTimeUrlLookupServiceBase* url_lookup_service) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); return safe_browsing::BrowserURLLoaderThrottle::Create( @@ -103,10 +120,7 @@ SafeBrowsingService::CreateURLLoaderThrottle( }, base::Unretained(this)), wc_getter, frame_tree_node_id, - // rt_lookup_service are used to - // perform real time url check, which is gated by UKM opted in. Since - // WebLayer currently doesn't support UKM, this feature is not enabled. - /*rt_lookup_service*/ nullptr); + url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr); } std::unique_ptr<content::NavigationThrottle> @@ -122,8 +136,7 @@ SafeBrowsingService::GetSafeBrowsingUrlCheckerDelegate() { if (!safe_browsing_url_checker_delegate_) { safe_browsing_url_checker_delegate_ = new UrlCheckerDelegateImpl( - GetSafeBrowsingDBManager(), GetSafeBrowsingUIManager(), - safe_browsing_disabled_); + GetSafeBrowsingDBManager(), GetSafeBrowsingUIManager()); } return safe_browsing_url_checker_delegate_; @@ -143,7 +156,7 @@ SafeBrowsingUIManager* SafeBrowsingService::GetSafeBrowsingUIManager() { void SafeBrowsingService::CreateSafeBrowsingUIManager() { DCHECK(!ui_manager_); - ui_manager_ = new SafeBrowsingUIManager(); + ui_manager_ = new SafeBrowsingUIManager(this); } void SafeBrowsingService::CreateAndStartSafeBrowsingDBManager() { @@ -163,8 +176,8 @@ scoped_refptr<network::SharedURLLoaderFactory> SafeBrowsingService::GetURLLoaderFactoryOnIOThread() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (!shared_url_loader_factory_on_io_) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&SafeBrowsingService::CreateURLLoaderFactoryForIO, base::Unretained(this), url_loader_factory_on_io_.BindNewPipeAndPassReceiver())); @@ -198,12 +211,12 @@ void SafeBrowsingService::AddInterface( base::BindRepeating( &SafeBrowsingService::GetSafeBrowsingUrlCheckerDelegate, base::Unretained(this))), - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})); + content::GetUIThreadTaskRunner({})); } void SafeBrowsingService::StopDBManager() { - base::PostTask(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&SafeBrowsingService::StopDBManagerOnIOThread, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&SafeBrowsingService::StopDBManagerOnIOThread, base::Unretained(this))); } @@ -215,24 +228,11 @@ void SafeBrowsingService::StopDBManagerOnIOThread() { } } -void SafeBrowsingService::SetSafeBrowsingDisabled(bool disabled) { - content::GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&SafeBrowsingService::SetSafeBrowsingDisabledOnIOThread, - base::Unretained(this), disabled)); -} - -void SafeBrowsingService::SetSafeBrowsingDisabledOnIOThread(bool disabled) { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - - if (safe_browsing_disabled_ != disabled) { - safe_browsing_disabled_ = disabled; - // If there is no safe_browsing_url_checker_delegate_ yet the opt_out - // setting will be set later during its creation. - if (safe_browsing_url_checker_delegate_) { - safe_browsing_url_checker_delegate_->SetSafeBrowsingDisabled(disabled); - } - } +scoped_refptr<network::SharedURLLoaderFactory> +SafeBrowsingService::GetURLLoaderFactory() { + if (!network_context_) + return nullptr; + return network_context_->GetURLLoaderFactory(); } } // namespace weblayer diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_service.h b/chromium/weblayer/browser/safe_browsing/safe_browsing_service.h index 3f5506031d5..1de5c0ff021 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_service.h +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_service.h @@ -29,6 +29,7 @@ class SharedURLLoaderFactory; namespace safe_browsing { class UrlCheckerDelegate; +class RealTimeUrlLookupServiceBase; class RemoteSafeBrowsingDatabaseManager; class SafeBrowsingApiHandler; class SafeBrowsingNetworkContext; @@ -49,13 +50,14 @@ class SafeBrowsingService { void Initialize(); std::unique_ptr<blink::URLLoaderThrottle> CreateURLLoaderThrottle( const base::RepeatingCallback<content::WebContents*()>& wc_getter, - int frame_tree_node_id); + int frame_tree_node_id, + safe_browsing::RealTimeUrlLookupServiceBase* url_lookup_service); std::unique_ptr<content::NavigationThrottle> CreateSafeBrowsingNavigationThrottle(content::NavigationHandle* handle); void AddInterface(service_manager::BinderRegistry* registry, content::RenderProcessHost* render_process_host); void StopDBManager(); - void SetSafeBrowsingDisabled(bool disabled); + scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory(); private: SafeBrowsingUIManager* GetSafeBrowsingUIManager(); @@ -72,7 +74,6 @@ class SafeBrowsingService { void CreateURLLoaderFactoryForIO( mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver); void StopDBManagerOnIOThread(); - void SetSafeBrowsingDisabledOnIOThread(bool disabled); // The UI manager handles showing interstitials. Accessed on both UI and IO // thread. @@ -98,8 +99,6 @@ class SafeBrowsingService { std::string user_agent_; - bool safe_browsing_disabled_; - DISALLOW_COPY_AND_ASSIGN(SafeBrowsingService); }; diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.cc b/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.cc index e2eecc83687..a616dfef1b7 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.cc +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.cc @@ -4,15 +4,28 @@ #include "weblayer/browser/safe_browsing/safe_browsing_ui_manager.h" +#include "components/safe_browsing/core/ping_manager.h" #include "content/public/browser/browser_thread.h" #include "weblayer/browser/safe_browsing/safe_browsing_blocking_page.h" +#include "weblayer/browser/safe_browsing/safe_browsing_service.h" #include "weblayer/browser/safe_browsing/safe_browsing_subresource_helper.h" using content::BrowserThread; +namespace { + +std::string GetProtocolConfigClientName() { + // Return a weblayer specific client name. + return "weblayer"; +} + +} // namespace + namespace weblayer { -SafeBrowsingUIManager::SafeBrowsingUIManager() { +SafeBrowsingUIManager::SafeBrowsingUIManager( + SafeBrowsingService* safe_browsing_service) + : safe_browsing_service_(safe_browsing_service) { DCHECK_CURRENTLY_ON(BrowserThread::UI); } @@ -21,8 +34,19 @@ SafeBrowsingUIManager::~SafeBrowsingUIManager() = default; void SafeBrowsingUIManager::SendSerializedThreatDetails( const std::string& serialized) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - // TODO(timvolodine): figure out if we want to send any threat reporting here. - // Note the base implementation does not send anything. + + if (!ping_manager_) { + ping_manager_ = ::safe_browsing::PingManager::Create( + safe_browsing_service_->GetURLLoaderFactory(), + safe_browsing::GetV4ProtocolConfig(GetProtocolConfigClientName(), + false /* auto_update */)); + } + + if (serialized.empty()) + return; + + DVLOG(1) << "Sending serialized threat details"; + ping_manager_->ReportThreatDetails(serialized); } safe_browsing::BaseBlockingPage* diff --git a/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.h b/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.h index 54e1c5136b5..989183c9c02 100644 --- a/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.h +++ b/chromium/weblayer/browser/safe_browsing/safe_browsing_ui_manager.h @@ -14,16 +14,21 @@ class WebContents; namespace safe_browsing { class BaseBlockingPage; +class PingManager; } namespace weblayer { +class SafeBrowsingService; class SafeBrowsingUIManager : public safe_browsing::BaseUIManager { public: // Construction needs to happen on the UI thread. - SafeBrowsingUIManager(); + SafeBrowsingUIManager(SafeBrowsingService* safe_browsing_service); // BaseUIManager overrides. + + // Called on the UI thread by the ThreatDetails with the serialized + // protocol buffer, so the service can send it over. void SendSerializedThreatDetails(const std::string& serialized) override; protected: @@ -35,6 +40,11 @@ class SafeBrowsingUIManager : public safe_browsing::BaseUIManager { const GURL& blocked_url, const UnsafeResource& unsafe_resource) override; + // Provides phishing and malware statistics. Accessed on IO thread. + std::unique_ptr<safe_browsing::PingManager> ping_manager_; + + SafeBrowsingService* safe_browsing_service_; + DISALLOW_COPY_AND_ASSIGN(SafeBrowsingUIManager); }; diff --git a/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc b/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc index 5eb42c49e92..7ff46a3158c 100644 --- a/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc +++ b/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc @@ -5,7 +5,6 @@ #include "weblayer/browser/safe_browsing/url_checker_delegate_impl.h" #include "base/bind.h" -#include "base/task/post_task.h" #include "components/safe_browsing/core/db/database_manager.h" #include "components/security_interstitials/core/unsafe_resource.h" #include "content/public/browser/browser_task_traits.h" @@ -16,11 +15,9 @@ namespace weblayer { UrlCheckerDelegateImpl::UrlCheckerDelegateImpl( scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager, - scoped_refptr<SafeBrowsingUIManager> ui_manager, - bool disabled) + scoped_refptr<SafeBrowsingUIManager> ui_manager) : database_manager_(std::move(database_manager)), ui_manager_(std::move(ui_manager)), - safe_browsing_disabled_(disabled), threat_types_(safe_browsing::CreateSBThreatTypeSet( {safe_browsing::SB_THREAT_TYPE_URL_MALWARE, safe_browsing::SB_THREAT_TYPE_URL_PHISHING, @@ -38,8 +35,8 @@ void UrlCheckerDelegateImpl::StartDisplayingBlockingPageHelper( const net::HttpRequestHeaders& headers, bool is_main_frame, bool has_user_gesture) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce( &UrlCheckerDelegateImpl::StartDisplayingDefaultBlockingPage, base::Unretained(this), resource)); @@ -61,8 +58,8 @@ void UrlCheckerDelegateImpl::StartDisplayingDefaultBlockingPage( } // Report back that it is not ok to proceed with loading the URL. - base::PostTask(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(resource.callback, false /* proceed */, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(resource.callback, false /* proceed */, false /* showed_interstitial */)); } @@ -71,17 +68,13 @@ bool UrlCheckerDelegateImpl::IsUrlWhitelisted(const GURL& url) { return false; } -void UrlCheckerDelegateImpl::SetSafeBrowsingDisabled(bool disabled) { - safe_browsing_disabled_ = disabled; -} - bool UrlCheckerDelegateImpl::ShouldSkipRequestCheck( const GURL& original_url, int frame_tree_node_id, int render_process_id, int render_frame_id, bool originated_from_service_worker) { - return safe_browsing_disabled_ ? true : false; + return false; } void UrlCheckerDelegateImpl::NotifySuspiciousSiteDetected( diff --git a/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.h b/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.h index d59745bed10..c940d7f7c98 100644 --- a/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.h +++ b/chromium/weblayer/browser/safe_browsing/url_checker_delegate_impl.h @@ -23,8 +23,7 @@ class UrlCheckerDelegateImpl : public safe_browsing::UrlCheckerDelegate { UrlCheckerDelegateImpl( scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager, - scoped_refptr<SafeBrowsingUIManager> ui_manager, - bool disabled); + scoped_refptr<SafeBrowsingUIManager> ui_manager); void SetSafeBrowsingDisabled(bool disabled); @@ -61,7 +60,6 @@ class UrlCheckerDelegateImpl : public safe_browsing::UrlCheckerDelegate { scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager_; scoped_refptr<SafeBrowsingUIManager> ui_manager_; - bool safe_browsing_disabled_; safe_browsing::SBThreatTypeSet threat_types_; DISALLOW_COPY_AND_ASSIGN(UrlCheckerDelegateImpl); diff --git a/chromium/weblayer/browser/site_isolation_browsertest.cc b/chromium/weblayer/browser/site_isolation_browsertest.cc new file mode 100644 index 00000000000..d10c39f10fc --- /dev/null +++ b/chromium/weblayer/browser/site_isolation_browsertest.cc @@ -0,0 +1,256 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/base_switches.h" +#include "base/files/file_path.h" +#include "base/system/sys_info.h" +#include "build/build_config.h" +#include "components/prefs/pref_service.h" +#include "components/site_isolation/features.h" +#include "components/site_isolation/pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/site_isolation_policy.h" +#include "content/public/common/content_client.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" +#include "net/dns/mock_host_resolver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/content_browser_client_impl.h" +#include "weblayer/browser/profile_impl.h" +#include "weblayer/browser/tab_impl.h" +#include "weblayer/shell/browser/shell.h" +#include "weblayer/test/weblayer_browser_test.h" +#include "weblayer/test/weblayer_browser_test_utils.h" + +namespace weblayer { +using testing::IsEmpty; +using testing::UnorderedElementsAre; + +class SiteIsolationBrowserTest : public WebLayerBrowserTest { + public: + SiteIsolationBrowserTest() { + feature_list_.InitWithFeaturesAndParameters( + {{site_isolation::features::kSitePerProcessOnlyForHighMemoryClients, + {{site_isolation::features:: + kSitePerProcessOnlyForHighMemoryClientsParamName, + "128"}}}, + {site_isolation::features::kSiteIsolationForPasswordSites, {}}}, + {}); + } + + std::vector<std::string> GetSavedIsolatedSites() { + PrefService* prefs = + user_prefs::UserPrefs::Get(GetProfile()->GetBrowserContext()); + auto* list = + prefs->GetList(site_isolation::prefs::kUserTriggeredIsolatedOrigins); + std::vector<std::string> sites; + for (const base::Value& value : list->GetList()) + sites.push_back(value.GetString()); + return sites; + } + + // WebLayerBrowserTest: + void SetUpOnMainThread() override { + original_client_ = content::SetBrowserClientForTesting(&browser_client_); + host_resolver()->AddRule("*", "127.0.0.1"); + embedded_test_server()->ServeFilesFromSourceDirectory("weblayer/test/data"); + ASSERT_TRUE(embedded_test_server()->Start()); + + ASSERT_FALSE( + content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites()); + ASSERT_TRUE( + content::SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled()); + } + + void TearDownOnMainThread() override { + content::SetBrowserClientForTesting(original_client_); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch("ignore-certificate-errors"); + + // This way the test always sees the same amount of physical memory + // (kLowMemoryDeviceThresholdMB = 512MB), regardless of how much memory is + // available in the testing environment. + base::CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableLowEndDeviceMode); + EXPECT_EQ(512, base::SysInfo::AmountOfPhysicalMemoryMB()); + } + + content::WebContents* GetWebContents() { + return static_cast<TabImpl*>(shell()->tab())->web_contents(); + } + + private: + // A browser client which forces off strict site isolation, so the test can + // assume password isolation is enabled. + class SiteIsolationContentBrowserClient : public ContentBrowserClientImpl { + public: + SiteIsolationContentBrowserClient() : ContentBrowserClientImpl(nullptr) {} + + bool ShouldEnableStrictSiteIsolation() override { return false; } + }; + + SiteIsolationContentBrowserClient browser_client_; + content::ContentBrowserClient* original_client_ = nullptr; + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, + SiteIsIsolatedAfterEnteringPassword) { + GURL url = embedded_test_server()->GetURL("sub.foo.com", + "/simple_password_form.html"); + NavigateAndWaitForCompletion(url, shell()); + content::WebContents* contents = GetWebContents(); + + // foo.com should not be isolated to start with. Verify that a cross-site + // iframe does not become an OOPIF. + EXPECT_FALSE( + contents->GetMainFrame()->GetSiteInstance()->RequiresDedicatedProcess()); + std::string kAppendIframe = R"( + var i = document.createElement('iframe'); + i.id = 'child'; + document.body.appendChild(i);)"; + EXPECT_TRUE(content::ExecJs(contents, kAppendIframe)); + GURL bar_url(embedded_test_server()->GetURL("bar.com", "/simple_page.html")); + EXPECT_TRUE(NavigateIframeToURL(contents, "child", bar_url)); + content::RenderFrameHost* child = ChildFrameAt(contents->GetMainFrame(), 0); + EXPECT_FALSE(child->IsCrossProcessSubframe()); + + // Fill a form and submit through a <input type="submit"> button. + content::TestNavigationObserver observer(contents); + std::string kFillAndSubmit = + "document.getElementById('username_field').value = 'temp';" + "document.getElementById('password_field').value = 'random';" + "document.getElementById('input_submit_button').click()"; + EXPECT_TRUE(content::ExecJs(contents, kFillAndSubmit)); + observer.Wait(); + + // Since there were no script references from other windows, we should've + // swapped BrowsingInstances and put the result of the form submission into a + // dedicated process, locked to foo.com. Check that a cross-site iframe now + // becomes an OOPIF. + EXPECT_TRUE( + contents->GetMainFrame()->GetSiteInstance()->RequiresDedicatedProcess()); + EXPECT_TRUE(ExecJs(contents, kAppendIframe)); + EXPECT_TRUE(NavigateIframeToURL(contents, "child", bar_url)); + child = ChildFrameAt(contents->GetMainFrame(), 0); + EXPECT_TRUE(child->IsCrossProcessSubframe()); +} + +// TODO(crbug.com/654704): Android does not support PRE_ tests. +#if !defined(OS_ANDROID) +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, + PRE_IsolatedSitesPersistAcrossRestarts) { + // There shouldn't be any saved isolated origins to start with. + EXPECT_THAT(GetSavedIsolatedSites(), IsEmpty()); + + // Isolate saved.com and saved2.com persistently. + GURL saved_url = + embedded_test_server()->GetURL("saved.com", "/simple_page.html"); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + GURL saved2_url = + embedded_test_server()->GetURL("saved2.com", "/simple_page.html"); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved2_url); + + NavigateAndWaitForCompletion(saved_url, shell()); + EXPECT_TRUE(GetWebContents() + ->GetMainFrame() + ->GetSiteInstance() + ->RequiresDedicatedProcess()); + + // Check that saved.com and saved2.com were saved to disk. + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("http://saved.com", "http://saved2.com")); +} + +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, + IsolatedSitesPersistAcrossRestarts) { + // Check that saved.com and saved2.com are still saved to disk. + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("http://saved.com", "http://saved2.com")); + + // Check that these sites utilize a dedicated process after restarting, but a + // non-isolated foo.com URL does not. + GURL saved_url = + embedded_test_server()->GetURL("saved.com", "/simple_page.html"); + GURL saved2_url = + embedded_test_server()->GetURL("saved2.com", "/simple_page2.html"); + GURL foo_url = + embedded_test_server()->GetURL("foo.com", "/simple_page3.html"); + NavigateAndWaitForCompletion(saved_url, shell()); + content::WebContents* contents = GetWebContents(); + EXPECT_TRUE( + contents->GetMainFrame()->GetSiteInstance()->RequiresDedicatedProcess()); + NavigateAndWaitForCompletion(saved2_url, shell()); + EXPECT_TRUE( + contents->GetMainFrame()->GetSiteInstance()->RequiresDedicatedProcess()); + NavigateAndWaitForCompletion(foo_url, shell()); + EXPECT_FALSE( + contents->GetMainFrame()->GetSiteInstance()->RequiresDedicatedProcess()); +} +#endif + +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, IsolatedSiteIsSavedOnlyOnce) { + GURL saved_url = + embedded_test_server()->GetURL("saved.com", "/simple_page.html"); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("http://saved.com")); +} + +// Verify that serving a Clear-Site-Data header does not clear saved isolated +// sites. Saved isolated sites should only be cleared by user-initiated actions. +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, + ClearSiteDataHeaderDoesNotClearSavedIsolatedSites) { + // Start an HTTPS server, as Clear-Site-Data is only available on HTTPS URLs. + net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); + https_server.AddDefaultHandlers( + base::FilePath(FILE_PATH_LITERAL("weblayer/test/data"))); + ASSERT_TRUE(https_server.Start()); + + // Isolate saved.com and verify it's been saved to disk. + GURL saved_url = https_server.GetURL("saved.com", "/clear_site_data.html"); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("https://saved.com")); + + // Navigate to a URL that serves a Clear-Site-Data header for cache, cookies, + // and DOM storage. This is the most that a Clear-Site-Data header could + // clear, and this should not clear saved isolated sites. + NavigateAndWaitForCompletion(saved_url, shell()); + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("https://saved.com")); +} + +IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, + ExplicitClearBrowsingDataClearsSavedIsolatedSites) { + GURL saved_url = + embedded_test_server()->GetURL("saved.com", "/simple_page.html"); + content::SiteInstance::StartIsolatingSite(GetProfile()->GetBrowserContext(), + saved_url); + EXPECT_THAT(GetSavedIsolatedSites(), + UnorderedElementsAre("http://saved.com")); + + base::RunLoop run_loop; + base::Time now = base::Time::Now(); + GetProfile()->ClearBrowsingData({BrowsingDataType::COOKIES_AND_SITE_DATA}, + now - base::TimeDelta::FromDays(1), now, + run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_THAT(GetSavedIsolatedSites(), IsEmpty()); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/site_isolation_policy_unittest.cc b/chromium/weblayer/browser/site_isolation_policy_unittest.cc new file mode 100644 index 00000000000..1ec3c0f711d --- /dev/null +++ b/chromium/weblayer/browser/site_isolation_policy_unittest.cc @@ -0,0 +1,113 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/site_isolation/site_isolation_policy.h" + +#include "base/base_switches.h" +#include "base/system/sys_info.h" +#include "base/test/scoped_feature_list.h" +#include "build/build_config.h" +#include "components/site_isolation/features.h" +#include "components/site_isolation/preloaded_isolated_origins.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/site_isolation_policy.h" +#include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/browser_task_environment.h" +#include "content/public/test/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace weblayer { +namespace { +using testing::UnorderedElementsAreArray; + +// Some command-line switches override field trials - the tests need to be +// skipped in this case. +bool ShouldSkipBecauseOfConflictingCommandLineSwitches() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSitePerProcess)) + return true; + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableSiteIsolation)) + return true; + + return false; +} + +} // namespace + +class SiteIsolationPolicyTest : public testing::Test { + public: + SiteIsolationPolicyTest() = default; + + void SetUp() override { + // This way the test always sees the same amount of physical memory + // (kLowMemoryDeviceThresholdMB = 512MB), regardless of how much memory is + // available in the testing environment. + base::CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableLowEndDeviceMode); + EXPECT_EQ(512, base::SysInfo::AmountOfPhysicalMemoryMB()); + } + + void SetMemoryThreshold(const std::string& threshold) { + threshold_feature_.InitAndEnableFeatureWithParameters( + site_isolation::features::kSitePerProcessOnlyForHighMemoryClients, + {{site_isolation::features:: + kSitePerProcessOnlyForHighMemoryClientsParamName, + threshold}}); + } + + private: + content::BrowserTaskEnvironment task_environment_; + base::test::ScopedFeatureList threshold_feature_; + + DISALLOW_COPY_AND_ASSIGN(SiteIsolationPolicyTest); +}; + +TEST_F(SiteIsolationPolicyTest, NoIsolationBelowMemoryThreshold) { + if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) + return; + + SetMemoryThreshold("768"); + EXPECT_FALSE( + content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites()); + EXPECT_FALSE( + content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled()); +} + +TEST_F(SiteIsolationPolicyTest, IsolationAboveMemoryThreshold) { + if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) + return; + + SetMemoryThreshold("128"); + // Android should only use the preloaded origin list, while desktop should + // isolate all sites. +#if defined(OS_ANDROID) + EXPECT_FALSE( + content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites()); + EXPECT_TRUE( + content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled()); +#else + EXPECT_TRUE(content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites()); + EXPECT_FALSE( + content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled()); +#endif +} + +TEST_F(SiteIsolationPolicyTest, IsolatedOriginsContainPreloadedOrigins) { + if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) + return; + + content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins(); + + std::vector<url::Origin> expected_embedder_origins = + site_isolation::GetBrowserSpecificBuiltInIsolatedOrigins(); + auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance(); + std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(); + EXPECT_THAT(expected_embedder_origins, + UnorderedElementsAreArray(isolated_origins)); +} +} // namespace weblayer diff --git a/chromium/weblayer/browser/ssl_browsertest.cc b/chromium/weblayer/browser/ssl_browsertest.cc index 82973b2b143..0ef9fa45322 100644 --- a/chromium/weblayer/browser/ssl_browsertest.cc +++ b/chromium/weblayer/browser/ssl_browsertest.cc @@ -7,12 +7,15 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "base/optional.h" +#include "base/scoped_observer.h" #include "build/build_config.h" #include "components/network_time/network_time_tracker.h" #include "components/security_interstitials/content/ssl_error_handler.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/weblayer_security_blocking_page_factory.h" +#include "weblayer/public/browser.h" +#include "weblayer/public/browser_observer.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/interstitial_utils.h" #include "weblayer/test/load_completion_observer.h" @@ -20,6 +23,37 @@ #include "weblayer/test/weblayer_browser_test_utils.h" namespace weblayer { +namespace { + +#if defined(OS_ANDROID) +// Waits for a new tab to be created, and then load |url|. +class NewTabWaiter : public BrowserObserver { + public: + NewTabWaiter(Browser* browser, const GURL& url) : url_(url) { + observer_.Add(browser); + } + + void OnTabAdded(Tab* tab) override { + navigation_observer_ = std::make_unique<TestNavigationObserver>( + url_, TestNavigationObserver::NavigationEvent::kStart, tab); + run_loop_.Quit(); + } + + void Wait() { + if (!navigation_observer_) + run_loop_.Run(); + navigation_observer_->Wait(); + } + + private: + GURL url_; + std::unique_ptr<TestNavigationObserver> navigation_observer_; + base::RunLoop run_loop_; + ScopedObserver<Browser, BrowserObserver> observer_{this}; +}; +#endif + +} // namespace class SSLBrowserTest : public WebLayerBrowserTest { public: @@ -166,13 +200,12 @@ class SSLBrowserTest : public WebLayerBrowserTest { // Note: The embedded test server cannot actually load the captive portal // login URL, so simply detect the start of the navigation to the page. - TestNavigationObserver navigation_observer( - WebLayerSecurityBlockingPageFactory:: - GetCaptivePortalLoginPageUrlForTesting(), - TestNavigationObserver::NavigationEvent::kStart, shell()); + NewTabWaiter waiter(shell()->browser(), + WebLayerSecurityBlockingPageFactory:: + GetCaptivePortalLoginPageUrlForTesting()); ExecuteScript(shell(), "window.certificateErrorPageController.openLogin();", false /*use_separate_isolate*/); - navigation_observer.Wait(); + waiter.Wait(); } #endif diff --git a/chromium/weblayer/browser/system_network_context_manager.cc b/chromium/weblayer/browser/system_network_context_manager.cc index bb6a56918de..2f7900fc33d 100644 --- a/chromium/weblayer/browser/system_network_context_manager.cc +++ b/chromium/weblayer/browser/system_network_context_manager.cc @@ -6,7 +6,6 @@ #include "build/build_config.h" #include "components/variations/net/variations_http_headers.h" -#include "content/public/browser/cors_exempt_headers.h" #include "content/public/browser/network_service_instance.h" #include "services/network/public/cpp/shared_url_loader_factory.h" @@ -52,9 +51,10 @@ SystemNetworkContextManager::CreateDefaultNetworkContextParams( const std::string& user_agent) { network::mojom::NetworkContextParamsPtr network_context_params = network::mojom::NetworkContextParams::New(); + network_context_params->cert_verifier_params = content::GetCertVerifierParams( + network::mojom::CertVerifierCreationParams::New()); ConfigureDefaultNetworkContextParams(network_context_params.get(), user_agent); - content::UpdateCorsExemptHeader(network_context_params.get()); variations::UpdateCorsExemptHeaderForVariations(network_context_params.get()); return network_context_params; } @@ -94,8 +94,6 @@ SystemNetworkContextManager::GetSystemNetworkContext() { void SystemNetworkContextManager::OnNetworkServiceCreated( network::mojom::NetworkService* network_service) { - // The system NetworkContext must be created first, since it sets - // |primary_network_context| to true. system_network_context_.reset(); network_service->CreateNetworkContext( system_network_context_.BindNewPipeAndPassReceiver(), @@ -108,7 +106,6 @@ SystemNetworkContextManager::CreateSystemNetworkContextManagerParams() { CreateDefaultNetworkContextParams(user_agent_); network_context_params->context_name = std::string("system"); - network_context_params->primary_network_context = true; return network_context_params; } diff --git a/chromium/weblayer/browser/tab_impl.cc b/chromium/weblayer/browser/tab_impl.cc index 605821fcacc..052dbcb9517 100644 --- a/chromium/weblayer/browser/tab_impl.cc +++ b/chromium/weblayer/browser/tab_impl.cc @@ -9,22 +9,32 @@ #include "base/auto_reset.h" #include "base/guid.h" #include "base/logging.h" -#include "base/task/post_task.h" +#include "base/no_destructor.h" #include "base/task/thread_pool.h" +#include "base/time/default_tick_clock.h" #include "cc/layers/layer.h" #include "components/autofill/content/browser/content_autofill_driver_factory.h" #include "components/autofill/core/browser/autofill_manager.h" #include "components/autofill/core/browser/autofill_provider.h" +#include "components/blocked_content/popup_blocker.h" +#include "components/blocked_content/popup_blocker_tab_helper.h" +#include "components/blocked_content/popup_opener_tab_helper.h" +#include "components/blocked_content/popup_tracker.h" #include "components/captive_portal/core/buildflags.h" #include "components/client_hints/browser/client_hints.h" #include "components/content_settings/browser/tab_specific_content_settings.h" #include "components/find_in_page/find_tab_helper.h" #include "components/find_in_page/find_types.h" +#include "components/js_injection/browser/js_communication_host.h" +#include "components/js_injection/browser/web_message_host.h" +#include "components/js_injection/browser/web_message_host_factory.h" #include "components/permissions/permission_manager.h" #include "components/permissions/permission_request_manager.h" #include "components/permissions/permission_result.h" #include "components/prefs/pref_service.h" #include "components/sessions/content/session_tab_helper.h" +#include "components/translate/core/browser/translate_manager.h" +#include "components/ukm/content/source_url_recorder.h" #include "components/webrtc/media_stream_devices_controller.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -38,23 +48,32 @@ #include "content/public/browser/web_contents.h" #include "content/public/common/web_preferences.h" #include "third_party/blink/public/mojom/renderer_preferences.mojom.h" +#include "third_party/blink/public/mojom/window_features/window_features.mojom.h" #include "ui/base/window_open_disposition.h" #include "weblayer/browser/autofill_client_impl.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/content_browser_client_impl.h" #include "weblayer/browser/file_select_helper.h" #include "weblayer/browser/host_content_settings_map_factory.h" #include "weblayer/browser/i18n_util.h" +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/js_communication/web_message_host_factory_wrapper.h" #include "weblayer/browser/navigation_controller_impl.h" #include "weblayer/browser/page_load_metrics_initialize.h" +#include "weblayer/browser/password_manager_driver_factory.h" #include "weblayer/browser/permissions/permission_manager_factory.h" #include "weblayer/browser/persistence/browser_persister.h" +#include "weblayer/browser/popup_navigation_delegate_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_specific_content_settings_delegate.h" #include "weblayer/browser/translate_client_impl.h" +#include "weblayer/browser/weblayer_features.h" #include "weblayer/common/isolated_world_ids.h" #include "weblayer/public/fullscreen_delegate.h" +#include "weblayer/public/js_communication/web_message.h" +#include "weblayer/public/js_communication/web_message_host_factory.h" #include "weblayer/public/new_tab_delegate.h" #include "weblayer/public/tab_observer.h" @@ -64,10 +83,11 @@ #if defined(OS_ANDROID) #include "base/android/callback_android.h" +#include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/json/json_writer.h" #include "base/trace_event/trace_event.h" -#include "components/autofill/android/autofill_provider_android.h" +#include "components/autofill/android/provider/autofill_provider_android.h" #include "components/embedder_support/android/contextmenu/context_menu_builder.h" #include "components/embedder_support/android/delegate/color_chooser_android.h" #include "components/javascript_dialogs/tab_modal_dialog_manager.h" // nogncheck @@ -76,8 +96,10 @@ #include "weblayer/browser/browser_controls_container_view.h" #include "weblayer/browser/browser_controls_navigation_state_handler.h" #include "weblayer/browser/controls_visibility_reason.h" +#include "weblayer/browser/http_auth_handler_impl.h" #include "weblayer/browser/java/jni/TabImpl_jni.h" #include "weblayer/browser/javascript_tab_modal_dialog_manager_delegate_android.h" +#include "weblayer/browser/js_communication/web_message_host_factory_proxy.h" #include "weblayer/browser/weblayer_factory_impl_android.h" #include "weblayer/browser/webrtc/media_stream_manager.h" #endif @@ -98,6 +120,9 @@ namespace weblayer { namespace { +// Maximum size of data when calling SetData(). +constexpr int kMaxDataSize = 4096; + #if defined(OS_ANDROID) bool g_system_autofill_disabled_for_testing = false; @@ -127,16 +152,11 @@ NewTabType NewTabTypeFromWindowDisposition(WindowOpenDisposition disposition) { // Opens a captive portal login page in |web_contents|. void OpenCaptivePortalLoginTabInWebContents( content::WebContents* web_contents) { - // In Chrome this opens in a new tab, but WebLayer's TabImpl has no support - // for opening new tabs (its OpenURLFromTab() method DCHECKs if the - // disposition is not |CURRENT_TAB|). - // TODO(crbug.com/1047130): Revisit if TabImpl gets support for opening URLs - // in new tabs. content::OpenURLParams params( CaptivePortalServiceFactory::GetForBrowserContext( web_contents->GetBrowserContext()) ->test_url(), - content::Referrer(), WindowOpenDisposition::CURRENT_TAB, + content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false); web_contents->OpenURL(params); } @@ -147,12 +167,10 @@ void OpenCaptivePortalLoginTabInWebContents( constexpr int kWebContentsUserDataKey = 0; struct UserData : public base::SupportsUserData::Data { - TabImpl* controller = nullptr; + TabImpl* tab = nullptr; }; #if defined(OS_ANDROID) -Tab* g_last_tab; - void HandleJavaScriptResult(const ScopedJavaGlobalRef<jobject>& callback, base::Value result) { std::string json; @@ -179,8 +197,8 @@ void ConvertToJavaBitmapBackgroundThread( // Make sure to only pass ScopedJavaGlobalRef between threads. ScopedJavaGlobalRef<jobject> java_bitmap = ScopedJavaGlobalRef<jobject>( gfx::ConvertToJavaBitmap(&bitmap, gfx::OomBehavior::kReturnNullOnOom)); - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(std::move(callback), std::move(java_bitmap))); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), std::move(java_bitmap))); } void OnScreenShotCaptured(const ScopedJavaGlobalRef<jobject>& value_callback, @@ -204,9 +222,26 @@ void OnScreenShotCaptured(const ScopedJavaGlobalRef<jobject>& value_callback, #endif // OS_ANDROID +std::set<TabImpl*>& GetTabs() { + static base::NoDestructor<std::set<TabImpl*>> s_all_tab_impl; + return *s_all_tab_impl; +} + } // namespace #if defined(OS_ANDROID) + +static ScopedJavaLocalRef<jobject> JNI_TabImpl_FromWebContents( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& j_web_contents) { + content::WebContents* web_contents = + content::WebContents::FromJavaWebContents(j_web_contents); + TabImpl* tab = TabImpl::FromWebContents(web_contents); + if (tab) + return ScopedJavaLocalRef<jobject>(tab->GetJavaTab()); + return nullptr; +} + TabImpl::TabImpl(ProfileImpl* profile, const JavaParamRef<jobject>& java_impl) : TabImpl(profile) { java_impl_ = java_impl; @@ -219,9 +254,7 @@ TabImpl::TabImpl(ProfileImpl* profile, : profile_(profile), web_contents_(std::move(web_contents)), guid_(guid.empty() ? base::GenerateGUID() : guid) { -#if defined(OS_ANDROID) - g_last_tab = this; -#endif + GetTabs().insert(this); if (web_contents_) { // This code path is hit when the page requests a new tab, which should // only be possible from the same profile. @@ -245,7 +278,7 @@ TabImpl::TabImpl(ProfileImpl* profile, &TabImpl::UpdateRendererPrefs, base::Unretained(this), true)); std::unique_ptr<UserData> user_data = std::make_unique<UserData>(); - user_data->controller = this; + user_data->tab = this; web_contents_->SetUserData(&kWebContentsUserDataKey, std::move(user_data)); web_contents_->SetDelegate(this); @@ -253,13 +286,14 @@ TabImpl::TabImpl(ProfileImpl* profile, navigation_controller_ = std::make_unique<NavigationControllerImpl>(this); +#if defined(OS_ANDROID) + InfoBarService::CreateForWebContents(web_contents_.get()); +#endif + find_in_page::FindTabHelper::CreateForWebContents(web_contents_.get()); GetFindTabHelper()->AddObserver(this); - // TODO(crbug.com/1072334): Resolve incorporation of translate in incognito - // mode. - if (!web_contents_->GetBrowserContext()->IsOffTheRecord()) - TranslateClientImpl::CreateForWebContents(web_contents_.get()); + TranslateClientImpl::CreateForWebContents(web_contents_.get()); sessions::SessionTabHelper::CreateForWebContents( web_contents_.get(), @@ -278,10 +312,19 @@ TabImpl::TabImpl(ProfileImpl* profile, content_settings::TabSpecificContentSettings::CreateForWebContents( web_contents_.get(), std::make_unique<TabSpecificContentSettingsDelegate>( web_contents_.get())); + blocked_content::PopupBlockerTabHelper::CreateForWebContents( + web_contents_.get()); + blocked_content::PopupOpenerTabHelper::CreateForWebContents( + web_contents_.get(), base::DefaultTickClock::GetInstance(), + HostContentSettingsMapFactory::GetForBrowserContext( + web_contents_->GetBrowserContext())); + PasswordManagerDriverFactory::CreateForWebContents(web_contents_.get()); InitializePageLoadMetricsForWebContents(web_contents_.get()); + ukm::InitializeSourceUrlRecorderForWebContents(web_contents_.get()); #if defined(OS_ANDROID) + InfoBarService::CreateForWebContents(web_contents_.get()); javascript_dialogs::TabModalDialogManager::CreateForWebContents( web_contents_.get(), std::make_unique<JavaScriptTabModalDialogManagerDelegateAndroid>( @@ -318,6 +361,7 @@ TabImpl::~TabImpl() { Observe(nullptr); web_contents_->SetDelegate(nullptr); web_contents_.reset(); + GetTabs().erase(this); } // static @@ -325,9 +369,22 @@ TabImpl* TabImpl::FromWebContents(content::WebContents* web_contents) { if (!web_contents) return nullptr; - return reinterpret_cast<UserData*>( - web_contents->GetUserData(&kWebContentsUserDataKey)) - ->controller; + UserData* user_data = reinterpret_cast<UserData*>( + web_contents->GetUserData(&kWebContentsUserDataKey)); + return user_data ? user_data->tab : nullptr; +} + +// static +std::set<TabImpl*> TabImpl::GetAllTabImpl() { + return GetTabs(); +} + +void TabImpl::AddDataObserver(DataObserver* observer) { + data_observers_.AddObserver(observer); +} + +void TabImpl::RemoveDataObserver(DataObserver* observer) { + data_observers_.RemoveObserver(observer); } void TabImpl::SetErrorPageDelegate(ErrorPageDelegate* delegate) { @@ -388,6 +445,35 @@ const std::string& TabImpl::GetGuid() { return guid_; } +void TabImpl::SetData(const std::map<std::string, std::string>& data) { + bool result = SetDataInternal(data); + DCHECK(result) << "Data given to SetData() was too large."; +} + +const std::map<std::string, std::string>& TabImpl::GetData() { + return data_; +} + +base::string16 TabImpl::AddWebMessageHostFactory( + std::unique_ptr<WebMessageHostFactory> factory, + const base::string16& js_object_name, + const std::vector<std::string>& allowed_origin_rules) { + if (!js_communication_host_) { + js_communication_host_ = + std::make_unique<js_injection::JsCommunicationHost>( + web_contents_.get()); + } + return js_communication_host_->AddWebMessageHostFactory( + std::make_unique<WebMessageHostFactoryWrapper>(std::move(factory)), + js_object_name, allowed_origin_rules); +} + +void TabImpl::RemoveWebMessageHostFactory( + const base::string16& js_object_name) { + if (js_communication_host_) + js_communication_host_->RemoveWebMessageHostFactory(js_object_name); +} + void TabImpl::ExecuteScriptWithUserGestureForTests( const base::string16& script) { web_contents_->GetMainFrame()->ExecuteJavaScriptWithUserGestureForTests( @@ -430,6 +516,28 @@ void TabImpl::ShowContextMenu(const content::ContextMenuParams& params) { #endif } +void TabImpl::ShowHttpAuthPrompt(HttpAuthHandlerImpl* auth_handler) { + CHECK(!auth_handler_); + auth_handler_ = auth_handler; +#if defined(OS_ANDROID) + JNIEnv* env = AttachCurrentThread(); + GURL url = auth_handler_->url(); + Java_TabImpl_showHttpAuthPrompt( + env, java_impl_, base::android::ConvertUTF8ToJavaString(env, url.host()), + base::android::ConvertUTF8ToJavaString(env, url.spec())); +#endif +} + +void TabImpl::CloseHttpAuthPrompt() { + if (!auth_handler_) + return; + auth_handler_ = nullptr; +#if defined(OS_ANDROID) + JNIEnv* env = AttachCurrentThread(); + Java_TabImpl_closeHttpAuthPrompt(env, java_impl_); +#endif +} + #if defined(OS_ANDROID) // static void TabImpl::DisableAutofillSystemIntegrationForTesting() { @@ -444,13 +552,10 @@ static jlong JNI_TabImpl_CreateTab(JNIEnv* env, } static void JNI_TabImpl_DeleteTab(JNIEnv* env, jlong tab) { - std::unique_ptr<Tab> owned_tab; TabImpl* tab_impl = reinterpret_cast<TabImpl*>(tab); DCHECK(tab_impl); - if (tab_impl->browser()) - owned_tab = tab_impl->browser()->RemoveTab(tab_impl); - else - owned_tab.reset(tab_impl); + DCHECK(tab_impl->browser()); + tab_impl->browser()->DestroyTab(tab_impl); } ScopedJavaLocalRef<jobject> TabImpl::GetWebContents(JNIEnv* env) { @@ -571,6 +676,8 @@ void TabImpl::UpdateBrowserControlsStateImpl( content::BrowserControlsState old_state, bool animate) { current_browser_controls_state_ = new_state; + if (base::FeatureList::IsEnabled(kImmediatelyHideBrowserControlsForTest)) + animate = false; web_contents_->GetMainFrame()->UpdateBrowserControlsState(new_state, old_state, animate); } @@ -596,24 +703,116 @@ void TabImpl::CaptureScreenShot( base::BindOnce(&OnScreenShotCaptured, ScopedJavaGlobalRef<jobject>(value_callback))); } + +jboolean TabImpl::SetData( + JNIEnv* env, + const base::android::JavaParamRef<jobjectArray>& data) { + std::vector<std::string> flattened_map; + base::android::AppendJavaStringArrayToStringVector(env, data, &flattened_map); + std::map<std::string, std::string> data_map; + for (size_t i = 0; i < flattened_map.size(); i += 2) { + data_map.insert({flattened_map[i], flattened_map[i + 1]}); + } + return SetDataInternal(data_map); +} + +base::android::ScopedJavaLocalRef<jobjectArray> TabImpl::GetData(JNIEnv* env) { + std::vector<std::string> flattened_map; + for (const auto& kv : data_) { + flattened_map.push_back(kv.first); + flattened_map.push_back(kv.second); + } + return base::android::ToJavaArrayOfStrings(env, flattened_map); +} + jboolean TabImpl::IsRendererControllingBrowserControlsOffsets(JNIEnv* env) { return browser_controls_navigation_state_handler_ ->IsRendererControllingOffsets(); } +void TabImpl::SetHttpAuth( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& username, + const base::android::JavaParamRef<jstring>& password) { + auth_handler_->Proceed( + base::android::ConvertJavaStringToUTF16(env, username), + base::android::ConvertJavaStringToUTF16(env, password)); + CloseHttpAuthPrompt(); +} + +void TabImpl::CancelHttpAuth(JNIEnv* env) { + auth_handler_->Cancel(); + CloseHttpAuthPrompt(); +} + +base::android::ScopedJavaLocalRef<jstring> TabImpl::RegisterWebMessageCallback( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& js_object_name, + const base::android::JavaParamRef<jobjectArray>& js_origins, + const base::android::JavaParamRef<jobject>& client) { + auto proxy = std::make_unique<WebMessageHostFactoryProxy>(client); + std::vector<std::string> origins; + base::android::AppendJavaStringArrayToStringVector(env, js_origins, &origins); + base::string16 result = AddWebMessageHostFactory( + std::move(proxy), + base::android::ConvertJavaStringToUTF16(env, js_object_name), origins); + return base::android::ConvertUTF16ToJavaString(env, result); +} + +void TabImpl::UnregisterWebMessageCallback( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& js_object_name) { + base::string16 name; + base::android::ConvertJavaStringToUTF16(env, js_object_name, &name); + RemoveWebMessageHostFactory(name); +} + +jboolean TabImpl::CanTranslate(JNIEnv* env) { + return TranslateClientImpl::FromWebContents(web_contents()) + ->GetTranslateManager() + ->CanManuallyTranslate(); +} + +void TabImpl::ShowTranslateUi(JNIEnv* env) { + TranslateClientImpl::FromWebContents(web_contents()) + ->ManualTranslateWhenReady(); +} #endif // OS_ANDROID content::WebContents* TabImpl::OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) { - if (params.disposition != WindowOpenDisposition::CURRENT_TAB) { - NOTIMPLEMENTED(); - return nullptr; + if (blocked_content::ConsiderForPopupBlocking(params.disposition)) { + bool blocked = blocked_content::MaybeBlockPopup( + source, nullptr, + std::make_unique<PopupNavigationDelegateImpl>( + params, source, nullptr), + ¶ms, blink::mojom::WindowFeatures(), + HostContentSettingsMapFactory::GetForBrowserContext( + source->GetBrowserContext())) == nullptr; + if (blocked) + return nullptr; + } + + if (params.disposition == WindowOpenDisposition::CURRENT_TAB) { + source->GetController().LoadURLWithParams( + content::NavigationController::LoadURLParams(params)); + return source; } - source->GetController().LoadURLWithParams( + // All URLs not opening in the current tab will get a new tab. + std::unique_ptr<content::WebContents> new_tab_contents = + content::WebContents::Create(content::WebContents::CreateParams( + web_contents()->GetBrowserContext())); + content::WebContents* new_tab_contents_raw = new_tab_contents.get(); + bool was_blocked = false; + AddNewContents(web_contents(), std::move(new_tab_contents), params.url, + params.disposition, {}, params.user_gesture, &was_blocked); + if (was_blocked) + return nullptr; + new_tab_contents_raw->GetController().LoadURLWithParams( content::NavigationController::LoadURLParams(params)); - return source; + return new_tab_contents_raw; } void TabImpl::ShowRepostFormWarningDialog(content::WebContents* source) { @@ -681,10 +880,9 @@ void TabImpl::RunFileChooser( int TabImpl::GetTopControlsHeight() { #if defined(OS_ANDROID) - int height = top_controls_container_view_ - ? top_controls_container_view_->GetControlsHeight() - : 0; - return height; + return top_controls_container_view_ + ? top_controls_container_view_->GetControlsHeight() + : 0; #else return 0; #endif @@ -747,8 +945,7 @@ bool TabImpl::CheckMediaAccessPermission( } void TabImpl::EnterFullscreenModeForTab( - content::WebContents* web_contents, - const GURL& origin, + content::RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options) { // TODO: support |options|. is_fullscreen_ = true; @@ -791,12 +988,19 @@ void TabImpl::AddNewContents(content::WebContents* source, const gfx::Rect& initial_rect, bool user_gesture, bool* was_blocked) { - if (!new_tab_delegate_) + if (!new_tab_delegate_) { + *was_blocked = true; return; + } + + // At this point the |new_contents| is beyond the popup blocker, but we use + // the same logic for determining if the popup tracker needs to be attached. + if (source && blocked_content::ConsiderForPopupBlocking(disposition)) { + blocked_content::PopupTracker::CreateForWebContents(new_contents.get(), + source, disposition); + } - std::unique_ptr<Tab> tab = - std::make_unique<TabImpl>(profile_, std::move(new_contents)); - new_tab_delegate_->OnNewTab(std::move(tab), + new_tab_delegate_->OnNewTab(browser_->CreateTab(std::move(new_contents)), NewTabTypeFromWindowDisposition(disposition)); } @@ -820,7 +1024,7 @@ void TabImpl::CloseContents(content::WebContents* source) { // return. } #else - browser_->RemoveTab(this); + browser_->DestroyTab(this); #endif } @@ -952,16 +1156,6 @@ void TabImpl::SetBrowserControlsConstraint( } #endif -std::unique_ptr<Tab> Tab::Create(Profile* profile) { - return std::make_unique<TabImpl>(static_cast<ProfileImpl*>(profile)); -} - -#if defined(OS_ANDROID) -Tab* Tab::GetLastTabForTesting() { - return g_last_tab; -} -#endif - void TabImpl::InitializeAutofillForTests( std::unique_ptr<autofill::AutofillProvider> provider) { DCHECK(!autofill_provider_); @@ -995,4 +1189,16 @@ sessions::SessionTabHelperDelegate* TabImpl::GetSessionServiceTabHelperDelegate( return browser_ ? browser_->browser_persister() : nullptr; } +bool TabImpl::SetDataInternal(const std::map<std::string, std::string>& data) { + int total_size = 0; + for (const auto& kv : data) + total_size += kv.first.size() + kv.second.size(); + if (total_size > kMaxDataSize) + return false; + data_ = data; + for (auto& observer : data_observers_) + observer.OnDataChanged(this, data_); + return true; +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/tab_impl.h b/chromium/weblayer/browser/tab_impl.h index 7d1c3949ad3..a42739b4856 100644 --- a/chromium/weblayer/browser/tab_impl.h +++ b/chromium/weblayer/browser/tab_impl.h @@ -6,6 +6,7 @@ #define WEBLAYER_BROWSER_TAB_IMPL_H_ #include <memory> +#include <set> #include "base/callback_forward.h" #include "base/macros.h" @@ -25,6 +26,10 @@ #include "weblayer/browser/browser_controls_navigation_state_handler_delegate.h" #endif +namespace js_injection { +class JsCommunicationHost; +} + namespace autofill { class AutofillProvider; } // namespace autofill @@ -52,10 +57,12 @@ class FullscreenDelegate; class NavigationControllerImpl; class NewTabDelegate; class ProfileImpl; +class HttpAuthHandlerImpl; #if defined(OS_ANDROID) class BrowserControlsContainerView; enum class ControlsVisibilityReason; +class WebMessageHostFactoryProxy; #endif class TabImpl : public Tab, @@ -81,6 +88,14 @@ class TabImpl : public Tab, kBitmapAllocationFailed, }; + class DataObserver { + public: + // Called when SetData() is called on |tab|. + virtual void OnDataChanged( + TabImpl* tab, + const std::map<std::string, std::string>& data) = 0; + }; + // TODO(sky): investigate a better way to not have so many ifdefs. #if defined(OS_ANDROID) TabImpl(ProfileImpl* profile, @@ -95,6 +110,8 @@ class TabImpl : public Tab, // null if |web_contents| was not created by a TabImpl. static TabImpl* FromWebContents(content::WebContents* web_contents); + static std::set<TabImpl*> GetAllTabImpl(); + ProfileImpl* profile() { return profile_; } void set_browser(BrowserImpl* browser) { browser_ = browser; } @@ -103,6 +120,7 @@ class TabImpl : public Tab, content::WebContents* web_contents() const { return web_contents_.get(); } bool has_new_tab_delegate() const { return new_tab_delegate_ != nullptr; } + NewTabDelegate* new_tab_delegate() const { return new_tab_delegate_; } // Called from Browser when this Tab is losing active status. void OnLosingActive(); @@ -111,6 +129,9 @@ class TabImpl : public Tab, void ShowContextMenu(const content::ContextMenuParams& params); + void ShowHttpAuthPrompt(HttpAuthHandlerImpl* auth_handler); + void CloseHttpAuthPrompt(); + #if defined(OS_ANDROID) base::android::ScopedJavaGlobalRef<jobject> GetJavaTab() { return java_impl_; @@ -143,23 +164,41 @@ class TabImpl : public Tab, void OnAutofillProviderChanged( JNIEnv* env, const base::android::JavaParamRef<jobject>& autofill_provider); - void UpdateBrowserControlsState(JNIEnv* env, jint raw_new_state, jboolean animate); base::android::ScopedJavaLocalRef<jstring> GetGuid(JNIEnv* env); - void CaptureScreenShot( JNIEnv* env, jfloat scale, const base::android::JavaParamRef<jobject>& value_callback); + jboolean SetData(JNIEnv* env, + const base::android::JavaParamRef<jobjectArray>& data); + base::android::ScopedJavaLocalRef<jobjectArray> GetData(JNIEnv* env); jboolean IsRendererControllingBrowserControlsOffsets(JNIEnv* env); + void SetHttpAuth(JNIEnv* env, + const base::android::JavaParamRef<jstring>& username, + const base::android::JavaParamRef<jstring>& password); + void CancelHttpAuth(JNIEnv* env); + base::android::ScopedJavaLocalRef<jstring> RegisterWebMessageCallback( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& js_object_name, + const base::android::JavaParamRef<jobjectArray>& origins, + const base::android::JavaParamRef<jobject>& client); + void UnregisterWebMessageCallback( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& js_object_name); + jboolean CanTranslate(JNIEnv* env); + void ShowTranslateUi(JNIEnv* env); #endif ErrorPageDelegate* error_page_delegate() { return error_page_delegate_; } + void AddDataObserver(DataObserver* observer); + void RemoveDataObserver(DataObserver* observer); + // Tab: void SetErrorPageDelegate(ErrorPageDelegate* delegate) override; void SetFullscreenDelegate(FullscreenDelegate* delegate) override; @@ -171,6 +210,14 @@ class TabImpl : public Tab, bool use_separate_isolate, JavaScriptResultCallback callback) override; const std::string& GetGuid() override; + void SetData(const std::map<std::string, std::string>& data) override; + const std::map<std::string, std::string>& GetData() override; + base::string16 AddWebMessageHostFactory( + std::unique_ptr<WebMessageHostFactory> factory, + const base::string16& js_object_name, + const std::vector<std::string>& js_origins) override; + void RemoveWebMessageHostFactory( + const base::string16& js_object_name) override; #if !defined(OS_ANDROID) void AttachToView(views::WebView* web_view) override; #endif @@ -216,8 +263,7 @@ class TabImpl : public Tab, const GURL& security_origin, blink::mojom::MediaStreamType type) override; void EnterFullscreenModeForTab( - content::WebContents* web_contents, - const GURL& origin, + content::RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options) override; void ExitFullscreenModeForTab(content::WebContents* web_contents) override; bool IsFullscreenForTabOrPending( @@ -294,6 +340,8 @@ class TabImpl : public Tab, void UpdateBrowserVisibleSecurityStateIfNecessary(); + bool SetDataInternal(const std::map<std::string, std::string>& data); + BrowserImpl* browser_ = nullptr; ErrorPageDelegate* error_page_delegate_ = nullptr; FullscreenDelegate* fullscreen_delegate_ = nullptr; @@ -314,6 +362,9 @@ class TabImpl : public Tab, // Last value supplied to UpdateBrowserControlsState(). content::BrowserControlsState current_browser_controls_state_ = content::BROWSER_CONTROLS_STATE_SHOWN; + + std::map<std::string, std::unique_ptr<WebMessageHostFactoryProxy>> + js_name_to_proxy_; #endif bool is_fullscreen_ = false; @@ -324,8 +375,15 @@ class TabImpl : public Tab, const std::string guid_; + std::map<std::string, std::string> data_; + base::ObserverList<DataObserver>::Unchecked data_observers_; + base::string16 title_; + HttpAuthHandlerImpl* auth_handler_ = nullptr; + + std::unique_ptr<js_injection::JsCommunicationHost> js_communication_host_; + base::WeakPtrFactory<TabImpl> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(TabImpl); diff --git a/chromium/weblayer/browser/tab_specific_content_settings_delegate.cc b/chromium/weblayer/browser/tab_specific_content_settings_delegate.cc index 8235f633e2f..13c9c791d74 100644 --- a/chromium/weblayer/browser/tab_specific_content_settings_delegate.cc +++ b/chromium/weblayer/browser/tab_specific_content_settings_delegate.cc @@ -6,9 +6,11 @@ #include "base/bind_helpers.h" #include "components/content_settings/core/common/content_settings.h" +#include "components/permissions/permission_decision_auto_blocker.h" #include "content/public/browser/render_process_host.h" #include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/host_content_settings_map_factory.h" +#include "weblayer/browser/permissions/permission_decision_auto_blocker_factory.h" #include "weblayer/common/renderer_configuration.mojom.h" namespace weblayer { @@ -59,6 +61,15 @@ HostContentSettingsMap* TabSpecificContentSettingsDelegate::GetSettingsMap() { web_contents_->GetBrowserContext()); } +ContentSetting TabSpecificContentSettingsDelegate::GetEmbargoSetting( + const GURL& request_origin, + ContentSettingsType permission) { + return PermissionDecisionAutoBlockerFactory::GetForBrowserContext( + web_contents_->GetBrowserContext()) + ->GetEmbargoResult(request_origin, permission) + .content_setting; +} + std::vector<storage::FileSystemType> TabSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() { return {}; diff --git a/chromium/weblayer/browser/tab_specific_content_settings_delegate.h b/chromium/weblayer/browser/tab_specific_content_settings_delegate.h index c33befe3189..10dcf2fe26f 100644 --- a/chromium/weblayer/browser/tab_specific_content_settings_delegate.h +++ b/chromium/weblayer/browser/tab_specific_content_settings_delegate.h @@ -32,6 +32,8 @@ class TabSpecificContentSettingsDelegate const RendererContentSettingRules& rules) override; PrefService* GetPrefs() override; HostContentSettingsMap* GetSettingsMap() override; + ContentSetting GetEmbargoSetting(const GURL& request_origin, + ContentSettingsType permission) override; std::vector<storage::FileSystemType> GetAdditionalFileSystemTypes() override; browsing_data::CookieHelper::IsDeletionDisabledCallback GetIsDeletionDisabledCallback() override; diff --git a/chromium/weblayer/browser/translate_browsertest.cc b/chromium/weblayer/browser/translate_browsertest.cc index 77daf3c92e4..2824d8b91ea 100644 --- a/chromium/weblayer/browser/translate_browsertest.cc +++ b/chromium/weblayer/browser/translate_browsertest.cc @@ -2,13 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "build/build_config.h" #include "components/translate/content/browser/translate_waiter.h" #include "components/translate/core/browser/language_state.h" #include "components/translate/core/browser/translate_error_details.h" #include "components/translate/core/browser/translate_manager.h" #include "components/translate/core/common/translate_switches.h" +#include "content/public/browser/browser_context.h" +#include "net/base/mock_network_change_notifier.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" +#include "weblayer/browser/browser_context_impl.h" +#include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/browser/translate_client_impl.h" #include "weblayer/public/tab.h" @@ -16,6 +21,15 @@ #include "weblayer/test/weblayer_browser_test.h" #include "weblayer/test/weblayer_browser_test_utils.h" +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "components/infobars/core/infobar_manager.h" // nogncheck +#include "components/translate/core/browser/translate_download_manager.h" +#include "weblayer/browser/infobar_android.h" +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/translate_compact_infobar.h" +#endif + namespace weblayer { namespace { @@ -98,6 +112,35 @@ void WaitUntilPageTranslated(Shell* shell) { } // namespace +#if defined(OS_ANDROID) +class TestInfoBarManagerObserver : public infobars::InfoBarManager::Observer { + public: + TestInfoBarManagerObserver() = default; + ~TestInfoBarManagerObserver() override = default; + void OnInfoBarAdded(infobars::InfoBar* infobar) override { + if (on_infobar_added_callback_) + std::move(on_infobar_added_callback_).Run(); + } + + void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override { + if (on_infobar_removed_callback_) + std::move(on_infobar_removed_callback_).Run(); + } + + void set_on_infobar_added_callback(base::OnceClosure callback) { + on_infobar_added_callback_ = std::move(callback); + } + + void set_on_infobar_removed_callback(base::OnceClosure callback) { + on_infobar_removed_callback_ = std::move(callback); + } + + private: + base::OnceClosure on_infobar_added_callback_; + base::OnceClosure on_infobar_removed_callback_; +}; +#endif // if defined(OS_ANDROID) + class TranslateBrowserTest : public WebLayerBrowserTest { public: TranslateBrowserTest() { @@ -112,6 +155,26 @@ class TranslateBrowserTest : public WebLayerBrowserTest { embedded_test_server()->RegisterRequestHandler(base::BindRepeating( &TranslateBrowserTest::HandleRequest, base::Unretained(this))); embedded_test_server()->StartAcceptingConnections(); + + // Translation will not be offered if NetworkChangeNotifier reports that the + // app is offline, which can occur on bots. Prevent this. + // NOTE: MockNetworkChangeNotifier cannot be instantiated earlier than this + // due to its dependence on browser state having been created. + mock_network_change_notifier_ = + std::make_unique<net::test::ScopedMockNetworkChangeNotifier>(); + mock_network_change_notifier_->mock_network_change_notifier() + ->SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); + + // By default, translation is not offered if the Google API key is not set. + GetTranslateClient(shell()) + ->GetTranslateManager() + ->SetIgnoreMissingKeyForTesting(true); + + GetTranslateClient(shell())->GetTranslatePrefs()->ResetToDefaults(); + } + + void TearDownOnMainThread() override { + mock_network_change_notifier_.reset(); } void SetUpCommandLine(base::CommandLine* command_line) override { @@ -146,6 +209,9 @@ class TranslateBrowserTest : public WebLayerBrowserTest { error_type_ = details.error; } + std::unique_ptr<net::test::ScopedMockNetworkChangeNotifier> + mock_network_change_notifier_; + translate::TranslateErrors::Type error_type_ = translate::TranslateErrors::NONE; std::unique_ptr< @@ -203,8 +269,54 @@ IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, PageTranslationSuccess) { EXPECT_EQ(translate::TranslateErrors::NONE, GetPageTranslatedResult()); } +class IncognitoTranslateBrowserTest : public TranslateBrowserTest { + public: + IncognitoTranslateBrowserTest() { SetShellStartsInIncognitoMode(); } +}; + +// Test that the translation infrastructure is set up properly when the user is +// in incognito mode. +IN_PROC_BROWSER_TEST_F(IncognitoTranslateBrowserTest, + PageTranslationSuccess_IncognitoMode) { + ASSERT_TRUE(GetProfile()->GetBrowserContext()->IsOffTheRecord()); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + // Navigate to a page in French. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + // Translate the page through TranslateManager. + translate::TranslateManager* manager = + translate_client->GetTranslateManager(); + manager->TranslatePage( + translate_client->GetLanguageState().original_language(), "en", true); + + WaitUntilPageTranslated(shell()); + + EXPECT_FALSE(translate_client->GetLanguageState().translation_error()); + EXPECT_EQ(translate::TranslateErrors::NONE, GetPageTranslatedResult()); +} + // Test if there was an error during translation. IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, PageTranslationError) { +#if defined(OS_ANDROID) + // TODO(crbug.com/1094903): Determine why this test times out on the M + // trybot. + if (base::android::BuildInfo::GetInstance()->sdk_int() <= + base::android::SDK_VERSION_MARSHMALLOW) { + return; + } +#endif + SetTranslateScript(kTestValidScript); TranslateClientImpl* translate_client = GetTranslateClient(shell()); @@ -285,4 +397,380 @@ IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, PageTranslationTimeoutError) { GetPageTranslatedResult()); } +// Test that autotranslation kicks in if configured via prefs. +IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, Autotranslation) { +#if defined(OS_ANDROID) + // TODO(crbug.com/1094903): Determine why this test times out on the M + // trybot. + if (base::android::BuildInfo::GetInstance()->sdk_int() <= + base::android::SDK_VERSION_MARSHMALLOW) { + return; + } +#endif + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + // Before browsing, set autotranslate from French to Chinese. + translate_client->GetTranslatePrefs()->WhitelistLanguagePair("fr", "zh-CN"); + + // Navigate to a page in French. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + // Autotranslation should kick in. + WaitUntilPageTranslated(shell()); + + EXPECT_FALSE(translate_client->GetLanguageState().translation_error()); + EXPECT_EQ(translate::TranslateErrors::NONE, GetPageTranslatedResult()); + EXPECT_EQ("zh-CN", translate_client->GetLanguageState().current_language()); +} + +#if defined(OS_ANDROID) +// Test that the translation infobar is presented when visiting a page with a +// translation opportunity and removed when navigating away. +IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, TranslateInfoBarPresentation) { + auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents(); + auto* infobar_service = InfoBarService::FromWebContents(web_contents); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + TestInfoBarManagerObserver infobar_observer; + infobar_service->AddObserver(&infobar_observer); + + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + EXPECT_EQ(0u, infobar_service->infobar_count()); + // Navigate to a page in French. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + // The translate infobar should be added. + run_loop.Run(); + + EXPECT_EQ(1u, infobar_service->infobar_count()); + auto* infobar = static_cast<InfoBarAndroid*>(infobar_service->infobar_at(0)); + EXPECT_TRUE(infobar->HasSetJavaInfoBar()); + + base::RunLoop run_loop2; + infobar_observer.set_on_infobar_removed_callback(run_loop2.QuitClosure()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + + // The translate infobar should be removed. + run_loop2.Run(); + + EXPECT_EQ(0u, infobar_service->infobar_count()); + infobar_service->RemoveObserver(&infobar_observer); +} +#endif + +#if defined(OS_ANDROID) +// Test that the translation can be successfully initiated via infobar. +IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, TranslationViaInfoBar) { + // TODO(crbug.com/1094903): Determine why this test times out on the M + // trybot. + if (base::android::BuildInfo::GetInstance()->sdk_int() <= + base::android::SDK_VERSION_MARSHMALLOW) { + return; + } + + auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents(); + auto* infobar_service = InfoBarService::FromWebContents(web_contents); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + TestInfoBarManagerObserver infobar_observer; + infobar_service->AddObserver(&infobar_observer); + + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + // Navigate to a page in French and wait for the infobar to be added. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + + // Select the target language via the Java infobar and ensure that translation + // occurs. + auto* infobar = + static_cast<TranslateCompactInfoBar*>(infobar_service->infobar_at(0)); + infobar->SelectButtonForTesting(InfoBarAndroid::ActionType::ACTION_TRANSLATE); + + WaitUntilPageTranslated(shell()); + + EXPECT_FALSE(translate_client->GetLanguageState().translation_error()); + EXPECT_EQ(translate::TranslateErrors::NONE, GetPageTranslatedResult()); + + // The translate infobar should still be present. + EXPECT_EQ(1u, infobar_service->infobar_count()); + + // NOTE: The notification that the translate state of the page changed can + // occur synchronously once reversion is initiated, so it's necessary to start + // listening for that notification prior to initiating the reversion. + auto translate_reversion_waiter = CreateTranslateWaiter( + shell(), translate::TranslateWaiter::WaitEvent::kIsPageTranslatedChanged); + + // Revert to the source language via the Java infobar and ensure that the + // translation is undone. + infobar->SelectButtonForTesting( + InfoBarAndroid::ActionType::ACTION_TRANSLATE_SHOW_ORIGINAL); + + translate_reversion_waiter->Wait(); + EXPECT_EQ("fr", translate_client->GetLanguageState().current_language()); + + // The translate infobar should still be present. + EXPECT_EQ(1u, infobar_service->infobar_count()); + + infobar_service->RemoveObserver(&infobar_observer); +} +#endif + +#if defined(OS_ANDROID) +// Test that the translation infobar stays present when the "never translate +// language" item is clicked. Note that this behavior is intentionally different +// from that of Chrome, where the infobar is removed in this case and a snackbar +// is shown. As WebLayer has no snackbars, the UX decision was to simply leave +// the infobar open to allow the user to revert the decision if desired. +IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, + TranslateInfoBarNeverTranslateLanguage) { + auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents(); + auto* infobar_service = InfoBarService::FromWebContents(web_contents); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + TestInfoBarManagerObserver infobar_observer; + infobar_service->AddObserver(&infobar_observer); + + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + // Navigate to a page in French and wait for the infobar to be added. + EXPECT_EQ(0u, infobar_service->infobar_count()); + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + + auto* infobar = + static_cast<TranslateCompactInfoBar*>(infobar_service->infobar_at(0)); + infobar->ClickOverflowMenuItemForTesting( + TranslateCompactInfoBar::OverflowMenuItemId::NEVER_TRANSLATE_LANGUAGE); + + // The translate infobar should still be present. + EXPECT_EQ(1u, infobar_service->infobar_count()); + + // However, the infobar should not be shown on a new navigation to a page in + // French. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page2.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + // NOTE: There is no notification to wait for for the event of the infobar not + // showing. However, in practice the infobar is added synchronously, so if it + // were to be shown, this check would fail. + EXPECT_EQ(0u, infobar_service->infobar_count()); + + // The infobar *should* be shown on a navigation to this site if the page's + // language is detected as something other than French. + base::RunLoop run_loop2; + infobar_observer.set_on_infobar_added_callback(run_loop2.QuitClosure()); + + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/german_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("de", translate_client->GetLanguageState().original_language()); + + run_loop2.Run(); + + EXPECT_EQ(1u, infobar_service->infobar_count()); + + infobar_service->RemoveObserver(&infobar_observer); +} + +// Test that the translation infobar stays present when the "never translate +// site" item is clicked. Note that this behavior is intentionally different +// from that of Chrome, where the infobar is removed in this case and a snackbar +// is shown. As WebLayer has no snackbars, the UX decision was to simply leave +// the infobar open to allow the user to revert the decision if desired. +IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, + TranslateInfoBarNeverTranslateSite) { + auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents(); + auto* infobar_service = InfoBarService::FromWebContents(web_contents); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + TestInfoBarManagerObserver infobar_observer; + infobar_service->AddObserver(&infobar_observer); + + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + // Navigate to a page in French and wait for the infobar to be added. + EXPECT_EQ(0u, infobar_service->infobar_count()); + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + + auto* infobar = + static_cast<TranslateCompactInfoBar*>(infobar_service->infobar_at(0)); + infobar->ClickOverflowMenuItemForTesting( + TranslateCompactInfoBar::OverflowMenuItemId::NEVER_TRANSLATE_SITE); + + // The translate infobar should still be present. + EXPECT_EQ(1u, infobar_service->infobar_count()); + + // However, the infobar should not be shown on a new navigation to this site, + // independent of the detected language. + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page2.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + // NOTE: There is no notification to wait for for the event of the infobar not + // showing. However, in practice the infobar is added synchronously, so if it + // were to be shown, this check would fail. + EXPECT_EQ(0u, infobar_service->infobar_count()); + + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/german_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("de", translate_client->GetLanguageState().original_language()); + EXPECT_EQ(0u, infobar_service->infobar_count()); + + infobar_service->RemoveObserver(&infobar_observer); +} + +// Parameterized to run tests on the "never translate language" and "never +// translate site" menu items. +class NeverTranslateMenuItemTranslateBrowserTest + : public TranslateBrowserTest, + public testing::WithParamInterface< + TranslateCompactInfoBar::OverflowMenuItemId> {}; + +// Test that clicking and unclicking a never translate item ends up being a +// no-op. +IN_PROC_BROWSER_TEST_P(NeverTranslateMenuItemTranslateBrowserTest, + TranslateInfoBarToggleAndToggleBackNeverTranslateItem) { + auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents(); + auto* infobar_service = InfoBarService::FromWebContents(web_contents); + + SetTranslateScript(kTestValidScript); + + TranslateClientImpl* translate_client = GetTranslateClient(shell()); + + NavigateAndWaitForCompletion(GURL("about:blank"), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("und", translate_client->GetLanguageState().original_language()); + + TestInfoBarManagerObserver infobar_observer; + infobar_service->AddObserver(&infobar_observer); + + // Navigate to a page in French, wait for the infobar to be added, and click + // twice on the given overflow menu item. + { + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + EXPECT_EQ(0u, infobar_service->infobar_count()); + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + + auto* infobar = + static_cast<TranslateCompactInfoBar*>(infobar_service->infobar_at(0)); + infobar->ClickOverflowMenuItemForTesting(GetParam()); + + // The translate infobar should still be present. + EXPECT_EQ(1u, infobar_service->infobar_count()); + + infobar->ClickOverflowMenuItemForTesting(GetParam()); + } + + // The infobar should be shown on a new navigation to a page in the same + // language. + { + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/french_page2.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("fr", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + } + + // The infobar should be shown on a new navigation to a page in a different + // language in the same site. + { + base::RunLoop run_loop; + infobar_observer.set_on_infobar_added_callback(run_loop.QuitClosure()); + + NavigateAndWaitForCompletion( + GURL(embedded_test_server()->GetURL("/german_page.html")), shell()); + WaitUntilLanguageDetermined(shell()); + EXPECT_EQ("de", translate_client->GetLanguageState().original_language()); + + run_loop.Run(); + } + + infobar_service->RemoveObserver(&infobar_observer); +} + +INSTANTIATE_TEST_SUITE_P( + All, + NeverTranslateMenuItemTranslateBrowserTest, + ::testing::Values( + TranslateCompactInfoBar::OverflowMenuItemId::NEVER_TRANSLATE_LANGUAGE, + TranslateCompactInfoBar::OverflowMenuItemId::NEVER_TRANSLATE_SITE)); + +#endif // #if defined(OS_ANDROID) + } // namespace weblayer diff --git a/chromium/weblayer/browser/translate_client_impl.cc b/chromium/weblayer/browser/translate_client_impl.cc index 8166e11b1ab..dbe11d68bbc 100644 --- a/chromium/weblayer/browser/translate_client_impl.cc +++ b/chromium/weblayer/browser/translate_client_impl.cc @@ -7,6 +7,7 @@ #include <memory> #include <vector> +#include "build/build_config.h" #include "components/infobars/core/infobar.h" #include "components/language/core/browser/pref_names.h" #include "components/translate/content/browser/content_translate_driver.h" @@ -21,6 +22,11 @@ #include "weblayer/browser/translate_accept_languages_factory.h" #include "weblayer/browser/translate_ranker_factory.h" +#if defined(OS_ANDROID) +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/translate_compact_infobar.h" +#endif + namespace weblayer { namespace { @@ -54,6 +60,7 @@ TranslateClientImpl::TranslateClientImpl(content::WebContents* web_contents) TranslateRankerFactory::GetForBrowserContext( web_contents->GetBrowserContext()), /*language_model=*/nullptr)) { + observer_.Add(&translate_driver_); translate_driver_.set_translate_manager(translate_manager_.get()); } @@ -69,6 +76,18 @@ bool TranslateClientImpl::ShowTranslateUI( const std::string& target_language, translate::TranslateErrors::Type error_type, bool triggered_from_menu) { + if (error_type != translate::TranslateErrors::NONE) + step = translate::TRANSLATE_STEP_TRANSLATE_ERROR; + +#if defined(OS_ANDROID) + translate::TranslateInfoBarDelegate::Create( + step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE, + translate_manager_->GetWeakPtr(), + InfoBarService::FromWebContents(web_contents()), + web_contents()->GetBrowserContext()->IsOffTheRecord(), step, + source_language, target_language, error_type, triggered_from_menu); + return true; +#endif return false; } @@ -100,8 +119,7 @@ TranslateClientImpl::GetTranslateAcceptLanguages() { #if defined(OS_ANDROID) std::unique_ptr<infobars::InfoBar> TranslateClientImpl::CreateInfoBar( std::unique_ptr<translate::TranslateInfoBarDelegate> delegate) const { - NOTREACHED(); - return nullptr; + return std::make_unique<TranslateCompactInfoBar>(std::move(delegate)); } int TranslateClientImpl::GetInfobarIconID() const { @@ -119,6 +137,22 @@ void TranslateClientImpl::ShowReportLanguageDetectionErrorUI( NOTREACHED(); } +void TranslateClientImpl::OnLanguageDetermined( + const translate::LanguageDetectionDetails& details) { + if (manual_translate_on_ready_) { + GetTranslateManager()->InitiateManualTranslation(); + manual_translate_on_ready_ = false; + } +} + +void TranslateClientImpl::ManualTranslateWhenReady() { + if (GetLanguageState().original_language().empty()) { + manual_translate_on_ready_ = true; + } else { + GetTranslateManager()->InitiateManualTranslation(); + } +} + void TranslateClientImpl::WebContentsDestroyed() { // Translation process can be interrupted. // Destroying the TranslateManager now guarantees that it never has to deal diff --git a/chromium/weblayer/browser/translate_client_impl.h b/chromium/weblayer/browser/translate_client_impl.h index 760d69dbfaf..c33e77eb77b 100644 --- a/chromium/weblayer/browser/translate_client_impl.h +++ b/chromium/weblayer/browser/translate_client_impl.h @@ -8,6 +8,7 @@ #include <memory> #include <string> +#include "base/scoped_observer.h" #include "build/build_config.h" #include "components/translate/content/browser/content_translate_driver.h" #include "components/translate/core/browser/translate_client.h" @@ -27,6 +28,7 @@ namespace weblayer { class TranslateClientImpl : public translate::TranslateClient, + public translate::ContentTranslateDriver::Observer, public content::WebContentsObserver, public content::WebContentsUserData<TranslateClientImpl> { public: @@ -63,6 +65,14 @@ class TranslateClientImpl bool IsTranslatableURL(const GURL& url) override; void ShowReportLanguageDetectionErrorUI(const GURL& report_url) override; + // ContentTranslateDriver::Observer implementation. + void OnLanguageDetermined( + const translate::LanguageDetectionDetails& details) override; + + // Trigger a manual translation when the necessary state (e.g. source + // language) is ready. + void ManualTranslateWhenReady(); + private: explicit TranslateClientImpl(content::WebContents* web_contents); friend class content::WebContentsUserData<TranslateClientImpl>; @@ -73,6 +83,13 @@ class TranslateClientImpl translate::ContentTranslateDriver translate_driver_; std::unique_ptr<translate::TranslateManager> translate_manager_; + // Whether to trigger a manual translation when ready. + bool manual_translate_on_ready_ = false; + + ScopedObserver<translate::ContentTranslateDriver, + translate::ContentTranslateDriver::Observer> + observer_{this}; + WEB_CONTENTS_USER_DATA_KEY_DECL(); DISALLOW_COPY_AND_ASSIGN(TranslateClientImpl); diff --git a/chromium/weblayer/browser/translate_compact_infobar.cc b/chromium/weblayer/browser/translate_compact_infobar.cc new file mode 100644 index 00000000000..7584fe4e99a --- /dev/null +++ b/chromium/weblayer/browser/translate_compact_infobar.cc @@ -0,0 +1,246 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/translate_compact_infobar.h" + +#include <stddef.h> + +#include <memory> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/jni_weak_ref.h" +#include "components/translate/core/browser/translate_infobar_delegate.h" +#include "components/variations/variations_associated_data.h" +#include "content/public/browser/browser_context.h" +#include "weblayer/browser/infobar_service.h" +#include "weblayer/browser/java/jni/TranslateCompactInfoBar_jni.h" +#include "weblayer/browser/tab_impl.h" +#include "weblayer/browser/translate_client_impl.h" +#include "weblayer/browser/translate_utils.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace weblayer { + +// Finch parameter names: +const char kTranslateTabDefaultTextColor[] = "translate_tab_default_text_color"; + +// TranslateInfoBar ----------------------------------------------------------- + +TranslateCompactInfoBar::TranslateCompactInfoBar( + std::unique_ptr<translate::TranslateInfoBarDelegate> delegate) + : InfoBarAndroid(std::move(delegate)), action_flags_(FLAG_NONE) { + GetDelegate()->AddObserver(this); + + // Flip the translate bit if auto translate is enabled. + if (GetDelegate()->translate_step() == translate::TRANSLATE_STEP_TRANSLATING) + action_flags_ |= FLAG_TRANSLATE; +} + +TranslateCompactInfoBar::~TranslateCompactInfoBar() { + GetDelegate()->RemoveObserver(this); +} + +ScopedJavaLocalRef<jobject> TranslateCompactInfoBar::CreateRenderInfoBar( + JNIEnv* env) { + translate::TranslateInfoBarDelegate* delegate = GetDelegate(); + + base::android::ScopedJavaLocalRef<jobjectArray> java_languages = + TranslateUtils::GetJavaLanguages(env, delegate); + base::android::ScopedJavaLocalRef<jobjectArray> java_codes = + TranslateUtils::GetJavaLanguageCodes(env, delegate); + base::android::ScopedJavaLocalRef<jintArray> java_hash_codes = + TranslateUtils::GetJavaLanguageHashCodes(env, delegate); + + ScopedJavaLocalRef<jstring> source_language_code = + base::android::ConvertUTF8ToJavaString( + env, delegate->original_language_code()); + + ScopedJavaLocalRef<jstring> target_language_code = + base::android::ConvertUTF8ToJavaString(env, + delegate->target_language_code()); + content::WebContents* web_contents = + InfoBarService::WebContentsFromInfoBar(this); + + TabImpl* tab = + web_contents ? TabImpl::FromWebContents(web_contents) : nullptr; + + return Java_TranslateCompactInfoBar_create( + env, tab ? tab->GetJavaTab() : nullptr, delegate->translate_step(), + source_language_code, target_language_code, + delegate->ShouldAlwaysTranslate(), delegate->triggered_from_menu(), + java_languages, java_codes, java_hash_codes, TabDefaultTextColor()); +} + +void TranslateCompactInfoBar::ProcessButton(int action) { + if (!owner()) + return; // We're closing; don't call anything, it might access the owner. + + translate::TranslateInfoBarDelegate* delegate = GetDelegate(); + if (action == InfoBarAndroid::ACTION_TRANSLATE) { + action_flags_ |= FLAG_TRANSLATE; + delegate->Translate(); + if (delegate->ShouldAutoAlwaysTranslate()) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_TranslateCompactInfoBar_setAutoAlwaysTranslate(env, + GetJavaInfoBar()); + } + } else if (action == InfoBarAndroid::ACTION_TRANSLATE_SHOW_ORIGINAL) { + action_flags_ |= FLAG_REVERT; + delegate->RevertWithoutClosingInfobar(); + } else { + DCHECK_EQ(InfoBarAndroid::ACTION_NONE, action); + } +} + +void TranslateCompactInfoBar::SetJavaInfoBar( + const base::android::JavaRef<jobject>& java_info_bar) { + InfoBarAndroid::SetJavaInfoBar(java_info_bar); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_TranslateCompactInfoBar_setNativePtr(env, java_info_bar, + reinterpret_cast<intptr_t>(this)); +} + +void TranslateCompactInfoBar::ApplyStringTranslateOption( + JNIEnv* env, + const JavaParamRef<jobject>& obj, + int option, + const JavaParamRef<jstring>& value) { + translate::TranslateInfoBarDelegate* delegate = GetDelegate(); + if (option == TranslateUtils::OPTION_SOURCE_CODE) { + std::string source_code = + base::android::ConvertJavaStringToUTF8(env, value); + if (delegate->original_language_code().compare(source_code) != 0) + delegate->UpdateOriginalLanguage(source_code); + } else if (option == TranslateUtils::OPTION_TARGET_CODE) { + std::string target_code = + base::android::ConvertJavaStringToUTF8(env, value); + if (delegate->target_language_code().compare(target_code) != 0) + delegate->UpdateTargetLanguage(target_code); + } else { + DCHECK(false); + } +} + +void TranslateCompactInfoBar::ApplyBoolTranslateOption( + JNIEnv* env, + const JavaParamRef<jobject>& obj, + int option, + jboolean value) { + translate::TranslateInfoBarDelegate* delegate = GetDelegate(); + if (option == TranslateUtils::OPTION_ALWAYS_TRANSLATE) { + if (delegate->ShouldAlwaysTranslate() != value) { + action_flags_ |= FLAG_ALWAYS_TRANSLATE; + delegate->ToggleAlwaysTranslate(); + } + } else if (option == TranslateUtils::OPTION_NEVER_TRANSLATE) { + bool language_blocklisted = !delegate->IsTranslatableLanguageByPrefs(); + if (language_blocklisted != value) { + action_flags_ |= FLAG_NEVER_LANGUAGE; + delegate->ToggleTranslatableLanguageByPrefs(); + } + } else if (option == TranslateUtils::OPTION_NEVER_TRANSLATE_SITE) { + if (delegate->IsSiteBlacklisted() != value) { + action_flags_ |= FLAG_NEVER_SITE; + delegate->ToggleSiteBlacklist(); + } + } else { + DCHECK(false); + } +} + +jboolean TranslateCompactInfoBar::ShouldAutoNeverTranslate( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + jboolean menu_expanded) { + // Flip menu expanded bit. + if (menu_expanded) + action_flags_ |= FLAG_EXPAND_MENU; + + if (!IsDeclinedByUser()) + return false; + + return GetDelegate()->ShouldAutoNeverTranslate(); +} + +// Returns true if the current tab is an incognito tab. +jboolean TranslateCompactInfoBar::IsIncognito( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj) { + content::WebContents* web_contents = + InfoBarService::WebContentsFromInfoBar(this); + if (!web_contents) + return false; + return web_contents->GetBrowserContext()->IsOffTheRecord(); +} + +int TranslateCompactInfoBar::GetParam(const std::string& paramName, + int default_value) { + std::map<std::string, std::string> params; + if (!variations::GetVariationParams(translate::kTranslateCompactUI.name, + ¶ms)) + return default_value; + int value = 0; + base::StringToInt(params[paramName], &value); + return value <= 0 ? default_value : value; +} + +int TranslateCompactInfoBar::TabDefaultTextColor() { + return GetParam(kTranslateTabDefaultTextColor, 0); +} + +translate::TranslateInfoBarDelegate* TranslateCompactInfoBar::GetDelegate() { + return delegate()->AsTranslateInfoBarDelegate(); +} + +void TranslateCompactInfoBar::OnTranslateStepChanged( + translate::TranslateStep step, + translate::TranslateErrors::Type error_type) { + if (!owner()) + return; // We're closing; don't call anything. + + if ((step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) || + (step == translate::TRANSLATE_STEP_TRANSLATE_ERROR)) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_TranslateCompactInfoBar_onPageTranslated(env, GetJavaInfoBar(), + error_type); + } +} + +bool TranslateCompactInfoBar::IsDeclinedByUser() { + // Whether there is any affirmative action bit. + return action_flags_ == FLAG_NONE; +} + +void TranslateCompactInfoBar::OnTranslateInfoBarDelegateDestroyed( + translate::TranslateInfoBarDelegate* delegate) { + DCHECK_EQ(GetDelegate(), delegate); + GetDelegate()->RemoveObserver(this); +} + +void TranslateCompactInfoBar::SelectButtonForTesting(ActionType action_type) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_TranslateCompactInfoBar_selectTabForTesting(env, GetJavaInfoBar(), + action_type); +} + +void TranslateCompactInfoBar::ClickOverflowMenuItemForTesting( + OverflowMenuItemId item_id) { + JNIEnv* env = base::android::AttachCurrentThread(); + switch (item_id) { + case OverflowMenuItemId::NEVER_TRANSLATE_LANGUAGE: + Java_TranslateCompactInfoBar_clickNeverTranslateLanguageMenuItemForTesting( + env, GetJavaInfoBar()); + return; + case OverflowMenuItemId::NEVER_TRANSLATE_SITE: + Java_TranslateCompactInfoBar_clickNeverTranslateSiteMenuItemForTesting( + env, GetJavaInfoBar()); + return; + } +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/translate_compact_infobar.h b/chromium/weblayer/browser/translate_compact_infobar.h new file mode 100644 index 00000000000..d4cfc20c3fd --- /dev/null +++ b/chromium/weblayer/browser/translate_compact_infobar.h @@ -0,0 +1,112 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_TRANSLATE_COMPACT_INFOBAR_H_ +#define WEBLAYER_BROWSER_TRANSLATE_COMPACT_INFOBAR_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "components/translate/core/browser/translate_infobar_delegate.h" +#include "components/translate/core/browser/translate_step.h" +#include "components/translate/core/common/translate_errors.h" +#include "weblayer/browser/infobar_android.h" + +namespace translate { +class TranslateInfoBarDelegate; +} + +namespace weblayer { + +class TranslateCompactInfoBar + : public InfoBarAndroid, + public translate::TranslateInfoBarDelegate::Observer { + public: + explicit TranslateCompactInfoBar( + std::unique_ptr<translate::TranslateInfoBarDelegate> delegate); + ~TranslateCompactInfoBar() override; + + enum class OverflowMenuItemId { + NEVER_TRANSLATE_LANGUAGE = 0, + NEVER_TRANSLATE_SITE = 1, + }; + + // JNI method specific to string settings in translate. + void ApplyStringTranslateOption( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + int option, + const base::android::JavaParamRef<jstring>& value); + + // JNI method specific to boolean settings in translate. + void ApplyBoolTranslateOption(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + int option, + jboolean value); + + // Check whether we should automatically trigger "Never Translate Language". + jboolean ShouldAutoNeverTranslate( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + jboolean menu_expanded); + + // Returns true if the current tab is an incognito tab. + jboolean IsIncognito(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj); + + // TranslateInfoBarDelegate::Observer implementation. + void OnTranslateStepChanged( + translate::TranslateStep step, + translate::TranslateErrors::Type error_type) override; + // Returns true if the user didn't take any affirmative action. + // The function will be called when the translate infobar is dismissed. + // If it's true, we will record a declined event. + bool IsDeclinedByUser() override; + void OnTranslateInfoBarDelegateDestroyed( + translate::TranslateInfoBarDelegate* delegate) override; + + // Instructs the Java infobar to select the button corresponding to + // |action_type|. + void SelectButtonForTesting(ActionType action_type); + + // Instructs the Java infobar to click the specified overflow menu item. + void ClickOverflowMenuItemForTesting(OverflowMenuItemId item_id); + + private: + // InfoBarAndroid: + base::android::ScopedJavaLocalRef<jobject> CreateRenderInfoBar( + JNIEnv* env) override; + void ProcessButton(int action) override; + void SetJavaInfoBar( + const base::android::JavaRef<jobject>& java_info_bar) override; + + // Get the value of a specified finch parameter in TranslateCompactUI. If the + // finch parameter does not exist, default_value will be returned. + int GetParam(const std::string& paramName, int default_value); + // Get the value of the finch parameter: translate_tab_default_text_color. + // Default value is 0, which means using TabLayout default color. + // If it's not 0, we will set the text color manually based on the value. + int TabDefaultTextColor(); + + translate::TranslateInfoBarDelegate* GetDelegate(); + + // Bits for trace user's affirmative actions. + unsigned int action_flags_; + + // Affirmative action flags to record what the user has done in one session. + enum ActionFlag { + FLAG_NONE = 0, + FLAG_TRANSLATE = 1 << 0, + FLAG_REVERT = 1 << 1, + FLAG_ALWAYS_TRANSLATE = 1 << 2, + FLAG_NEVER_LANGUAGE = 1 << 3, + FLAG_NEVER_SITE = 1 << 4, + FLAG_EXPAND_MENU = 1 << 5, + }; + + DISALLOW_COPY_AND_ASSIGN(TranslateCompactInfoBar); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_TRANSLATE_COMPACT_INFOBAR_H_ diff --git a/chromium/weblayer/browser/translate_ranker_factory.cc b/chromium/weblayer/browser/translate_ranker_factory.cc index 90522a17ed3..1c24edac45c 100644 --- a/chromium/weblayer/browser/translate_ranker_factory.cc +++ b/chromium/weblayer/browser/translate_ranker_factory.cc @@ -38,4 +38,9 @@ KeyedService* TranslateRankerFactory::BuildServiceInstanceFor( translate::TranslateRankerImpl::GetModelURL(), ukm::UkmRecorder::Get()); } +content::BrowserContext* TranslateRankerFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + return context; +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/translate_ranker_factory.h b/chromium/weblayer/browser/translate_ranker_factory.h index 228f0db5db6..91a6e48cb0b 100644 --- a/chromium/weblayer/browser/translate_ranker_factory.h +++ b/chromium/weblayer/browser/translate_ranker_factory.h @@ -20,12 +20,6 @@ namespace weblayer { // TranslateRankerFactory is a way to associate a TranslateRanker instance to a // BrowserContext. -// TODO(crbug.com/1072334): In //chrome, when the service is requested for a -// Profile in incognito mode the factory supplies the associated original -// Profile. However, WebLayer doesn't have a concept of incognito profiles being -// associated with regular profiles. For now, we just stay with -// GetBrowserContextToUse()'s default behavior of returning nullptr in this case -// pending resolution of this question. class TranslateRankerFactory : public BrowserContextKeyedServiceFactory { public: TranslateRankerFactory(const TranslateRankerFactory&) = delete; @@ -44,6 +38,14 @@ class TranslateRankerFactory : public BrowserContextKeyedServiceFactory { // BrowserContextKeyedServiceFactory: KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; + + // Note: In //chrome, when the service is requested for a + // Profile in incognito mode the factory supplies the associated original + // Profile. However, WebLayer doesn't have a concept of incognito profiles + // being associated with regular profiles, so the service gets its own + // instance in incognito mode. + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; }; } // namespace weblayer diff --git a/chromium/weblayer/browser/translate_utils.cc b/chromium/weblayer/browser/translate_utils.cc new file mode 100644 index 00000000000..aac744a115e --- /dev/null +++ b/chromium/weblayer/browser/translate_utils.cc @@ -0,0 +1,54 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/translate_utils.h" + +#include <stddef.h> + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/jni_weak_ref.h" +#include "components/metrics/metrics_log.h" +#include "components/translate/core/browser/translate_infobar_delegate.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace weblayer { + +ScopedJavaLocalRef<jobjectArray> TranslateUtils::GetJavaLanguages( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate) { + std::vector<base::string16> languages; + languages.reserve(delegate->num_languages()); + for (size_t i = 0; i < delegate->num_languages(); ++i) { + languages.push_back(delegate->language_name_at(i)); + } + return base::android::ToJavaArrayOfStrings(env, languages); +} + +ScopedJavaLocalRef<jobjectArray> TranslateUtils::GetJavaLanguageCodes( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate) { + std::vector<std::string> codes; + codes.reserve(delegate->num_languages()); + for (size_t i = 0; i < delegate->num_languages(); ++i) { + codes.push_back(delegate->language_code_at(i)); + } + return base::android::ToJavaArrayOfStrings(env, codes); +} + +ScopedJavaLocalRef<jintArray> TranslateUtils::GetJavaLanguageHashCodes( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate) { + std::vector<int> hashCodes; + hashCodes.reserve(delegate->num_languages()); + for (size_t i = 0; i < delegate->num_languages(); ++i) { + hashCodes.push_back( + metrics::MetricsLog::Hash(delegate->language_code_at(i))); + } + return base::android::ToJavaIntArray(env, hashCodes); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/translate_utils.h b/chromium/weblayer/browser/translate_utils.h new file mode 100644 index 00000000000..9bee56b5d7a --- /dev/null +++ b/chromium/weblayer/browser/translate_utils.h @@ -0,0 +1,43 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_TRANSLATE_UTILS_H_ +#define WEBLAYER_BROWSER_TRANSLATE_UTILS_H_ + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" + +namespace translate { +class TranslateInfoBarDelegate; +} + +namespace weblayer { + +class TranslateUtils { + public: + // A Java counterpart will be generated for this enum. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private + // GENERATED_JAVA_PREFIX_TO_STRIP:OPTION_ + enum TranslateOption { + OPTION_SOURCE_CODE, + OPTION_TARGET_CODE, + OPTION_ALWAYS_TRANSLATE, + OPTION_NEVER_TRANSLATE, + OPTION_NEVER_TRANSLATE_SITE + }; + + static base::android::ScopedJavaLocalRef<jobjectArray> GetJavaLanguages( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate); + static base::android::ScopedJavaLocalRef<jobjectArray> GetJavaLanguageCodes( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate); + static base::android::ScopedJavaLocalRef<jintArray> GetJavaLanguageHashCodes( + JNIEnv* env, + translate::TranslateInfoBarDelegate* delegate); +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_TRANSLATE_UTILS_H_ diff --git a/chromium/weblayer/browser/ukm_browsertest.cc b/chromium/weblayer/browser/ukm_browsertest.cc new file mode 100644 index 00000000000..52b734a4559 --- /dev/null +++ b/chromium/weblayer/browser/ukm_browsertest.cc @@ -0,0 +1,158 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/bind_test_util.h" +#include "components/ukm/ukm_test_helper.h" +#include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h" +#include "weblayer/browser/profile_impl.h" +#include "weblayer/public/navigation_controller.h" +#include "weblayer/public/tab.h" +#include "weblayer/shell/android/browsertests_apk/metrics_test_helper.h" +#include "weblayer/shell/browser/shell.h" +#include "weblayer/test/weblayer_browser_test.h" + +namespace weblayer { + +namespace { + +ProfileImpl* GetProfileByName(const std::string& name) { + for (auto* profile : ProfileImpl::GetAllProfiles()) { + if (profile->name() == name) + return profile; + } + + return nullptr; +} + +} // namespace + +class UkmBrowserTest : public WebLayerBrowserTest { + public: + void SetUp() override { + InstallTestGmsBridge(user_consent_); + + WebLayerBrowserTest::SetUp(); + } + + void TearDown() override { + RemoveTestGmsBridge(); + WebLayerBrowserTest::TearDown(); + } + + ukm::UkmService* GetUkmService() { + return WebLayerMetricsServiceClient::GetInstance()->GetUkmService(); + } + + void disable_user_consent() { user_consent_ = false; } + + private: + bool user_consent_ = true; +}; + +// Even if there's user consent for UMA, need to explicitly enable UKM. +IN_PROC_BROWSER_TEST_F(UkmBrowserTest, UserConsentButNotEnabled) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); +} + +// UKMs are only enabled when there's user consent and it's explicitly enabled. +IN_PROC_BROWSER_TEST_F(UkmBrowserTest, Enabled) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled()); +} + +// If UKMs are disabled it's reflected accordingly. +IN_PROC_BROWSER_TEST_F(UkmBrowserTest, EnabledThenDisable) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled()); + + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, false); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); +} + +// Make sure that UKM is disabled while an incognito profile is alive. +IN_PROC_BROWSER_TEST_F(UkmBrowserTest, RegularPlusIncognitoCheck) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled()); + uint64_t original_client_id = ukm_test_helper.GetClientId(); + EXPECT_NE(0U, original_client_id); + + // Incognito profiles have an empty name. + CreateProfile(std::string()); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + // Creating another regular profile mustn't enable UKM. + CreateProfile("foo"); + GetProfileByName("foo")->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + // Note WebLayer can only have one incognito profile so we can't test creating + // and destroying another one here. + + DestroyProfile("foo"); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + DestroyProfile(std::string()); + EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled()); + // Client ID should not have been reset. + EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId()); +} + +// Make sure creating a real profile after Incognito doesn't enable UKM. +IN_PROC_BROWSER_TEST_F(UkmBrowserTest, IncognitoPlusRegularCheck) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + + // Incognito profiles have an empty name. + CreateProfile(std::string()); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + CreateProfile("foo"); + GetProfileByName("foo")->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + DestroyProfile(std::string()); + EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled()); +} + +class UkmDisabledBrowserTest : public UkmBrowserTest { + public: + UkmDisabledBrowserTest() { disable_user_consent(); } +}; + +// If there's no user consent UKMs are disabled. +IN_PROC_BROWSER_TEST_F(UkmDisabledBrowserTest, Disabled) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); + + // Ensure enabling UKMs still doesn't enable it. + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); +} + +class UkmIncognitoBrowserTest : public UkmBrowserTest { + public: + UkmIncognitoBrowserTest() { SetShellStartsInIncognitoMode(); } +}; + +// Starting with one incognito window should disable UKM. +IN_PROC_BROWSER_TEST_F(UkmIncognitoBrowserTest, Disabled) { + ukm::UkmTestHelper ukm_test_helper(GetUkmService()); + + // Enabling UKMs doesn't enable it because of the incognito window. + GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true); + + EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled()); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/url_bar/url_bar_browsertest.cc b/chromium/weblayer/browser/url_bar/url_bar_browsertest.cc index 76c39e11cd6..22dbd2092c3 100644 --- a/chromium/weblayer/browser/url_bar/url_bar_browsertest.cc +++ b/chromium/weblayer/browser/url_bar/url_bar_browsertest.cc @@ -8,6 +8,7 @@ #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" +#include "weblayer/shell/browser/shell.h" #include "weblayer/test/weblayer_browser_test.h" #include "weblayer/test/weblayer_browser_test_utils.h" @@ -22,16 +23,13 @@ class UrlBarBrowserTest : public WebLayerBrowserTest { void SetUpOnMainThread() override { WebLayerBrowserTest::SetUpOnMainThread(); ASSERT_TRUE(embedded_test_server()->Start()); - browser_ = Browser::Create(GetProfile(), nullptr); - tab_ = static_cast<TabImpl*>(browser_->AddTab(Tab::Create(GetProfile()))); - another_tab_ = - static_cast<TabImpl*>(browser_->AddTab(Tab::Create(GetProfile()))); - browser_->SetActiveTab(tab_); + tab_ = static_cast<TabImpl*>(shell()->browser()->CreateTab()); + another_tab_ = static_cast<TabImpl*>(shell()->browser()->CreateTab()); + SetActiveTab(tab_); } void PostRunTestOnMainThread() override { tab_ = nullptr; another_tab_ = nullptr; - browser_.reset(); WebLayerBrowserTest::PostRunTestOnMainThread(); } @@ -45,16 +43,15 @@ class UrlBarBrowserTest : public WebLayerBrowserTest { std::move(closure)); } - void SetActiveTab(TabImpl* tab) { browser_->SetActiveTab(tab); } + void SetActiveTab(TabImpl* tab) { shell()->browser()->SetActiveTab(tab); } protected: TabImpl* tab_ = nullptr; TabImpl* another_tab_ = nullptr; private: - std::unique_ptr<Browser> browser_; BrowserImpl* browser_impl() { - return static_cast<BrowserImpl*>(browser_.get()); + return static_cast<BrowserImpl*>(shell()->browser()); } }; diff --git a/chromium/weblayer/browser/verdict_cache_manager_factory.cc b/chromium/weblayer/browser/verdict_cache_manager_factory.cc new file mode 100644 index 00000000000..24ecb137b74 --- /dev/null +++ b/chromium/weblayer/browser/verdict_cache_manager_factory.cc @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/verdict_cache_manager_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/safe_browsing/core/verdict_cache_manager.h" +#include "content/public/browser/browser_context.h" +#include "weblayer/browser/host_content_settings_map_factory.h" + +namespace weblayer { + +// static +safe_browsing::VerdictCacheManager* +VerdictCacheManagerFactory::GetForBrowserContext( + content::BrowserContext* browser_context) { + return static_cast<safe_browsing::VerdictCacheManager*>( + GetInstance()->GetServiceForBrowserContext(browser_context, + /* create= */ true)); +} + +// static +VerdictCacheManagerFactory* VerdictCacheManagerFactory::GetInstance() { + return base::Singleton<VerdictCacheManagerFactory>::get(); +} + +VerdictCacheManagerFactory::VerdictCacheManagerFactory() + : BrowserContextKeyedServiceFactory( + "VerdictCacheManager", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(HostContentSettingsMapFactory::GetInstance()); +} + +KeyedService* VerdictCacheManagerFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new safe_browsing::VerdictCacheManager( + nullptr /* history service */, + HostContentSettingsMapFactory::GetForBrowserContext(context)); +} + +} // namespace weblayer
\ No newline at end of file diff --git a/chromium/weblayer/browser/verdict_cache_manager_factory.h b/chromium/weblayer/browser/verdict_cache_manager_factory.h new file mode 100644 index 00000000000..038347f8787 --- /dev/null +++ b/chromium/weblayer/browser/verdict_cache_manager_factory.h @@ -0,0 +1,51 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_VERDICT_CACHE_MANAGER_FACTORY_H_ +#define WEBLAYER_BROWSER_VERDICT_CACHE_MANAGER_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +class KeyedService; + +namespace content { +class BrowserContext; +} + +namespace safe_browsing { +class VerdictCacheManager; +} + +namespace weblayer { + +// Singleton that owns VerdictCacheManager objects and associates them +// them with BrowserContextImpl instances. +class VerdictCacheManagerFactory : public BrowserContextKeyedServiceFactory { + public: + // Creates the manager if it doesn't exist already for the given + // |browser_context|. If the manager already exists, return its pointer. + static safe_browsing::VerdictCacheManager* GetForBrowserContext( + content::BrowserContext* browser_context); + + // Get the singleton instance. + static VerdictCacheManagerFactory* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits<VerdictCacheManagerFactory>; + + VerdictCacheManagerFactory(); + ~VerdictCacheManagerFactory() override = default; + VerdictCacheManagerFactory(const VerdictCacheManagerFactory&) = delete; + VerdictCacheManagerFactory& operator=(const VerdictCacheManagerFactory&) = + delete; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_VERDICT_CACHE_MANAGER_FACTORY_H_
\ No newline at end of file diff --git a/chromium/weblayer/browser/weblayer_browser_interface_binders.cc b/chromium/weblayer/browser/weblayer_browser_interface_binders.cc index a9e8fa6b606..a0043214a7e 100644 --- a/chromium/weblayer/browser/weblayer_browser_interface_binders.cc +++ b/chromium/weblayer/browser/weblayer_browser_interface_binders.cc @@ -19,6 +19,8 @@ #if defined(OS_ANDROID) #include "mojo/public/cpp/bindings/self_owned_receiver.h" +#include "services/service_manager/public/cpp/interface_provider.h" +#include "third_party/blink/public/mojom/webshare/webshare.mojom.h" #endif namespace weblayer { @@ -37,11 +39,6 @@ void BindContentTranslateDriver( if (!contents) return; - // TODO(crbug.com/1072334): Resolve incorporation of translate in incognito - // mode. - if (contents->GetBrowserContext()->IsOffTheRecord()) - return; - TranslateClientImpl* const translate_client = TranslateClientImpl::FromWebContents(contents); translate_client->translate_driver()->AddReceiver(std::move(receiver)); @@ -90,6 +87,15 @@ class StubInstalledAppProvider : public blink::mojom::InstalledAppProvider { std::move(receiver)); } }; + +template <typename Interface> +void ForwardToJavaWebContents(content::RenderFrameHost* frame_host, + mojo::PendingReceiver<Interface> receiver) { + content::WebContents* contents = + content::WebContents::FromRenderFrameHost(frame_host); + if (contents) + contents->GetJavaInterfaces()->GetInterface(std::move(receiver)); +} #endif } // namespace @@ -107,6 +113,8 @@ void PopulateWebLayerFrameBinders( // TODO(https://crbug.com/1037884): Remove this. map->Add<blink::mojom::InstalledAppProvider>( base::BindRepeating(&StubInstalledAppProvider::Create)); + map->Add<blink::mojom::ShareService>(base::BindRepeating( + &ForwardToJavaWebContents<blink::mojom::ShareService>)); #endif } diff --git a/chromium/weblayer/browser/weblayer_features.cc b/chromium/weblayer/browser/weblayer_features.cc new file mode 100644 index 00000000000..b6cce79dc3f --- /dev/null +++ b/chromium/weblayer/browser/weblayer_features.cc @@ -0,0 +1,15 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/weblayer_features.h" + +namespace weblayer { + +#if defined(OS_ANDROID) +// Used to disable browser-control animations. +const base::Feature kImmediatelyHideBrowserControlsForTest{ + "ImmediatelyHideBrowserControlsForTest", base::FEATURE_DISABLED_BY_DEFAULT}; +#endif + +} // namespace weblayer diff --git a/chromium/weblayer/browser/weblayer_features.h b/chromium/weblayer/browser/weblayer_features.h new file mode 100644 index 00000000000..40254557d4b --- /dev/null +++ b/chromium/weblayer/browser/weblayer_features.h @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_WEBLAYER_FEATURES_H_ +#define WEBLAYER_BROWSER_WEBLAYER_FEATURES_H_ + +#include "base/feature_list.h" +#include "build/build_config.h" + +namespace weblayer { + +#if defined(OS_ANDROID) +extern const base::Feature kImmediatelyHideBrowserControlsForTest; +#endif + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_WEBLAYER_FEATURES_H_ diff --git a/chromium/weblayer/browser/weblayer_field_trials.cc b/chromium/weblayer/browser/weblayer_field_trials.cc new file mode 100644 index 00000000000..8ba011f7f09 --- /dev/null +++ b/chromium/weblayer/browser/weblayer_field_trials.cc @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/weblayer_field_trials.h" + +#include "base/path_service.h" +#include "components/metrics/persistent_histograms.h" +#include "weblayer/common/weblayer_paths.h" + +namespace weblayer { + +void WebLayerFieldTrials::SetupFieldTrials() { + // Persistent histograms must be enabled as soon as possible. + base::FilePath metrics_dir; + if (base::PathService::Get(DIR_USER_DATA, &metrics_dir)) { + InstantiatePersistentHistograms(metrics_dir); + } +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/weblayer_field_trials.h b/chromium/weblayer/browser/weblayer_field_trials.h index 54454c3ba89..a2fc67cfb89 100644 --- a/chromium/weblayer/browser/weblayer_field_trials.h +++ b/chromium/weblayer/browser/weblayer_field_trials.h @@ -14,11 +14,11 @@ namespace weblayer { // functions are stubs, as WebLayer has no specific field trials. class WebLayerFieldTrials : public variations::PlatformFieldTrials { public: - WebLayerFieldTrials() {} - ~WebLayerFieldTrials() override {} + WebLayerFieldTrials() = default; + ~WebLayerFieldTrials() override = default; // variations::PlatformFieldTrials: - void SetupFieldTrials() override {} + void SetupFieldTrials() override; void SetupFeatureControllingFieldTrials( bool has_seed, base::FeatureList* feature_list) override {} diff --git a/chromium/weblayer/browser/weblayer_impl_android.cc b/chromium/weblayer/browser/weblayer_impl_android.cc index 3f54f31edf7..d07dc34ef0e 100644 --- a/chromium/weblayer/browser/weblayer_impl_android.cc +++ b/chromium/weblayer/browser/weblayer_impl_android.cc @@ -13,6 +13,7 @@ #include "weblayer/browser/java/jni/WebLayerImpl_jni.h" #include "weblayer/browser/url_bar/page_info_client_impl.h" #include "weblayer/browser/user_agent.h" +#include "weblayer/common/crash_reporter/crash_keys.h" using base::android::JavaParamRef; @@ -30,7 +31,7 @@ static jboolean JNI_WebLayerImpl_IsRemoteDebuggingEnabled(JNIEnv* env) { static void JNI_WebLayerImpl_SetIsWebViewCompatMode(JNIEnv* env, jboolean value) { static crash_reporter::CrashKeyString<1> crash_key( - "WEBLAYER_WEB_VIEW_COMPAT_MODE"); + crash_keys::kWeblayerWebViewCompatMode); crash_key.Set(value ? "1" : "0"); } @@ -44,7 +45,6 @@ static void JNI_WebLayerImpl_RegisterExternalExperimentIDs( JNIEnv* env, const JavaParamRef<jstring>& jtrial_name, const JavaParamRef<jintArray>& jexperiment_ids) { - const std::string trial_name_utf8(ConvertJavaStringToUTF8(env, jtrial_name)); std::vector<int> experiment_ids; // A null |jexperiment_ids| is the same as an empty list. if (jexperiment_ids) { @@ -52,8 +52,8 @@ static void JNI_WebLayerImpl_RegisterExternalExperimentIDs( &experiment_ids); } - WebLayerMetricsServiceClient::GetInstance() - ->RegisterSyntheticMultiGroupFieldTrial(trial_name_utf8, experiment_ids); + WebLayerMetricsServiceClient::GetInstance()->RegisterExternalExperiments( + experiment_ids); } base::string16 GetClientApplicationName() { diff --git a/chromium/weblayer/browser/weblayer_security_blocking_page_factory.cc b/chromium/weblayer/browser/weblayer_security_blocking_page_factory.cc index 33de9c7e102..37d3676f4f0 100644 --- a/chromium/weblayer/browser/weblayer_security_blocking_page_factory.cc +++ b/chromium/weblayer/browser/weblayer_security_blocking_page_factory.cc @@ -38,14 +38,10 @@ void OpenLoginPage(content::WebContents* web_contents) { // ChromeSecurityBlockingPageFactory::OpenLoginPage(), from which this is // adapted. #if defined(OS_ANDROID) - // In Chrome this opens in a new tab, but WebLayer's TabImpl has no support - // for opening new tabs (its OpenURLFromTab() method DCHECKs if the - // disposition is not |CURRENT_TAB|). - // TODO(crbug.com/1047130): Revisit if TabImpl gets support for opening URLs - // in new tabs. - content::OpenURLParams params( - GetCaptivePortalLoginPageUrlInternal(), content::Referrer(), - WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_LINK, false); + content::OpenURLParams params(GetCaptivePortalLoginPageUrlInternal(), + content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, false); web_contents->OpenURL(params); #else NOTIMPLEMENTED(); @@ -201,6 +197,16 @@ WebLayerSecurityBlockingPageFactory::CreateBlockedInterceptionBlockingPage( return interstitial_page; } +std::unique_ptr<security_interstitials::InsecureFormBlockingPage> +WebLayerSecurityBlockingPageFactory::CreateInsecureFormBlockingPage( + content::WebContents* web_contents, + const GURL& request_url) { + // TODO(crbug.com/1093102): Insecure form warnings are not yet implemented in + // Weblayer. + NOTREACHED(); + return nullptr; +} + #if defined(OS_ANDROID) // static GURL WebLayerSecurityBlockingPageFactory:: diff --git a/chromium/weblayer/browser/weblayer_security_blocking_page_factory.h b/chromium/weblayer/browser/weblayer_security_blocking_page_factory.h index d0a2a5ad291..e154d433c32 100644 --- a/chromium/weblayer/browser/weblayer_security_blocking_page_factory.h +++ b/chromium/weblayer/browser/weblayer_security_blocking_page_factory.h @@ -11,6 +11,7 @@ #include "components/security_interstitials/content/bad_clock_blocking_page.h" #include "components/security_interstitials/content/blocked_interception_blocking_page.h" #include "components/security_interstitials/content/captive_portal_blocking_page.h" +#include "components/security_interstitials/content/insecure_form_blocking_page.h" #include "components/security_interstitials/content/mitm_software_blocking_page.h" #include "components/security_interstitials/content/security_blocking_page_factory.h" #include "components/security_interstitials/content/ssl_blocking_page.h" @@ -73,6 +74,9 @@ class WebLayerSecurityBlockingPageFactory : public SecurityBlockingPageFactory { const GURL& request_url, std::unique_ptr<SSLCertReporter> ssl_cert_reporter, const net::SSLInfo& ssl_info) override; + std::unique_ptr<security_interstitials::InsecureFormBlockingPage> + CreateInsecureFormBlockingPage(content::WebContents* web_contents, + const GURL& request_url) override; #if defined(OS_ANDROID) // Returns the URL that will be navigated to when the user clicks on the diff --git a/chromium/weblayer/browser/weblayer_site_settings_client.cc b/chromium/weblayer/browser/weblayer_site_settings_client.cc deleted file mode 100644 index bb65eeacf32..00000000000 --- a/chromium/weblayer/browser/weblayer_site_settings_client.cc +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "weblayer/browser/java/jni/WebLayerSiteSettingsClient_jni.h" - -#include "components/content_settings/core/common/pref_names.h" -#include "components/embedder_support/android/browser_context/browser_context_handle.h" -#include "components/prefs/pref_service.h" -#include "components/user_prefs/user_prefs.h" -#include "content/public/browser/browser_context.h" - -using base::android::JavaParamRef; - -namespace weblayer { - -namespace { - -PrefService* GetPrefService( - const JavaParamRef<jobject>& jbrowser_context_handle) { - content::BrowserContext* browser_context = - browser_context::BrowserContextFromJavaHandle(jbrowser_context_handle); - if (!browser_context) - return nullptr; - return user_prefs::UserPrefs::Get(browser_context); -} - -} // namespace - -static jboolean JNI_WebLayerSiteSettingsClient_GetBlockThirdPartyCookies( - JNIEnv* env, - const JavaParamRef<jobject>& jbrowser_context_handle) { - PrefService* pref_service = GetPrefService(jbrowser_context_handle); - if (!pref_service) - return false; - return pref_service->GetBoolean(prefs::kBlockThirdPartyCookies); -} - -static void JNI_WebLayerSiteSettingsClient_SetBlockThirdPartyCookies( - JNIEnv* env, - const JavaParamRef<jobject>& jbrowser_context_handle, - jboolean value) { - PrefService* pref_service = GetPrefService(jbrowser_context_handle); - if (!pref_service) - return; - pref_service->SetBoolean(prefs::kBlockThirdPartyCookies, value); -} - -static jint JNI_WebLayerSiteSettingsClient_GetCookieControlsMode( - JNIEnv* env, - const JavaParamRef<jobject>& jbrowser_context_handle) { - PrefService* pref_service = GetPrefService(jbrowser_context_handle); - if (!pref_service) - return 0; - return pref_service->GetInteger(prefs::kCookieControlsMode); -} - -static void JNI_WebLayerSiteSettingsClient_SetCookieControlsMode( - JNIEnv* env, - const JavaParamRef<jobject>& jbrowser_context_handle, - jint value) { - PrefService* pref_service = GetPrefService(jbrowser_context_handle); - if (!pref_service) - return; - pref_service->SetInteger(prefs::kCookieControlsMode, value); -} - -} // namespace weblayer diff --git a/chromium/weblayer/browser/weblayer_speech_recognition_manager_delegate.cc b/chromium/weblayer/browser/weblayer_speech_recognition_manager_delegate.cc index 7738351203b..8c941e61ea8 100644 --- a/chromium/weblayer/browser/weblayer_speech_recognition_manager_delegate.cc +++ b/chromium/weblayer/browser/weblayer_speech_recognition_manager_delegate.cc @@ -8,7 +8,6 @@ #include "base/bind.h" #include "base/macros.h" -#include "base/task/post_task.h" #include "build/build_config.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -84,8 +83,8 @@ void WebLayerSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed( // Check that the render frame type is appropriate, and whether or not we // need to request permission from the user. - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&CheckRenderFrameType, std::move(callback), + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&CheckRenderFrameType, std::move(callback), render_process_id, render_frame_id)); } @@ -109,8 +108,8 @@ void WebLayerSpeechRecognitionManagerDelegate::CheckRenderFrameType( DCHECK_CURRENTLY_ON(BrowserThread::UI); // Regular tab contents. - base::PostTask( - FROM_HERE, {BrowserThread::IO}, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), true /* check_permission */, true /* allowed */)); } |