Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: match Chrome's font fallback behavior #15486

Merged
merged 6 commits into from Nov 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.gn
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions atom/browser/atom_browser_client.cc
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
181 changes: 181 additions & 0 deletions atom/browser/font_defaults.cc
@@ -0,0 +1,181 @@
// Copyright (c) 2018 Slack Technologies, Inc.
jkleinsc marked this conversation as resolved.
Show resolved Hide resolved
// 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 <string>
#include <unordered_map>

#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<const char*, base::string16>;

// Map from font family to ScriptFontMap.
// Key comparison uses pointer equality.
using FontFamilyMap = std::unordered_map<const char*, ScriptFontMap>;

// 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
18 changes: 18 additions & 0 deletions 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_
2 changes: 2 additions & 0 deletions electron_paks.gni
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions filenames.gni
Expand Up @@ -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",
Expand Down
57 changes: 57 additions & 0 deletions spec/chromium-spec.js
Expand Up @@ -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')
Expand Down Expand Up @@ -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 = `<body style="font-family: sans-serif">test</body>`
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.
nornagon marked this conversation as resolved.
Show resolved Hide resolved
}[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 = `
<html lang="ja-JP">
<head>
<meta charset="utf-8" />
</head>
<body style="font-family: sans-serif">test 智史</body>
</html>
`
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])
})
})