Closed Thorin-Oakenpants closed 1 year ago
godamn it .. 243 is not half of 468 .. it's 234, and no, I'm not dyslexic .. this is just what happens when you're tired (and overworked)
Hah ...
creepy font test uses 485 ... TZP uses 486: I suspect you're not including the old emoji font EmojiOne Mozilla
: All my code is tested for FF60+ .. and wouldn't you know it, TwemojiMozilla.ttf
replaced EmojiOneMozilla.ttf
in FF61+ ... sucks they didn't sneak it in in time for ESR (or backport it to ESR dot releases)
Probably a moot point, since I might prune the font list. But if I do, I'll probably add a checkbox so reruns can use the full OS list
@abrahamjuliot : hope you are ok with me linking to the creepy stand-alone font test
Got a sweet local version here that only tests the base fonts if isRFP && !isTB && isVer > 79
. I can do this for windows and mac (android is not covered, and linux is gated to a few distros).
So far I've done the windows one: it reduces the font list from 485 (I had a typo one in there by mistake) to 212, so less than half, saves lots.. in my case about 250ms
note: "base fonts" is the base list allowed by font vis (93 fonts in windows), plus system fonts (Times, MS Serif etc), plus FF bundled emoji fonts, plus any font family styles, plus the non-western char ones (which get ignored after 89 or 90) = hence 212
if you change the RFP pref and rerun the section or a global rerun, or run the font fallback test, it picks up the change in RFP. I had to fix the isRFP test (for now) to ignore most of the extra checks: i.e changing RFP does not update the css4 pseudo elements: but I have a plan to work around this
fonts listed in the section header are the full font lists, and if the base font list is used, it notes that in the result (clickable). The 485
count indicates it's the windows list your OS. Below I loaded the test with RFP off, then I flipped RFP on and ran the font fallback test
Mac base list is 183, full list is 755: will be interesting to see what I can prune that down to
I might expand this for isTB base lists for windows and mac
note: using base fonts would not catch out those who bypass the mechanism by bundling extra fonts: however, that would have been the case with non-OS fonts anyway
https://arkenfox.github.io/TZP/tests/fontlists.html
bold
, bold italic
, italic
, regular
231
items from all, and base can be reduced from 212
to 114
currently the base + RFP
is limited to windows
currently the styles
test only works for windows, Mac/Linux will be a false positive [0/0]
since the style lists are empty until I build them - I really wish I had access to a Mac for testing
windows 7
Here's Mac...
Mac FF 89 (RFP + Base)
all: 06ca3165804ca63857d20d683ea79e1dd4a2ed91 [189/755] base: 63ccc987c5d5b94c53c39173ac4da7c6a081c096 [162/175] ✖
FOUND FONTS NOT IN BASE: [27]
Avenir Black, Avenir Black Oblique, Avenir Book, Avenir Heavy, Avenir Light, Avenir Medium, Avenir Next Demi Bold, Avenir Next Heavy, Avenir Next Medium, Avenir Next Ultra Light, Charter Black, Hiragino Maru Gothic ProN W4, Hiragino Mincho ProN W3, Hiragino Mincho ProN W6, Hiragino Sans GB W3, Hiragino Sans GB W6, Hiragino Sans W0, Hiragino Sans W1, Hiragino Sans W2, Hiragino Sans W3, Hiragino Sans W4, Hiragino Sans W5, Hiragino Sans W6, Hiragino Sans W7, Hiragino Sans W8, Hiragino Sans W9, SignPainter-HouseScript
BASE FONTS NOT FOUND: [13]
Apple Braille, Arial Hebrew, Arial Hebrew Scholar, EmojiOne Mozilla, Farah, Muna, Nadeem Regular, New Peninim MT, Raanana, Symbol, Twemoji Mozilla, Webdings, Wingdings 2
Mac FF 89 RFP disabled (Styles)
all: fe4c2b69cf90cf5b076feac6647e404bf7e34850 [219/755] styles: 85dac4f7a0cd3fed353356d637c7ddbab9733377 [5/297] ✖
FOUND FONTS IN STYLE: [5]
Avenir Black Oblique, Hiragino Kaku Gothic ProN W3, Hiragino Kaku Gothic ProN W6, Hiragino Mincho ProN W3, Hiragino Mincho ProN W6
On a side note, I have a new promise-based function that detects a distinct set of fonts not detected by element measurements and it's blazing fast. Thus far, I'm testing on Windows, Ubuntu, Fedora, Mac, Chrome OS, Android, and iPhone.
For example, on Android, this detects Noto fonts and a set of Google fonts that vary:
Carrois Gothic SC
Coming Soon
Cutive Mono (Linux/Chrome OS Android 9)
Dancing Script
Droid Sans Mono
Noto Color Emoji (Linux/Chrome OS Android 9)
Noto Serif
Roboto
Roboto Condensed
In TB, this seems to shine the most in Linux and Android builds. Mac and Windows yield nothing in TB.
fontList = [...]
// map font list to a list of FontFace objects
fontFaceList = fontList.map(font => new FontFace(font, `local("${font}")`))
// load each font, settle each promise to 'rejected' or 'fulfilled', then reduce list to 'fulfilled' promises
Promise.allSettled(fontFaceList.map(font => font.load())).then(res => {
const supportedFonts = res.reduce((acc, font) => {
// supported fonts have a 'fulfilled' status
if (font.status == 'fulfilled') {
return [...acc, font.value.family]
}
return acc
}, [])
return console.log(supportedFonts)
}).catch(error => console.error(error))
Thanks for the Mac info - I'll decipher it and see what that means in terms of what constitutes a "font family"
Edit: Unfortunately I can't run a Mac VM with an AMD mobo. Otherwise I would, and I would install all optional language packs so I could then use full results to weed out non-detectable "faces" and then RFP to create base. This looks similar to windows - I can see some general rules of thumb, but with exceptions
I made the font lists more nuanced: fntAlways, fntBundled, fntTB - in both the test and TZP
81
fonts to check - test passes on win7 + win10
117
- test passes on win7 + win10249
Might be something weird going on in Win10 though, but I need to test more. e.g. in my VM, it picks up Franklin Gothic Medium
and HoloLens MDL2 Assets
(both of which are in the base list) when RFP is off, but not when it is on (but they're in the base list!!) - however, that was with FF81 (that's how long it's been since I did anything on my Windows10 VM), and maybe that's been fixed - I seriously need a break
can't wait to get mac sorted out
On a side note,
Yeah, I saw all those commits and font face fuckery .. wondered what you were doing. I plan to add some more manual font tests to TZP - such as domrect/textmetrics measuring methods. If font face can be used in FF as another method, cool.
Your test though, what is it meant to achieve? I can already tell the OS (although I do plan to use a tiny font test to determine say win7 vs win10, not sure about win8.1, in order to create my own UA with zero reliance on navigator: oscpu is about the only one I can't fully determine lies). TZP font tests are FF only. So I'm not sure how your test fits in here? Can you elaborate? .. have some 🥧
I use the font face test to detect OS, but font measurements can acheive this too. It's non-essential. But, using font face, I only need 7 fonts in most cases. It works for webkit, blink, and gecko, but not TB on windows/mac. So, I'm measuring a max of 30 fonts (which works for TB) and then I load the 7 fonts in font face for Android and Linux. I'm still testing different Linux builds, but so far the 37 fonts together are 20-70 ms.
Ahh OK. TZP doesn't do a font tests for non-FF. But at some stage whacking in one that covers a small range for "some entropy" would be nice. And when Brave gets there's done, I want to add something for that - i.e render it as a stable value rather than randomized: could be as simple as ignoring a randomlist, who knows - wait and see what Peter comes up with
So is this to fully replace your current font detection list?
I just posted some shit at https://github.com/arkenfox/user.js/issues/1211
with some rough timing stats .. ps, got any friends with macs? also, rerun yours when you get time :)
...is this to fully replace your current font detection list?
Yes, on the main page. The font test page will focus on large lists.
...friends with macs
Negative 😭. I'll send you my stats.
Yes, on the main page
Hmmm. Can extensions fubar it? The seven measuring methods in TZP mean we can pick up BS. Domrect measuring increases the methods. Textmetrics needs some work I think (different counts) - but on TZP I decided seven was enough (for now)
perf wise, I don't think much is gained per method drop, as the bulk of the time is taken by checking each font
That said, if font face is untamperable and faster than for this metric then 👍
stats
You can post new test results at https://github.com/arkenfox/user.js/issues/1211
- I added a kBaseFonts integrity check - for example on all windows versions, Franklin Gothic Medium is a base font, but with font vis or RFP on, it doesn't get picked up. Would love to see if Mac shows anything
Can extensions fubar it?
Yes, but it's a delicate task and requires extra work (similar to new Date
). I can see it getting blocked or deleted over an attempt to rewrite the API.
Sounds good :)
fyi - version 1.0 font in Windows 11
Segoe Fluent Icons
https://docs.microsoft.com/en-us/typography/fonts/windows_11_font_list
Yes. I also need to update Mac fonts
@abrahamjuliot
So I think I've hit on a way to speed up the current font test
see this
const baseFonts = ['monospace','sans-serif','serif']
when I run it with only one item in the array, here's what I got
// each baseFont takes about 55/65ms, and combined about 150/160
// so excluding some overhead, each baseFont is 1/3rd
// 061036d2becc235167e3038392bed69bbbe2bb08 150fnts all-3
// 66e9690baae5fbef2478fae8fb62e0935160c0b9 148fnts monospace
// misses: Consolas, Twemoji Mozilla
// 66e33b0be5d2de69cc14f99e6bc237b38e6a99f3 147fnts sans-serif
// misses: Arial, Helvetica, Small Fonts
// 57fb601120016eaba69079b3c5067b5daff996b5 145fnts serif
// misess: MS Serif, Mongolian Baiti, Roman, Times, Times New Roman
That's out of 251 windows fonts
But we adding to a Set, we only need to detect each font once
251
fonts x 3
baseFont = 753
measurements457
measurements = 457/753
= 61%, so roughly :star: 1.5x as fast :star:so starting here https://github.com/arkenfox/TZP/blob/cd4d401790c3c2f5e638bc80544c793a4992b911/js/fonts.js#L364
family
is a combined font + baseFont, e.g. Arial monospace
, and we need to loop each baseFont
and this line https://github.com/arkenfox/TZP/blob/cd4d401790c3c2f5e638bc80544c793a4992b911/js/fonts.js#L368
is required if any of the eight detectedVia
hasn't yet been found
So this only works if we loop each baseFont per font and all eight need to be found (which works if no-one is screwing with fonts). I've looked at how I would code it, but I think you could come up with something more elegant, plus it;s your original code
ps: I was looking at collecting the sizes with each font, per baseFont, but almost no font has multiple sizes (two on my windows 7), and this way I could actually collect the data like this below (because we only get the first baseFont, this is for info purposes), but the dimensions add entropy to the metric (probably equivalency), but I like this idea. And IDK, but if we use clientRect we might get more precision and maybe subpixel entropy as well (dimensions would change with zoom etc), but it's not hard to return the fonts names as a separate hash
detectedViaPixelSize.add(font +" : "+ basefont +" : "+ dimensions.width +" x "+ dimensions.height)
Anyway, I am dead keen to get this done. I don't think we need a test PoC with two run buttons, old vs new - but if you want one, we can do
actually, we don't need to check if all 8 methods detected a font, we just need to check that at least one did (the others should, if supported), so if they don't then they're being blocked or whatever (rechecking them with subsequent baseFonts won't do anything)
@abrahamjuliot ^ done - https://github.com/arkenfox/TZP/commit/b95b12737e8a70e6342806268e831853066ed2d5 - how does that look to you?
I toggled RFP off in tor browser (it still uses a reduced list cuz .. smart code) but now you can get actual perf: nice! (glyphs is another area for perf attention, just not in this ticket)
https://abrahamjuliot.github.io/creepjs/tests/fonts.html
I know this is a test PoC and not the main creepy test, but a) perf question (see below) and b) what fonts are you testing, can you add that to the console
So ... I do all 4 pixel and all 3 length tests on TZP, with no font protections, getting 150 detected on TZP same as creepy PoC page - I used to take about 150ms for all seven tests, now down to abut 100ms
actual perf diffs will depend on if you have the font or not
We could determine what OS fonts to check based on the user's reported platform. If the user spoofs the platform, then we give them that OS font list (the wrong list), measure the performance and see how many fonts match up.
We could even make special routes knowing full well the platform is a lie and then add additional font measurements or more OS font lists for that user based on the severity of the lie.
Might be a bad idea for devs. It would make testing some extensions a slower experience (maybe not that bad).
actual perf diffs will depend on if you have the font or not
I'm actually going to do a test for this. I have 86 fonts which I have that I can block in FF via the font vis pref (the same one that RFP uses). So I will test those 86 fonts only with and without them allowed in web content. And I will also test them both with three baseFonts checked vs 1 baseFont checked (when allowed). Will be interesting to see what the cost/benefit of not loading a font vs having to do three checks is
I already set a font list based on OS, and I edited the original post with more steps I can do, so so it's listed. The less fonts tested the faster. One thing I've noticed so far, at least on windows, is the third basefont doesn't seem to be needed - so that would save a lot - e.g. if 187+ fonts are not found (font vis pref with no RFP using default win 251 list - 64 found) .. that's a large chunk
obviously the third (and second) check depends on how many fonts aren't detected, so something like the tb whitelist list we're down to like 10-14 fonts missing, so fuck all to gain
edit: if we knew ALL fonts that had to use a non-monospace style, we could actually get away with one test per font, even if it's not detected
b95b127
This looks very nice. Just a few comments.
Should there be a return after each case the font is detected and then skip the remaining dimension checks
isDetected = true
return
Small suggestion here. We could return early to reduce nesting. Either way works.
if (isDetected) {
return
}
const family = "'"+ font +"', "+ basefont
span.style.setProperty('--font', family)
https://github.com/arkenfox/TZP/blob/b95b12737e8a70e6342806268e831853066ed2d5/js/fonts.js#L361-L365
Here's FF100 on Chrome OS Android 9.
Should there be a return after each case the font is detected and then skip the remaining dimension checks
is that a question? Do you mean like this
if (dimensions.sizeWidth != base[basefont].sizeWidth ||
dimensions.sizeHeight != base[basefont].sizeHeight) {
// record in SIZE set
isDetected = true
return
}
if (dimensions.scrollWidth != base[basefont].scrollWidth ||
dimensions.scrollHeight != base[basefont].scrollHeight) {
// record in SCROLL set
isDetected = true
return
}
// stats
baseFontTests[basefont]++
if (isDetected) {
baseFontDetected[basefont]++
if (basefont !== basefontFirst) {
oTempBaseFonts[basefont].push(font)
}
}
return
am I missing something, if we return on the first dimension check, then the others will never populate, and I'll never record the stats, etc
edit: and I wouldn't pick up on differences between each detectedVia method if it's being affected by an extension (blocked etc) - e.g. with cydec my code returns two methods as null (from memory) .And it also wouldn't let me pick up on code changes in Moz that might cause a diff, or pick up my faulty code? Am I missing something here?
Here's FF100 on Chrome OS Android 9.
interesting... on that 3rd baseFont - I haven't had a chance to test FF on android yet. I think any decision on the 3rd baseFont being skipped is going to have to be heavily tested (and then even if I miss a font or two due to lack of testing, at least the PoC is the same for all - might even add entropy/equivalency)
PS: if you click the little mini hash, it will debug log those 5 fonts to console (1 for sans-serif, 4 for serif)
are you running and timing Pixels and Lengths as separate functions
Yes, for comparison, I'm running each separate with the fpjs base + extended list (485 total). FontFaceSet
and FontFace
are async, so I load the full collection with them here, which includes TZP platform fonts and Google fonts (2927 total). I will add these to the console.
if we return on the first dimension check, then the others will never populate
Ah, I see. It's good then.
Ah, I see. It's good then.
just edited my comment with more reasons. thought I had missed something simple in my logic .. I need MOAR coffee
Yes, for comparison, I'm running each separate
right, so you can time them as two separate test types. I don't see the benefit of that. I mean you could mix or match any of those seven, it doesn't mean anything really, does it?
You could run it as a single function and then split the results, just return a common perf, i.e on Pixel console line say perf xxx [with length]
and on Length console line say perf xxx [with Pixel]
and add in my perf win for some MASSIVE savings
I unified the tor browser detection and multiple debugging fields into one, and added alerts to it. And it just keeps updating (it gets reset when you you a global rerun), so e.g. you can zoom, rerun the the screen section, and the dpi calculations are appended etc. Alerts are still kept in a global array (snapshot in time) and a red alerts link shows up at the top. And all alerts now also console.error
- alerts are sanity checks for code or to pick up on unexpected results
I've been using that perf and debugging table at the bottom for outputting android shit when troubleshooting (I'm unable to debug via about:debugging#/setup and I can't be arsed sorting it out). Anyway, now I can add as many alerts, and on-screen debugs as I want .. to infinity and beyond
obligatory pic
The point being that I debugged the fonts stats data there, so now you can see what those fonts are on android. I got 2 as well on my device, in the 3rd baseFont
I wouldn't pick up on differences between each detectedVia method...
This makes sense. I was thinking to skip checking the further via methods since we got the goods, but diff checking provides more entropy.
random thoughts time
One of the perf items to consider in OP, is to reduce the list of fonts, e.g. fonts that are always expected (e.g. Arial on windows). Less fonts to load and test = less work. Of course that doesn't help the additional styles being set + dimensions being measured in subsequent baseFont (because they would have been detected first baseFont). Just saying this is one of the options
But now I am collecting the sizes, so I need to be careful: but I'm guessing that any tiny diffs caused by e.g. clearType, devicePixelRatio and other factors would probably already create max entropy in the fonts we do test. It's a little unknown and a classic perf vs payoff scenario. Obviously we don't want to cull fonts too far
Which brings me to the subsequent baseFont tests... if the total number of fonts found on baseFont[1] and higher is really tiny, maybe those fonts and/or the subsequent baseFont(s) can be dropped altogether
So I can see another 30-40% perf gains here: we could effectively trim a handful of fonts and eliminate all but one baseFont
@fxbrit can you do a mac test please. Don't give we washy stuff, just load it, run it, reload it, run it a dozen times until you get a stable result (am still thinking about your wonky mac fonts, which is how I came across this perf win - thinking too fucking hard)
edit: I just want to know what fonts you get in the debug section in the footer
I was thinking to skip checking the further via methods since we got the goods
but we've already gathered the dimensions for all methods, right? So I don't think you'll gain much by skipping a few if x == y
's and Set.add
's - IDK, maybe it saves 5ms. Not sneezing at it, papercuts are important
^^ edit: if that was subsubstantial, it might be possible to check after the 2nd font, that results are the same in each method, and if so set a flag that all are equal, and continue to collect just one (and later report them as all the same) - sounds dodgy TBH ;)
or .. we split fonts by baseType per platform, which would allow us to not discard fonts just because of their baseFont. This would be the ultimate setup IMO, once it is known which baseFont every font belongs, then only untested fonts (like non-system ones or system fonts we haven't been able to ever get test) would do extra checks (default) if undetected
If you add collect metrics per baseFont on creepy, we could analyze it
lols, all those font section reruns .. well done. Now that's looking good for even more evidence that fuck all fonts are detected outside monospace after the first one (but I want to compile data with monospace first in all cases)
hmmm
Hiding in the Crowd: an Analysis of the Effectiveness of Browser Fingerprinting at Large Scale Alejandro Gómez-Boix, Pierre Laperdrix, Benoit Baudry 2018
Before deploying our script in production, we identified a limitation in how JavaScript font probing operates. We found out that some fonts can have the exact same dimensions as the ones from the fallback font. Figure 1 illustrates this problem. In the example, the two tested fonts are metrically comparable and have the exact same width and height. However, they are not identical as it can be seen in the shapes of some of the letters (especially “e”, “a” and “w”). This means that font probing here will report incorrect results if one were to ask Times New Roman on a system with the Tinos font installed (or vice versa). To fix this problem, we measured the dimensions of a div against three font style variants. There are different typefaces that can be used by a web browser with the most popular ones being serif, sans-serif, monospace, cursive and fantasy. We chose the first three and we tested each font against the three of them, resulting in
66 ∗ 3
=198
different tests. This way, we avoid reporting false negatives as the three fallback fonts have different dimensions.
Phew ... we can't get reduce the chances of getting a false negative, because we check each style if !isDetected
Edit:
What if we remove the use of Math.round
? I think I added that to ignore tampering noise, but such noise can be detected and put to good use in the diff analysis.
// instead of pixelsToInt...
const pixelsToNumber = pixels => +pixels.replace('px','')
const originPixelsToNumber = pixels => 2*pixels.replace('px', '')
I wonder if adding something like transform: scale(1.0001)
to the CSS will give us better precision and affect the results?
IDK about the math bit, but I already thought (and had a quick attempt two days ago) at transforming to force decimals in fonts
I'm using this and going to expand it into other element tests https://github.com/arkenfox/TZP/blob/2e19aef91c2d2f7385cea9b1213ecd74c8462186/css/index.css#L309-L311
PS: I'm also grabbing the base data, since it's already run, but expanding it
const baseFontsFull = [
'none','monospace','sans-serif','serif','cursive','fantasy','fangsong',
'system-ui','ui-monospace','ui-rounded','ui-serif','math','emoji'
]
// base: all your base are belong to us
// should we trap type mismatches for each baseFont
const base = baseFontsFull.reduce((acc, font) => { // <--- changed to baseFontsFull
span.style.setProperty('--font', font)
const dimensions = getDimensions(span, style)
detectLies.compute(dimensions)
acc[font] = dimensions
return acc
}, {})
obligatory pic
What if we remove the use of ...
I did a heap of testing on perf (will post the rest of it and finish it another day), see below. So looking at what I did, I should be able to do a single method test and remove the math.round and other stuff, and it still hits around the 90's for me then that's not going to make much differnce
OK, I think I know exactly where almost all the time is being spent
method
fntList.forEach(font => {
loop (no other code)```js const dimensions = { //width: pixelsToInt(style.width), //height: pixelsToInt(style.height), transformWidth: originPixelsToInt(transform[0]), transformHeight: originPixelsToInt(transform[1]), //perspectiveWidth: originPixelsToInt(perspective[0]), //perspectiveHeight: originPixelsToInt(perspective[1]), //sizeWidth: pixelsToInt(style.inlineSize), //sizeHeight: pixelsToInt(style.blockSize), //scrollWidth: span.scrollWidth, //scrollHeight: span.scrollHeight, //offsetWidth: span.offsetWidth, //offsetHeight: span.offsetHeight, //clientWidth: span.clientWidth, //clientHeight: span.clientHeight } return dimensions // and let t0font = performance.now() fntList.forEach(font => { let isDetected = false // reset each font baseFonts.forEach(basefont => { if (isDetected) { return } const family = "'"+ font +"', "+ basefont span.style.setProperty('--font', family) const style = getComputedStyle(span) const dimensions = getDimensions(span, style) //detectLies.compute(dimensions) /* if (dimensions.width != base[basefont].width || dimensions.height != base[basefont].height) { detectedViaPixel.add(font +":"+ dimensions.width +" x "+ dimensions.height) isDetected = true } if (dimensions.sizeWidth != base[basefont].sizeWidth || dimensions.sizeHeight != base[basefont].sizeHeight) { detectedViaPixelSize.add(font +":"+ dimensions.sizeWidth +" x "+ dimensions.sizeHeight) isDetected = true } if (dimensions.scrollWidth != base[basefont].scrollWidth || dimensions.scrollHeight != base[basefont].scrollHeight) { detectedViaScroll.add(font +":"+ dimensions.scrollWidth +" x "+ dimensions.scrollHeight) isDetected = true } if (dimensions.offsetWidth != base[basefont].offsetWidth || dimensions.offsetHeight != base[basefont].offsetHeight) { detectedViaOffset.add(font +":"+ dimensions.offsetWidth +" x "+ dimensions.offsetHeight) isDetected = true } if (dimensions.clientWidth != base[basefont].clientWidth || dimensions.clientHeight != base[basefont].clientHeight) { detectedViaClient.add(font +":"+ dimensions.clientWidth +" x "+ dimensions.clientHeight) isDetected = true } */ if (dimensions.transformWidth != base[basefont].transformWidth || dimensions.transformHeight != base[basefont].transformHeight) { detectedViaTransform.add(font +":"+ dimensions.transformWidth +" x "+ dimensions.transformHeight) isDetected = true } /* if (dimensions.perspectiveWidth != base[basefont].perspectiveWidth || dimensions.perspectiveHeight != base[basefont].perspectiveHeight) { detectedViaPerspective.add(font +":"+ dimensions.perspectiveWidth +" x "+ dimensions.perspectiveHeight) isDetected = true } */ /* skip stats // stats baseFontTests[basefont]++ if (isDetected) { baseFontDetected[basefont]++ if (basefont !== basefontFirst) { oTempBaseFonts[basefont].push(font) } } */ return }) }) let t1font = performance.now() log_debug("", t1font-t0font) ```
Then I would load TZP, and after the initial page load (we can ignore this time, as it is almost always slightly longer), I would then run the font section a dozen times (I used nightly: also console closed which IMO anecdotally can cause perf issues: it has a lot going on), and here's the results
all seven: 105 100 101 102 100 101 99 99 99 97 98 98
detectedViaPixel: 97 94 93 94 92 94 91 91 94 92 92 92
detectedViaPixelSize: 97 94 93 92 93 92 93 91 92 92 92 93
detectedViaScroll: 92 92 91 90 90 90 90 89 89 90 89 91
detectedViaOffset: 93 92 92 92 90 91 90 90 90 89 90 89
detectedViaClient: 94 91 90 91 89 88 89 90 90 92 89 91
detectedViaTransform: 95 94 93 92 93 92 89 89 90 91 92 91
detectedViaPerspective: 92 92 92 91 91 91 92 90 90 90 89 93
I won't bother averaging, as it's pretty clear that additional methods here aren't really adding anything
Next, with all seven methods used, I did these
... I did another five things so far to narrow down where all the time is spent
https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/40919
This reminds me, have you noticed NoScript affects dom rect results for a few months now. It's somewhat jittery and then stable after a few reloads. I'm not sure, but it seems to be a result of appending elements, but only the dom rect is affected.
I repeated the test for just detectedViaOffset
and made it the same as before with commented out stuff but this time I also did these, since they aren't being used
in other words the only diff in these tests is those two lines
//const pixelsToInt = pixels => Math.round(+pixels.replace('px',''))
//const originPixelsToInt = pixels => Math.round(2*pixels.replace('px', ''))
here's the old test vs the new test, which will give an idea of the cost
[ToInt] detectedViaOffset: 93 92 92 92 90 91 90 90 90 89 90 89
[ToNumber] detectedViaOffset: 93 91 91 90 91 92 89 90 89 90 90 88
diff: - 1 1 2 1 -1 1 - 1 -1 - 1 : total 6
That's 6ms faster over 12 runs = .5ms per test on on average. And replacement lines will still eat time. I don;t think it's worth the change for perf sakes
What if we remove the use of
Math.round
? I think I added that to ignore tampering noise, but such noise can be detected and put to good use in the diff analysis.
So the question is do we want to do it for more precision(?) ... what do you mean tampering noise? I want as much stable entropy as I can get
what do you mean tampering noise
Ahh, so I tired it, and I see what you mean: I didn't check the differences in measurements. So it splits the results (for me) into three - those that don't use pixelsToNumber, those that use originPixelsToNumber and those that don't use either
Since I'm expecting them to all be the same, and I need 4/7 to be the same to determine a result, I end up flummoxed and return a red lie. This is my lie detector - comparing sets. I think yours would have the same issue
But ... I could still use this. Add more methods (the perf cost is very cheap it seems) and Sets, use the ToInt ones for lies, but display and show and record the ToNumber
And get transform:scale going
I'm going to open a new issue on these things and keep this one for perf
which is faster: detected or missing?
86 detected fonts: 21 20 19 19 20 20 19 19 19 19 18 20 19
86 missing fonts: 15 17 15 15 15 14 15 15 14 14 14 14 15
detected: 86/86 | 0/0 | 0/0 | total: 86/86
missing: 0/86 | 0/0 | 0/0 | total: 0/86
So my non-expert conclusion is that the cost of loading a supported font vs not loading it is quite small. Say at best 1ms per 10 fonts. Now that comes with some caveats: since we never set any detected fonts, IDK if any font fallback needed to occur, whereas if the previous style was a detected font, then I suspect nothing was gained - my oh-so-IANAE-about-fonts gut instinct tells me the slight perf seen above is because of that.
And the other caveat is that of course a missing font triggers additional baseFont tests, wiping out any possible gain
Edit: I could add 40 of each into a set of 80 and test in two orders: detected-then-missing vs alternate, but I think I'm so over this as far as info goes on which is faster :)
...
Next, with all seven methods used, I did these: note I left the stats enabled (maybe that adds 1ms perf)
// ignore: this is just us resetting the style between each font? it made no diff to perf or results
span.style.setProperty('--font', family)
//const style = getComputedStyle(span)
const dimensions = getDimensions(span, style)
// don't get dimensions
span.style.setProperty('--font', family)
const style = getComputedStyle(span)
const dimensions = {} // <-- NEVER COMPUTE ANY DIMENSIONS
// don't setProperty
//span.style.setProperty('--font', family) // <-- NYAH NYAH
const style = getComputedStyle(span)
const dimensions = getDimensions(span, style)
results (all seven = original baseline)
all seven: 105 100 101 102 100 101 99 99 99 97 98 98 :
dimensions = {}: 4 2 3 3 2 3 2 2 2 3 2 3 : 251/251 | 0/0 | 0/0
no styleSetProperty: 6 6 5 4 5 5 5 4 4 4 3 4 : 251/251 | 0/0 | 0/0
So that tells me in my IANAE mode, that the time is all spent in a combo of font-family changing and then the measuring having to wait for the change to happen: if there is no pending change, the dimensions are cached so super fast. And if we don't measure, then setting the font-family isn't held up and can go full tit
Which leads me to an idea. We may never have to change any baseFonts (just reset them), and only set each font once. This would in theory limit setProperty to a maximum of the fntList.length. We still measure and the measurement may change, so IDK if this adds any perf improvements. I will explain a little later
OK, so here are our testing elements (it happens to be "MS P明朝 monospace" in the pic because that is the last font tested in my windows list, and it was found in monospace)
Here is my idea: instead of a single span (font-fingerprint-detector), we have one for each baseFont (for now just hardcode the three). And we set the font on the parent, but only reset the baseFont each font
loop fontList
<generic-name>
/baseFont on each child (hopefully zero perf cost because it's not changing)If I am correct. this would effectively wipe out baseFont[1+] perf costs, but still allow us to test them (and we can ignore trying to pigeon hole fonts into per baseFont pigeon holes as a possible solution)
Or I could be totally wrong. I want to test it
@abrahamjuliot I'm not sure how to structure this, and get dimensions wold need to know what span to target, and IDK about the inheriting and if we use a span or divs
<div id="font-fingerprint"> // do we even need this div
<style> blah blah</style> // as per pic : reset styles between fonts
<div id="font-fingerprint-detector" style="--font: Arial"> // use a div
<style> content: "mmmWWWWWllliiiiiiiiimmmWWwhatever"</style>
<span id="font-fingerprint-monospace" style="monospace"> // reset baseFont between fonts
::after
</span>
<span id="font-fingerprint-sans-serif" style="sans-serif"> // reset baseFont between fonts
::after
</span>
<span id="font-fingerprint-serif" style="serif"> // reset baseFont between fonts
::after
</span>
</span>
</div>
Ideas/ToDo
MS Gothic + MS Mincho
=MS ゴシック+MS 明朝
MS PGothic
=MS Pゴシック
MS PMincho
=MS P明朝
Avenir Next Demi Bold
is unstable on ESRinstead we could fold it into the fntList loop as a first itemoriginal post
creepy fonts eats a lot of time: it depends on the size of the font list (set by detected OS) font list sizes: windows 468, linux 449, mac 755, droid 140
for example: if I load TZP and only run
outputFonts()
as windows (and I am windows, so the fonts exist), here's the logPerf results for that section: font list = 486a few more restarts + tests: creepJS fonts results are : 422, 418, 418 some font section re-runs: 341, 343, 354, 342
now halve the font list (slice the array to 243 items)
diffs
So basically, outside of a little perf used in setting up elements etc: the perf is directly related to the size of the font list
Perf is also related to whether or not the font exists (it takes time to load a font)
Twemoji Mozilla
on the subject of if the font exists it uses some time to load, thus affected perf, if I block document fonts, the perf is instantly 50% faster
anyway, this is a ticket for me to look at removing fonts from lists that we always expect the OS to have