forked from bcaller/font-privacy-chrome
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.js
156 lines (134 loc) · 4.76 KB
/
content.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Font Privacy extension for Chrome by Ben Caller
var fontPrivacy = '(' + function () {
'use strict';
function vecw(val, e, c, w) {
// Makes an object describing a property
return {
value: val,
enumerable: !!e,
configurable: !!c,
writable: !!w
}
}
var DEFAULT = 'auto'
var originalStyleSetProperty = CSSStyleDeclaration.prototype.setProperty
var originalSetAttrib = Element.prototype.setAttribute
var originalNodeAppendChild = Node.prototype.appendChild
var baseFonts = ["monospace", "sans-serif", "serif"]
var commonFonts = ["Wingdings","Wingdings 2","Wingdings 3"].map(function(x){return x.toLowerCase()})
var keywords = ["inherit", "auto", "default"]
baseFonts.push.apply(baseFonts, commonFonts)
baseFonts.push.apply(baseFonts, keywords)
function getAllowedFontFamily(family) {
var fonts = family.replace(/"|'/g,'').split(',')
var allowedFonts = fonts.filter(function(font) {
if(font && font.length) {
var normalised = font.trim().toLowerCase()
// Allow base fonts
for(var allowed of baseFonts)
if(normalised == allowed) return true
// Allow web fonts
for (var allowed of document.fonts.values())
if(normalised == allowed) return true
}
})
return allowedFonts.map(function(f){
var trimmed = f.trim()
return ~trimmed.indexOf(' ') ? "'" + trimmed + "'" : trimmed
}).join(", ")
}
function modifiedCssSetProperty(key, val) {
if(key.toLowerCase() == 'font-family') {
var allowed = getAllowedFontFamily(val)
var oldFF = this.fontFamily
return originalStyleSetProperty.call(this, 'font-family', allowed || DEFAULT)
}
return originalStyleSetProperty.call(this, key, val)
}
function makeModifiedSetCssText(originalSetCssText) {
return function modifiedSetCssText(css) {
var fontFamilyMatch = css.match(/\b(?:font-family:([^;]+)(?:;|$))/i)
if(fontFamilyMatch && fontFamilyMatch.length == 2) {
css = css.replace(/\b(font-family:[^;]+(;|$))/i, '').trim()
var allowed = getAllowedFontFamily(fontFamilyMatch[1]) || DEFAULT
if(css.length && css[css.length - 1] != ';')
css += ';'
css += "font-family: " + allowed + ";"
}
return originalSetCssText.call(this, css)
}
}
var modifiedSetAttribute = (function() {
var innerModify = makeModifiedSetCssText(function (val) {
return originalSetAttrib.call(this, 'style', val)
})
return function modifiedSetAttribute(key, val) {
if(key.toLowerCase() == 'style') {
return innerModify.call(this, val)
}
return originalSetAttrib.call(this, key, val)
}
})();
function makeModifiedInnerHTML(originalInnerHTML) {
return function modifiedInnerHTML(html) {
//Add as normal, then fix before returning
var retval = originalInnerHTML.call(this, html)
recursivelyModifyFonts(this.parentNode)
return retval
}
}
function recursivelyModifyFonts(elem) {
if(elem) {
if(elem.style && elem.style.fontFamily) {
modifiedCssSetProperty.call(elem.style, 'font-family', elem.style.fontFamily) // Uses the special setter
}
if(elem.childNodes)
elem.childNodes.forEach(recursivelyModifyFonts)
}
return elem
}
function modifiedAppend(child) {
child = recursivelyModifyFonts(child)
return originalNodeAppendChild.call(this, child)
}
var success = true
function overrideFunc(obj, name, f) {
try {
Object.defineProperty(obj.prototype, name, vecw(f, true))
} catch(e) {success=false;}
}
function overrideSetter(obj, name, makeSetter) {
try {
var current = Object.getOwnPropertyDescriptor(obj.prototype, name)
current.set = makeSetter(current.set)
current.configurable = false
Object.defineProperty(obj.prototype, name, current)
} catch(e) {success=false;}
}
overrideFunc(Node, 'appendChild', modifiedAppend)
overrideFunc(CSSStyleDeclaration, 'setProperty', modifiedCssSetProperty)
overrideFunc(Element, 'setAttribute', modifiedSetAttribute)
try {
Object.defineProperty(CSSStyleDeclaration.prototype, "fontFamily", {
set: function fontFamily(f) {
modifiedCssSetProperty.call(this, 'font-family', f)
},
get: function fontFamily() {
return this.getPropertyValue('font-family')
}
})
} catch(e) {success=false;}
overrideSetter(CSSStyleDeclaration,'cssText', makeModifiedSetCssText)
overrideSetter(Element,'innerHTML', makeModifiedInnerHTML)
overrideSetter(Element,'outerHTML', makeModifiedInnerHTML)
if(success) console.log("FontPrivacy hides your fonts", location.href)
} + ')();';
//Without CSP
document.documentElement.setAttribute('onreset', fontPrivacy)
document.documentElement.dispatchEvent(new CustomEvent('reset'))
document.documentElement.removeAttribute('onreset')
//With CSP
var script = document.createElement('script')
script.textContent = fontPrivacy;
(document.head||document.documentElement).appendChild(script)
script.parentNode.removeChild(script)