Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 // **********
8 // Title: utils.js
10 this.EXPORTED_SYMBOLS = ["Point", "Rect", "Range", "Subscribable", "Utils", "MRUList"];
12 // #########
13 const Ci = Components.interfaces;
14 const Cu = Components.utils;
16 Cu.import("resource://gre/modules/Services.jsm");
18 // ##########
19 // Class: Point
20 // A simple point.
21 //
22 // Constructor: Point
23 // If a is a Point, creates a copy of it. Otherwise, expects a to be x,
24 // and creates a Point with it along with y. If either a or y are omitted,
25 // 0 is used in their place.
26 this.Point = function Point(a, y) {
27 if (Utils.isPoint(a)) {
28 this.x = a.x;
29 this.y = a.y;
30 } else {
31 this.x = (Utils.isNumber(a) ? a : 0);
32 this.y = (Utils.isNumber(y) ? y : 0);
33 }
34 };
36 Point.prototype = {
37 // ----------
38 // Function: toString
39 // Prints [Point (x,y)] for debug use
40 toString: function Point_toString() {
41 return "[Point (" + this.x + "," + this.y + ")]";
42 },
44 // ----------
45 // Function: distance
46 // Returns the distance from this point to the given <Point>.
47 distance: function Point_distance(point) {
48 var ax = this.x - point.x;
49 var ay = this.y - point.y;
50 return Math.sqrt((ax * ax) + (ay * ay));
51 }
52 };
54 // ##########
55 // Class: Rect
56 // A simple rectangle. Note that in addition to the left and width, it also has
57 // a right property; changing one affects the others appropriately. Same for the
58 // vertical properties.
59 //
60 // Constructor: Rect
61 // If a is a Rect, creates a copy of it. Otherwise, expects a to be left,
62 // and creates a Rect with it along with top, width, and height.
63 this.Rect = function Rect(a, top, width, height) {
64 // Note: perhaps 'a' should really be called 'rectOrLeft'
65 if (Utils.isRect(a)) {
66 this.left = a.left;
67 this.top = a.top;
68 this.width = a.width;
69 this.height = a.height;
70 } else {
71 this.left = a;
72 this.top = top;
73 this.width = width;
74 this.height = height;
75 }
76 };
78 Rect.prototype = {
79 // ----------
80 // Function: toString
81 // Prints [Rect (left,top,width,height)] for debug use
82 toString: function Rect_toString() {
83 return "[Rect (" + this.left + "," + this.top + "," +
84 this.width + "," + this.height + ")]";
85 },
87 get right() this.left + this.width,
88 set right(value) {
89 this.width = value - this.left;
90 },
92 get bottom() this.top + this.height,
93 set bottom(value) {
94 this.height = value - this.top;
95 },
97 // ----------
98 // Variable: xRange
99 // Gives you a new <Range> for the horizontal dimension.
100 get xRange() new Range(this.left, this.right),
102 // ----------
103 // Variable: yRange
104 // Gives you a new <Range> for the vertical dimension.
105 get yRange() new Range(this.top, this.bottom),
107 // ----------
108 // Function: intersects
109 // Returns true if this rectangle intersects the given <Rect>.
110 intersects: function Rect_intersects(rect) {
111 return (rect.right > this.left &&
112 rect.left < this.right &&
113 rect.bottom > this.top &&
114 rect.top < this.bottom);
115 },
117 // ----------
118 // Function: intersection
119 // Returns a new <Rect> with the intersection of this rectangle and the give <Rect>,
120 // or null if they don't intersect.
121 intersection: function Rect_intersection(rect) {
122 var box = new Rect(Math.max(rect.left, this.left), Math.max(rect.top, this.top), 0, 0);
123 box.right = Math.min(rect.right, this.right);
124 box.bottom = Math.min(rect.bottom, this.bottom);
125 if (box.width > 0 && box.height > 0)
126 return box;
128 return null;
129 },
131 // ----------
132 // Function: contains
133 // Returns a boolean denoting if the <Rect> or <Point> is contained inside
134 // this rectangle.
135 //
136 // Parameters
137 // - A <Rect> or a <Point>
138 contains: function Rect_contains(a) {
139 if (Utils.isPoint(a))
140 return (a.x > this.left &&
141 a.x < this.right &&
142 a.y > this.top &&
143 a.y < this.bottom);
145 return (a.left >= this.left &&
146 a.right <= this.right &&
147 a.top >= this.top &&
148 a.bottom <= this.bottom);
149 },
151 // ----------
152 // Function: center
153 // Returns a new <Point> with the center location of this rectangle.
154 center: function Rect_center() {
155 return new Point(this.left + (this.width / 2), this.top + (this.height / 2));
156 },
158 // ----------
159 // Function: size
160 // Returns a new <Point> with the dimensions of this rectangle.
161 size: function Rect_size() {
162 return new Point(this.width, this.height);
163 },
165 // ----------
166 // Function: position
167 // Returns a new <Point> with the top left of this rectangle.
168 position: function Rect_position() {
169 return new Point(this.left, this.top);
170 },
172 // ----------
173 // Function: area
174 // Returns the area of this rectangle.
175 area: function Rect_area() {
176 return this.width * this.height;
177 },
179 // ----------
180 // Function: inset
181 // Makes the rect smaller (if the arguments are positive) as if a margin is added all around
182 // the initial rect, with the margin widths (symmetric) being specified by the arguments.
183 //
184 // Paramaters
185 // - A <Point> or two arguments: x and y
186 inset: function Rect_inset(a, b) {
187 if (Utils.isPoint(a)) {
188 b = a.y;
189 a = a.x;
190 }
192 this.left += a;
193 this.width -= a * 2;
194 this.top += b;
195 this.height -= b * 2;
196 },
198 // ----------
199 // Function: offset
200 // Moves (translates) the rect by the given vector.
201 //
202 // Paramaters
203 // - A <Point> or two arguments: x and y
204 offset: function Rect_offset(a, b) {
205 if (Utils.isPoint(a)) {
206 this.left += a.x;
207 this.top += a.y;
208 } else {
209 this.left += a;
210 this.top += b;
211 }
212 },
214 // ----------
215 // Function: equals
216 // Returns true if this rectangle is identical to the given <Rect>.
217 equals: function Rect_equals(rect) {
218 return (rect.left == this.left &&
219 rect.top == this.top &&
220 rect.width == this.width &&
221 rect.height == this.height);
222 },
224 // ----------
225 // Function: union
226 // Returns a new <Rect> with the union of this rectangle and the given <Rect>.
227 union: function Rect_union(a) {
228 var newLeft = Math.min(a.left, this.left);
229 var newTop = Math.min(a.top, this.top);
230 var newWidth = Math.max(a.right, this.right) - newLeft;
231 var newHeight = Math.max(a.bottom, this.bottom) - newTop;
232 var newRect = new Rect(newLeft, newTop, newWidth, newHeight);
234 return newRect;
235 },
237 // ----------
238 // Function: copy
239 // Copies the values of the given <Rect> into this rectangle.
240 copy: function Rect_copy(a) {
241 this.left = a.left;
242 this.top = a.top;
243 this.width = a.width;
244 this.height = a.height;
245 }
246 };
248 // ##########
249 // Class: Range
250 // A physical interval, with a min and max.
251 //
252 // Constructor: Range
253 // Creates a Range with the given min and max
254 this.Range = function Range(min, max) {
255 if (Utils.isRange(min) && !max) { // if the one variable given is a range, copy it.
256 this.min = min.min;
257 this.max = min.max;
258 } else {
259 this.min = min || 0;
260 this.max = max || 0;
261 }
262 };
264 Range.prototype = {
265 // ----------
266 // Function: toString
267 // Prints [Range (min,max)] for debug use
268 toString: function Range_toString() {
269 return "[Range (" + this.min + "," + this.max + ")]";
270 },
272 // Variable: extent
273 // Equivalent to max-min
274 get extent() {
275 return (this.max - this.min);
276 },
278 set extent(extent) {
279 this.max = extent - this.min;
280 },
282 // ----------
283 // Function: contains
284 // Whether the <Range> contains the given <Range> or value or not.
285 //
286 // Parameters
287 // - a number or <Range>
288 contains: function Range_contains(value) {
289 if (Utils.isNumber(value))
290 return value >= this.min && value <= this.max;
291 if (Utils.isRange(value))
292 return value.min >= this.min && value.max <= this.max;
293 return false;
294 },
296 // ----------
297 // Function: overlaps
298 // Whether the <Range> overlaps with the given <Range> value or not.
299 //
300 // Parameters
301 // - a number or <Range>
302 overlaps: function Range_overlaps(value) {
303 if (Utils.isNumber(value))
304 return this.contains(value);
305 if (Utils.isRange(value))
306 return !(value.max < this.min || this.max < value.min);
307 return false;
308 },
310 // ----------
311 // Function: proportion
312 // Maps the given value to the range [0,1], so that it returns 0 if the value is <= the min,
313 // returns 1 if the value >= the max, and returns an interpolated "proportion" in (min, max).
314 //
315 // Parameters
316 // - a number
317 // - (bool) smooth? If true, a smooth tanh-based function will be used instead of the linear.
318 proportion: function Range_proportion(value, smooth) {
319 if (value <= this.min)
320 return 0;
321 if (this.max <= value)
322 return 1;
324 var proportion = (value - this.min) / this.extent;
326 if (smooth) {
327 // The ease function ".5+.5*Math.tanh(4*x-2)" is a pretty
328 // little graph. It goes from near 0 at x=0 to near 1 at x=1
329 // smoothly and beautifully.
330 // http://www.wolframalpha.com/input/?i=.5+%2B+.5+*+tanh%28%284+*+x%29+-+2%29
331 let tanh = function tanh(x) {
332 var e = Math.exp(x);
333 return (e - 1/e) / (e + 1/e);
334 };
336 return .5 - .5 * tanh(2 - 4 * proportion);
337 }
339 return proportion;
340 },
342 // ----------
343 // Function: scale
344 // Takes the given value in [0,1] and maps it to the associated value on the Range.
345 //
346 // Parameters
347 // - a number in [0,1]
348 scale: function Range_scale(value) {
349 if (value > 1)
350 value = 1;
351 if (value < 0)
352 value = 0;
353 return this.min + this.extent * value;
354 }
355 };
357 // ##########
358 // Class: Subscribable
359 // A mix-in for allowing objects to collect subscribers for custom events.
360 this.Subscribable = function Subscribable() {
361 this.subscribers = null;
362 };
364 Subscribable.prototype = {
365 // ----------
366 // Function: addSubscriber
367 // The given callback will be called when the Subscribable fires the given event.
368 addSubscriber: function Subscribable_addSubscriber(eventName, callback) {
369 try {
370 Utils.assertThrow(typeof callback == "function", "callback must be a function");
371 Utils.assertThrow(eventName && typeof eventName == "string",
372 "eventName must be a non-empty string");
373 } catch(e) {
374 Utils.log(e);
375 return;
376 }
378 if (!this.subscribers)
379 this.subscribers = {};
381 if (!this.subscribers[eventName])
382 this.subscribers[eventName] = [];
384 let subscribers = this.subscribers[eventName];
385 if (subscribers.indexOf(callback) == -1)
386 subscribers.push(callback);
387 },
389 // ----------
390 // Function: removeSubscriber
391 // Removes the subscriber associated with the event for the given callback.
392 removeSubscriber: function Subscribable_removeSubscriber(eventName, callback) {
393 try {
394 Utils.assertThrow(typeof callback == "function", "callback must be a function");
395 Utils.assertThrow(eventName && typeof eventName == "string",
396 "eventName must be a non-empty string");
397 } catch(e) {
398 Utils.log(e);
399 return;
400 }
402 if (!this.subscribers || !this.subscribers[eventName])
403 return;
405 let subscribers = this.subscribers[eventName];
406 let index = subscribers.indexOf(callback);
408 if (index > -1)
409 subscribers.splice(index, 1);
410 },
412 // ----------
413 // Function: _sendToSubscribers
414 // Internal routine. Used by the Subscribable to fire events.
415 _sendToSubscribers: function Subscribable__sendToSubscribers(eventName, eventInfo) {
416 try {
417 Utils.assertThrow(eventName && typeof eventName == "string",
418 "eventName must be a non-empty string");
419 } catch(e) {
420 Utils.log(e);
421 return;
422 }
424 if (!this.subscribers || !this.subscribers[eventName])
425 return;
427 let subsCopy = this.subscribers[eventName].concat();
428 subsCopy.forEach(function (callback) {
429 try {
430 callback(this, eventInfo);
431 } catch(e) {
432 Utils.log(e);
433 }
434 }, this);
435 }
436 };
438 // ##########
439 // Class: Utils
440 // Singelton with common utility functions.
441 this.Utils = {
442 // ----------
443 // Function: toString
444 // Prints [Utils] for debug use
445 toString: function Utils_toString() {
446 return "[Utils]";
447 },
449 // ___ Logging
450 useConsole: true, // as opposed to dump
451 showTime: false,
453 // ----------
454 // Function: log
455 // Prints the given arguments to the JavaScript error console as a message.
456 // Pass as many arguments as you want, it'll print them all.
457 log: function Utils_log() {
458 var text = this.expandArgumentsForLog(arguments);
459 var prefix = this.showTime ? Date.now() + ': ' : '';
460 if (this.useConsole)
461 Services.console.logStringMessage(prefix + text);
462 else
463 dump(prefix + text + '\n');
464 },
466 // ----------
467 // Function: error
468 // Prints the given arguments to the JavaScript error console as an error.
469 // Pass as many arguments as you want, it'll print them all.
470 error: function Utils_error() {
471 var text = this.expandArgumentsForLog(arguments);
472 var prefix = this.showTime ? Date.now() + ': ' : '';
473 if (this.useConsole)
474 Cu.reportError(prefix + "tabview error: " + text);
475 else
476 dump(prefix + "TABVIEW ERROR: " + text + '\n');
477 },
479 // ----------
480 // Function: trace
481 // Prints the given arguments to the JavaScript error console as a message,
482 // along with a full stack trace.
483 // Pass as many arguments as you want, it'll print them all.
484 trace: function Utils_trace() {
485 var text = this.expandArgumentsForLog(arguments);
487 // cut off the first line of the stack trace, because that's just this function.
488 let stack = Error().stack.split("\n").slice(1);
490 // if the caller was assert, cut out the line for the assert function as well.
491 if (stack[0].indexOf("Utils_assert(") == 0)
492 stack.splice(0, 1);
494 this.log('trace: ' + text + '\n' + stack.join("\n"));
495 },
497 // ----------
498 // Function: assert
499 // Prints a stack trace along with label (as a console message) if condition is false.
500 assert: function Utils_assert(condition, label) {
501 if (!condition) {
502 let text;
503 if (typeof label != 'string')
504 text = 'badly formed assert';
505 else
506 text = "tabview assert: " + label;
508 this.trace(text);
509 }
510 },
512 // ----------
513 // Function: assertThrow
514 // Throws label as an exception if condition is false.
515 assertThrow: function Utils_assertThrow(condition, label) {
516 if (!condition) {
517 let text;
518 if (typeof label != 'string')
519 text = 'badly formed assert';
520 else
521 text = "tabview assert: " + label;
523 // cut off the first line of the stack trace, because that's just this function.
524 let stack = Error().stack.split("\n").slice(1);
526 throw text + "\n" + stack.join("\n");
527 }
528 },
530 // ----------
531 // Function: expandObject
532 // Prints the given object to a string, including all of its properties.
533 expandObject: function Utils_expandObject(obj) {
534 var s = obj + ' = {';
535 for (let prop in obj) {
536 let value;
537 try {
538 value = obj[prop];
539 } catch(e) {
540 value = '[!!error retrieving property]';
541 }
543 s += prop + ': ';
544 if (typeof value == 'string')
545 s += '\'' + value + '\'';
546 else if (typeof value == 'function')
547 s += 'function';
548 else
549 s += value;
551 s += ', ';
552 }
553 return s + '}';
554 },
556 // ----------
557 // Function: expandArgumentsForLog
558 // Expands all of the given args (an array) into a single string.
559 expandArgumentsForLog: function Utils_expandArgumentsForLog(args) {
560 var that = this;
561 return Array.map(args, function(arg) {
562 return typeof arg == 'object' ? that.expandObject(arg) : arg;
563 }).join('; ');
564 },
566 // ___ Misc
568 // ----------
569 // Function: isLeftClick
570 // Given a DOM mouse event, returns true if it was for the left mouse button.
571 isLeftClick: function Utils_isLeftClick(event) {
572 return event.button == 0;
573 },
575 // ----------
576 // Function: isMiddleClick
577 // Given a DOM mouse event, returns true if it was for the middle mouse button.
578 isMiddleClick: function Utils_isMiddleClick(event) {
579 return event.button == 1;
580 },
582 // ----------
583 // Function: isRightClick
584 // Given a DOM mouse event, returns true if it was for the right mouse button.
585 isRightClick: function Utils_isRightClick(event) {
586 return event.button == 2;
587 },
589 // ----------
590 // Function: isDOMElement
591 // Returns true if the given object is a DOM element.
592 isDOMElement: function Utils_isDOMElement(object) {
593 return object instanceof Ci.nsIDOMElement;
594 },
596 // ----------
597 // Function: isValidXULTab
598 // A xulTab is valid if it has not been closed,
599 // and it has not been removed from the DOM
600 // Returns true if the tab is valid.
601 isValidXULTab: function Utils_isValidXULTab(xulTab) {
602 return !xulTab.closing && xulTab.parentNode;
603 },
605 // ----------
606 // Function: isNumber
607 // Returns true if the argument is a valid number.
608 isNumber: function Utils_isNumber(n) {
609 return typeof n == 'number' && !isNaN(n);
610 },
612 // ----------
613 // Function: isRect
614 // Returns true if the given object (r) looks like a <Rect>.
615 isRect: function Utils_isRect(r) {
616 return (r &&
617 this.isNumber(r.left) &&
618 this.isNumber(r.top) &&
619 this.isNumber(r.width) &&
620 this.isNumber(r.height));
621 },
623 // ----------
624 // Function: isRange
625 // Returns true if the given object (r) looks like a <Range>.
626 isRange: function Utils_isRange(r) {
627 return (r &&
628 this.isNumber(r.min) &&
629 this.isNumber(r.max));
630 },
632 // ----------
633 // Function: isPoint
634 // Returns true if the given object (p) looks like a <Point>.
635 isPoint: function Utils_isPoint(p) {
636 return (p && this.isNumber(p.x) && this.isNumber(p.y));
637 },
639 // ----------
640 // Function: isPlainObject
641 // Check to see if an object is a plain object (created using "{}" or "new Object").
642 isPlainObject: function Utils_isPlainObject(obj) {
643 // Must be an Object.
644 // Make sure that DOM nodes and window objects don't pass through, as well
645 if (!obj || Object.prototype.toString.call(obj) !== "[object Object]" ||
646 obj.nodeType || obj.setInterval) {
647 return false;
648 }
650 // Not own constructor property must be Object
651 const hasOwnProperty = Object.prototype.hasOwnProperty;
653 if (obj.constructor &&
654 !hasOwnProperty.call(obj, "constructor") &&
655 !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
656 return false;
657 }
659 // Own properties are enumerated firstly, so to speed up,
660 // if last one is own, then all properties are own.
662 var key;
663 for (key in obj) {}
665 return key === undefined || hasOwnProperty.call(obj, key);
666 },
668 // ----------
669 // Function: isEmptyObject
670 // Returns true if the given object has no members.
671 isEmptyObject: function Utils_isEmptyObject(obj) {
672 for (let name in obj)
673 return false;
674 return true;
675 },
677 // ----------
678 // Function: copy
679 // Returns a copy of the argument. Note that this is a shallow copy; if the argument
680 // has properties that are themselves objects, those properties will be copied by reference.
681 copy: function Utils_copy(value) {
682 if (value && typeof value == 'object') {
683 if (Array.isArray(value))
684 return this.extend([], value);
685 return this.extend({}, value);
686 }
687 return value;
688 },
690 // ----------
691 // Function: merge
692 // Merge two array-like objects into the first and return it.
693 merge: function Utils_merge(first, second) {
694 Array.forEach(second, function(el) Array.push(first, el));
695 return first;
696 },
698 // ----------
699 // Function: extend
700 // Pass several objects in and it will combine them all into the first object and return it.
701 extend: function Utils_extend() {
703 // copy reference to target object
704 let target = arguments[0] || {};
705 // Deep copy is not supported
706 if (typeof target === "boolean") {
707 this.assert(false, "The first argument of extend cannot be a boolean." +
708 "Deep copy is not supported.");
709 return target;
710 }
712 // Back when this was in iQ + iQ.fn, so you could extend iQ objects with it.
713 // This is no longer supported.
714 let length = arguments.length;
715 if (length === 1) {
716 this.assert(false, "Extending the iQ prototype using extend is not supported.");
717 return target;
718 }
720 // Handle case when target is a string or something
721 if (typeof target != "object" && typeof target != "function") {
722 target = {};
723 }
725 for (let i = 1; i < length; i++) {
726 // Only deal with non-null/undefined values
727 let options = arguments[i];
728 if (options != null) {
729 // Extend the base object
730 for (let name in options) {
731 let copy = options[name];
733 // Prevent never-ending loop
734 if (target === copy)
735 continue;
737 if (copy !== undefined)
738 target[name] = copy;
739 }
740 }
741 }
743 // Return the modified object
744 return target;
745 },
747 // ----------
748 // Function: attempt
749 // Tries to execute a number of functions. Returns immediately the return
750 // value of the first non-failed function without executing successive
751 // functions, or null.
752 attempt: function Utils_attempt() {
753 let args = arguments;
755 for (let i = 0; i < args.length; i++) {
756 try {
757 return args[i]();
758 } catch (e) {}
759 }
761 return null;
762 }
763 };
765 // ##########
766 // Class: MRUList
767 // A most recently used list.
768 //
769 // Constructor: MRUList
770 // If a is an array of entries, creates a copy of it.
771 this.MRUList = function MRUList(a) {
772 if (Array.isArray(a))
773 this._list = a.concat();
774 else
775 this._list = [];
776 };
778 MRUList.prototype = {
779 // ----------
780 // Function: toString
781 // Prints [List (entry1, entry2, ...)] for debug use
782 toString: function MRUList_toString() {
783 return "[List (" + this._list.join(", ") + ")]";
784 },
786 // ----------
787 // Function: update
788 // Updates/inserts the given entry as the most recently used one in the list.
789 update: function MRUList_update(entry) {
790 this.remove(entry);
791 this._list.unshift(entry);
792 },
794 // ----------
795 // Function: remove
796 // Removes the given entry from the list.
797 remove: function MRUList_remove(entry) {
798 let index = this._list.indexOf(entry);
799 if (index > -1)
800 this._list.splice(index, 1);
801 },
803 // ----------
804 // Function: peek
805 // Returns the most recently used entry. If a filter exists, gets the most
806 // recently used entry which matches the filter.
807 peek: function MRUList_peek(filter) {
808 let match = null;
809 if (filter && typeof filter == "function")
810 this._list.some(function MRUList_peek_getEntry(entry) {
811 if (filter(entry)) {
812 match = entry
813 return true;
814 }
815 return false;
816 });
817 else
818 match = this._list.length > 0 ? this._list[0] : null;
820 return match;
821 },
822 };