diff options
Diffstat (limited to 'widget/tests/test_textScaleFactor_system_font.html')
-rw-r--r-- | widget/tests/test_textScaleFactor_system_font.html | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/widget/tests/test_textScaleFactor_system_font.html b/widget/tests/test_textScaleFactor_system_font.html new file mode 100644 index 0000000000..2d0333e5fa --- /dev/null +++ b/widget/tests/test_textScaleFactor_system_font.html @@ -0,0 +1,139 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test that system font sizing is independent from ui.textScaleFactor</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> + <style> + p { width: max-content } + #menu { font: menu } + </style> +</head> +<body> + <p id="menu">"menu" text.</p> + <p id="default">Default text.</p> +</body> +<script> +"use strict"; + +const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Returns a Number for the font size in CSS pixels. +function elementFontSize(element) { + return parseFloat(getComputedStyle(element).getPropertyValue("font-size")); +} + +// "look-and-feel-changed" may be dispatched twice: once for the pref +// change and once after receiving the new values from +// ContentChild::RecvThemeChanged(). +// pushPrefEnv() resolves after the former. This resolves after the latter. +function promiseNewFontSizeOnThemeChange(element) { + return new Promise(resolve => { + const lastSize = elementFontSize(element); + + function ThemeChanged() { + const size = elementFontSize(element); + if (size != lastSize) { + resolve(size); + } + } + // "look-and-feel-changed" is dispatched before the style system is flushed, + // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/layout/base/nsPresContext.cpp#1684,1703-1705 + // so use an async observer to get a notification after style changes. + SpecialPowers.addAsyncObserver(ThemeChanged, "look-and-feel-changed"); + SimpleTest.registerCleanupFunction(function() { + SpecialPowers.removeAsyncObserver(ThemeChanged, "look-and-feel-changed"); + }); + }); +} + +function fuzzyCompareLength(actual, expected, message, tolerance, expectFn) { + expectFn(Math.abs(actual-expected) <= tolerance, + `${message} - got ${actual}, expected ${expected} +/- ${tolerance}`); +} + +add_task(async () => { + // MOZ_HEADLESS is set in content processes with GTK regardless of the + // headless state of the parent process. Check the parent state. + const headless = await SpecialPowers.spawnChrome([], function get_headless() { + return Services.env.get("MOZ_HEADLESS"); + }); + // LookAndFeel::TextScaleFactor::FloatID is implemented only for WINNT and + // GTK. ui.textScaleFactor happens to scale CSS pixels on other platforms + // but system font integration has not been implemented. + const expectSuccess = AppConstants.MOZ_WIDGET_TOOLKIT == "windows" || + (AppConstants.MOZ_WIDGET_TOOLKIT == "gtk" && + // Headless GTK doesn't get system font sizes from the system, but + // uses sizes fixed in CSS pixels. + !headless); + + async function setScaleAndPromiseFontSize(scale, element) { + const prefPromise = SpecialPowers.pushPrefEnv({ + set: [["ui.textScaleFactor", scale]], + }); + if (!expectSuccess) { + // The size is not expected to change but get it afresh to check our + // assumption. + await prefPromise; + return elementFontSize(element); + } + const [size] = await Promise.all([ + promiseNewFontSizeOnThemeChange(element), + prefPromise, + ]); + return size; + } + + const menu = document.getElementById("menu"); + const def = document.getElementById("default"); + // Choose a scaleFactor value different enough from possible default values + // that app unit rounding does not prevent a change in devicePixelRatio. + // A scaleFactor of 120 also has no rounding of app units per dev pixel. + const referenceScale = 120; + const menuSize1 = await setScaleAndPromiseFontSize(referenceScale, menu); + const menuRect1 = menu.getBoundingClientRect(); + const defSize1 = elementFontSize(def); + const defRect1 = def.getBoundingClientRect(); + + const expectFn = expectSuccess ? ok : todo; + const menuSize2 = await setScaleAndPromiseFontSize(2*referenceScale, menu); + { + const singlePrecisionULP = Math.pow(2, -23); + // Precision seems to be lost in the conversion to decimal string for the + // property value. + const reltolerance = 30 * singlePrecisionULP; + fuzzyCompareLength(menuSize2, menuSize1/2, "size of menu font", + reltolerance*menuSize1/2, expectFn); + } + { + const menuRect2 = menu.getBoundingClientRect(); + // The menu font text renders exactly the same and app-unit rects are + // equal, but the DOMRect conversion is rounded to 1/65536 CSS pixels. + // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/dom/base/DOMRect.cpp#151-159 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1640441#c28 + const absTolerance = 1/65536 + fuzzyCompareLength(menuRect2.width, menuRect1.width/2, + "width of menu font <p> in px", absTolerance, expectFn); + fuzzyCompareLength(menuRect2.height, menuRect1.height/2, + "height of menu font <p> in px", absTolerance, expectFn); + } + + const defSize2 = elementFontSize(def); + is(defSize2, defSize1, "size of default font"); + { + const defRect2 = def.getBoundingClientRect(); + // Wider tolerance for hinting and snapping + const relTolerance = 1/12; + fuzzyCompareLength(defRect2.width, defRect1.width, + "width of default font <p> in px", + relTolerance*defRect1.width, ok); + fuzzyCompareLength(defRect2.height, defRect1.height, + "height of default font <p> in px", + relTolerance*defRect1.height, ok); + } +}); +</script> +</html> |