Strip out more characters when creating permalinks
[WebKit-https.git] / PerformanceTests / MotionMark / resources / extensions.js
1 /*
2  * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 Utilities =
26 {
27     _parse: function(str, sep)
28     {
29         var output = {};
30         str.split(sep).forEach(function(part) {
31             var item = part.split("=");
32             var value = decodeURIComponent(item[1]);
33             if (value[0] == "'" )
34                 output[item[0]] = value.substr(1, value.length - 2);
35             else
36                 output[item[0]] = value;
37           });
38         return output;
39     },
40
41     parseParameters: function()
42     {
43         return this._parse(window.location.search.substr(1), "&");
44     },
45
46     parseArguments: function(str)
47     {
48         return this._parse(str, " ");
49     },
50
51     extendObject: function(obj1, obj2)
52     {
53         for (var attrname in obj2)
54             obj1[attrname] = obj2[attrname];
55         return obj1;
56     },
57
58     copyObject: function(obj)
59     {
60         return this.extendObject({}, obj);
61     },
62
63     mergeObjects: function(obj1, obj2)
64     {
65         return this.extendObject(this.copyObject(obj1), obj2);
66     },
67
68     createClass: function(classConstructor, classMethods)
69     {
70         classConstructor.prototype = classMethods;
71         return classConstructor;
72     },
73
74     createSubclass: function(superclass, classConstructor, classMethods)
75     {
76         classConstructor.prototype = Object.create(superclass.prototype);
77         classConstructor.prototype.constructor = classConstructor;
78         if (classMethods)
79             Utilities.extendObject(classConstructor.prototype, classMethods);
80         return classConstructor;
81     },
82
83     createElement: function(name, attrs, parentElement)
84     {
85         var element = document.createElement(name);
86
87         for (var key in attrs)
88             element.setAttribute(key, attrs[key]);
89
90         parentElement.appendChild(element);
91         return element;
92     },
93
94     createSVGElement: function(name, attrs, xlinkAttrs, parentElement)
95     {
96         const svgNamespace = "http://www.w3.org/2000/svg";
97         const xlinkNamespace = "http://www.w3.org/1999/xlink";
98
99         var element = document.createElementNS(svgNamespace, name);
100
101         for (var key in attrs)
102             element.setAttribute(key, attrs[key]);
103
104         for (var key in xlinkAttrs)
105             element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
106
107         parentElement.appendChild(element);
108         return element;
109     },
110
111     browserPrefix: function()
112     {
113         if (this._browserPrefix !== undefined)
114             return this._browserPrefix;
115
116         // Get the HTML element's CSSStyleDeclaration
117         var styles = window.getComputedStyle(document.documentElement, '');
118
119         // Convert the styles list to an array
120         var stylesArray = Array.prototype.slice.call(styles);
121
122         // Concatenate all the styles in one big string
123         var stylesString = stylesArray.join('');
124
125         // Search the styles string for a known prefix type, settle on Opera if none is found.
126         var prefixes = stylesString.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']);
127
128         // prefixes has two elements; e.g. for webkit it has ['-webkit-', 'webkit'];
129         var prefix = prefixes[1];
130
131         // Have 'O' before 'Moz' in the string so it is matched first.
132         var dom = ('WebKit|O|Moz|MS').match(new RegExp(prefix, 'i'))[0];
133
134         // Return all the required prefixes.
135         this._browserPrefix = {
136             dom: dom,
137             lowercase: prefix,
138             css: '-' + prefix + '-',
139             js: prefix[0].toUpperCase() + prefix.substr(1)
140         };
141
142         return this._browserPrefix;
143     },
144
145     setElementPrefixedProperty: function(element, property, value)
146     {
147         element.style[property] = element.style[this.browserPrefix().js + property[0].toUpperCase() + property.substr(1)] = value;
148     },
149
150     stripUnwantedCharactersForURL: function(inputString)
151     {
152         return inputString.replace(/\W/g, '');
153     },
154
155     convertObjectToQueryString: function(object)
156     {
157         var queryString = [];
158         for (var property in object) {
159             if (object.hasOwnProperty(property))
160                 queryString.push(encodeURIComponent(property) + "=" + encodeURIComponent(object[property]));
161         }
162         return "?" + queryString.join("&");
163     },
164
165     convertQueryStringToObject: function(queryString)
166     {
167         queryString = queryString.substring(1);
168         if (!queryString)
169             return null;
170
171         var object = {};
172         queryString.split("&").forEach(function(parameter) {
173             var components = parameter.split("=");
174             object[components[0]] = components[1];
175         });
176         return object;
177     },
178
179     progressValue: function(value, min, max)
180     {
181         return (value - min) / (max - min);
182     },
183
184     lerp: function(value, min, max)
185     {
186         return min + (max - min) * value;
187     },
188
189     toFixedNumber: function(number, precision)
190     {
191         if (number.toFixed)
192             return Number(number.toFixed(precision));
193         return number;
194     }
195 };
196
197 Array.prototype.swap = function(i, j)
198 {
199     var t = this[i];
200     this[i] = this[j];
201     this[j] = t;
202     return this;
203 }
204
205 if (!Array.prototype.fill) {
206     Array.prototype.fill = function(value) {
207         if (this == null)
208             throw new TypeError('Array.prototype.fill called on null or undefined');
209
210         var object = Object(this);
211         var len = parseInt(object.length, 10);
212         var start = arguments[1];
213         var relativeStart = parseInt(start, 10) || 0;
214         var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
215         var end = arguments[2];
216         var relativeEnd = end === undefined ? len : (parseInt(end) || 0) ;
217         var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
218
219         for (; k < final; k++)
220             object[k] = value;
221
222         return object;
223     };
224 }
225
226 if (!Array.prototype.find) {
227     Array.prototype.find = function(predicate) {
228         if (this == null)
229             throw new TypeError('Array.prototype.find called on null or undefined');
230         if (typeof predicate !== 'function')
231             throw new TypeError('predicate must be a function');
232
233         var list = Object(this);
234         var length = list.length >>> 0;
235         var thisArg = arguments[1];
236         var value;
237
238         for (var i = 0; i < length; i++) {
239             value = list[i];
240             if (predicate.call(thisArg, value, i, list))
241                 return value;
242         }
243         return undefined;
244     };
245 }
246
247 Array.prototype.shuffle = function()
248 {
249     for (var index = this.length - 1; index >= 0; --index) {
250         var randomIndex = Math.floor(Math.random() * (index + 1));
251         this.swap(index, randomIndex);
252     }
253     return this;
254 }
255
256 Point = Utilities.createClass(
257     function(x, y)
258     {
259         this.x = x;
260         this.y = y;
261     }, {
262
263     // Used when the point object is used as a size object.
264     get width()
265     {
266         return this.x;
267     },
268
269     // Used when the point object is used as a size object.
270     get height()
271     {
272         return this.y;
273     },
274
275     // Used when the point object is used as a size object.
276     get center()
277     {
278         return new Point(this.x / 2, this.y / 2);
279     },
280
281     str: function()
282     {
283         return "x = " + this.x + ", y = " + this.y;
284     },
285
286     add: function(other)
287     {
288         if(isNaN(other.x))
289             return new Point(this.x + other, this.y + other);
290         return new Point(this.x + other.x, this.y + other.y);
291     },
292
293     subtract: function(other)
294     {
295         if(isNaN(other.x))
296             return new Point(this.x - other, this.y - other);
297         return new Point(this.x - other.x, this.y - other.y);
298     },
299
300     multiply: function(other)
301     {
302         if(isNaN(other.x))
303             return new Point(this.x * other, this.y * other);
304         return new Point(this.x * other.x, this.y * other.y);
305     },
306
307     move: function(angle, velocity, timeDelta)
308     {
309         return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
310     },
311
312     length: function() {
313         return Math.sqrt( this.x * this.x + this.y * this.y );
314     },
315
316     normalize: function() {
317         var l = Math.sqrt( this.x * this.x + this.y * this.y );
318         this.x /= l;
319         this.y /= l;
320         return this;
321     }
322 });
323
324 Utilities.extendObject(Point, {
325     zero: new Point(0, 0),
326
327     pointOnCircle: function(angle, radius)
328     {
329         return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
330     },
331
332     pointOnEllipse: function(angle, radiuses)
333     {
334         return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
335     },
336
337     elementClientSize: function(element)
338     {
339         var rect = element.getBoundingClientRect();
340         return new Point(rect.width, rect.height);
341     }
342 });
343
344 Insets = Utilities.createClass(
345     function(top, right, bottom, left)
346     {
347         this.top = top;
348         this.right = right;
349         this.bottom = bottom;
350         this.left = left;
351     }, {
352
353     get width()
354     {
355         return this.left + this.right;
356     },
357
358     get height()
359     {
360         return this.top + this.bottom;
361     },
362
363     get size()
364     {
365         return new Point(this.width, this.height);
366     }
367 });
368
369 Insets.elementPadding = function(element)
370 {
371     var styles = window.getComputedStyle(element);
372     return new Insets(
373         parseFloat(styles.paddingTop),
374         parseFloat(styles.paddingRight),
375         parseFloat(styles.paddingBottom),
376         parseFloat(styles.paddingTop));
377 }
378
379 UnitBezier = Utilities.createClass(
380     function(point1, point2)
381     {
382         // First and last points in the B├ęzier curve are assumed to be (0,0) and (!,1)
383         this._c = point1.multiply(3);
384         this._b = point2.subtract(point1).multiply(3).subtract(this._c);
385         this._a = new Point(1, 1).subtract(this._c).subtract(this._b);
386     }, {
387
388     epsilon: 1e-5,
389     derivativeEpsilon: 1e-6,
390
391     solve: function(x)
392     {
393         return this.sampleY(this.solveForT(x));
394     },
395
396     sampleX: function(t)
397     {
398         return ((this._a.x * t + this._b.x) * t + this._c.x) * t;
399     },
400
401     sampleY: function(t)
402     {
403         return ((this._a.y * t + this._b.y) * t + this._c.y) * t;kkkj
404     },
405
406     sampleDerivativeX: function(t)
407     {
408         return(3 * this._a.x * t + 2 * this._b.x) * t + this._c.x;
409     },
410
411     solveForT: function(x)
412     {
413         var t0, t1, t2, x2, d2, i;
414
415         for (t2 = x, i = 0; i < 8; ++i) {
416             x2 = this.sampleX(t2) - x;
417             if (Math.abs(x2) < this.epsilon)
418                 return t2;
419             d2 = this.sampleDerivativeX(t2);
420             if (Math.abs(d2) < this.derivativeEpsilon)
421                 break;
422             t2 = t2 - x2 / d2;
423         }
424
425         t0 = 0;
426         t1 = 1;
427         t2 = x;
428
429         if (t2 < t0)
430             return t0;
431         if (t2 > t1)
432             return t1;
433
434         while (t0 < t1) {
435             x2 = this.sampleX(t2);
436             if (Math.abs(x2 - x) < this.epsilon)
437                 return t2;
438             if (x > x2)
439                 t0 = t2;
440             else
441                 t1 = t2;
442             t2 = (t1 - t0) * .5 + t0;
443         }
444
445         return t2;
446     }
447 });
448
449 SimplePromise = Utilities.createClass(
450     function()
451     {
452         this._chainedPromise = null;
453         this._callback = null;
454     }, {
455
456     then: function (callback)
457     {
458         if (this._callback)
459             throw "SimplePromise doesn't support multiple calls to then";
460
461         this._callback = callback;
462         this._chainedPromise = new SimplePromise;
463
464         if (this._resolved)
465             this.resolve(this._resolvedValue);
466
467         return this._chainedPromise;
468     },
469
470     resolve: function (value)
471     {
472         if (!this._callback) {
473             this._resolved = true;
474             this._resolvedValue = value;
475             return;
476         }
477
478         var result = this._callback(value);
479         if (result instanceof SimplePromise) {
480             var chainedPromise = this._chainedPromise;
481             result.then(function (result) { chainedPromise.resolve(result); });
482         } else
483             this._chainedPromise.resolve(result);
484     }
485 });
486
487 var Heap = Utilities.createClass(
488     function(maxSize, compare)
489     {
490         this._maxSize = maxSize;
491         this._compare = compare;
492         this._size = 0;
493         this._values = new Array(this._maxSize);
494     }, {
495
496     // This is a binary heap represented in an array. The root element is stored
497     // in the first element in the array. The root is followed by its two children.
498     // Then its four grandchildren and so on. So every level in the binary heap is
499     // doubled in the following level. Here is an example of the node indices and
500     // how they are related to their parents and children.
501     // ===========================================================================
502     //              0       1       2       3       4       5       6
503     // PARENT       -1      0       0       1       1       2       2
504     // LEFT         1       3       5       7       9       11      13
505     // RIGHT        2       4       6       8       10      12      14
506     // ===========================================================================
507     _parentIndex: function(i)
508     {
509         return i > 0 ? Math.floor((i - 1) / 2) : -1;
510     },
511
512     _leftIndex: function(i)
513     {
514         var leftIndex = i * 2 + 1;
515         return leftIndex < this._size ? leftIndex : -1;
516     },
517
518     _rightIndex: function(i)
519     {
520         var rightIndex = i * 2 + 2;
521         return rightIndex < this._size ? rightIndex : -1;
522     },
523
524     // Return the child index that may violate the heap property at index i.
525     _childIndex: function(i)
526     {
527         var left = this._leftIndex(i);
528         var right = this._rightIndex(i);
529
530         if (left != -1 && right != -1)
531             return this._compare(this._values[left], this._values[right]) > 0 ? left : right;
532
533         return left != -1 ? left : right;
534     },
535
536     init: function()
537     {
538         this._size = 0;
539     },
540
541     top: function()
542     {
543         return this._size ? this._values[0] : NaN;
544     },
545
546     push: function(value)
547     {
548         if (this._size == this._maxSize) {
549             // If size is bounded and the new value can be a parent of the top()
550             // if the size were unbounded, just ignore the new value.
551             if (this._compare(value, this.top()) > 0)
552                 return;
553             this.pop();
554         }
555         this._values[this._size++] = value;
556         this._bubble(this._size - 1);
557     },
558
559     pop: function()
560     {
561         if (!this._size)
562             return NaN;
563
564         this._values[0] = this._values[--this._size];
565         this._sink(0);
566     },
567
568     _bubble: function(i)
569     {
570         // Fix the heap property at index i given that parent is the only node that
571         // may violate the heap property.
572         for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) {
573             if (this._compare(this._values[pi], this._values[i]) > 0)
574                 break;
575
576             this._values.swap(pi, i);
577         }
578     },
579
580     _sink: function(i)
581     {
582         // Fix the heap property at index i given that each of the left and the right
583         // sub-trees satisfies the heap property.
584         for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) {
585             if (this._compare(this._values[i], this._values[ci]) > 0)
586                 break;
587
588             this._values.swap(ci, i);
589         }
590     },
591
592     str: function()
593     {
594         var out = "Heap[" + this._size + "] = [";
595         for (var i = 0; i < this._size; ++i) {
596             out += this._values[i];
597             if (i < this._size - 1)
598                 out += ", ";
599         }
600         return out + "]";
601     },
602
603     values: function(size) {
604         // Return the last "size" heap elements values.
605         var values = this._values.slice(0, this._size);
606         return values.sort(this._compare).slice(0, Math.min(size, this._size));
607     }
608 });
609
610 Utilities.extendObject(Heap, {
611     createMinHeap: function(maxSize)
612     {
613         return new Heap(maxSize, function(a, b) { return b - a; });
614     },
615
616     createMaxHeap: function(maxSize) {
617         return new Heap(maxSize, function(a, b) { return a - b; });
618     }
619 });
620
621 var SampleData = Utilities.createClass(
622     function(fieldMap, data)
623     {
624         this.fieldMap = fieldMap || {};
625         this.data = data || [];
626     }, {
627
628     get length()
629     {
630         return this.data.length;
631     },
632
633     addField: function(name, index)
634     {
635         this.fieldMap[name] = index;
636     },
637
638     push: function(datum)
639     {
640         this.data.push(datum);
641     },
642
643     sort: function(sortFunction)
644     {
645         this.data.sort(sortFunction);
646     },
647
648     slice: function(begin, end)
649     {
650         return new SampleData(this.fieldMap, this.data.slice(begin, end));
651     },
652
653     forEach: function(iterationFunction)
654     {
655         this.data.forEach(iterationFunction);
656     },
657
658     createDatum: function()
659     {
660         return [];
661     },
662
663     getFieldInDatum: function(datum, fieldName)
664     {
665         if (typeof datum === 'number')
666             datum = this.data[datum];
667         return datum[this.fieldMap[fieldName]];
668     },
669
670     setFieldInDatum: function(datum, fieldName, value)
671     {
672         if (typeof datum === 'number')
673             datum = this.data[datum];
674         return datum[this.fieldMap[fieldName]] = value;
675     },
676
677     at: function(index)
678     {
679         return this.data[index];
680     },
681
682     toArray: function()
683     {
684         var array = [];
685
686         this.data.forEach(function(datum) {
687             var newDatum = {};
688             array.push(newDatum);
689
690             for (var fieldName in this.fieldMap) {
691                 var value = this.getFieldInDatum(datum, fieldName);
692                 if (value !== null && value !== undefined)
693                     newDatum[fieldName] = value;
694             }
695         }, this);
696
697         return array;
698     }
699 });