diff --git a/BUILD.gn b/BUILD.gn index 9782fe4cdbd6d..1ed1a0e34f30a 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -208,6 +208,7 @@ static_library("electron_lib") { "//base", "//base:base_static", "//base:i18n", + "//chrome/app/resources:platform_locale_settings", "//chrome/common", "//components/certificate_transparency", "//components/net_log", diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 7f18f0750b93d..30774955141b2 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -21,6 +21,7 @@ #include "atom/browser/atom_resource_dispatcher_host_delegate.h" #include "atom/browser/atom_speech_recognition_manager_delegate.h" #include "atom/browser/child_web_contents_tracker.h" +#include "atom/browser/font_defaults.h" #include "atom/browser/io_thread.h" #include "atom/browser/media/media_capture_devices_dispatcher.h" #include "atom/browser/native_window.h" @@ -301,6 +302,8 @@ void AtomBrowserClient::OverrideWebkitPrefs(content::RenderViewHost* host, prefs->default_maximum_page_scale_factor = 1.f; prefs->navigate_on_drag_drop = false; + SetFontDefaults(prefs); + // Custom preferences of guest page. auto* web_contents = content::WebContents::FromRenderViewHost(host); auto* web_preferences = WebContentsPreferences::From(web_contents); diff --git a/atom/browser/font_defaults.cc b/atom/browser/font_defaults.cc new file mode 100644 index 0000000000000..85e57feb9d708 --- /dev/null +++ b/atom/browser/font_defaults.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2018 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/font_defaults.h" + +#include +#include + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/pref_names.h" +#include "chrome/grit/platform_locale_settings.h" +#include "content/public/common/web_preferences.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// The following list of font defaults was copied from +// https://chromium.googlesource.com/chromium/src/+/69.0.3497.106/chrome/browser/ui/prefs/prefs_tab_helper.cc#152 +// +// The only updates that should be made to this list are copying updates that +// were made in Chromium. +// +// vvvvv DO NOT EDIT vvvvv + +struct FontDefault { + const char* pref_name; + int resource_id; +}; + +// Font pref defaults. The prefs that have defaults vary by platform, since not +// all platforms have fonts for all scripts for all generic families. +// TODO(falken): add proper defaults when possible for all +// platforms/scripts/generic families. +const FontDefault kFontDefaults[] = { + {prefs::kWebKitStandardFontFamily, IDS_STANDARD_FONT_FAMILY}, + {prefs::kWebKitFixedFontFamily, IDS_FIXED_FONT_FAMILY}, + {prefs::kWebKitSerifFontFamily, IDS_SERIF_FONT_FAMILY}, + {prefs::kWebKitSansSerifFontFamily, IDS_SANS_SERIF_FONT_FAMILY}, + {prefs::kWebKitCursiveFontFamily, IDS_CURSIVE_FONT_FAMILY}, + {prefs::kWebKitFantasyFontFamily, IDS_FANTASY_FONT_FAMILY}, + {prefs::kWebKitPictographFontFamily, IDS_PICTOGRAPH_FONT_FAMILY}, +#if defined(OS_CHROMEOS) || defined(OS_MACOSX) || defined(OS_WIN) + {prefs::kWebKitStandardFontFamilyJapanese, + IDS_STANDARD_FONT_FAMILY_JAPANESE}, + {prefs::kWebKitFixedFontFamilyJapanese, IDS_FIXED_FONT_FAMILY_JAPANESE}, + {prefs::kWebKitSerifFontFamilyJapanese, IDS_SERIF_FONT_FAMILY_JAPANESE}, + {prefs::kWebKitSansSerifFontFamilyJapanese, + IDS_SANS_SERIF_FONT_FAMILY_JAPANESE}, + {prefs::kWebKitStandardFontFamilyKorean, IDS_STANDARD_FONT_FAMILY_KOREAN}, + {prefs::kWebKitSerifFontFamilyKorean, IDS_SERIF_FONT_FAMILY_KOREAN}, + {prefs::kWebKitSansSerifFontFamilyKorean, + IDS_SANS_SERIF_FONT_FAMILY_KOREAN}, + {prefs::kWebKitStandardFontFamilySimplifiedHan, + IDS_STANDARD_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitSerifFontFamilySimplifiedHan, + IDS_SERIF_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitSansSerifFontFamilySimplifiedHan, + IDS_SANS_SERIF_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitStandardFontFamilyTraditionalHan, + IDS_STANDARD_FONT_FAMILY_TRADITIONAL_HAN}, + {prefs::kWebKitSerifFontFamilyTraditionalHan, + IDS_SERIF_FONT_FAMILY_TRADITIONAL_HAN}, + {prefs::kWebKitSansSerifFontFamilyTraditionalHan, + IDS_SANS_SERIF_FONT_FAMILY_TRADITIONAL_HAN}, +#endif +#if defined(OS_MACOSX) || defined(OS_WIN) + {prefs::kWebKitCursiveFontFamilySimplifiedHan, + IDS_CURSIVE_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitCursiveFontFamilyTraditionalHan, + IDS_CURSIVE_FONT_FAMILY_TRADITIONAL_HAN}, +#endif +#if defined(OS_CHROMEOS) + {prefs::kWebKitStandardFontFamilyArabic, IDS_STANDARD_FONT_FAMILY_ARABIC}, + {prefs::kWebKitSerifFontFamilyArabic, IDS_SERIF_FONT_FAMILY_ARABIC}, + {prefs::kWebKitSansSerifFontFamilyArabic, + IDS_SANS_SERIF_FONT_FAMILY_ARABIC}, + {prefs::kWebKitFixedFontFamilyKorean, IDS_FIXED_FONT_FAMILY_KOREAN}, + {prefs::kWebKitFixedFontFamilySimplifiedHan, + IDS_FIXED_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitFixedFontFamilyTraditionalHan, + IDS_FIXED_FONT_FAMILY_TRADITIONAL_HAN}, +#elif defined(OS_WIN) + {prefs::kWebKitFixedFontFamilyArabic, IDS_FIXED_FONT_FAMILY_ARABIC}, + {prefs::kWebKitSansSerifFontFamilyArabic, + IDS_SANS_SERIF_FONT_FAMILY_ARABIC}, + {prefs::kWebKitStandardFontFamilyCyrillic, + IDS_STANDARD_FONT_FAMILY_CYRILLIC}, + {prefs::kWebKitFixedFontFamilyCyrillic, IDS_FIXED_FONT_FAMILY_CYRILLIC}, + {prefs::kWebKitSerifFontFamilyCyrillic, IDS_SERIF_FONT_FAMILY_CYRILLIC}, + {prefs::kWebKitSansSerifFontFamilyCyrillic, + IDS_SANS_SERIF_FONT_FAMILY_CYRILLIC}, + {prefs::kWebKitStandardFontFamilyGreek, IDS_STANDARD_FONT_FAMILY_GREEK}, + {prefs::kWebKitFixedFontFamilyGreek, IDS_FIXED_FONT_FAMILY_GREEK}, + {prefs::kWebKitSerifFontFamilyGreek, IDS_SERIF_FONT_FAMILY_GREEK}, + {prefs::kWebKitSansSerifFontFamilyGreek, IDS_SANS_SERIF_FONT_FAMILY_GREEK}, + {prefs::kWebKitFixedFontFamilyKorean, IDS_FIXED_FONT_FAMILY_KOREAN}, + {prefs::kWebKitCursiveFontFamilyKorean, IDS_CURSIVE_FONT_FAMILY_KOREAN}, + {prefs::kWebKitFixedFontFamilySimplifiedHan, + IDS_FIXED_FONT_FAMILY_SIMPLIFIED_HAN}, + {prefs::kWebKitFixedFontFamilyTraditionalHan, + IDS_FIXED_FONT_FAMILY_TRADITIONAL_HAN}, +#endif +}; +const size_t kFontDefaultsLength = arraysize(kFontDefaults); + +// ^^^^^ DO NOT EDIT ^^^^^ + +std::string GetDefaultFontForPref(const char* pref_name) { + for (size_t i = 0; i < kFontDefaultsLength; ++i) { + FontDefault pref = kFontDefaults[i]; + if (strcmp(pref.pref_name, pref_name) == 0) { + return l10n_util::GetStringUTF8(pref.resource_id); + } + } + return std::string(); +} + +// Map from script to font. +// Key comparison uses pointer equality. +using ScriptFontMap = std::unordered_map; + +// Map from font family to ScriptFontMap. +// Key comparison uses pointer equality. +using FontFamilyMap = std::unordered_map; + +// A lookup table mapping (font-family, script) -> font-name +// e.g. ("sans-serif", "Zyyy") -> "Arial" +FontFamilyMap g_font_cache; + +base::string16 FetchFont(const char* script, const char* map_name) { + FontFamilyMap::const_iterator it = g_font_cache.find(map_name); + if (it != g_font_cache.end()) { + ScriptFontMap::const_iterator it2 = it->second.find(script); + if (it2 != it->second.end()) + return it2->second; + } + + std::string pref_name = base::StringPrintf("%s.%s", map_name, script); + std::string font = GetDefaultFontForPref(pref_name.c_str()); + base::string16 font16 = base::UTF8ToUTF16(font); + + ScriptFontMap& map = g_font_cache[map_name]; + map[script] = font16; + return font16; +} + +void FillFontFamilyMap(const char* map_name, + content::ScriptFontFamilyMap* map) { + for (size_t i = 0; i < prefs::kWebKitScriptsForFontFamilyMapsLength; ++i) { + const char* script = prefs::kWebKitScriptsForFontFamilyMaps[i]; + base::string16 result = FetchFont(script, map_name); + if (!result.empty()) { + (*map)[script] = result; + } + } +} + +} // namespace + +namespace atom { + +void SetFontDefaults(content::WebPreferences* prefs) { + FillFontFamilyMap(prefs::kWebKitStandardFontFamilyMap, + &prefs->standard_font_family_map); + FillFontFamilyMap(prefs::kWebKitFixedFontFamilyMap, + &prefs->fixed_font_family_map); + FillFontFamilyMap(prefs::kWebKitSerifFontFamilyMap, + &prefs->serif_font_family_map); + FillFontFamilyMap(prefs::kWebKitSansSerifFontFamilyMap, + &prefs->sans_serif_font_family_map); + FillFontFamilyMap(prefs::kWebKitCursiveFontFamilyMap, + &prefs->cursive_font_family_map); + FillFontFamilyMap(prefs::kWebKitFantasyFontFamilyMap, + &prefs->fantasy_font_family_map); + FillFontFamilyMap(prefs::kWebKitPictographFontFamilyMap, + &prefs->pictograph_font_family_map); +} + +} // namespace atom diff --git a/atom/browser/font_defaults.h b/atom/browser/font_defaults.h new file mode 100644 index 0000000000000..95ffe6b0194f2 --- /dev/null +++ b/atom/browser/font_defaults.h @@ -0,0 +1,18 @@ +// Copyright (c) 2018 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_FONT_DEFAULTS_H_ +#define ATOM_BROWSER_FONT_DEFAULTS_H_ + +namespace content { +struct WebPreferences; +} // namespace content + +namespace atom { + +void SetFontDefaults(content::WebPreferences* prefs); + +} // namespace atom + +#endif // ATOM_BROWSER_FONT_DEFAULTS_H_ diff --git a/electron_paks.gni b/electron_paks.gni index c1f577330099c..0db5201fdce29 100644 --- a/electron_paks.gni +++ b/electron_paks.gni @@ -143,12 +143,14 @@ template("electron_paks") { } source_patterns = [ + "${root_gen_dir}/chrome/platform_locale_settings_", "${root_gen_dir}/components/strings/components_strings_", "${root_gen_dir}/content/app/strings/content_strings_", "${root_gen_dir}/ui/strings/app_locale_settings_", "${root_gen_dir}/ui/strings/ui_strings_", ] deps = [ + "//chrome/app/resources:platform_locale_settings", "//components/strings:components_strings", "//content/app/strings", "//ui/strings:app_locale_settings", diff --git a/filenames.gni b/filenames.gni index 7ce785819ed94..e09be505abb74 100644 --- a/filenames.gni +++ b/filenames.gni @@ -120,6 +120,8 @@ filenames = { "atom/app/uv_task_runner.cc", "atom/app/uv_task_runner.h", "atom/browser/api/atom_api_app.cc", + "atom/browser/font_defaults.cc", + "atom/browser/font_defaults.h", "atom/browser/api/atom_api_app.h", "atom/browser/api/atom_api_auto_updater.cc", "atom/browser/api/atom_api_auto_updater.h", diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 5cf0645699862..d9d0d030f45c6 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -10,6 +10,7 @@ const ChildProcess = require('child_process') const { ipcRenderer, remote } = require('electron') const { closeWindow } = require('./window-helpers') const { resolveGetters } = require('./assert-helpers') +const { emittedOnce } = require('./events-helpers') const { app, BrowserWindow, ipcMain, protocol, session, webContents } = remote const isCI = remote.getGlobal('isCi') const features = process.atomBinding('features') @@ -1312,3 +1313,59 @@ describe('chromium feature', () => { }) }) }) + +describe('font fallback', () => { + async function getRenderedFonts (html) { + const w = new BrowserWindow({ show: false }) + try { + const loaded = emittedOnce(w.webContents, 'did-finish-load') + w.loadURL(`data:text/html,${html}`) + await loaded + w.webContents.debugger.attach() + const sendCommand = (...args) => new Promise((resolve, reject) => { + w.webContents.debugger.sendCommand(...args, (e, r) => { + if (e) { reject(e) } else { resolve(r) } + }) + }) + const { nodeId } = (await sendCommand('DOM.getDocument')).root.children[0] + await sendCommand('CSS.enable') + const { fonts } = await sendCommand('CSS.getPlatformFontsForNode', { nodeId }) + return fonts + } finally { + w.close() + } + } + + it('should use Helvetica for sans-serif on Mac, and Arial on Windows and Linux', async () => { + const html = `test` + const fonts = await getRenderedFonts(html) + expect(fonts).to.be.an('array') + expect(fonts).to.have.length(1) + expect(fonts[0].familyName).to.equal({ + 'win32': 'Arial', + 'darwin': 'Helvetica', + 'linux': 'DejaVu Sans' // I think this depends on the distro? We don't specify a default. + }[process.platform]) + }) + + it('should fall back to Japanese font for sans-serif Japanese script', async function () { + if (process.platform === 'linux') { + return this.skip() + } + const html = ` + + + + + test 智史 + + ` + const fonts = await getRenderedFonts(html) + expect(fonts).to.be.an('array') + expect(fonts).to.have.length(1) + expect(fonts[0].familyName).to.equal({ + 'win32': 'Meiryo', + 'darwin': 'Hiragino Kaku Gothic ProN' + }[process.platform]) + }) +})