-
Notifications
You must be signed in to change notification settings - Fork 383
/
AgentUtils.js
351 lines (316 loc) · 16.8 KB
/
AgentUtils.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/*
* Copyright 2016, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
const AgentUtils = {
getWindowSize: function() {
let width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
let height = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
return {maxWidth: width, maxHeight: height};
},
/**
* This has been cloned from https://github.com/dwighthouse/onfontready
* because it was throwing a syntax error with IE11
*
* fontName : Font name used in the `@font-face` declaration
* onReady : Function called upon successful font load and parse detection
* options : Optional parameters
* options.timeoutAfter : Milliseconds to wait before giving up Triggers options.onTimeout call Unset or 0 will result in an indefinite wait
* options.onTimeout : Called after options.timeoutAfter milliseconds have elapsed without an onReady call
* options.sampleText : Text string used to test font loading Defaults to " " (space character)
* options.generic : Boolean set to true if attempting to detect generic family font
* root : Undefined variable used by function
* tryFinish : Undefined variable used by function
*/
onfontready: (fontNameNew, onReady, options = {}, rootNew, tryFinishNew) => {
let fontName = fontNameNew;
let root = rootNew;
let tryFinish = tryFinishNew;
// root and tryFinish parameters prevent the need for var statement
let fontNameCopy = fontName;
if (process.env.isTest) {
// Store a copy because later code will reuse the fontName variable
// Use var to pull it out of the if block's scope
const tests = {};
const tryCreate = (name) => {
tests[name] = tests[name] || {
rootCount: 0,
iframesCreated: false,
timedOut: false,
fontLoaded: false,
requiredExtraTimeout: false
};
return tests[name];
};
// A helper function tracks info about internal processes for testing
window.reporter = window.reporter || {
modifyRootCount(name, increment) {
tryCreate(name).rootCount += increment;
},
iframesCreated(name) {
tryCreate(name).iframesCreated = true;
},
timedOut(name) {
tryCreate(name).timedOut = true;
},
fontLoaded(name) {
tryCreate(name).fontLoaded = true;
},
requiredExtraTimeout(name) {
tryCreate(name).requiredExtraTimeout = true;
},
getTests() {
return tests;
}
};
}
// Ensure options is defined to prevent access errors
// A 0 timeoutAfter will prevent the timeout functionality
if (options.timeoutAfter) {
setTimeout(() => {
// Prevent onTimeout call after shutdown
if (root) {
if (process.env.isTest) {
window.reporter.modifyRootCount(fontNameCopy, -1);
}
// Shutdown should occur even if onTimeout is not defined
document.body.removeChild(root);
// Break the reference to the DOM element to allow GC to run
// Assigning to 0 also results in falsy root tests elsewhere
root = 0;
// This won't prevent TypeError if onTimeout is not a function
if (options.onTimeout) {
if (process.env.isTest) {
window.reporter.timedOut(fontNameCopy);
}
options.onTimeout();
}
}
}, options.timeoutAfter);
}
// Measures the test elements to determine if the font has loaded
// Always safe to call, even after shutdown
// Using function assignment compresses better than function declaration
tryFinish = () => {
// Prevent test or onReady call after shutdown
// The width of the parent elements are influenced by the children,
// so it is sufficient to measure the parents
// clientWidth only measures to integer accuracy
// This is sufficient for such large font sizes (999px)
// Both compared values are integers, so double equality is sufficient
if (root && root.firstChild.clientWidth === root.lastChild.clientWidth) {
if (process.env.isTest) {
window.reporter.modifyRootCount(fontNameCopy, -1);
}
document.body.removeChild(root);
// Break the reference to the DOM element to allow GC to run
// Assigning to 0 also results in falsy root tests elsewhere
root = 0;
if (process.env.isTest) {
window.reporter.fontLoaded(fontNameCopy);
}
onReady();
}
};
if (process.env.isTest) {
window.reporter.modifyRootCount(fontNameCopy, 1);
}
if (!process.env.isLegacy) {
// Attempt to finish early if the font is already loaded
// The tryFinish call happens after the test elements are added
tryFinish(
// Save bytes by creating and assigning the root div inside call
// appendChild returns the root, allowing innerHTML usage inline
document.body.appendChild(root = document.createElement('div')).innerHTML =
// position:fixed breaks the element out of page flow
// Being out of flow makes the div size to the text
// white-space:pre ensures no text wrapping will occur
// Out of bounds percentage bottom/right prevents scrollbars
// font combines font-size and font-family
// Font size 999px differentiates fallback fonts
// Using font size in pixels prevents possible
// failure due to zero-sized default page fonts
// Using a <pre> instead of a <div> tag might be smaller, but
// it is more likely to interfere with page styles
'<div style="position:fixed;white-space:pre;bottom:999%;right:999%;font:999px ' +
// Generic fonts should be quote-less
(options.generic ? '' : "'") + fontName + (options.generic ? '' : "'") +
// Fallback font sizes text differently from
// the adjacent div until the font has loaded
',serif">' +
// A single space is the text default
(options.sampleText || ' ') +
'</div>' +
'<div style="position:fixed;white-space:pre;bottom:999%;right:999%;font:999px ' +
(options.generic ? '' : "'") + fontName + (options.generic ? '' : "'") +
',monospace">' +
(options.sampleText || ' ') +
'</div>'
);
}
if (process.env.isLegacy) {
// Attempt to finish early if the font is already loaded
// The tryFinish call happens after the test elements are added
tryFinish(
// Save bytes by creating and assigning the root div inside call
// appendChild returns the root, allowing innerHTML usage inline
document.body.appendChild(root = document.createElement('div')).innerHTML =
// IE6 cannot create automatically sized divs that will
// contain an absolutely positioned element
// Such elements will instead break out of their bounds
// The only other method to associate the width of one
// element's natural size with the size of another is table
// Style value with no spaces does not require quotes
// position:absolute breaks the element out of page flow
// IE6 does not support position:fixed
// Out of bounds percentage bottom/right prevents scrollbars
// width:auto prevents interference from width:100% styles
// which are commonly added
'<table style=position:absolute;bottom:999%;right:999%;width:auto>' +
// <tbody> tag is implied
'<tr>' +
// position:relative allows the iframe's absolute
// positioning to be relative to the <td>
'<td style=position:relative>' +
// </td> is implied
// </tr> is implied
'<tr>' +
// Inner <span> needs surrounding, equal-sized periods
// to prevent some older browsers from collapsing the
// whitespace character (space) at insertion time
// Monospace font compresses better than serif here
// white-space:pre ensures no text wrapping will occur
'<td style="font:999px monospace;white-space:pre">' +
// font combines font-size and font-family
// Font size 999px differentiates fallback fonts
// Using font size in pixels prevents possible
// failure due to zero-sized default page fonts
'.<span style="font:999px ' +
// Generic fonts should be quote-less
// (except in IE6 and IE7, due to bug)
(options.generic ? '' : "'") + fontName + (options.generic ? '' : "'") +
// Fallback font sizes text differently from
// the adjacent div until the font has loaded
',serif">' +
// A single space is the text default
(options.sampleText || ' ') +
'</span>.' +
// Closing tags for <td>, <tr>, and <tbody> are implied
'</table>' +
'<table style=position:absolute;bottom:999%;right:999%;width:auto>' +
'<tr>' +
'<td style=position:relative>' +
'<tr>' +
'<td style="font:999px monospace;white-space:pre">' +
'.<span style="font:999px ' +
(options.generic ? '' : "'") + fontName + (options.generic ? '' : "'") +
',monospace">' +
(options.sampleText || ' ') +
'</span>.' +
'</table>'
);
}
// If the font is already loaded, tryFinish will have already destroyed
// the root reference, so the iframes will never be inserted
if (root) {
if (process.env.isTest) {
window.reporter.iframesCreated(fontNameCopy);
}
if (!process.env.isLegacy) {
// The fontName value has already been used, reuse for reference
// Save bytes by creating and assigning the iframe inside call
// appendChild returns the iframe, allowing style usage inline
// The iframe's width only needs to be relative to the parent's
root.firstChild.appendChild(
fontName = document.createElement('iframe')
).style.width = '999%';
// contentWindow becomes available upon DOM insertion
// Assigning a non-closure function to onresize prevents the
// possibility of memory leaks through event handlers
fontName.contentWindow.onresize = tryFinish;
// By reusing the fontName (via reassignment), the DOM reference
// to the first iframe is broken, reducing memory leak potential
root.lastChild.appendChild(
fontName = document.createElement('iframe')
).style.width = '999%';
fontName.contentWindow.onresize = tryFinish;
}
if (process.env.isLegacy) {
// The fontName value has already been used, reuse for reference
// Save bytes by creating and assigning the iframe inside call
// Save bytes by duplicating the deeply nested DOM insertion
// appendChild returns the iframe, allowing style usage inline
// position:absolute prevents the iframe from influencing the
// table's width
// Some IE browsers will generate scrollbars if the iframe
// isn't positioned to the top-left
// The iframe's width only needs to be relative to the parent's
root.firstChild.firstChild.firstChild.firstChild.appendChild(
fontName = document.createElement('iframe')
).style.cssText = 'position:absolute;bottom:999%;right:999%;width:999%';
// contentWindow becomes available upon DOM insertion
// Assigning a non-closure function to onresize prevents the
// possibility of memory leaks through event handlers
// Older IE browsers require iframe onresize event handlers
// to be attached via attachEvent
if (fontName.attachEvent) {
fontName.contentWindow.attachEvent('onresize', tryFinish);
} else {
fontName.contentWindow.onresize = tryFinish;
}
// By reusing the fontName (via reassignment), the DOM reference
// to the first iframe is broken, reducing memory leak potential
root.lastChild.firstChild.firstChild.firstChild.appendChild(
fontName = document.createElement('iframe')
).style.cssText = 'position:absolute;bottom:999%;right:999%;width:999%';
if (fontName.attachEvent) {
fontName.contentWindow.attachEvent('onresize', tryFinish);
} else {
fontName.contentWindow.onresize = tryFinish;
}
}
if (!process.env.isTest) {
// Because of iframe loading nuances, sometimes the font can finish
// loading after the root is inserted, but before the onresize
// event handler can be added to an iframe, creating a potential
// for a missed resize event
// This setTimeout gives the browser an additional chance to catch
// a font load after returning control to the browser
// By assigning the result of setTimeout (a timeout ID) to the
// fontName, the DOM reference to the second iframe is broken,
// further reducing the posibility of memory leaks
fontName = setTimeout(tryFinish);
}
if (process.env.isTest) {
// When testing, report that this timeout was used
fontName = setTimeout(function() {
if (root) {
window.reporter.requiredExtraTimeout(fontNameCopy);
tryFinish();
}
});
}
}
},
/**
uses the onfontready function in a Promise throwing an error it the promise rejects (after 3000ms)
*/
loadFont: (fontName, options = {}) =>
new Promise((resolve, reject) => {
AgentUtils.onfontready(fontName, resolve, {
timeoutAfter: options.timeoutAfter,
// Catch should only occur if onTimeout would normally be called
onTimeout: reject,
sampleText: options.sampleText,
generic: options.generic
});
})
};
module.exports = AgentUtils;