Add Websites/browserbench.org
[WebKit-https.git] / Websites / browserbench.org / MotionMark / 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     stripNonASCIICharacters: function(inputString)
122     {
123         return inputString.replace(/[ .,]/g, '');
124     },
125
126     convertObjectToQueryString: function(object)
127     {
128         var queryString = [];
129         for (var property in object) {
130             if (object.hasOwnProperty(property))
131                 queryString.push(encodeURIComponent(property) + "=" + encodeURIComponent(object[property]));
132         }
133         return "?" + queryString.join("&");
134     },
135
136     convertQueryStringToObject: function(queryString)
137     {
138         queryString = queryString.substring(1);
139         if (!queryString)
140             return null;
141
142         var object = {};
143         queryString.split("&").forEach(function(parameter) {
144             var components = parameter.split("=");
145             object[components[0]] = components[1];
146         });
147         return object;
148     },
149
150     progressValue: function(value, min, max)
151     {
152         return (value - min) / (max - min);
153     },
154
155     lerp: function(value, min, max)
156     {
157         return min + (max - min) * value;
158     },
159
160     toFixedNumber: function(number, precision)
161     {
162         if (number.toFixed)
163             return Number(number.toFixed(precision));
164         return number;
165     }
166 };
167
168 Array.prototype.swap = function(i, j)
169 {
170     var t = this[i];
171     this[i] = this[j];
172     this[j] = t;
173     return this;
174 }
175
176 if (!Array.prototype.fill) {
177     Array.prototype.fill = function(value) {
178         if (this == null)
179             throw new TypeError('Array.prototype.fill called on null or undefined');
180
181         var object = Object(this);
182         var len = parseInt(object.length, 10);
183         var start = arguments[1];
184         var relativeStart = parseInt(start, 10) || 0;
185         var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
186         var end = arguments[2];
187         var relativeEnd = end === undefined ? len : (parseInt(end) || 0) ;
188         var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
189
190         for (; k < final; k++)
191             object[k] = value;
192
193         return object;
194     };
195 }
196
197 if (!Array.prototype.find) {
198     Array.prototype.find = function(predicate) {
199         if (this == null)
200             throw new TypeError('Array.prototype.find called on null or undefined');
201         if (typeof predicate !== 'function')
202             throw new TypeError('predicate must be a function');
203
204         var list = Object(this);
205         var length = list.length >>> 0;
206         var thisArg = arguments[1];
207         var value;
208
209         for (var i = 0; i < length; i++) {
210             value = list[i];
211             if (predicate.call(thisArg, value, i, list))
212                 return value;
213         }
214         return undefined;
215     };
216 }
217
218 Array.prototype.shuffle = function()
219 {
220     for (var index = this.length - 1; index >= 0; --index) {
221         var randomIndex = Math.floor(Math.random() * (index + 1));
222         this.swap(index, randomIndex);
223     }
224     return this;
225 }
226
227 Point = Utilities.createClass(
228     function(x, y)
229     {
230         this.x = x;
231         this.y = y;
232     }, {
233
234     // Used when the point object is used as a size object.
235     get width()
236     {
237         return this.x;
238     },
239
240     // Used when the point object is used as a size object.
241     get height()
242     {
243         return this.y;
244     },
245
246     // Used when the point object is used as a size object.
247     get center()
248     {
249         return new Point(this.x / 2, this.y / 2);
250     },
251
252     str: function()
253     {
254         return "x = " + this.x + ", y = " + this.y;
255     },
256
257     add: function(other)
258     {
259         if(isNaN(other.x))
260             return new Point(this.x + other, this.y + other);
261         return new Point(this.x + other.x, this.y + other.y);
262     },
263
264     subtract: function(other)
265     {
266         if(isNaN(other.x))
267             return new Point(this.x - other, this.y - other);
268         return new Point(this.x - other.x, this.y - other.y);
269     },
270
271     multiply: function(other)
272     {
273         if(isNaN(other.x))
274             return new Point(this.x * other, this.y * other);
275         return new Point(this.x * other.x, this.y * other.y);
276     },
277
278     move: function(angle, velocity, timeDelta)
279     {
280         return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
281     },
282
283     length: function() {
284         return Math.sqrt( this.x * this.x + this.y * this.y );
285     },
286
287     normalize: function() {
288         var l = Math.sqrt( this.x * this.x + this.y * this.y );
289         this.x /= l;
290         this.y /= l;
291         return this;
292     }
293 });
294
295 Utilities.extendObject(Point, {
296     zero: new Point(0, 0),
297
298     pointOnCircle: function(angle, radius)
299     {
300         return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
301     },
302
303     pointOnEllipse: function(angle, radiuses)
304     {
305         return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
306     },
307
308     elementClientSize: function(element)
309     {
310         var rect = element.getBoundingClientRect();
311         return new Point(rect.width, rect.height);
312     }
313 });
314
315 Insets = Utilities.createClass(
316     function(top, right, bottom, left)
317     {
318         this.top = top;
319         this.right = right;
320         this.bottom = bottom;
321         this.left = left;
322     }, {
323
324     get width()
325     {
326         return this.left + this.right;
327     },
328
329     get height()
330     {
331         return this.top + this.bottom;
332     },
333
334     get size()
335     {
336         return new Point(this.width, this.height);
337     }
338 });
339
340 Insets.elementPadding = function(element)
341 {
342     var styles = window.getComputedStyle(element);
343     return new Insets(
344         parseFloat(styles.paddingTop),
345         parseFloat(styles.paddingRight),
346         parseFloat(styles.paddingBottom),
347         parseFloat(styles.paddingTop));
348 }
349
350 UnitBezier = Utilities.createClass(
351     function(point1, point2)
352     {
353         // First and last points in the B├ęzier curve are assumed to be (0,0) and (!,1)
354         this._c = point1.multiply(3);
355         this._b = point2.subtract(point1).multiply(3).subtract(this._c);
356         this._a = new Point(1, 1).subtract(this._c).subtract(this._b);
357     }, {
358
359     epsilon: 1e-5,
360     derivativeEpsilon: 1e-6,
361
362     solve: function(x)
363     {
364         return this.sampleY(this.solveForT(x));
365     },
366
367     sampleX: function(t)
368     {
369         return ((this._a.x * t + this._b.x) * t + this._c.x) * t;
370     },
371
372     sampleY: function(t)
373     {
374         return ((this._a.y * t + this._b.y) * t + this._c.y) * t;kkkj
375     },
376
377     sampleDerivativeX: function(t)
378     {
379         return(3 * this._a.x * t + 2 * this._b.x) * t + this._c.x;
380     },
381
382     solveForT: function(x)
383     {
384         var t0, t1, t2, x2, d2, i;
385
386         for (t2 = x, i = 0; i < 8; ++i) {
387             x2 = this.sampleX(t2) - x;
388             if (Math.abs(x2) < this.epsilon)
389                 return t2;
390             d2 = this.sampleDerivativeX(t2);
391             if (Math.abs(d2) < this.derivativeEpsilon)
392                 break;
393             t2 = t2 - x2 / d2;
394         }
395
396         t0 = 0;
397         t1 = 1;
398         t2 = x;
399
400         if (t2 < t0)
401             return t0;
402         if (t2 > t1)
403             return t1;
404
405         while (t0 < t1) {
406             x2 = this.sampleX(t2);
407             if (Math.abs(x2 - x) < this.epsilon)
408                 return t2;
409             if (x > x2)
410                 t0 = t2;
411             else
412                 t1 = t2;
413             t2 = (t1 - t0) * .5 + t0;
414         }
415
416         return t2;
417     }
418 });
419
420 SimplePromise = Utilities.createClass(
421     function()
422     {
423         this._chainedPromise = null;
424         this._callback = null;
425     }, {
426
427     then: function (callback)
428     {
429         if (this._callback)
430             throw "SimplePromise doesn't support multiple calls to then";
431
432         this._callback = callback;
433         this._chainedPromise = new SimplePromise;
434
435         if (this._resolved)
436             this.resolve(this._resolvedValue);
437
438         return this._chainedPromise;
439     },
440
441     resolve: function (value)
442     {
443         if (!this._callback) {
444             this._resolved = true;
445             this._resolvedValue = value;
446             return;
447         }
448
449         var result = this._callback(value);
450         if (result instanceof SimplePromise) {
451             var chainedPromise = this._chainedPromise;
452             result.then(function (result) { chainedPromise.resolve(result); });
453         } else
454             this._chainedPromise.resolve(result);
455     }
456 });
457
458 var Heap = Utilities.createClass(
459     function(maxSize, compare)
460     {
461         this._maxSize = maxSize;
462         this._compare = compare;
463         this._size = 0;
464         this._values = new Array(this._maxSize);
465     }, {
466
467     // This is a binary heap represented in an array. The root element is stored
468     // in the first element in the array. The root is followed by its two children.
469     // Then its four grandchildren and so on. So every level in the binary heap is
470     // doubled in the following level. Here is an example of the node indices and
471     // how they are related to their parents and children.
472     // ===========================================================================
473     //              0       1       2       3       4       5       6
474     // PARENT       -1      0       0       1       1       2       2
475     // LEFT         1       3       5       7       9       11      13
476     // RIGHT        2       4       6       8       10      12      14
477     // ===========================================================================
478     _parentIndex: function(i)
479     {
480         return i > 0 ? Math.floor((i - 1) / 2) : -1;
481     },
482
483     _leftIndex: function(i)
484     {
485         var leftIndex = i * 2 + 1;
486         return leftIndex < this._size ? leftIndex : -1;
487     },
488
489     _rightIndex: function(i)
490     {
491         var rightIndex = i * 2 + 2;
492         return rightIndex < this._size ? rightIndex : -1;
493     },
494
495     // Return the child index that may violate the heap property at index i.
496     _childIndex: function(i)
497     {
498         var left = this._leftIndex(i);
499         var right = this._rightIndex(i);
500
501         if (left != -1 && right != -1)
502             return this._compare(this._values[left], this._values[right]) > 0 ? left : right;
503
504         return left != -1 ? left : right;
505     },
506
507     init: function()
508     {
509         this._size = 0;
510     },
511
512     top: function()
513     {
514         return this._size ? this._values[0] : NaN;
515     },
516
517     push: function(value)
518     {
519         if (this._size == this._maxSize) {
520             // If size is bounded and the new value can be a parent of the top()
521             // if the size were unbounded, just ignore the new value.
522             if (this._compare(value, this.top()) > 0)
523                 return;
524             this.pop();
525         }
526         this._values[this._size++] = value;
527         this._bubble(this._size - 1);
528     },
529
530     pop: function()
531     {
532         if (!this._size)
533             return NaN;
534
535         this._values[0] = this._values[--this._size];
536         this._sink(0);
537     },
538
539     _bubble: function(i)
540     {
541         // Fix the heap property at index i given that parent is the only node that
542         // may violate the heap property.
543         for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) {
544             if (this._compare(this._values[pi], this._values[i]) > 0)
545                 break;
546
547             this._values.swap(pi, i);
548         }
549     },
550
551     _sink: function(i)
552     {
553         // Fix the heap property at index i given that each of the left and the right
554         // sub-trees satisfies the heap property.
555         for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) {
556             if (this._compare(this._values[i], this._values[ci]) > 0)
557                 break;
558
559             this._values.swap(ci, i);
560         }
561     },
562
563     str: function()
564     {
565         var out = "Heap[" + this._size + "] = [";
566         for (var i = 0; i < this._size; ++i) {
567             out += this._values[i];
568             if (i < this._size - 1)
569                 out += ", ";
570         }
571         return out + "]";
572     },
573
574     values: function(size) {
575         // Return the last "size" heap elements values.
576         var values = this._values.slice(0, this._size);
577         return values.sort(this._compare).slice(0, Math.min(size, this._size));
578     }
579 });
580
581 Utilities.extendObject(Heap, {
582     createMinHeap: function(maxSize)
583     {
584         return new Heap(maxSize, function(a, b) { return b - a; });
585     },
586
587     createMaxHeap: function(maxSize) {
588         return new Heap(maxSize, function(a, b) { return a - b; });
589     }
590 });
591
592 var SampleData = Utilities.createClass(
593     function(fieldMap, data)
594     {
595         this.fieldMap = fieldMap || {};
596         this.data = data || [];
597     }, {
598
599     get length()
600     {
601         return this.data.length;
602     },
603
604     addField: function(name, index)
605     {
606         this.fieldMap[name] = index;
607     },
608
609     push: function(datum)
610     {
611         this.data.push(datum);
612     },
613
614     sort: function(sortFunction)
615     {
616         this.data.sort(sortFunction);
617     },
618
619     slice: function(begin, end)
620     {
621         return new SampleData(this.fieldMap, this.data.slice(begin, end));
622     },
623
624     forEach: function(iterationFunction)
625     {
626         this.data.forEach(iterationFunction);
627     },
628
629     createDatum: function()
630     {
631         return [];
632     },
633
634     getFieldInDatum: function(datum, fieldName)
635     {
636         if (typeof datum === 'number')
637             datum = this.data[datum];
638         return datum[this.fieldMap[fieldName]];
639     },
640
641     setFieldInDatum: function(datum, fieldName, value)
642     {
643         if (typeof datum === 'number')
644             datum = this.data[datum];
645         return datum[this.fieldMap[fieldName]] = value;
646     },
647
648     at: function(index)
649     {
650         return this.data[index];
651     },
652
653     toArray: function()
654     {
655         var array = [];
656
657         this.data.forEach(function(datum) {
658             var newDatum = {};
659             array.push(newDatum);
660
661             for (var fieldName in this.fieldMap) {
662                 var value = this.getFieldInDatum(datum, fieldName);
663                 if (value !== null && value !== undefined)
664                     newDatum[fieldName] = value;
665             }
666         }, this);
667
668         return array;
669     }
670 });