diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 4611b0c62613..6b0b622cece8 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -544,6 +544,28 @@ Or change later at $2brave://settings/ext this setting + + + Geolocation with Brave + + + The Brave browser is attempting to access your location. You may not get accurate results unless you enable your operating system's $1Location services setting and also enable $2Let apps access your location. + + + Location services + + + Let apps access your location + + + With those enabled, your location results should be much more accurate. $1Learn more + + + Learn more + + + Don't show this again + Close all diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 433dac00322b..b1ef34a9b869 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -109,6 +109,7 @@ #if !BUILDFLAG(IS_ANDROID) #include "brave/browser/search_engines/search_engine_provider_util.h" +#include "brave/browser/ui/geolocation/pref_names.h" #include "brave/browser/ui/tabs/brave_tab_prefs.h" #include "brave/components/brave_private_new_tab_ui/common/pref_names.h" #endif @@ -439,6 +440,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { base::Value(true)); registry->RegisterBooleanPref(kEnableWindowClosingConfirm, true); registry->RegisterBooleanPref(kEnableClosingLastTab, true); + registry->RegisterBooleanPref(kShowGeolocationAccuracyHelperDialog, true); brave_tabs::RegisterBraveProfilePrefs(registry); #endif diff --git a/browser/brave_tab_helpers.cc b/browser/brave_tab_helpers.cc index 910e7d491510..5ae0a284435a 100644 --- a/browser/brave_tab_helpers.cc +++ b/browser/brave_tab_helpers.cc @@ -44,6 +44,7 @@ #if !BUILDFLAG(IS_ANDROID) #include "brave/browser/ui/brave_shields_data_controller.h" +#include "brave/browser/ui/geolocation/geolocation_accuracy_tab_helper.h" #include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h" #endif @@ -106,6 +107,7 @@ void AttachTabHelpers(content::WebContents* web_contents) { BraveBookmarkTabHelper::CreateForWebContents(web_contents); brave_shields::BraveShieldsDataController::CreateForWebContents(web_contents); ThumbnailTabHelper::CreateForWebContents(web_contents); + GeolocationAccuracyTabHelper::MaybeCreateForWebContents(web_contents); #endif brave_rewards::RewardsTabHelper::CreateForWebContents(web_contents); diff --git a/browser/geolocation/brave_geolocation_permission_context_delegate.cc b/browser/geolocation/brave_geolocation_permission_context_delegate.cc index 75dff77283b0..9ea029d10fc3 100644 --- a/browser/geolocation/brave_geolocation_permission_context_delegate.cc +++ b/browser/geolocation/brave_geolocation_permission_context_delegate.cc @@ -13,11 +13,15 @@ #include "content/public/browser/web_contents.h" #include "url/gurl.h" +#if !BUILDFLAG(IS_ANDROID) +#include "brave/browser/ui/geolocation/geolocation_accuracy_tab_helper.h" +#endif + BraveGeolocationPermissionContextDelegate:: BraveGeolocationPermissionContextDelegate( content::BrowserContext* browser_context) : GeolocationPermissionContextDelegate(browser_context), - profile_(Profile::FromBrowserContext(browser_context)) {} + profile_(*Profile::FromBrowserContext(browser_context)) {} BraveGeolocationPermissionContextDelegate:: ~BraveGeolocationPermissionContextDelegate() = default; @@ -33,6 +37,23 @@ bool BraveGeolocationPermissionContextDelegate::DecidePermission( return true; } - return GeolocationPermissionContextDelegate::DecidePermission( - id, requesting_origin, user_gesture, callback, context); + if (GeolocationPermissionContextDelegate::DecidePermission( + id, requesting_origin, user_gesture, callback, context)) { + return true; + } + +#if !BUILDFLAG(IS_ANDROID) + content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(id.global_render_frame_host_id()); + DCHECK(rfh); + + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(rfh); + if (GeolocationAccuracyTabHelper* accuracy_tab_helper = + GeolocationAccuracyTabHelper::FromWebContents(web_contents)) { + accuracy_tab_helper->LaunchAccuracyHelperDialogIfNeeded(); + } +#endif + + return false; } diff --git a/browser/geolocation/brave_geolocation_permission_context_delegate.h b/browser/geolocation/brave_geolocation_permission_context_delegate.h index d3ba9517dc19..7c569f838f7d 100644 --- a/browser/geolocation/brave_geolocation_permission_context_delegate.h +++ b/browser/geolocation/brave_geolocation_permission_context_delegate.h @@ -6,7 +6,7 @@ #ifndef BRAVE_BROWSER_GEOLOCATION_BRAVE_GEOLOCATION_PERMISSION_CONTEXT_DELEGATE_H_ #define BRAVE_BROWSER_GEOLOCATION_BRAVE_GEOLOCATION_PERMISSION_CONTEXT_DELEGATE_H_ -#include "base/memory/raw_ptr.h" +#include "base/memory/raw_ref.h" #include "chrome/browser/geolocation/geolocation_permission_context_delegate.h" class Profile; @@ -30,7 +30,7 @@ class BraveGeolocationPermissionContextDelegate permissions::GeolocationPermissionContext* context) override; private: - raw_ptr profile_ = nullptr; + const raw_ref profile_; }; #endif // BRAVE_BROWSER_GEOLOCATION_BRAVE_GEOLOCATION_PERMISSION_CONTEXT_DELEGATE_H_ diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index d0ddfece5c7a..9bf5d5e15ccd 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -97,6 +97,9 @@ source_set("ui") { "content_settings/brave_autoplay_content_setting_bubble_model.h", "content_settings/brave_content_setting_image_models.cc", "content_settings/brave_content_setting_image_models.h", + "geolocation/geolocation_accuracy_tab_helper.cc", + "geolocation/geolocation_accuracy_tab_helper.h", + "geolocation/pref_names.h", "omnibox/brave_omnibox_client_impl.cc", "omnibox/brave_omnibox_client_impl.h", "page_action/brave_page_action_icon_type.h", @@ -306,6 +309,8 @@ source_set("ui") { "views/frame/brave_opaque_browser_frame_view.h", "views/frame/brave_window_frame_graphic.cc", "views/frame/brave_window_frame_graphic.h", + "views/geolocation_accuracy_helper_dialog_view.cc", + "views/geolocation_accuracy_helper_dialog_view.h", "views/omnibox/brave_omnibox_popup_view_views.cc", "views/omnibox/brave_omnibox_popup_view_views.h", "views/omnibox/brave_omnibox_result_view.cc", @@ -427,6 +432,13 @@ source_set("ui") { ] } + if (is_win) { + sources += [ + "geolocation/geolocation_accuracy_utils_win.cc", + "geolocation/geolocation_accuracy_utils_win.h", + ] + } + # brave's obsolete infobar is only used on Windows and Mac now. if (is_win || is_mac) { sources += [ @@ -552,6 +564,7 @@ source_set("ui") { "//components/strings:components_strings", "//components/tab_groups", "//components/update_client", + "//components/user_prefs", "//content/public/browser", "//content/public/common", "//mojo/public/cpp/bindings", diff --git a/browser/ui/browser_dialogs.h b/browser/ui/browser_dialogs.h index fe8e313a1765..8168aeeb983e 100644 --- a/browser/ui/browser_dialogs.h +++ b/browser/ui/browser_dialogs.h @@ -25,6 +25,10 @@ void ShowCrashReportPermissionAskDialog(Browser* browser); // Run |callback| when dialog closed. void ShowObsoleteSystemConfirmDialog(base::OnceCallback callback); +// Launch dialog to inform that system location service is not enabled. +void ShowGeolocationAccuracyHelperDialog(content::WebContents* web_contents, + base::OnceClosure closing_callback); + #if BUILDFLAG(ENABLE_TEXT_RECOGNITION) // Show web modal dialog for showing text that recognized from |image|. void ShowTextRecognitionDialog(content::WebContents* web_contents, diff --git a/browser/ui/geolocation/BUILD.gn b/browser/ui/geolocation/BUILD.gn new file mode 100644 index 000000000000..fd59b8576a5d --- /dev/null +++ b/browser/ui/geolocation/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright (c) 2023 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +source_set("browser_tests") { + testonly = true + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + + sources = [ "geolocation_accuracy_browsertest.cc" ] + + deps = [ + "//base", + "//brave/browser", + "//chrome/browser", + "//chrome/browser/ui", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//components/permissions", + "//components/permissions:test_support", + "//components/prefs", + "//content/test:test_support", + ] +} diff --git a/browser/ui/geolocation/geolocation_accuracy_browsertest.cc b/browser/ui/geolocation/geolocation_accuracy_browsertest.cc new file mode 100644 index 000000000000..2b046711cff5 --- /dev/null +++ b/browser/ui/geolocation/geolocation_accuracy_browsertest.cc @@ -0,0 +1,166 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include + +#include "base/functional/bind.h" +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "brave/browser/ui/geolocation/geolocation_accuracy_tab_helper.h" +#include "brave/browser/ui/geolocation/pref_names.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/permissions/permission_request_manager.h" +#include "components/permissions/test/mock_permission_prompt_factory.h" +#include "components/prefs/pref_service.h" +#include "components/web_modal/web_contents_modal_dialog_manager.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_mock_cert_verifier.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +class GeolocationAccuracyBrowserTest : public InProcessBrowserTest { + public: + GeolocationAccuracyBrowserTest() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} + + GeolocationAccuracyBrowserTest(const GeolocationAccuracyBrowserTest&) = + delete; + GeolocationAccuracyBrowserTest& operator=( + const GeolocationAccuracyBrowserTest&) = delete; + ~GeolocationAccuracyBrowserTest() override = default; + + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + mock_cert_verifier_.SetUpCommandLine(command_line); + } + + void SetUpInProcessBrowserTestFixture() override { + InProcessBrowserTest::SetUpInProcessBrowserTestFixture(); + mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); + } + + void TearDownInProcessBrowserTestFixture() override { + InProcessBrowserTest::TearDownInProcessBrowserTestFixture(); + mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); + } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); + permissions::PermissionRequestManager* manager = + permissions::PermissionRequestManager::FromWebContents( + GetActiveWebContents()); + mock_permission_prompt_factory_ = + std::make_unique(manager); + + host_resolver()->AddRule("*", "127.0.0.1"); + https_server()->ServeFilesFromSourceDirectory(GetChromeTestDataDir()); + ASSERT_TRUE(https_server()->Start()); + } + + void TearDownOnMainThread() override { + mock_permission_prompt_factory_.reset(); + } + + permissions::MockPermissionPromptFactory* prompt_factory() { + return mock_permission_prompt_factory_.get(); + } + + net::EmbeddedTestServer* https_server() { return &https_server_; } + base::RunLoop* run_loop() const { return run_loop_.get(); } + + void WaitUntil(base::RepeatingCallback condition) { + if (condition.Run()) { + return; + } + + base::RepeatingTimer scheduler; + scheduler.Start(FROM_HERE, base::Milliseconds(100), + base::BindLambdaForTesting([this, &condition]() { + if (condition.Run()) { + run_loop_->Quit(); + } + })); + Run(); + } + + void Run() { + run_loop_ = std::make_unique(); + run_loop()->Run(); + } + + content::WebContents* GetActiveWebContents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + content::RenderFrameHost* GetActiveMainFrame() { + return GetActiveWebContents()->GetPrimaryMainFrame(); + } + + void AcceptDialogForTesting() { + web_modal::WebContentsModalDialogManager* manager = + web_modal::WebContentsModalDialogManager::FromWebContents( + GetActiveWebContents()); + DCHECK(manager); + manager->CloseAllDialogs(); + } + + content::ContentMockCertVerifier mock_cert_verifier_; + net::test_server::EmbeddedTestServer https_server_; + std::unique_ptr + mock_permission_prompt_factory_; + std::unique_ptr run_loop_; +}; + +IN_PROC_BROWSER_TEST_F(GeolocationAccuracyBrowserTest, DialogLaunchTest) { + const GURL& url = https_server()->GetURL("/empty.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + mock_permission_prompt_factory_->set_response_type( + permissions::PermissionRequestManager::AutoResponseType::DISMISS); + + // Check bubble is shown and dialog is also launched. + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + mock_permission_prompt_factory_->WaitForPermissionBubble(); + + GeolocationAccuracyTabHelper* accuracy_tab_helper = + GeolocationAccuracyTabHelper::FromWebContents(GetActiveWebContents()); + if (!accuracy_tab_helper) { + return; + } + + EXPECT_TRUE(accuracy_tab_helper->is_dialog_running_); + EXPECT_TRUE(accuracy_tab_helper->dialog_asked_in_current_navigation_); + + // Wait to dialog closing. + AcceptDialogForTesting(); + WaitUntil(base::BindLambdaForTesting( + [&]() { return !accuracy_tab_helper->is_dialog_running_; })); + EXPECT_TRUE(accuracy_tab_helper->dialog_asked_in_current_navigation_); + + // Navigate again and check flags are cleared. + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_FALSE(accuracy_tab_helper->is_dialog_running_); + EXPECT_FALSE(accuracy_tab_helper->dialog_asked_in_current_navigation_); + + // Disable dialog launching. + browser()->profile()->GetPrefs()->SetBoolean( + kShowGeolocationAccuracyHelperDialog, false); + + // Check bubble is shown again but dialog is not launched. + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + mock_permission_prompt_factory_->WaitForPermissionBubble(); + EXPECT_FALSE(accuracy_tab_helper->is_dialog_running_); +} diff --git a/browser/ui/geolocation/geolocation_accuracy_tab_helper.cc b/browser/ui/geolocation/geolocation_accuracy_tab_helper.cc new file mode 100644 index 000000000000..9f6900d31af4 --- /dev/null +++ b/browser/ui/geolocation/geolocation_accuracy_tab_helper.cc @@ -0,0 +1,81 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/geolocation/geolocation_accuracy_tab_helper.h" + +#include "brave/browser/ui/browser_dialogs.h" +#include "brave/browser/ui/geolocation/pref_names.h" +#include "build/build_config.h" +#include "chrome/browser/ui/browser.h" +#include "components/prefs/pref_service.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/web_contents.h" + +#if BUILDFLAG(IS_WIN) +#include "brave/browser/ui/geolocation/geolocation_accuracy_utils_win.h" +#endif + +// static +void GeolocationAccuracyTabHelper::MaybeCreateForWebContents( + content::WebContents* contents) { +#if BUILDFLAG(IS_WIN) + content::WebContentsUserData< + GeolocationAccuracyTabHelper>::CreateForWebContents(contents); +#endif +} + +GeolocationAccuracyTabHelper::GeolocationAccuracyTabHelper( + content::WebContents* contents) + : WebContentsUserData(*contents), WebContentsObserver(contents) {} + +GeolocationAccuracyTabHelper::~GeolocationAccuracyTabHelper() = default; + +void GeolocationAccuracyTabHelper::LaunchAccuracyHelperDialogIfNeeded() { + if (auto* prefs = + user_prefs::UserPrefs::Get(web_contents()->GetBrowserContext()); + !prefs->GetBoolean(kShowGeolocationAccuracyHelperDialog)) { + return; + } + + if (is_dialog_running_) { + return; + } + + if (dialog_asked_in_current_navigation_) { + return; + } + +#if BUILDFLAG(IS_WIN) + if (IsSystemLocationSettingEnabled()) { + DVLOG(2) << __func__ << " : system location service is enabled."; + return; + } +#endif + + dialog_asked_in_current_navigation_ = true; + is_dialog_running_ = true; + brave::ShowGeolocationAccuracyHelperDialog( + web_contents(), + base::BindOnce(&GeolocationAccuracyTabHelper::OnDialogClosed, + weak_ptr_factory_.GetWeakPtr())); +} + +void GeolocationAccuracyTabHelper::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || + navigation_handle->IsSameDocument()) { + return; + } + + dialog_asked_in_current_navigation_ = false; +} + +void GeolocationAccuracyTabHelper::OnDialogClosed() { + is_dialog_running_ = false; +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(GeolocationAccuracyTabHelper); diff --git a/browser/ui/geolocation/geolocation_accuracy_tab_helper.h b/browser/ui/geolocation/geolocation_accuracy_tab_helper.h new file mode 100644 index 000000000000..47de3e50c5c0 --- /dev/null +++ b/browser/ui/geolocation/geolocation_accuracy_tab_helper.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_TAB_HELPER_H_ +#define BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_TAB_HELPER_H_ + +#include "base/memory/weak_ptr.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +class GeolocationAccuracyTabHelper + : public content::WebContentsUserData, + public content::WebContentsObserver { + public: + static void MaybeCreateForWebContents(content::WebContents* contents); + + ~GeolocationAccuracyTabHelper() override; + + // Launches per-tab dialog. + void LaunchAccuracyHelperDialogIfNeeded(); + + // content::WebContentsObserver overrides: + void DidStartNavigation( + content::NavigationHandle* navigation_handle) override; + + private: + friend WebContentsUserData; + FRIEND_TEST_ALL_PREFIXES(GeolocationAccuracyBrowserTest, DialogLaunchTest); + FRIEND_TEST_ALL_PREFIXES(GeolocationAccuracyBrowserTest, + DialogLaunchDisabledTest); + + explicit GeolocationAccuracyTabHelper(content::WebContents* contents); + + void OnDialogClosed(); + + bool dialog_asked_in_current_navigation_ = false; + bool is_dialog_running_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +#endif // BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_TAB_HELPER_H_ diff --git a/browser/ui/geolocation/geolocation_accuracy_utils_win.cc b/browser/ui/geolocation/geolocation_accuracy_utils_win.cc new file mode 100644 index 000000000000..7e22185678ec --- /dev/null +++ b/browser/ui/geolocation/geolocation_accuracy_utils_win.cc @@ -0,0 +1,68 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/geolocation/geolocation_accuracy_utils_win.h" + +#include + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/threading/scoped_thread_priority.h" +#include "base/win/core_winrt_util.h" + +using ABI::Windows::Devices::Enumeration::DeviceAccessStatus; +using ABI::Windows::Devices::Enumeration::DeviceClass; +using ABI::Windows::Devices::Enumeration::IDeviceAccessInformation; +using ABI::Windows::Devices::Enumeration::IDeviceAccessInformationStatics; +using Microsoft::WRL::ComPtr; + +// Copied from services/device/geolocation/win/location_provider_winrt.cc +bool IsSystemLocationSettingEnabled() { + ComPtr dev_access_info_statics; + HRESULT hr = base::win::GetActivationFactory< + IDeviceAccessInformationStatics, + RuntimeClass_Windows_Devices_Enumeration_DeviceAccessInformation>( + &dev_access_info_statics); + if (FAILED(hr)) { + VLOG(1) << "IDeviceAccessInformationStatics failed: " << hr; + return true; + } + + ComPtr dev_access_info; + hr = dev_access_info_statics->CreateFromDeviceClass( + DeviceClass::DeviceClass_Location, &dev_access_info); + if (FAILED(hr)) { + VLOG(1) << "IDeviceAccessInformation failed: " << hr; + return true; + } + + auto status = DeviceAccessStatus::DeviceAccessStatus_Unspecified; + dev_access_info->get_CurrentStatus(&status); + + return !(status == DeviceAccessStatus::DeviceAccessStatus_DeniedBySystem || + status == DeviceAccessStatus::DeviceAccessStatus_DeniedByUser); +} + +void LaunchLocationServiceSettings() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); + + SHELLEXECUTEINFO sei = {sizeof(sei)}; + sei.nShow = SW_SHOWNORMAL; + sei.lpVerb = L"open"; + sei.lpFile = L"ms-settings:privacy-location"; + + // Mitigate the issues caused by loading DLLs on a background thread + // (http://crbug/973868). + SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY(); + + ::ShellExecuteExW(&sei); +} diff --git a/browser/ui/geolocation/geolocation_accuracy_utils_win.h b/browser/ui/geolocation/geolocation_accuracy_utils_win.h new file mode 100644 index 000000000000..07aa6e9ceac7 --- /dev/null +++ b/browser/ui/geolocation/geolocation_accuracy_utils_win.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_UTILS_WIN_H_ +#define BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_UTILS_WIN_H_ + +// True when system location service is available to applications. +bool IsSystemLocationSettingEnabled(); +void LaunchLocationServiceSettings(); + +#endif // BRAVE_BROWSER_UI_GEOLOCATION_GEOLOCATION_ACCURACY_UTILS_WIN_H_ diff --git a/browser/ui/geolocation/pref_names.h b/browser/ui/geolocation/pref_names.h new file mode 100644 index 000000000000..a0efda167326 --- /dev/null +++ b/browser/ui/geolocation/pref_names.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_GEOLOCATION_PREF_NAMES_H_ +#define BRAVE_BROWSER_UI_GEOLOCATION_PREF_NAMES_H_ + +constexpr char kShowGeolocationAccuracyHelperDialog[] = + "brave.show_geolocation_accuracy_helper_dialog"; + +#endif // BRAVE_BROWSER_UI_GEOLOCATION_PREF_NAMES_H_ diff --git a/browser/ui/views/geolocation_accuracy_helper_dialog_view.cc b/browser/ui/views/geolocation_accuracy_helper_dialog_view.cc new file mode 100644 index 000000000000..a8b2c4535705 --- /dev/null +++ b/browser/ui/views/geolocation_accuracy_helper_dialog_view.cc @@ -0,0 +1,223 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/views/geolocation_accuracy_helper_dialog_view.h" + +#include +#include +#include + +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "base/task/thread_pool.h" +#include "brave/browser/ui/color/leo/colors.h" +#include "brave/browser/ui/geolocation/pref_names.h" +#include "brave/browser/ui/views/infobars/custom_styled_label.h" +#include "brave/grit/brave_generated_resources.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "components/constrained_window/constrained_window_views.h" +#include "components/prefs/pref_service.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/web_contents.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/views/controls/button/checkbox.h" +#include "ui/views/controls/button/md_text_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/link.h" +#include "ui/views/controls/styled_label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/view_class_properties.h" + +#if BUILDFLAG(IS_WIN) +#include "brave/browser/ui/geolocation/geolocation_accuracy_utils_win.h" +#endif + +namespace brave { + +void ShowGeolocationAccuracyHelperDialog(content::WebContents* web_contents, + base::OnceClosure closing_callback) { + constrained_window::ShowWebModalDialogViews( + new GeolocationAccuracyHelperDialogView( + user_prefs::UserPrefs::Get(web_contents->GetBrowserContext()), + std::move(closing_callback)), + web_contents); +} + +} // namespace brave + +namespace { + +constexpr char kLearnMoreURL[] = + "https://support.microsoft.com/en-us/windows/" + "windows-location-service-and-privacy-3a8eee0a-5b0b-dc07-eede-2a5ca1c49088"; + +// Subclass for custom font. +class DontShowAgainCheckbox : public views::Checkbox { + public: + METADATA_HEADER(DontShowAgainCheckbox); + + using views::Checkbox::Checkbox; + ~DontShowAgainCheckbox() override = default; + DontShowAgainCheckbox(const DontShowAgainCheckbox&) = delete; + DontShowAgainCheckbox& operator=(const DontShowAgainCheckbox&) = delete; + + void SetFontList(const gfx::FontList& font_list) { + label()->SetFontList(font_list); + } +}; + +BEGIN_METADATA(DontShowAgainCheckbox, views::Checkbox) +END_METADATA + +} // namespace + +GeolocationAccuracyHelperDialogView::GeolocationAccuracyHelperDialogView( + PrefService* prefs, + base::OnceClosure closing_callback) + : prefs_(*prefs) { + RegisterWindowClosingCallback(std::move(closing_callback)); + set_should_ignore_snapping(true); + SetModalType(ui::MODAL_TYPE_CHILD); + SetShowCloseButton(false); + SetButtons(ui::DIALOG_BUTTON_OK); + + // Safe to use Unretained() here because this callback is only called before + // closing this widget. + SetAcceptCallback(base::BindOnce( + &GeolocationAccuracyHelperDialogView::OnAccept, base::Unretained(this))); + + constexpr int kPadding = 24; + SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical, + gfx::Insets(kPadding), kPadding)) + ->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kStart); +} + +GeolocationAccuracyHelperDialogView::~GeolocationAccuracyHelperDialogView() = + default; + +void GeolocationAccuracyHelperDialogView::AddedToWidget() { + SetupChildViews(); +} + +void GeolocationAccuracyHelperDialogView::OnWidgetInitialized() { + // dialog button should be accessed after widget initialized. + // See the comment of DialogDelegate::GetOkButton(). + GetOkButton()->SetKind(views::MdTextButton::kPrimary); + GetOkButton()->SetProminent(false); +} + +void GeolocationAccuracyHelperDialogView::SetupChildViews() { + auto* header_label = + AddChildView(std::make_unique(l10n_util::GetStringUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_HEADER_LABEL))); + header_label->SetFontList(gfx::FontList("SF Pro Text, Semi-Bold 16px")); + header_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + + std::vector offsets; + const std::u16string contents_text_part_one = l10n_util::GetStringUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_CONTENTS_PART_ONE_LABEL); + const std::u16string contents_text_part_two = l10n_util::GetStringUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_CONTENTS_PART_TWO_LABEL); + const std::u16string contents_text = l10n_util::GetStringFUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_CONTENTS_LABEL, + contents_text_part_one, contents_text_part_two, &offsets); + + auto* contents_label = AddChildView(std::make_unique()); + contents_label->SetText(contents_text); + views::StyledLabel::RangeStyleInfo part_style; + part_style.custom_font = gfx::FontList("SF Pro Text, Semi-Bold 14px"); + contents_label->AddStyleRange( + gfx::Range(offsets[0], offsets[0] + contents_text_part_one.length()), + part_style); + contents_label->AddStyleRange( + gfx::Range(offsets[1], offsets[1] + contents_text_part_two.length()), + part_style); + contents_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + constexpr int kMaxWidth = 445; + contents_label->SizeToFit(kMaxWidth); + + views::StyledLabel::RangeStyleInfo default_style; + default_style.custom_font = gfx::FontList("SF Pro Text, Normal 14px"); + contents_label->AddStyleRange(gfx::Range(0, offsets[0]), default_style); + contents_label->AddStyleRange( + gfx::Range(offsets[0] + contents_text_part_one.length(), offsets[1]), + default_style); + contents_label->AddStyleRange( + gfx::Range(offsets[1] + contents_text_part_two.length(), + contents_text.length()), + default_style); + + size_t offset; + const std::u16string learn_more_part_text = l10n_util::GetStringUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_LEARN_MORE_LABEL_PART); + const std::u16string contents_second_text = l10n_util::GetStringFUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_CONTENTS_SECOND_LABEL, + learn_more_part_text, &offset); + auto* contents_second_label = + AddChildView(std::make_unique()); + contents_second_label->SetText(contents_second_text); + contents_second_label->AddStyleRange(gfx::Range(0, offset), default_style); + + // Safe to use Unretained() here because this link is owned by this class. + views::StyledLabel::RangeStyleInfo learn_more_style = + views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating( + &GeolocationAccuracyHelperDialogView::OnLearnMoreClicked, + base::Unretained(this))); + learn_more_style.custom_font = gfx::FontList("SF Pro Text, Normal 14px"); + learn_more_style.override_color = leo::GetColor( + leo::Color::kColorTextInteractive, GetNativeTheme()->ShouldUseDarkColors() + ? leo::Theme::kDark + : leo::Theme::kLight); + + contents_second_label->AddStyleRange( + gfx::Range(offset, offset + learn_more_part_text.length()), + learn_more_style); + contents_second_label->SizeToFit(kMaxWidth); + + // Using Unretained() is safe as |checkbox| is owned by this class. + auto* checkbox = AddChildView(std::make_unique( + l10n_util::GetStringUTF16( + IDS_GEOLOCATION_ACCURACY_HELPER_DLG_DONT_SHOW_AGAIN_LABEL), + base::BindRepeating( + &GeolocationAccuracyHelperDialogView::OnCheckboxUpdated, + base::Unretained(this)))); + checkbox->SetFontList(gfx::FontList("SF Pro Text, Normal 14px")); + dont_show_again_checkbox_ = checkbox; +} + +void GeolocationAccuracyHelperDialogView::OnCheckboxUpdated() { + prefs_->SetBoolean(kShowGeolocationAccuracyHelperDialog, + !dont_show_again_checkbox_->GetChecked()); +} + +void GeolocationAccuracyHelperDialogView::OnAccept() { +#if BUILDFLAG(IS_WIN) + base::ThreadPool::PostTask( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::USER_BLOCKING, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, + base::BindOnce(&LaunchLocationServiceSettings)); +#endif +} + +void GeolocationAccuracyHelperDialogView::OnLearnMoreClicked() { + // Using active window is fine here as this dialog is tied with active tab. + if (auto* browser = chrome::FindBrowserWithActiveWindow()) { + chrome::AddSelectedTabWithURL(browser, GURL(kLearnMoreURL), + ui::PAGE_TRANSITION_AUTO_TOPLEVEL); + } +} + +BEGIN_METADATA(GeolocationAccuracyHelperDialogView, views::DialogDelegateView) +END_METADATA diff --git a/browser/ui/views/geolocation_accuracy_helper_dialog_view.h b/browser/ui/views/geolocation_accuracy_helper_dialog_view.h new file mode 100644 index 000000000000..9d6aa3cbe92a --- /dev/null +++ b/browser/ui/views/geolocation_accuracy_helper_dialog_view.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_VIEWS_GEOLOCATION_ACCURACY_HELPER_DIALOG_VIEW_H_ +#define BRAVE_BROWSER_UI_VIEWS_GEOLOCATION_ACCURACY_HELPER_DIALOG_VIEW_H_ + +#include "base/functional/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/raw_ref.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/window/dialog_delegate.h" + +class PrefService; + +namespace views { +class Checkbox; +} // namespace views + +class GeolocationAccuracyHelperDialogView : public views::DialogDelegateView { + public: + METADATA_HEADER(GeolocationAccuracyHelperDialogView); + + GeolocationAccuracyHelperDialogView(PrefService* prefs, + base::OnceClosure closing_callback); + GeolocationAccuracyHelperDialogView( + const GeolocationAccuracyHelperDialogView&) = delete; + GeolocationAccuracyHelperDialogView& operator=( + const GeolocationAccuracyHelperDialogView&) = delete; + ~GeolocationAccuracyHelperDialogView() override; + + private: + // views::DialogDelegateView overrides: + void AddedToWidget() override; + void OnWidgetInitialized() override; + + void SetupChildViews(); + void OnCheckboxUpdated(); + void OnAccept(); + void OnLearnMoreClicked(); + + const raw_ref prefs_; + raw_ptr dont_show_again_checkbox_ = nullptr; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_GEOLOCATION_ACCURACY_HELPER_DIALOG_VIEW_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index 20086d8b92c0..f17e594eccbe 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -1118,6 +1118,7 @@ test("brave_browser_tests") { "//brave/app/theme:brave_theme_resources_grit", "//brave/app/theme:brave_unscaled_resources_grit", "//brave/browser/sharing_hub:browser_tests", + "//brave/browser/ui/geolocation:browser_tests", "//brave/browser/ui/whats_new:browser_test", "//chrome/browser/apps/app_service:app_service", "//chrome/browser/apps/app_service:constants",