diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 95aa52ce6f6..b8c2eba7ece 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -1299,6 +1299,32 @@ Or change later at $2brave://settings/ext Brave Player + + + + $1http://foo.com wants to open + + + An embedded page on $1http://foo.com wants to open + + + This page wants to open + + + An embedded page on this page wants to open + + + $1http://foo.com wants to save + + + An embedded page on $1http://foo.com wants to save + + + This page wants to save + + + An embedded page on this page wants to save + diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 167a5dc91cf..bf181289619 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -150,6 +150,8 @@ source_set("ui") { "brave_browser_content_setting_bubble_model_delegate.h", "brave_browser_window.cc", "brave_browser_window.h", + "brave_file_select_utils.cc", + "brave_file_select_utils.h", "brave_icon_with_badge_image_source.cc", "brave_icon_with_badge_image_source.h", "brave_layout_constants.cc", @@ -1335,9 +1337,14 @@ source_set("ui") { source_set("unit_tests") { if (!is_android) { testonly = true - sources = [ "brave_layout_constants_unittest.cc" ] + sources = [ + "brave_file_select_utils_unittest.cc", + "brave_layout_constants_unittest.cc", + ] deps = [ + "//brave/components/l10n/common:test_support", "//chrome/browser/ui", + "//components/javascript_dialogs", "//testing/gtest", "//ui/base:test_support", "//ui/gfx", diff --git a/browser/ui/brave_browser.cc b/browser/ui/brave_browser.cc index c1b6007495b..e9e51b6203c 100644 --- a/browser/ui/brave_browser.cc +++ b/browser/ui/brave_browser.cc @@ -14,6 +14,7 @@ #include "base/functional/callback_helpers.h" #include "brave/browser/brave_browser_features.h" #include "brave/browser/ui/brave_browser_window.h" +#include "brave/browser/ui/brave_file_select_utils.h" #include "brave/browser/ui/tabs/brave_tab_prefs.h" #include "brave/browser/ui/tabs/features.h" #include "brave/components/constants/pref_names.h" @@ -27,7 +28,9 @@ #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/common/webui_url_constants.h" #include "components/prefs/pref_service.h" +#include "content/public/browser/file_select_listener.h" #include "content/public/common/url_constants.h" +#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" #include "url/gurl.h" #if defined(TOOLKIT_VIEWS) @@ -128,6 +131,33 @@ void BraveBrowser::TabStripEmpty() { true, std::nullopt)); } +void BraveBrowser::RunFileChooser( + content::RenderFrameHost* render_frame_host, + scoped_refptr listener, + const blink::mojom::FileChooserParams& params) { +#if BUILDFLAG(IS_ANDROID) + Browser::RunFileChooser(render_frame_host, listener, params); +#else + auto new_params = params.Clone(); + if (new_params->title.empty()) { + // Fill title of file chooser with origin of the frame. + + // Note that save mode param is for PPAPI. 'Save As...' or downloading + // something doesn't reach here. They show 'select file dialog' from + // DownloadFilePicker::DownloadFilePicker directly. + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/choosers/file_chooser.mojom;l=27;drc=047c7dc4ee1ce908d7fea38ca063fa2f80f92c77 + + new_params->title = brave::GetFileSelectTitle( + content::WebContents::FromRenderFrameHost(render_frame_host), + render_frame_host->GetLastCommittedOrigin(), + params.mode == blink::mojom::FileChooserParams::Mode::kSave + ? brave::FileSelectTitleType::kSave + : brave::FileSelectTitleType::kOpen); + } + Browser::RunFileChooser(render_frame_host, listener, *new_params); +#endif +} + bool BraveBrowser::ShouldDisplayFavicon( content::WebContents* web_contents) const { // Override to not show favicon for NTP in tab. diff --git a/browser/ui/brave_browser.h b/browser/ui/brave_browser.h index 6ea5acb4cd4..f4e02d2b1e3 100644 --- a/browser/ui/brave_browser.h +++ b/browser/ui/brave_browser.h @@ -54,6 +54,10 @@ class BraveBrowser : public Browser { void OnTabClosing(content::WebContents* contents) override; void TabStripEmpty() override; + void RunFileChooser(content::RenderFrameHost* render_frame_host, + scoped_refptr listener, + const blink::mojom::FileChooserParams& params) override; + // Returns true when we should ask browser closing to users before handling // any warning/onbeforeunload handlers. bool ShouldAskForBrowserClosingBeforeHandlers(); diff --git a/browser/ui/brave_file_select_utils.cc b/browser/ui/brave_file_select_utils.cc new file mode 100644 index 00000000000..1bbb60cefed --- /dev/null +++ b/browser/ui/brave_file_select_utils.cc @@ -0,0 +1,151 @@ +/* Copyright (c) 2024 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/brave_file_select_utils.h" + +#include + +#include "base/i18n/rtl.h" +#include "base/no_destructor.h" +#include "brave/grit/brave_generated_resources.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/url_identity.h" +#include "components/strings/grit/components_strings.h" +#include "components/url_formatter/elide_url.h" +#include "content/public/browser/web_contents.h" +#include "ui/base/l10n/l10n_util.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace brave { + +namespace { + +// If an origin is opaque but has a precursor, then returns the precursor +// origin. If the origin is not opaque, returns it unchanged. Unwrapping origins +// allows the dialog code to provide the user with a clearer picture of which +// page is actually showing the dialog. +url::Origin UnwrapOriginIfOpaque(const url::Origin& origin) { + if (!origin.opaque()) { + return origin; + } + + const url::SchemeHostPort& precursor = + origin.GetTupleOrPrecursorTupleIfOpaque(); + if (!precursor.IsValid()) { + return origin; + } + + return url::Origin::CreateFromNormalizedTuple( + precursor.scheme(), precursor.host(), precursor.port()); +} + +} // namespace + +std::u16string GetFileSelectTitle(content::WebContents* web_contents, + const url::Origin& alerting_frame_origin, + FileSelectTitleType file_select_type) { + // This implementation partially mirrors + // ChromeAppModalDialogManagerDelegate::GetTitle(). + // TODO(sko) It's hard to test this behavior is in sync at this moment. Even + // upstream tests aren't covering this. Need to figure out how we can test + // extension and isolated web app case. + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + + UrlIdentity url_identity = UrlIdentity::CreateFromUrl( + profile, alerting_frame_origin.GetURL(), + /*allowed_types*/ + {UrlIdentity::Type::kDefault, UrlIdentity::Type::kFile, + UrlIdentity::Type::kIsolatedWebApp, UrlIdentity::Type::kChromeExtension}, + /*default_options*/ {.default_options = {}}); + + if (url_identity.type == UrlIdentity::Type::kChromeExtension) { + return url_identity.name; + } + + if (url_identity.type == UrlIdentity::Type::kIsolatedWebApp) { + return url_identity.name; + } + + const auto main_frame_origin = + web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(); + return GetSiteFrameTitleForFileSelect( + GetSiteFrameTitleType(main_frame_origin, alerting_frame_origin), + alerting_frame_origin, file_select_type); +} + +std::u16string GetSiteFrameTitleForFileSelect( + SiteFrameTitleType frame_type, + const url::Origin& alerting_frame_origin, + FileSelectTitleType file_select_type) { + constexpr std::array< + std::array(SiteFrameTitleType::kSize)>, + static_cast(FileSelectTitleType::kSize)> + kResourceIDs = { + {/*FileSelectTitleType::kOpen,*/ + { + IDS_BRAVE_FILE_SELECT_OPEN_TITLE, // brave::SiteFrameTitleType::kStandardSameOrigin + IDS_BRAVE_FILE_SELECT_OPEN_TITLE_IFRAME, // brave::SiteFrameTitleType::kStandardDifferentOrigin + IDS_BRAVE_FILE_SELECT_OPEN_TITLE_NONSTANDARD_URL, // brave::SiteFrameTitleType::kNonStandardSameOrigin + IDS_BRAVE_FILE_SELECT_OPEN_TITLE_NONSTANDARD_URL_IFRAME // brave::SiteFrameTitleType::kNonStandardDifferentOrigin + }, + /*FileSelectTitleType::kSave,*/ + { + IDS_BRAVE_FILE_SELECT_SAVE_TITLE, // brave::SiteFrameTitleType::kStandardSameOrigin + IDS_BRAVE_FILE_SELECT_SAVE_TITLE_IFRAME, // brave::SiteFrameTitleType::kStandardDifferentOrigin + IDS_BRAVE_FILE_SELECT_SAVE_TITLE_NONSTANDARD_URL, // brave::SiteFrameTitleType::kNonStandardSameOrigin + IDS_BRAVE_FILE_SELECT_SAVE_TITLE_NONSTANDARD_URL_IFRAME // brave::SiteFrameTitleType::kNonStandardDifferentOrigin + }, + /*FileSelectTitleType::kChromiumDefault*/ + { + IDS_JAVASCRIPT_MESSAGEBOX_TITLE, // brave::SiteFrameTitleType::kStandardSameOrigin + IDS_JAVASCRIPT_MESSAGEBOX_TITLE_IFRAME, // brave::SiteFrameTitleType::kStandardDifferentOrigin, + IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL, // brave::SiteFrameTitleType::kNonStandardSameOrigin + IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME, // brave::SiteFrameTitleType::kNonStandardDifferentOrigin, + }}}; + + if (frame_type == SiteFrameTitleType::kStandardSameOrigin || + frame_type == SiteFrameTitleType::kStandardDifferentOrigin) { + std::u16string origin_string = + url_formatter::FormatOriginForSecurityDisplay( + UnwrapOriginIfOpaque(alerting_frame_origin), + url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS); + return l10n_util::GetStringFUTF16( + kResourceIDs[static_cast(file_select_type)] + [static_cast(frame_type)], + base::i18n::GetDisplayStringInLTRDirectionality(origin_string)); + } + + return l10n_util::GetStringUTF16(kResourceIDs[static_cast( + file_select_type)][static_cast(frame_type)]); +} + +SiteFrameTitleType GetSiteFrameTitleType( + const url::Origin& main_frame_origin, + const url::Origin& alerting_frame_origin) { + // This implementation mirrors `AppModalDialogManager::GetSiteFrameTitle()`. + // We have a test to check if the two implementations are in sync. + // - BraveFileSelectHelperUnitTest.GetSiteFrameTitleType_InSyncWithUpstream. + const url::Origin unwrapped_main_frame_origin = + UnwrapOriginIfOpaque(main_frame_origin); + const url::Origin unwrapped_alerting_frame_origin = + UnwrapOriginIfOpaque(alerting_frame_origin); + + const bool is_same_origin_as_main_frame = + unwrapped_alerting_frame_origin.IsSameOriginWith( + unwrapped_main_frame_origin); + if (unwrapped_alerting_frame_origin.GetURL().IsStandard() && + !unwrapped_alerting_frame_origin.GetURL().SchemeIsFile()) { + return is_same_origin_as_main_frame + ? SiteFrameTitleType::kStandardSameOrigin + : SiteFrameTitleType::kStandardDifferentOrigin; + } + return is_same_origin_as_main_frame + ? SiteFrameTitleType::kNonStandardSameOrigin + : SiteFrameTitleType::kNonStandardDifferentOrigin; +} + +} // namespace brave diff --git a/browser/ui/brave_file_select_utils.h b/browser/ui/brave_file_select_utils.h new file mode 100644 index 00000000000..9147c836f12 --- /dev/null +++ b/browser/ui/brave_file_select_utils.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2024 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_BRAVE_FILE_SELECT_UTILS_H_ +#define BRAVE_BROWSER_UI_BRAVE_FILE_SELECT_UTILS_H_ + +#include + +#include "base/containers/flat_map.h" + +namespace content { +class WebContents; +} // namespace content + +namespace url { +class Origin; +} // namespace url + +namespace brave { + +// This enum values are used to determine the title of the file select dialog. +// Basically it mirrors std::u16string +// AppModalDialogManager::GetSiteFrameTitle() implementation. +enum class SiteFrameTitleType { + kStandardSameOrigin, // alerting frame has http(s) scheme and has the same + // origin with main frame + kStandardDifferentOrigin, // alerting frame http(s) scheme and has a + // different origin with main frame + kNonStandardSameOrigin, // alerting frame has other schemes (e.g. file, + // data, javascript) and has the same origin with + // main frame + kNonStandardDifferentOrigin, // alerting frame has other schemes (e.g. file, + // data, javascript) and has a different origin + // with main frame + kSize +}; + +enum class FileSelectTitleType { + kOpen, + kSave, + kChromiumDefault, // used for comparing with the default title of the file + // select dialog in Chromium + kSize +}; + +std::u16string GetFileSelectTitle(content::WebContents* contents, + const url::Origin& alerting_frame_origin, + FileSelectTitleType file_select_type); + +std::u16string GetSiteFrameTitleForFileSelect( + SiteFrameTitleType frame_type, + const url::Origin& alerting_frame_origin, + FileSelectTitleType select_type); + +SiteFrameTitleType GetSiteFrameTitleType( + const url::Origin& main_frame_origin, + const url::Origin& alerting_frame_origin); + +} // namespace brave + +#endif // BRAVE_BROWSER_UI_BRAVE_FILE_SELECT_UTILS_H_ diff --git a/browser/ui/brave_file_select_utils_unittest.cc b/browser/ui/brave_file_select_utils_unittest.cc new file mode 100644 index 00000000000..5738960b4e2 --- /dev/null +++ b/browser/ui/brave_file_select_utils_unittest.cc @@ -0,0 +1,309 @@ +/* Copyright (c) 2024 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/brave_file_select_utils.h" + +#include "base/strings/utf_string_conversions.h" +#include "brave/components/l10n/common/test/scoped_default_locale.h" +#include "components/javascript_dialogs/app_modal_dialog_manager.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/origin.h" + +TEST(BraveFileSelectUtilsUnitTest, GetSiteFrameTitle_InSyncWithUpstream) { + constexpr struct Case { + // The name of the test case. + const char* case_name; + + // The URL of the main frame of the page. + const char* main_frame_url; + + // Whether the main frame is alerting. + bool is_main_frame; + + // If `is_main_frame` is false, the URL of the alerting frame of the page. + const char* alerting_frame_url; + } kCases[] = { + // Standard main frame alert. + {"standard", "http://foo.com/", true, ""}, + + // Subframe alert from the same origin. + {"subframe same origin", "http://foo.com/1", false, "http://foo.com/2"}, + // Subframe alert from a different origin. + {"subframe different origin", "http://foo.com/", false, + "http://bar.com/"}, + + // file: + // - main frame: + {"file main frame", "file:///path/to/page.html", true, ""}, + // - subframe: + {"file subframe", "http://foo.com/", false, "file:///path/to/page.html"}, + + // data: + // /!\ NOTE that this is for data URLs entered directly in the omnibox. + // For pages that generate frames with data URLs, see the browsertest. + // - main frame: + {"data main frame", "data:blahblah", true, ""}, + // - subframe: + {"data subframe", "http://foo.com/", false, "data:blahblah"}, + + // javascript: + // /!\ NOTE that this is for javascript URLs entered directly in the + // omnibox. For pages that generate frames with javascript URLs, see the + // browsertest. + // - main frame: + {"javascript main frame", "javascript:abc", true, ""}, + // - subframe: + {"javascript subframe", "http://foo.com/", false, "javascript:abc"}, + + // about: + // /!\ NOTE that this is for about:blank URLs entered directly in the + // omnibox. For pages that generate frames with about:blank URLs, see the + // browsertest. + // - main frame: + {"about main frame", "about:blank", true, ""}, + // - subframe: + {"about subframe", "http://foo.com/", false, "about:blank"}, + + // blob: + // - main frame: + {"blob main frame", + "blob:http://foo.com/66666666-6666-6666-6666-666666666666", true, ""}, + // - subframe: + {"blob subframe", "http://bar.com/", false, + "blob:http://foo.com/66666666-6666-6666-6666-666666666666"}, + + // filesystem: + // - main frame: + {"filesystem main frame", "filesystem:http://foo.com/bar.html", true, ""}, + // - subframe: + {"filesystem subframe", "http://bar.com/", false, + "filesystem:http://foo.com/bar.html"}, + }; + // Checks if our implementation is in sync with upstream. + + for (const auto& test_case : kCases) { + SCOPED_TRACE(test_case.case_name); + + url::Origin main_frame_origin = + url::Origin::Create(GURL(test_case.main_frame_url)); + url::Origin alerting_frame_origin = + test_case.is_main_frame + ? main_frame_origin + : url::Origin::Create(GURL(test_case.alerting_frame_url)); + EXPECT_EQ(javascript_dialogs::AppModalDialogManager::GetSiteFrameTitle( + main_frame_origin, alerting_frame_origin), + brave::GetSiteFrameTitleForFileSelect( + brave::GetSiteFrameTitleType(main_frame_origin, + alerting_frame_origin), + alerting_frame_origin, + brave::FileSelectTitleType::kChromiumDefault)); + } +} + +TEST(BraveFileSelectUtilsUnitTest, GetSiteFrameTitleForFileSelect_Open) { + constexpr struct Case { + // The name of the test case. + const char* case_name; + + // The URL of the main frame of the page. + const char* main_frame_url; + + // Whether the main frame is alerting. + bool is_main_frame; + + // If `is_main_frame` is false, the URL of the alerting frame of the page. + const char* alerting_frame_url; + + // The expected title for the alert. + const char* expected; + } kCases[] = { + // Standard main frame alert. + {"standard", "http://foo.com/", true, "", "foo.com wants to open"}, + + // Subframe alert from the same origin. + {"subframe same origin", "http://foo.com/1", false, "http://foo.com/2", + "foo.com wants to open"}, + // Subframe alert from a different origin. + {"subframe different origin", "http://foo.com/", false, "http://bar.com/", + "An embedded page on bar.com wants to open"}, + + // file: + // - main frame: + {"file main frame", "file:///path/to/page.html", true, "", + "This page wants to open"}, + // - subframe: + {"file subframe", "http://foo.com/", false, "file:///path/to/page.html", + "An embedded page on this page wants to open"}, + + // data: + // /!\ NOTE that this is for data URLs entered directly in the omnibox. + // For pages that generate frames with data URLs, see the browsertest. + // - main frame: + {"data main frame", "data:blahblah", true, "", "This page wants to open"}, + // - subframe: + {"data subframe", "http://foo.com/", false, "data:blahblah", + "An embedded page on this page wants to open"}, + + // javascript: + // /!\ NOTE that this is for javascript URLs entered directly in the + // omnibox. For pages that generate frames with javascript URLs, see the + // browsertest. + // - main frame: + {"javascript main frame", "javascript:abc", true, "", + "This page wants to open"}, + // - subframe: + {"javascript subframe", "http://foo.com/", false, "javascript:abc", + "An embedded page on this page wants to open"}, + + // about: + // /!\ NOTE that this is for about:blank URLs entered directly in the + // omnibox. For pages that generate frames with about:blank URLs, see the + // browsertest. + // - main frame: + {"about main frame", "about:blank", true, "", "This page wants to open"}, + // - subframe: + {"about subframe", "http://foo.com/", false, "about:blank", + "An embedded page on this page wants to open"}, + + // blob: + // - main frame: + {"blob main frame", + "blob:http://foo.com/66666666-6666-6666-6666-666666666666", true, "", + "foo.com wants to open"}, + // - subframe: + {"blob subframe", "http://bar.com/", false, + "blob:http://foo.com/66666666-6666-6666-6666-666666666666", + "An embedded page on foo.com wants to open"}, + + // filesystem: + // - main frame: + {"filesystem main frame", "filesystem:http://foo.com/bar.html", true, "", + "foo.com wants to open"}, + // - subframe: + {"filesystem subframe", "http://bar.com/", false, + "filesystem:http://foo.com/bar.html", + "An embedded page on foo.com wants to open"}, + }; + + brave_l10n::test::ScopedDefaultLocale scoped_locale("en-US"); + + for (const auto& test_case : kCases) { + SCOPED_TRACE(test_case.case_name); + url::Origin main_frame_origin = + url::Origin::Create(GURL(test_case.main_frame_url)); + url::Origin alerting_frame_origin = + test_case.is_main_frame + ? main_frame_origin + : url::Origin::Create(GURL(test_case.alerting_frame_url)); + EXPECT_EQ(base::UTF8ToUTF16(test_case.expected), + brave::GetSiteFrameTitleForFileSelect( + brave::GetSiteFrameTitleType(main_frame_origin, + alerting_frame_origin), + alerting_frame_origin, brave::FileSelectTitleType::kOpen)); + } +} + +TEST(BraveFileSelectUtilsUnitTest, GetSiteFrameTitleForFileSelect_Save) { + constexpr struct Case { + // The name of the test case. + const char* case_name; + + // The URL of the main frame of the page. + const char* main_frame_url; + + // Whether the main frame is alerting. + bool is_main_frame; + + // If `is_main_frame` is false, the URL of the alerting frame of the page. + const char* alerting_frame_url; + + // The expected title for the alert. + const char* expected; + } kCases[] = { + // Standard main frame alert. + {"standard", "http://foo.com/", true, "", "foo.com wants to save"}, + + // Subframe alert from the same origin. + {"subframe same origin", "http://foo.com/1", false, "http://foo.com/2", + "foo.com wants to save"}, + // Subframe alert from a different origin. + {"subframe different origin", "http://foo.com/", false, "http://bar.com/", + "An embedded page on bar.com wants to save"}, + + // file: + // - main frame: + {"file main frame", "file:///path/to/page.html", true, "", + "This page wants to save"}, + // - subframe: + {"file subframe", "http://foo.com/", false, "file:///path/to/page.html", + "An embedded page on this page wants to save"}, + + // data: + // /!\ NOTE that this is for data URLs entered directly in the omnibox. + // For pages that generate frames with data URLs, see the browsertest. + // - main frame: + {"data main frame", "data:blahblah", true, "", "This page wants to save"}, + // - subframe: + {"data subframe", "http://foo.com/", false, "data:blahblah", + "An embedded page on this page wants to save"}, + + // javascript: + // /!\ NOTE that this is for javascript URLs entered directly in the + // omnibox. For pages that generate frames with javascript URLs, see the + // browsertest. + // - main frame: + {"javascript main frame", "javascript:abc", true, "", + "This page wants to save"}, + // - subframe: + {"javascript subframe", "http://foo.com/", false, "javascript:abc", + "An embedded page on this page wants to save"}, + + // about: + // /!\ NOTE that this is for about:blank URLs entered directly in the + // omnibox. For pages that generate frames with about:blank URLs, see the + // browsertest. + // - main frame: + {"about main frame", "about:blank", true, "", "This page wants to save"}, + // - subframe: + {"about subframe", "http://foo.com/", false, "about:blank", + "An embedded page on this page wants to save"}, + + // blob: + // - main frame: + {"blob main frame", + "blob:http://foo.com/66666666-6666-6666-6666-666666666666", true, "", + "foo.com wants to save"}, + // - subframe: + {"blob subframe", "http://bar.com/", false, + "blob:http://foo.com/66666666-6666-6666-6666-666666666666", + "An embedded page on foo.com wants to save"}, + + // filesystem: + // - main frame: + {"filesystem main frame", "filesystem:http://foo.com/bar.html", true, "", + "foo.com wants to save"}, + // - subframe: + {"filesystem subframe", "http://bar.com/", false, + "filesystem:http://foo.com/bar.html", + "An embedded page on foo.com wants to save"}, + }; + + brave_l10n::test::ScopedDefaultLocale scoped_locale("en-US"); + + for (const auto& test_case : kCases) { + SCOPED_TRACE(test_case.case_name); + url::Origin main_frame_origin = + url::Origin::Create(GURL(test_case.main_frame_url)); + url::Origin alerting_frame_origin = + test_case.is_main_frame + ? main_frame_origin + : url::Origin::Create(GURL(test_case.alerting_frame_url)); + EXPECT_EQ(base::UTF8ToUTF16(test_case.expected), + brave::GetSiteFrameTitleForFileSelect( + brave::GetSiteFrameTitleType(main_frame_origin, + alerting_frame_origin), + alerting_frame_origin, brave::FileSelectTitleType::kSave)); + } +} diff --git a/chromium_src/chrome/browser/download/download_file_picker.cc b/chromium_src/chrome/browser/download/download_file_picker.cc new file mode 100644 index 00000000000..9b0c7529335 --- /dev/null +++ b/chromium_src/chrome/browser/download/download_file_picker.cc @@ -0,0 +1,42 @@ +/* Copyright (c) 2024 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 "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" +#include "ui/shell_dialogs/select_file_dialog.h" + +#if !BUILDFLAG(IS_ANDROID) +#include "brave/browser/ui/brave_file_select_utils.h" +#endif + +namespace { + +std::u16string GetTitle(content::RenderFrameHost* render_frame_host, + const std::u16string& original_title) { +#if BUILDFLAG(IS_ANDROID) + return original_title; +#else + return brave::GetFileSelectTitle( + content::WebContents::FromRenderFrameHost(render_frame_host), + render_frame_host->GetLastCommittedOrigin(), + brave::FileSelectTitleType::kSave); +#endif +} + +} // namespace + +// Override title of the file select dialog for downloads. +#define SelectFile(type, title, default_path, file_types, file_type_index, \ + default_extension, owning_window, params, caller) \ + SelectFile(type, GetTitle(render_frame_host, title), default_path, \ + file_types, file_type_index, default_extension, owning_window, \ + params, caller) + +#include "src/chrome/browser/download/download_file_picker.cc" + +#undef SelectFile diff --git a/chromium_src/ui/shell_dialogs/execute_select_file_win.cc b/chromium_src/ui/shell_dialogs/execute_select_file_win.cc new file mode 100644 index 00000000000..b398d40d4f1 --- /dev/null +++ b/chromium_src/ui/shell_dialogs/execute_select_file_win.cc @@ -0,0 +1,31 @@ +/* Copyright (c) 2024 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/. */ + +// Can be removed once +// https://chromium-review.googlesource.com/c/chromium/src/+/5711152 arrives in +// Brave. +#define BRAVE_EXECUTE_SELECT_SINGLE_FILE \ + return RunOpenFileDialog(owner, title, std::u16string(), default_path, \ + filter, 0, filter_index, paths); + +// Can be removed once +// https://chromium-review.googlesource.com/c/chromium/src/+/5711152 arrives in +// Brave. +#define BRAVE_EXECUTE_SELECT_MULTIPLE_FILE \ + return RunOpenFileDialog(owner, title, std::u16string(), default_path, \ + filter, dialog_options, filter_index, paths); + +// Can be removed once +// https://chromium-review.googlesource.com/c/chromium/src/+/5711152 arrives in +// Brave. +#define BRAVE_EXECUTE_SAVE_FILE \ + return RunSaveFileDialog(owner, title, default_path, filter, dialog_options, \ + def_ext, filter_index, path); + +#include "src/ui/shell_dialogs/execute_select_file_win.cc" + +#undef BRAVE_EXECUTE_SAVE_FILE +#undef BRAVE_EXECUTE_SELECT_MULTIPLE_FILE +#undef BRAVE_EXECUTE_SELECT_SINGLE_FILE diff --git a/patches/ui-shell_dialogs-execute_select_file_win.cc.patch b/patches/ui-shell_dialogs-execute_select_file_win.cc.patch new file mode 100644 index 00000000000..f0f1946f9ed --- /dev/null +++ b/patches/ui-shell_dialogs-execute_select_file_win.cc.patch @@ -0,0 +1,44 @@ +diff --git a/ui/shell_dialogs/execute_select_file_win.cc b/ui/shell_dialogs/execute_select_file_win.cc +index 5ef377bcbd69f2f2160aeed6ec4aa9fe3f20275c..ef56ddbe38b42410d7d6571796e025f3059c9c1a 100644 +--- a/ui/shell_dialogs/execute_select_file_win.cc ++++ b/ui/shell_dialogs/execute_select_file_win.cc +@@ -362,6 +362,7 @@ bool ExecuteSelectSingleFile(HWND owner, + std::vector* paths) { + // Note: The title is not passed down for historical reasons. + // TODO(pmonette): Figure out if it's a worthwhile improvement. ++ BRAVE_EXECUTE_SELECT_SINGLE_FILE + return RunOpenFileDialog(owner, std::u16string(), std::u16string(), + default_path, filter, 0, filter_index, paths); + } +@@ -376,12 +377,14 @@ bool ExecuteSelectMultipleFile(HWND owner, + + // Note: The title is not passed down for historical reasons. + // TODO(pmonette): Figure out if it's a worthwhile improvement. ++ BRAVE_EXECUTE_SELECT_MULTIPLE_FILE + return RunOpenFileDialog(owner, std::u16string(), std::u16string(), + default_path, filter, dialog_options, filter_index, + paths); + } + + bool ExecuteSaveFile(HWND owner, ++ const std::u16string& title, + const base::FilePath& default_path, + const std::vector& filter, + const std::wstring& def_ext, +@@ -396,6 +399,7 @@ bool ExecuteSaveFile(HWND owner, + + // Note: The title is not passed down for historical reasons. + // TODO(pmonette): Figure out if it's a worthwhile improvement. ++ BRAVE_EXECUTE_SAVE_FILE + return RunSaveFileDialog(owner, std::u16string(), default_path, filter, + dialog_options, def_ext, filter_index, path); + } +@@ -421,7 +425,7 @@ void ExecuteSelectFile( + break; + case SelectFileDialog::SELECT_SAVEAS_FILE: { + base::FilePath path; +- if (ExecuteSaveFile(owner, default_path, filter, default_extension, ++ if (ExecuteSaveFile(owner, title, default_path, filter, default_extension, + &file_type_index, &path)) { + paths.push_back(std::move(path)); + }