diff --git a/filenames.gni b/filenames.gni index 0d23c2baded68..961c82fef563b 100644 --- a/filenames.gni +++ b/filenames.gni @@ -518,6 +518,10 @@ filenames = { "shell/common/key_weak_map.h", "shell/common/keyboard_util.cc", "shell/common/keyboard_util.h", + "shell/common/language_util.h", + "shell/common/language_util_linux.cc", + "shell/common/language_util_mac.mm", + "shell/common/language_util_win.cc", "shell/common/mac/main_application_bundle.h", "shell/common/mac/main_application_bundle.mm", "shell/common/mouse_util.cc", diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index d8dbe640f86ef..7b474d16ab795 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -77,6 +77,8 @@ #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/gfx_converter.h" #include "shell/common/gin_helper/dictionary.h" +#include "shell/common/gin_helper/object_template_builder.h" +#include "shell/common/language_util.h" #include "shell/common/mouse_util.h" #include "shell/common/native_mate_converters/blink_converter.h" #include "shell/common/native_mate_converters/content_converter.h" @@ -94,9 +96,16 @@ #include "third_party/blink/public/common/page/page_zoom.h" #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h" #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" +<<<<<<< HEAD #include "third_party/blink/public/platform/web_cursor_info.h" #include "third_party/blink/public/platform/web_input_event.h" -#include "ui/display/screen.h" +======= +#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h" +#include "third_party/blink/public/mojom/renderer_preferences.mojom.h" +#include "ui/base/cursor/cursor.h" +#include "ui/base/mojom/cursor_type.mojom-shared.h" +>>>>>>> f176d2494... fix: respect system language preferences on Win/macOS (#23247) + #include "ui/display/screen.h" #include "ui/events/base_event_utils.h" #if BUILDFLAG(ENABLE_OSR) @@ -115,7 +124,6 @@ #endif #if defined(OS_LINUX) || defined(OS_WIN) -#include "third_party/blink/public/mojom/renderer_preferences.mojom.h" #include "ui/gfx/font_render_params.h" #endif @@ -123,194 +131,194 @@ #include "shell/browser/extensions/electron_extension_web_contents_observer.h" #endif -namespace mate { + namespace mate { #if BUILDFLAG(ENABLE_PRINTING) -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const printing::PrinterBasicInfo& val) { - gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate); - dict.Set("name", val.printer_name); - dict.Set("description", val.printer_description); - dict.Set("status", val.printer_status); - dict.Set("isDefault", val.is_default ? true : false); - dict.Set("options", val.options); - return dict.GetHandle(); - } -}; - -template <> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - printing::MarginType* out) { - std::string type; - if (ConvertFromV8(isolate, val, &type)) { - if (type == "default") { - *out = printing::DEFAULT_MARGINS; - return true; - } - if (type == "none") { - *out = printing::NO_MARGINS; - return true; - } - if (type == "printableArea") { - *out = printing::PRINTABLE_AREA_MARGINS; - return true; - } - if (type == "custom") { - *out = printing::CUSTOM_MARGINS; - return true; - } + template <> + struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const printing::PrinterBasicInfo& val) { + gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate); + dict.Set("name", val.printer_name); + dict.Set("description", val.printer_description); + dict.Set("status", val.printer_status); + dict.Set("isDefault", val.is_default ? true : false); + dict.Set("options", val.options); + return dict.GetHandle(); } - return false; - } -}; - -template <> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - printing::DuplexMode* out) { - std::string mode; - if (ConvertFromV8(isolate, val, &mode)) { - if (mode == "simplex") { - *out = printing::SIMPLEX; - return true; - } - if (mode == "longEdge") { - *out = printing::LONG_EDGE; - return true; + }; + + template <> + struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + printing::MarginType* out) { + std::string type; + if (ConvertFromV8(isolate, val, &type)) { + if (type == "default") { + *out = printing::DEFAULT_MARGINS; + return true; + } + if (type == "none") { + *out = printing::NO_MARGINS; + return true; + } + if (type == "printableArea") { + *out = printing::PRINTABLE_AREA_MARGINS; + return true; + } + if (type == "custom") { + *out = printing::CUSTOM_MARGINS; + return true; + } } - if (mode == "shortEdge") { - *out = printing::SHORT_EDGE; - return true; + return false; + } + }; + + template <> + struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + printing::DuplexMode* out) { + std::string mode; + if (ConvertFromV8(isolate, val, &mode)) { + if (mode == "simplex") { + *out = printing::SIMPLEX; + return true; + } + if (mode == "longEdge") { + *out = printing::LONG_EDGE; + return true; + } + if (mode == "shortEdge") { + *out = printing::SHORT_EDGE; + return true; + } } + return false; } - return false; - } -}; + }; #endif -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - WindowOpenDisposition val) { - std::string disposition = "other"; - switch (val) { - case WindowOpenDisposition::CURRENT_TAB: - disposition = "default"; - break; - case WindowOpenDisposition::NEW_FOREGROUND_TAB: - disposition = "foreground-tab"; - break; - case WindowOpenDisposition::NEW_BACKGROUND_TAB: - disposition = "background-tab"; - break; - case WindowOpenDisposition::NEW_POPUP: - case WindowOpenDisposition::NEW_WINDOW: - disposition = "new-window"; - break; - case WindowOpenDisposition::SAVE_TO_DISK: - disposition = "save-to-disk"; - break; - default: - break; + template <> + struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + WindowOpenDisposition val) { + std::string disposition = "other"; + switch (val) { + case WindowOpenDisposition::CURRENT_TAB: + disposition = "default"; + break; + case WindowOpenDisposition::NEW_FOREGROUND_TAB: + disposition = "foreground-tab"; + break; + case WindowOpenDisposition::NEW_BACKGROUND_TAB: + disposition = "background-tab"; + break; + case WindowOpenDisposition::NEW_POPUP: + case WindowOpenDisposition::NEW_WINDOW: + disposition = "new-window"; + break; + case WindowOpenDisposition::SAVE_TO_DISK: + disposition = "save-to-disk"; + break; + default: + break; + } + return mate::ConvertToV8(isolate, disposition); } - return mate::ConvertToV8(isolate, disposition); - } -}; - -template <> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - content::SavePageType* out) { - std::string save_type; - if (!ConvertFromV8(isolate, val, &save_type)) - return false; - save_type = base::ToLowerASCII(save_type); - if (save_type == "htmlonly") { - *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; - } else if (save_type == "htmlcomplete") { - *out = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; - } else if (save_type == "mhtml") { - *out = content::SAVE_PAGE_TYPE_AS_MHTML; - } else { - return false; + }; + + template <> + struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + content::SavePageType* out) { + std::string save_type; + if (!ConvertFromV8(isolate, val, &save_type)) + return false; + save_type = base::ToLowerASCII(save_type); + if (save_type == "htmlonly") { + *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; + } else if (save_type == "htmlcomplete") { + *out = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; + } else if (save_type == "mhtml") { + *out = content::SAVE_PAGE_TYPE_AS_MHTML; + } else { + return false; + } + return true; } - return true; - } -}; - -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - electron::api::WebContents::Type val) { - using Type = electron::api::WebContents::Type; - std::string type; - switch (val) { - case Type::BACKGROUND_PAGE: - type = "backgroundPage"; - break; - case Type::BROWSER_WINDOW: - type = "window"; - break; - case Type::BROWSER_VIEW: - type = "browserView"; - break; - case Type::REMOTE: - type = "remote"; - break; - case Type::WEB_VIEW: - type = "webview"; - break; - case Type::OFF_SCREEN: - type = "offscreen"; - break; - default: - break; + }; + + template <> + struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + electron::api::WebContents::Type val) { + using Type = electron::api::WebContents::Type; + std::string type; + switch (val) { + case Type::BACKGROUND_PAGE: + type = "backgroundPage"; + break; + case Type::BROWSER_WINDOW: + type = "window"; + break; + case Type::BROWSER_VIEW: + type = "browserView"; + break; + case Type::REMOTE: + type = "remote"; + break; + case Type::WEB_VIEW: + type = "webview"; + break; + case Type::OFF_SCREEN: + type = "offscreen"; + break; + default: + break; + } + return mate::ConvertToV8(isolate, type); } - return mate::ConvertToV8(isolate, type); - } - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - electron::api::WebContents::Type* out) { - using Type = electron::api::WebContents::Type; - std::string type; - if (!ConvertFromV8(isolate, val, &type)) - return false; - if (type == "backgroundPage") { - *out = Type::BACKGROUND_PAGE; - } else if (type == "browserView") { - *out = Type::BROWSER_VIEW; - } else if (type == "webview") { - *out = Type::WEB_VIEW; + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + electron::api::WebContents::Type* out) { + using Type = electron::api::WebContents::Type; + std::string type; + if (!ConvertFromV8(isolate, val, &type)) + return false; + if (type == "backgroundPage") { + *out = Type::BACKGROUND_PAGE; + } else if (type == "browserView") { + *out = Type::BROWSER_VIEW; + } else if (type == "webview") { + *out = Type::WEB_VIEW; #if BUILDFLAG(ENABLE_OSR) - } else if (type == "offscreen") { - *out = Type::OFF_SCREEN; + } else if (type == "offscreen") { + *out = Type::OFF_SCREEN; #endif - } else { - return false; + } else { + return false; + } + return true; } - return true; - } -}; - -template <> -struct Converter> { - static v8::Local ToV8( - v8::Isolate* isolate, - const scoped_refptr& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); - dict.Set("id", val->GetId()); - dict.Set("url", val->GetURL().spec()); - return dict.GetHandle(); - } -}; + }; + + template <> + struct Converter> { + static v8::Local ToV8( + v8::Isolate* isolate, + const scoped_refptr& val) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + dict.Set("id", val->GetId()); + dict.Set("url", val->GetURL().spec()); + return dict.GetHandle(); + } + }; } // namespace mate @@ -520,7 +528,22 @@ void WebContents::InitWithSessionAndOptions( managed_web_contents()->GetView()->SetDelegate(this); auto* prefs = web_contents()->GetMutableRendererPrefs(); - prefs->accept_languages = g_browser_process->GetApplicationLocale(); + + // Collect preferred languages from OS and browser process. accept_languages + // effects HTTP header, navigator.languages, and CJK fallback font selection. + // + // Note that an application locale set to the browser process might be + // different with the one set to the preference list. + // (e.g. overridden with --lang) + std::string accept_languages = + g_browser_process->GetApplicationLocale() + ","; + for (auto const& language : electron::GetPreferredLanguages()) { + if (language == g_browser_process->GetApplicationLocale()) + continue; + accept_languages += language + ","; + } + accept_languages.pop_back(); + prefs->accept_languages = accept_languages; #if defined(OS_LINUX) || defined(OS_WIN) // Update font settings. diff --git a/shell/common/language_util.h b/shell/common/language_util.h new file mode 100644 index 0000000000000..3e35073c142cc --- /dev/null +++ b/shell/common/language_util.h @@ -0,0 +1,26 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef SHELL_COMMON_LANGUAGE_UTIL_H_ +#define SHELL_COMMON_LANGUAGE_UTIL_H_ + +#include +#include + +#include "base/strings/string16.h" + +namespace electron { + +// Return a list of user preferred languages from OS. The list doesn't include +// overrides from command line arguments. +std::vector GetPreferredLanguages(); + +#if defined(OS_WIN) +bool GetPreferredLanguagesUsingGlobalization( + std::vector* languages); +#endif + +} // namespace electron + +#endif // SHELL_COMMON_LANGUAGE_UTIL_H_ diff --git a/shell/common/language_util_linux.cc b/shell/common/language_util_linux.cc new file mode 100644 index 0000000000000..76bc557741ba0 --- /dev/null +++ b/shell/common/language_util_linux.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/common/language_util.h" + +#include "ui/base/l10n/l10n_util.h" + +namespace electron { + +std::vector GetPreferredLanguages() { + // Return empty as there's no API to use. You may be able to use + // GetApplicationLocale() of a browser process. + return std::vector{}; +} + +} // namespace electron diff --git a/shell/common/language_util_mac.mm b/shell/common/language_util_mac.mm new file mode 100644 index 0000000000000..205d7087bf1f8 --- /dev/null +++ b/shell/common/language_util_mac.mm @@ -0,0 +1,25 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/common/language_util.h" + +#import +#include +#include + +#include "base/strings/sys_string_conversions.h" + +namespace electron { + +std::vector GetPreferredLanguages() { + __block std::vector languages; + [[NSLocale preferredLanguages] + enumerateObjectsUsingBlock:^(NSString* language, NSUInteger i, + BOOL* stop) { + languages.push_back(base::SysNSStringToUTF8(language)); + }]; + return languages; +} + +} // namespace electron diff --git a/shell/common/language_util_win.cc b/shell/common/language_util_win.cc new file mode 100644 index 0000000000000..d6717b31aec21 --- /dev/null +++ b/shell/common/language_util_win.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/common/language_util.h" + +#include +#include +#include + +#include "base/strings/sys_string_conversions.h" +#include "base/win/core_winrt_util.h" +#include "base/win/i18n.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" + +namespace electron { + +std::vector GetPreferredLanguages() { + std::vector languages16; + + // Attempt to use API available on Windows 10 or later, which + // returns the full list of language preferences. + if (!GetPreferredLanguagesUsingGlobalization(&languages16)) { + base::win::i18n::GetThreadPreferredUILanguageList(&languages16); + } + + std::vector languages; + for (const auto& language : languages16) { + languages.push_back(base::SysWideToUTF8(language)); + } + return languages; +} + +bool GetPreferredLanguagesUsingGlobalization( + std::vector* languages) { + if (base::win::GetVersion() < base::win::Version::WIN10) + return false; + if (!base::win::ResolveCoreWinRTDelayload() || + !base::win::ScopedHString::ResolveCoreWinRTStringDelayload()) + return false; + + base::win::ScopedHString guid = base::win::ScopedHString::Create( + RuntimeClass_Windows_System_UserProfile_GlobalizationPreferences); + Microsoft::WRL::ComPtr< + ABI::Windows::System::UserProfile::IGlobalizationPreferencesStatics> + prefs; + + HRESULT hr = + base::win::RoGetActivationFactory(guid.get(), IID_PPV_ARGS(&prefs)); + if (FAILED(hr)) + return false; + + ABI::Windows::Foundation::Collections::IVectorView* langs; + hr = prefs->get_Languages(&langs); + if (FAILED(hr)) + return false; + + unsigned size; + hr = langs->get_Size(&size); + if (FAILED(hr)) + return false; + + for (unsigned i = 0; i < size; ++i) { + HSTRING hstr; + hr = langs->GetAt(i, &hstr); + if (SUCCEEDED(hr)) { + base::WStringPiece str = base::win::ScopedHString(hstr).Get(); + languages->emplace_back(str.data(), str.size()); + } + } + + return true; +} + +} // namespace electron diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index b30298774578c..d6860db04e780 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -351,7 +351,9 @@ describe('chromium features', () => { const appLocale = app.getLocale() const w = new BrowserWindow({ show: false }) await w.loadURL('about:blank') - const languages = await w.webContents.executeJavaScript(`navigator.languages`) + const languages = await w.webContents.executeJavaScript('navigator.languages') + expect(languages.length).to.be.greaterThan(0); + expect(languages).to.contain(appLocale); expect(languages).to.deep.equal([appLocale]) }) })