Add text tests
[WebKit-https.git] / PerformanceTests / Animometer / resources / extensions.js
1 Utilities =
2 {
3     _parse: function(str, sep)
4     {
5         var output = {};
6         str.split(sep).forEach(function(part) {
7             var item = part.split("=");
8             var value = decodeURIComponent(item[1]);
9             if (value[0] == "'" )
10                 output[item[0]] = value.substr(1, value.length - 2);
11             else
12                 output[item[0]] = value;
13           });
14         return output;
15     },
16
17     parseParameters: function()
18     {
19         return this._parse(window.location.search.substr(1), "&");
20     },
21
22     parseArguments: function(str)
23     {
24         return this._parse(str, " ");
25     },
26
27     extendObject: function(obj1, obj2)
28     {
29         for (var attrname in obj2)
30             obj1[attrname] = obj2[attrname];
31         return obj1;
32     },
33
34     copyObject: function(obj)
35     {
36         return this.extendObject({}, obj);
37     },
38
39     mergeObjects: function(obj1, obj2)
40     {
41         return this.extendObject(this.copyObject(obj1), obj2);
42     },
43
44     createClass: function(classConstructor, classMethods)
45     {
46         classConstructor.prototype = classMethods;
47         return classConstructor;
48     },
49
50     createSubclass: function(superclass, classConstructor, classMethods)
51     {
52         classConstructor.prototype = Object.create(superclass.prototype);
53         classConstructor.prototype.constructor = classConstructor;
54         if (classMethods)
55             Utilities.extendObject(classConstructor.prototype, classMethods);
56         return classConstructor;
57     },
58
59     createElement: function(name, attrs, parentElement)
60     {
61         var element = document.createElement(name);
62
63         for (var key in attrs)
64             element.setAttribute(key, attrs[key]);
65
66         parentElement.appendChild(element);
67         return element;
68     },
69
70     createSVGElement: function(name, attrs, xlinkAttrs, parentElement)
71     {
72         const svgNamespace = "http://www.w3.org/2000/svg";
73         const xlinkNamespace = "http://www.w3.org/1999/xlink";
74
75         var element = document.createElementNS(svgNamespace, name);
76
77         for (var key in attrs)
78             element.setAttribute(key, attrs[key]);
79
80         for (var key in xlinkAttrs)
81             element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
82
83         parentElement.appendChild(element);
84         return element;
85     },
86
87     browserPrefix: function()
88     {
89         // Get the HTML element's CSSStyleDeclaration
90         var styles = window.getComputedStyle(document.documentElement, '');
91
92         // Convert the styles list to an array
93         var stylesArray = Array.prototype.slice.call(styles);
94
95         // Concatenate all the styles in one big string
96         var stylesString = stylesArray.join('');
97
98         // Search the styles string for a known prefix type, settle on Opera if none is found.
99         var prefixes = stylesString.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']);
100
101         // prefixes has two elements; e.g. for webkit it has ['-webkit-', 'webkit'];
102         var prefix = prefixes[1];
103
104         // Have 'O' before 'Moz' in the string so it is matched first.
105         var dom = ('WebKit|O|Moz|MS').match(new RegExp(prefix, 'i'))[0];
106
107         // Return all the required prefixes.
108         return {
109             dom: dom,
110             lowercase: prefix,
111             css: '-' + prefix + '-',
112             js: prefix[0].toUpperCase() + prefix.substr(1)
113         };
114     },
115
116     setElementPrefixedProperty: function(element, property, value)
117     {
118         element.style[property] = element.style[this.browserPrefix().js + property[0].toUpperCase() + property.substr(1)] = value;
119     },
120
121     progressValue: function(value, min, max)
122     {
123         return (value - min) / (max - min);
124     },
125
126     lerp: function(value, min, max)
127     {
128         return min + (max - min) * value;
129     }
130 };
131
132 Array.prototype.swap = function(i, j)
133 {
134     var t = this[i];
135     this[i] = this[j];
136     this[j] = t;
137     return this;
138 }
139
140 if (!Array.prototype.fill) {
141     Array.prototype.fill = function(value) {
142         if (this == null)
143             throw new TypeError('Array.prototype.fill called on null or undefined');
144
145         var object = Object(this);
146         var len = parseInt(object.length, 10);
147         var start = arguments[1];
148         var relativeStart = parseInt(start, 10) || 0;
149         var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
150         var end = arguments[2];
151         var relativeEnd = end === undefined ? len : (parseInt(end) || 0) ;
152         var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
153
154         for (; k < final; k++)
155             object[k] = value;
156
157         return object;
158     };
159 }
160
161 if (!Array.prototype.find) {
162     Array.prototype.find = function(predicate) {
163         if (this == null)
164             throw new TypeError('Array.prototype.find called on null or undefined');
165         if (typeof predicate !== 'function')
166             throw new TypeError('predicate must be a function');
167
168         var list = Object(this);
169         var length = list.length >>> 0;
170         var thisArg = arguments[1];
171         var value;
172
173         for (var i = 0; i < length; i++) {
174             value = list[i];
175             if (predicate.call(thisArg, value, i, list))
176                 return value;
177         }
178         return undefined;
179     };
180 }
181
182 Array.prototype.shuffle = function()
183 {
184     for (var index = this.length - 1; index >= 0; --index) {
185         var randomIndex = Math.floor(Math.random() * (index + 1));
186         this.swap(index, randomIndex);
187     }
188     return this;
189 }
190
191 Point = Utilities.createClass(
192     function(x, y)
193     {
194         this.x = x;
195         this.y = y;
196     }, {
197
198     // Used when the point object is used as a size object.
199     get width()
200     {
201         return this.x;
202     },
203
204     // Used when the point object is used as a size object.
205     get height()
206     {
207         return this.y;
208     },
209
210     // Used when the point object is used as a size object.
211     get center()
212     {
213         return new Point(this.x / 2, this.y / 2);
214     },
215
216     str: function()
217     {
218         return "x = " + this.x + ", y = " + this.y;
219     },
220
221     add: function(other)
222     {
223         if(isNaN(other.x))
224             return new Point(this.x + other, this.y + other);
225         return new Point(this.x + other.x, this.y + other.y);
226     },
227
228     subtract: function(other)
229     {
230         if(isNaN(other.x))
231             return new Point(this.x - other, this.y - other);
232         return new Point(this.x - other.x, this.y - other.y);
233     },
234
235     multiply: function(other)
236     {
237         if(isNaN(other.x))
238             return new Point(this.x * other, this.y * other);
239         return new Point(this.x * other.x, this.y * other.y);
240     },
241
242     move: function(angle, velocity, timeDelta)
243     {
244         return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
245     },
246
247     length: function() {
248         return Math.sqrt( this.x * this.x + this.y * this.y );
249     },
250
251     normalize: function() {
252         var l = Math.sqrt( this.x * this.x + this.y * this.y );
253         this.x /= l;
254         this.y /= l;
255         return this;
256     }
257 });
258
259 Utilities.extendObject(Point, {
260     zero: function()
261     {
262         return new Point(0, 0);
263     },
264
265     pointOnCircle: function(angle, radius)
266     {
267         return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
268     },
269
270     pointOnEllipse: function(angle, radiuses)
271     {
272         return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
273     },
274
275     elementClientSize: function(element)
276     {
277         var rect = element.getBoundingClientRect();
278         return new Point(rect.width, rect.height);
279     }
280 });
281
282 Insets = Utilities.createClass(
283     function(top, right, bottom, left)
284     {
285         this.top = top;
286         this.right = right;
287         this.bottom = bottom;
288         this.left = left;
289     }, {
290
291     get width()
292     {
293         return this.left + this.right;
294     },
295
296     get height()
297     {
298         return this.top + this.bottom;
299     },
300
301     get size()
302     {
303         return new Point(this.width, this.height);
304     }
305 });
306
307 Insets.elementPadding = function(element)
308 {
309     var styles = window.getComputedStyle(element);
310     return new Insets(
311         parseFloat(styles.paddingTop),
312         parseFloat(styles.paddingRight),
313         parseFloat(styles.paddingBottom),
314         parseFloat(styles.paddingTop));
315 }
316
317 UnitBezier = Utilities.createClass(
318     function(point1, point2)
319     {
320         // First and last points in the B├ęzier curve are assumed to be (0,0) and (!,1)
321         this._c = point1.multiply(3);
322         this._b = point2.subtract(point1).multiply(3).subtract(this._c);
323         this._a = new Point(1, 1).subtract(this._c).subtract(this._b);
324     }, {
325
326     epsilon: 1e-5,
327     derivativeEpsilon: 1e-6,
328
329     solve: function(x)
330     {
331         return this.sampleY(this.solveForT(x));
332     },
333
334     sampleX: function(t)
335     {
336         return ((this._a.x * t + this._b.x) * t + this._c.x) * t;
337     },
338
339     sampleY: function(t)
340     {
341         return ((this._a.y * t + this._b.y) * t + this._c.y) * t;kkkj
342     },
343
344     sampleDerivativeX: function(t)
345     {
346         return(3 * this._a.x * t + 2 * this._b.x) * t + this._c.x;
347     },
348
349     solveForT: function(x)
350     {
351         var t0, t1, t2, x2, d2, i;
352
353         for (t2 = x, i = 0; i < 8; ++i) {
354             x2 = this.sampleX(t2) - x;
355             if (Math.abs(x2) < this.epsilon)
356                 return t2;
357             d2 = this.sampleDerivativeX(t2);
358             if (Math.abs(d2) < this.derivativeEpsilon)
359                 break;
360             t2 = t2 - x2 / d2;
361         }
362
363         t0 = 0;
364         t1 = 1;
365         t2 = x;
366
367         if (t2 < t0)
368             return t0;
369         if (t2 > t1)
370             return t1;
371
372         while (t0 < t1) {
373             x2 = this.sampleX(t2);
374             if (Math.abs(x2 - x) < this.epsilon)
375                 return t2;
376             if (x > x2)
377                 t0 = t2;
378             else
379                 t1 = t2;
380             t2 = (t1 - t0) * .5 + t0;
381         }
382
383         return t2;
384     }
385 });
386
387 SimplePromise = Utilities.createClass(
388     function()
389     {
390         this._chainedPromise = null;
391         this._callback = null;
392     }, {
393
394     then: function (callback)
395     {
396         if (this._callback)
397             throw "SimplePromise doesn't support multiple calls to then";
398
399         this._callback = callback;
400         this._chainedPromise = new SimplePromise;
401
402         if (this._resolved)
403             this.resolve(this._resolvedValue);
404
405         return this._chainedPromise;
406     },
407
408     resolve: function (value)
409     {
410         if (!this._callback) {
411             this._resolved = true;
412             this._resolvedValue = value;
413             return;
414         }
415
416         var result = this._callback(value);
417         if (result instanceof SimplePromise) {
418             var chainedPromise = this._chainedPromise;
419             result.then(function (result) { chainedPromise.resolve(result); });
420         } else
421             this._chainedPromise.resolve(result);
422     }
423 });
424
425 var Heap = Utilities.createClass(
426     function(maxSize, compare)
427     {
428         this._maxSize = maxSize;
429         this._compare = compare;
430         this._size = 0;
431         this._values = new Array(this._maxSize);
432     }, {
433
434     // This is a binary heap represented in an array. The root element is stored
435     // in the first element in the array. The root is followed by its two children.
436     // Then its four grandchildren and so on. So every level in the binary heap is
437     // doubled in the following level. Here is an example of the node indices and
438     // how they are related to their parents and children.
439     // ===========================================================================
440     //              0       1       2       3       4       5       6
441     // PARENT       -1      0       0       1       1       2       2
442     // LEFT         1       3       5       7       9       11      13
443     // RIGHT        2       4       6       8       10      12      14
444     // ===========================================================================
445     _parentIndex: function(i)
446     {
447         return i > 0 ? Math.floor((i - 1) / 2) : -1;
448     },
449
450     _leftIndex: function(i)
451     {
452         var leftIndex = i * 2 + 1;
453         return leftIndex < this._size ? leftIndex : -1;
454     },
455
456     _rightIndex: function(i)
457     {
458         var rightIndex = i * 2 + 2;
459         return rightIndex < this._size ? rightIndex : -1;
460     },
461
462     // Return the child index that may violate the heap property at index i.
463     _childIndex: function(i)
464     {
465         var left = this._leftIndex(i);
466         var right = this._rightIndex(i);
467
468         if (left != -1 && right != -1)
469             return this._compare(this._values[left], this._values[right]) > 0 ? left : right;
470
471         return left != -1 ? left : right;
472     },
473
474     init: function()
475     {
476         this._size = 0;
477     },
478
479     top: function()
480     {
481         return this._size ? this._values[0] : NaN;
482     },
483
484     push: function(value)
485     {
486         if (this._size == this._maxSize) {
487             // If size is bounded and the new value can be a parent of the top()
488             // if the size were unbounded, just ignore the new value.
489             if (this._compare(value, this.top()) > 0)
490                 return;
491             this.pop();
492         }
493         this._values[this._size++] = value;
494         this._bubble(this._size - 1);
495     },
496
497     pop: function()
498     {
499         if (!this._size)
500             return NaN;
501
502         this._values[0] = this._values[--this._size];
503         this._sink(0);
504     },
505
506     _bubble: function(i)
507     {
508         // Fix the heap property at index i given that parent is the only node that
509         // may violate the heap property.
510         for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) {
511             if (this._compare(this._values[pi], this._values[i]) > 0)
512                 break;
513
514             this._values.swap(pi, i);
515         }
516     },
517
518     _sink: function(i)
519     {
520         // Fix the heap property at index i given that each of the left and the right
521         // sub-trees satisfies the heap property.
522         for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) {
523             if (this._compare(this._values[i], this._values[ci]) > 0)
524                 break;
525
526             this._values.swap(ci, i);
527         }
528     },
529
530     str: function()
531     {
532         var out = "Heap[" + this._size + "] = [";
533         for (var i = 0; i < this._size; ++i) {
534             out += this._values[i];
535             if (i < this._size - 1)
536                 out += ", ";
537         }
538         return out + "]";
539     },
540
541     values: function(size) {
542         // Return the last "size" heap elements values.
543         var values = this._values.slice(0, this._size);
544         return values.sort(this._compare).slice(0, Math.min(size, this._size));
545     }
546 });
547
548 Utilities.extendObject(Heap, {
549     createMinHeap: function(maxSize)
550     {
551         return new Heap(maxSize, function(a, b) { return b - a; });
552     },
553
554     createMaxHeap: function(maxSize) {
555         return new Heap(maxSize, function(a, b) { return a - b; });
556     }
557 });