From 343453b85b4ac5823fa8c32259a8694e1dafdd6f Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Tue, 5 May 2020 02:49:29 +0900 Subject: [PATCH] fix: respect system language preferences on Win/macOS (#23247) This commit fixes https://github.com/electron/electron/issues/18829 Previously the full preferences set to OS was not given to Chromium. Also, this commit improves fallback font selection for CJK text. Chromium uses browser languages to determine fallback fonts on Windows, especially kanji/han characters in CJK. For instance, when user sets 'en-US, ja-JP' to Accept-Language, while Chromium chooses Japanese font for kanji text, but Electron chooses Chinese font. This is because only the first language was given to Accept-Language on Electron. This patch is based on https://github.com/electron/electron/pull/15532 Co-authored-by: Nitish Sakhawalkar Co-authored-by: Kasumi Hanazuki Co-authored-by: Nitish Sakhawalkar Co-authored-by: Kasumi Hanazuki --- filenames.gni | 4 + .../browser/api/electron_api_web_contents.cc | 373 ++++++++++-------- shell/common/language_util.h | 26 ++ shell/common/language_util_linux.cc | 17 + shell/common/language_util_mac.mm | 25 ++ shell/common/language_util_win.cc | 76 ++++ spec-main/chromium-spec.ts | 4 +- 7 files changed, 349 insertions(+), 176 deletions(-) create mode 100644 shell/common/language_util.h create mode 100644 shell/common/language_util_linux.cc create mode 100644 shell/common/language_util_mac.mm create mode 100644 shell/common/language_util_win.cc 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]) }) })