1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/tabview/modules/utils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,823 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +// ********** 1.11 +// Title: utils.js 1.12 + 1.13 +this.EXPORTED_SYMBOLS = ["Point", "Rect", "Range", "Subscribable", "Utils", "MRUList"]; 1.14 + 1.15 +// ######### 1.16 +const Ci = Components.interfaces; 1.17 +const Cu = Components.utils; 1.18 + 1.19 +Cu.import("resource://gre/modules/Services.jsm"); 1.20 + 1.21 +// ########## 1.22 +// Class: Point 1.23 +// A simple point. 1.24 +// 1.25 +// Constructor: Point 1.26 +// If a is a Point, creates a copy of it. Otherwise, expects a to be x, 1.27 +// and creates a Point with it along with y. If either a or y are omitted, 1.28 +// 0 is used in their place. 1.29 +this.Point = function Point(a, y) { 1.30 + if (Utils.isPoint(a)) { 1.31 + this.x = a.x; 1.32 + this.y = a.y; 1.33 + } else { 1.34 + this.x = (Utils.isNumber(a) ? a : 0); 1.35 + this.y = (Utils.isNumber(y) ? y : 0); 1.36 + } 1.37 +}; 1.38 + 1.39 +Point.prototype = { 1.40 + // ---------- 1.41 + // Function: toString 1.42 + // Prints [Point (x,y)] for debug use 1.43 + toString: function Point_toString() { 1.44 + return "[Point (" + this.x + "," + this.y + ")]"; 1.45 + }, 1.46 + 1.47 + // ---------- 1.48 + // Function: distance 1.49 + // Returns the distance from this point to the given <Point>. 1.50 + distance: function Point_distance(point) { 1.51 + var ax = this.x - point.x; 1.52 + var ay = this.y - point.y; 1.53 + return Math.sqrt((ax * ax) + (ay * ay)); 1.54 + } 1.55 +}; 1.56 + 1.57 +// ########## 1.58 +// Class: Rect 1.59 +// A simple rectangle. Note that in addition to the left and width, it also has 1.60 +// a right property; changing one affects the others appropriately. Same for the 1.61 +// vertical properties. 1.62 +// 1.63 +// Constructor: Rect 1.64 +// If a is a Rect, creates a copy of it. Otherwise, expects a to be left, 1.65 +// and creates a Rect with it along with top, width, and height. 1.66 +this.Rect = function Rect(a, top, width, height) { 1.67 + // Note: perhaps 'a' should really be called 'rectOrLeft' 1.68 + if (Utils.isRect(a)) { 1.69 + this.left = a.left; 1.70 + this.top = a.top; 1.71 + this.width = a.width; 1.72 + this.height = a.height; 1.73 + } else { 1.74 + this.left = a; 1.75 + this.top = top; 1.76 + this.width = width; 1.77 + this.height = height; 1.78 + } 1.79 +}; 1.80 + 1.81 +Rect.prototype = { 1.82 + // ---------- 1.83 + // Function: toString 1.84 + // Prints [Rect (left,top,width,height)] for debug use 1.85 + toString: function Rect_toString() { 1.86 + return "[Rect (" + this.left + "," + this.top + "," + 1.87 + this.width + "," + this.height + ")]"; 1.88 + }, 1.89 + 1.90 + get right() this.left + this.width, 1.91 + set right(value) { 1.92 + this.width = value - this.left; 1.93 + }, 1.94 + 1.95 + get bottom() this.top + this.height, 1.96 + set bottom(value) { 1.97 + this.height = value - this.top; 1.98 + }, 1.99 + 1.100 + // ---------- 1.101 + // Variable: xRange 1.102 + // Gives you a new <Range> for the horizontal dimension. 1.103 + get xRange() new Range(this.left, this.right), 1.104 + 1.105 + // ---------- 1.106 + // Variable: yRange 1.107 + // Gives you a new <Range> for the vertical dimension. 1.108 + get yRange() new Range(this.top, this.bottom), 1.109 + 1.110 + // ---------- 1.111 + // Function: intersects 1.112 + // Returns true if this rectangle intersects the given <Rect>. 1.113 + intersects: function Rect_intersects(rect) { 1.114 + return (rect.right > this.left && 1.115 + rect.left < this.right && 1.116 + rect.bottom > this.top && 1.117 + rect.top < this.bottom); 1.118 + }, 1.119 + 1.120 + // ---------- 1.121 + // Function: intersection 1.122 + // Returns a new <Rect> with the intersection of this rectangle and the give <Rect>, 1.123 + // or null if they don't intersect. 1.124 + intersection: function Rect_intersection(rect) { 1.125 + var box = new Rect(Math.max(rect.left, this.left), Math.max(rect.top, this.top), 0, 0); 1.126 + box.right = Math.min(rect.right, this.right); 1.127 + box.bottom = Math.min(rect.bottom, this.bottom); 1.128 + if (box.width > 0 && box.height > 0) 1.129 + return box; 1.130 + 1.131 + return null; 1.132 + }, 1.133 + 1.134 + // ---------- 1.135 + // Function: contains 1.136 + // Returns a boolean denoting if the <Rect> or <Point> is contained inside 1.137 + // this rectangle. 1.138 + // 1.139 + // Parameters 1.140 + // - A <Rect> or a <Point> 1.141 + contains: function Rect_contains(a) { 1.142 + if (Utils.isPoint(a)) 1.143 + return (a.x > this.left && 1.144 + a.x < this.right && 1.145 + a.y > this.top && 1.146 + a.y < this.bottom); 1.147 + 1.148 + return (a.left >= this.left && 1.149 + a.right <= this.right && 1.150 + a.top >= this.top && 1.151 + a.bottom <= this.bottom); 1.152 + }, 1.153 + 1.154 + // ---------- 1.155 + // Function: center 1.156 + // Returns a new <Point> with the center location of this rectangle. 1.157 + center: function Rect_center() { 1.158 + return new Point(this.left + (this.width / 2), this.top + (this.height / 2)); 1.159 + }, 1.160 + 1.161 + // ---------- 1.162 + // Function: size 1.163 + // Returns a new <Point> with the dimensions of this rectangle. 1.164 + size: function Rect_size() { 1.165 + return new Point(this.width, this.height); 1.166 + }, 1.167 + 1.168 + // ---------- 1.169 + // Function: position 1.170 + // Returns a new <Point> with the top left of this rectangle. 1.171 + position: function Rect_position() { 1.172 + return new Point(this.left, this.top); 1.173 + }, 1.174 + 1.175 + // ---------- 1.176 + // Function: area 1.177 + // Returns the area of this rectangle. 1.178 + area: function Rect_area() { 1.179 + return this.width * this.height; 1.180 + }, 1.181 + 1.182 + // ---------- 1.183 + // Function: inset 1.184 + // Makes the rect smaller (if the arguments are positive) as if a margin is added all around 1.185 + // the initial rect, with the margin widths (symmetric) being specified by the arguments. 1.186 + // 1.187 + // Paramaters 1.188 + // - A <Point> or two arguments: x and y 1.189 + inset: function Rect_inset(a, b) { 1.190 + if (Utils.isPoint(a)) { 1.191 + b = a.y; 1.192 + a = a.x; 1.193 + } 1.194 + 1.195 + this.left += a; 1.196 + this.width -= a * 2; 1.197 + this.top += b; 1.198 + this.height -= b * 2; 1.199 + }, 1.200 + 1.201 + // ---------- 1.202 + // Function: offset 1.203 + // Moves (translates) the rect by the given vector. 1.204 + // 1.205 + // Paramaters 1.206 + // - A <Point> or two arguments: x and y 1.207 + offset: function Rect_offset(a, b) { 1.208 + if (Utils.isPoint(a)) { 1.209 + this.left += a.x; 1.210 + this.top += a.y; 1.211 + } else { 1.212 + this.left += a; 1.213 + this.top += b; 1.214 + } 1.215 + }, 1.216 + 1.217 + // ---------- 1.218 + // Function: equals 1.219 + // Returns true if this rectangle is identical to the given <Rect>. 1.220 + equals: function Rect_equals(rect) { 1.221 + return (rect.left == this.left && 1.222 + rect.top == this.top && 1.223 + rect.width == this.width && 1.224 + rect.height == this.height); 1.225 + }, 1.226 + 1.227 + // ---------- 1.228 + // Function: union 1.229 + // Returns a new <Rect> with the union of this rectangle and the given <Rect>. 1.230 + union: function Rect_union(a) { 1.231 + var newLeft = Math.min(a.left, this.left); 1.232 + var newTop = Math.min(a.top, this.top); 1.233 + var newWidth = Math.max(a.right, this.right) - newLeft; 1.234 + var newHeight = Math.max(a.bottom, this.bottom) - newTop; 1.235 + var newRect = new Rect(newLeft, newTop, newWidth, newHeight); 1.236 + 1.237 + return newRect; 1.238 + }, 1.239 + 1.240 + // ---------- 1.241 + // Function: copy 1.242 + // Copies the values of the given <Rect> into this rectangle. 1.243 + copy: function Rect_copy(a) { 1.244 + this.left = a.left; 1.245 + this.top = a.top; 1.246 + this.width = a.width; 1.247 + this.height = a.height; 1.248 + } 1.249 +}; 1.250 + 1.251 +// ########## 1.252 +// Class: Range 1.253 +// A physical interval, with a min and max. 1.254 +// 1.255 +// Constructor: Range 1.256 +// Creates a Range with the given min and max 1.257 +this.Range = function Range(min, max) { 1.258 + if (Utils.isRange(min) && !max) { // if the one variable given is a range, copy it. 1.259 + this.min = min.min; 1.260 + this.max = min.max; 1.261 + } else { 1.262 + this.min = min || 0; 1.263 + this.max = max || 0; 1.264 + } 1.265 +}; 1.266 + 1.267 +Range.prototype = { 1.268 + // ---------- 1.269 + // Function: toString 1.270 + // Prints [Range (min,max)] for debug use 1.271 + toString: function Range_toString() { 1.272 + return "[Range (" + this.min + "," + this.max + ")]"; 1.273 + }, 1.274 + 1.275 + // Variable: extent 1.276 + // Equivalent to max-min 1.277 + get extent() { 1.278 + return (this.max - this.min); 1.279 + }, 1.280 + 1.281 + set extent(extent) { 1.282 + this.max = extent - this.min; 1.283 + }, 1.284 + 1.285 + // ---------- 1.286 + // Function: contains 1.287 + // Whether the <Range> contains the given <Range> or value or not. 1.288 + // 1.289 + // Parameters 1.290 + // - a number or <Range> 1.291 + contains: function Range_contains(value) { 1.292 + if (Utils.isNumber(value)) 1.293 + return value >= this.min && value <= this.max; 1.294 + if (Utils.isRange(value)) 1.295 + return value.min >= this.min && value.max <= this.max; 1.296 + return false; 1.297 + }, 1.298 + 1.299 + // ---------- 1.300 + // Function: overlaps 1.301 + // Whether the <Range> overlaps with the given <Range> value or not. 1.302 + // 1.303 + // Parameters 1.304 + // - a number or <Range> 1.305 + overlaps: function Range_overlaps(value) { 1.306 + if (Utils.isNumber(value)) 1.307 + return this.contains(value); 1.308 + if (Utils.isRange(value)) 1.309 + return !(value.max < this.min || this.max < value.min); 1.310 + return false; 1.311 + }, 1.312 + 1.313 + // ---------- 1.314 + // Function: proportion 1.315 + // Maps the given value to the range [0,1], so that it returns 0 if the value is <= the min, 1.316 + // returns 1 if the value >= the max, and returns an interpolated "proportion" in (min, max). 1.317 + // 1.318 + // Parameters 1.319 + // - a number 1.320 + // - (bool) smooth? If true, a smooth tanh-based function will be used instead of the linear. 1.321 + proportion: function Range_proportion(value, smooth) { 1.322 + if (value <= this.min) 1.323 + return 0; 1.324 + if (this.max <= value) 1.325 + return 1; 1.326 + 1.327 + var proportion = (value - this.min) / this.extent; 1.328 + 1.329 + if (smooth) { 1.330 + // The ease function ".5+.5*Math.tanh(4*x-2)" is a pretty 1.331 + // little graph. It goes from near 0 at x=0 to near 1 at x=1 1.332 + // smoothly and beautifully. 1.333 + // http://www.wolframalpha.com/input/?i=.5+%2B+.5+*+tanh%28%284+*+x%29+-+2%29 1.334 + let tanh = function tanh(x) { 1.335 + var e = Math.exp(x); 1.336 + return (e - 1/e) / (e + 1/e); 1.337 + }; 1.338 + 1.339 + return .5 - .5 * tanh(2 - 4 * proportion); 1.340 + } 1.341 + 1.342 + return proportion; 1.343 + }, 1.344 + 1.345 + // ---------- 1.346 + // Function: scale 1.347 + // Takes the given value in [0,1] and maps it to the associated value on the Range. 1.348 + // 1.349 + // Parameters 1.350 + // - a number in [0,1] 1.351 + scale: function Range_scale(value) { 1.352 + if (value > 1) 1.353 + value = 1; 1.354 + if (value < 0) 1.355 + value = 0; 1.356 + return this.min + this.extent * value; 1.357 + } 1.358 +}; 1.359 + 1.360 +// ########## 1.361 +// Class: Subscribable 1.362 +// A mix-in for allowing objects to collect subscribers for custom events. 1.363 +this.Subscribable = function Subscribable() { 1.364 + this.subscribers = null; 1.365 +}; 1.366 + 1.367 +Subscribable.prototype = { 1.368 + // ---------- 1.369 + // Function: addSubscriber 1.370 + // The given callback will be called when the Subscribable fires the given event. 1.371 + addSubscriber: function Subscribable_addSubscriber(eventName, callback) { 1.372 + try { 1.373 + Utils.assertThrow(typeof callback == "function", "callback must be a function"); 1.374 + Utils.assertThrow(eventName && typeof eventName == "string", 1.375 + "eventName must be a non-empty string"); 1.376 + } catch(e) { 1.377 + Utils.log(e); 1.378 + return; 1.379 + } 1.380 + 1.381 + if (!this.subscribers) 1.382 + this.subscribers = {}; 1.383 + 1.384 + if (!this.subscribers[eventName]) 1.385 + this.subscribers[eventName] = []; 1.386 + 1.387 + let subscribers = this.subscribers[eventName]; 1.388 + if (subscribers.indexOf(callback) == -1) 1.389 + subscribers.push(callback); 1.390 + }, 1.391 + 1.392 + // ---------- 1.393 + // Function: removeSubscriber 1.394 + // Removes the subscriber associated with the event for the given callback. 1.395 + removeSubscriber: function Subscribable_removeSubscriber(eventName, callback) { 1.396 + try { 1.397 + Utils.assertThrow(typeof callback == "function", "callback must be a function"); 1.398 + Utils.assertThrow(eventName && typeof eventName == "string", 1.399 + "eventName must be a non-empty string"); 1.400 + } catch(e) { 1.401 + Utils.log(e); 1.402 + return; 1.403 + } 1.404 + 1.405 + if (!this.subscribers || !this.subscribers[eventName]) 1.406 + return; 1.407 + 1.408 + let subscribers = this.subscribers[eventName]; 1.409 + let index = subscribers.indexOf(callback); 1.410 + 1.411 + if (index > -1) 1.412 + subscribers.splice(index, 1); 1.413 + }, 1.414 + 1.415 + // ---------- 1.416 + // Function: _sendToSubscribers 1.417 + // Internal routine. Used by the Subscribable to fire events. 1.418 + _sendToSubscribers: function Subscribable__sendToSubscribers(eventName, eventInfo) { 1.419 + try { 1.420 + Utils.assertThrow(eventName && typeof eventName == "string", 1.421 + "eventName must be a non-empty string"); 1.422 + } catch(e) { 1.423 + Utils.log(e); 1.424 + return; 1.425 + } 1.426 + 1.427 + if (!this.subscribers || !this.subscribers[eventName]) 1.428 + return; 1.429 + 1.430 + let subsCopy = this.subscribers[eventName].concat(); 1.431 + subsCopy.forEach(function (callback) { 1.432 + try { 1.433 + callback(this, eventInfo); 1.434 + } catch(e) { 1.435 + Utils.log(e); 1.436 + } 1.437 + }, this); 1.438 + } 1.439 +}; 1.440 + 1.441 +// ########## 1.442 +// Class: Utils 1.443 +// Singelton with common utility functions. 1.444 +this.Utils = { 1.445 + // ---------- 1.446 + // Function: toString 1.447 + // Prints [Utils] for debug use 1.448 + toString: function Utils_toString() { 1.449 + return "[Utils]"; 1.450 + }, 1.451 + 1.452 + // ___ Logging 1.453 + useConsole: true, // as opposed to dump 1.454 + showTime: false, 1.455 + 1.456 + // ---------- 1.457 + // Function: log 1.458 + // Prints the given arguments to the JavaScript error console as a message. 1.459 + // Pass as many arguments as you want, it'll print them all. 1.460 + log: function Utils_log() { 1.461 + var text = this.expandArgumentsForLog(arguments); 1.462 + var prefix = this.showTime ? Date.now() + ': ' : ''; 1.463 + if (this.useConsole) 1.464 + Services.console.logStringMessage(prefix + text); 1.465 + else 1.466 + dump(prefix + text + '\n'); 1.467 + }, 1.468 + 1.469 + // ---------- 1.470 + // Function: error 1.471 + // Prints the given arguments to the JavaScript error console as an error. 1.472 + // Pass as many arguments as you want, it'll print them all. 1.473 + error: function Utils_error() { 1.474 + var text = this.expandArgumentsForLog(arguments); 1.475 + var prefix = this.showTime ? Date.now() + ': ' : ''; 1.476 + if (this.useConsole) 1.477 + Cu.reportError(prefix + "tabview error: " + text); 1.478 + else 1.479 + dump(prefix + "TABVIEW ERROR: " + text + '\n'); 1.480 + }, 1.481 + 1.482 + // ---------- 1.483 + // Function: trace 1.484 + // Prints the given arguments to the JavaScript error console as a message, 1.485 + // along with a full stack trace. 1.486 + // Pass as many arguments as you want, it'll print them all. 1.487 + trace: function Utils_trace() { 1.488 + var text = this.expandArgumentsForLog(arguments); 1.489 + 1.490 + // cut off the first line of the stack trace, because that's just this function. 1.491 + let stack = Error().stack.split("\n").slice(1); 1.492 + 1.493 + // if the caller was assert, cut out the line for the assert function as well. 1.494 + if (stack[0].indexOf("Utils_assert(") == 0) 1.495 + stack.splice(0, 1); 1.496 + 1.497 + this.log('trace: ' + text + '\n' + stack.join("\n")); 1.498 + }, 1.499 + 1.500 + // ---------- 1.501 + // Function: assert 1.502 + // Prints a stack trace along with label (as a console message) if condition is false. 1.503 + assert: function Utils_assert(condition, label) { 1.504 + if (!condition) { 1.505 + let text; 1.506 + if (typeof label != 'string') 1.507 + text = 'badly formed assert'; 1.508 + else 1.509 + text = "tabview assert: " + label; 1.510 + 1.511 + this.trace(text); 1.512 + } 1.513 + }, 1.514 + 1.515 + // ---------- 1.516 + // Function: assertThrow 1.517 + // Throws label as an exception if condition is false. 1.518 + assertThrow: function Utils_assertThrow(condition, label) { 1.519 + if (!condition) { 1.520 + let text; 1.521 + if (typeof label != 'string') 1.522 + text = 'badly formed assert'; 1.523 + else 1.524 + text = "tabview assert: " + label; 1.525 + 1.526 + // cut off the first line of the stack trace, because that's just this function. 1.527 + let stack = Error().stack.split("\n").slice(1); 1.528 + 1.529 + throw text + "\n" + stack.join("\n"); 1.530 + } 1.531 + }, 1.532 + 1.533 + // ---------- 1.534 + // Function: expandObject 1.535 + // Prints the given object to a string, including all of its properties. 1.536 + expandObject: function Utils_expandObject(obj) { 1.537 + var s = obj + ' = {'; 1.538 + for (let prop in obj) { 1.539 + let value; 1.540 + try { 1.541 + value = obj[prop]; 1.542 + } catch(e) { 1.543 + value = '[!!error retrieving property]'; 1.544 + } 1.545 + 1.546 + s += prop + ': '; 1.547 + if (typeof value == 'string') 1.548 + s += '\'' + value + '\''; 1.549 + else if (typeof value == 'function') 1.550 + s += 'function'; 1.551 + else 1.552 + s += value; 1.553 + 1.554 + s += ', '; 1.555 + } 1.556 + return s + '}'; 1.557 + }, 1.558 + 1.559 + // ---------- 1.560 + // Function: expandArgumentsForLog 1.561 + // Expands all of the given args (an array) into a single string. 1.562 + expandArgumentsForLog: function Utils_expandArgumentsForLog(args) { 1.563 + var that = this; 1.564 + return Array.map(args, function(arg) { 1.565 + return typeof arg == 'object' ? that.expandObject(arg) : arg; 1.566 + }).join('; '); 1.567 + }, 1.568 + 1.569 + // ___ Misc 1.570 + 1.571 + // ---------- 1.572 + // Function: isLeftClick 1.573 + // Given a DOM mouse event, returns true if it was for the left mouse button. 1.574 + isLeftClick: function Utils_isLeftClick(event) { 1.575 + return event.button == 0; 1.576 + }, 1.577 + 1.578 + // ---------- 1.579 + // Function: isMiddleClick 1.580 + // Given a DOM mouse event, returns true if it was for the middle mouse button. 1.581 + isMiddleClick: function Utils_isMiddleClick(event) { 1.582 + return event.button == 1; 1.583 + }, 1.584 + 1.585 + // ---------- 1.586 + // Function: isRightClick 1.587 + // Given a DOM mouse event, returns true if it was for the right mouse button. 1.588 + isRightClick: function Utils_isRightClick(event) { 1.589 + return event.button == 2; 1.590 + }, 1.591 + 1.592 + // ---------- 1.593 + // Function: isDOMElement 1.594 + // Returns true if the given object is a DOM element. 1.595 + isDOMElement: function Utils_isDOMElement(object) { 1.596 + return object instanceof Ci.nsIDOMElement; 1.597 + }, 1.598 + 1.599 + // ---------- 1.600 + // Function: isValidXULTab 1.601 + // A xulTab is valid if it has not been closed, 1.602 + // and it has not been removed from the DOM 1.603 + // Returns true if the tab is valid. 1.604 + isValidXULTab: function Utils_isValidXULTab(xulTab) { 1.605 + return !xulTab.closing && xulTab.parentNode; 1.606 + }, 1.607 + 1.608 + // ---------- 1.609 + // Function: isNumber 1.610 + // Returns true if the argument is a valid number. 1.611 + isNumber: function Utils_isNumber(n) { 1.612 + return typeof n == 'number' && !isNaN(n); 1.613 + }, 1.614 + 1.615 + // ---------- 1.616 + // Function: isRect 1.617 + // Returns true if the given object (r) looks like a <Rect>. 1.618 + isRect: function Utils_isRect(r) { 1.619 + return (r && 1.620 + this.isNumber(r.left) && 1.621 + this.isNumber(r.top) && 1.622 + this.isNumber(r.width) && 1.623 + this.isNumber(r.height)); 1.624 + }, 1.625 + 1.626 + // ---------- 1.627 + // Function: isRange 1.628 + // Returns true if the given object (r) looks like a <Range>. 1.629 + isRange: function Utils_isRange(r) { 1.630 + return (r && 1.631 + this.isNumber(r.min) && 1.632 + this.isNumber(r.max)); 1.633 + }, 1.634 + 1.635 + // ---------- 1.636 + // Function: isPoint 1.637 + // Returns true if the given object (p) looks like a <Point>. 1.638 + isPoint: function Utils_isPoint(p) { 1.639 + return (p && this.isNumber(p.x) && this.isNumber(p.y)); 1.640 + }, 1.641 + 1.642 + // ---------- 1.643 + // Function: isPlainObject 1.644 + // Check to see if an object is a plain object (created using "{}" or "new Object"). 1.645 + isPlainObject: function Utils_isPlainObject(obj) { 1.646 + // Must be an Object. 1.647 + // Make sure that DOM nodes and window objects don't pass through, as well 1.648 + if (!obj || Object.prototype.toString.call(obj) !== "[object Object]" || 1.649 + obj.nodeType || obj.setInterval) { 1.650 + return false; 1.651 + } 1.652 + 1.653 + // Not own constructor property must be Object 1.654 + const hasOwnProperty = Object.prototype.hasOwnProperty; 1.655 + 1.656 + if (obj.constructor && 1.657 + !hasOwnProperty.call(obj, "constructor") && 1.658 + !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) { 1.659 + return false; 1.660 + } 1.661 + 1.662 + // Own properties are enumerated firstly, so to speed up, 1.663 + // if last one is own, then all properties are own. 1.664 + 1.665 + var key; 1.666 + for (key in obj) {} 1.667 + 1.668 + return key === undefined || hasOwnProperty.call(obj, key); 1.669 + }, 1.670 + 1.671 + // ---------- 1.672 + // Function: isEmptyObject 1.673 + // Returns true if the given object has no members. 1.674 + isEmptyObject: function Utils_isEmptyObject(obj) { 1.675 + for (let name in obj) 1.676 + return false; 1.677 + return true; 1.678 + }, 1.679 + 1.680 + // ---------- 1.681 + // Function: copy 1.682 + // Returns a copy of the argument. Note that this is a shallow copy; if the argument 1.683 + // has properties that are themselves objects, those properties will be copied by reference. 1.684 + copy: function Utils_copy(value) { 1.685 + if (value && typeof value == 'object') { 1.686 + if (Array.isArray(value)) 1.687 + return this.extend([], value); 1.688 + return this.extend({}, value); 1.689 + } 1.690 + return value; 1.691 + }, 1.692 + 1.693 + // ---------- 1.694 + // Function: merge 1.695 + // Merge two array-like objects into the first and return it. 1.696 + merge: function Utils_merge(first, second) { 1.697 + Array.forEach(second, function(el) Array.push(first, el)); 1.698 + return first; 1.699 + }, 1.700 + 1.701 + // ---------- 1.702 + // Function: extend 1.703 + // Pass several objects in and it will combine them all into the first object and return it. 1.704 + extend: function Utils_extend() { 1.705 + 1.706 + // copy reference to target object 1.707 + let target = arguments[0] || {}; 1.708 + // Deep copy is not supported 1.709 + if (typeof target === "boolean") { 1.710 + this.assert(false, "The first argument of extend cannot be a boolean." + 1.711 + "Deep copy is not supported."); 1.712 + return target; 1.713 + } 1.714 + 1.715 + // Back when this was in iQ + iQ.fn, so you could extend iQ objects with it. 1.716 + // This is no longer supported. 1.717 + let length = arguments.length; 1.718 + if (length === 1) { 1.719 + this.assert(false, "Extending the iQ prototype using extend is not supported."); 1.720 + return target; 1.721 + } 1.722 + 1.723 + // Handle case when target is a string or something 1.724 + if (typeof target != "object" && typeof target != "function") { 1.725 + target = {}; 1.726 + } 1.727 + 1.728 + for (let i = 1; i < length; i++) { 1.729 + // Only deal with non-null/undefined values 1.730 + let options = arguments[i]; 1.731 + if (options != null) { 1.732 + // Extend the base object 1.733 + for (let name in options) { 1.734 + let copy = options[name]; 1.735 + 1.736 + // Prevent never-ending loop 1.737 + if (target === copy) 1.738 + continue; 1.739 + 1.740 + if (copy !== undefined) 1.741 + target[name] = copy; 1.742 + } 1.743 + } 1.744 + } 1.745 + 1.746 + // Return the modified object 1.747 + return target; 1.748 + }, 1.749 + 1.750 + // ---------- 1.751 + // Function: attempt 1.752 + // Tries to execute a number of functions. Returns immediately the return 1.753 + // value of the first non-failed function without executing successive 1.754 + // functions, or null. 1.755 + attempt: function Utils_attempt() { 1.756 + let args = arguments; 1.757 + 1.758 + for (let i = 0; i < args.length; i++) { 1.759 + try { 1.760 + return args[i](); 1.761 + } catch (e) {} 1.762 + } 1.763 + 1.764 + return null; 1.765 + } 1.766 +}; 1.767 + 1.768 +// ########## 1.769 +// Class: MRUList 1.770 +// A most recently used list. 1.771 +// 1.772 +// Constructor: MRUList 1.773 +// If a is an array of entries, creates a copy of it. 1.774 +this.MRUList = function MRUList(a) { 1.775 + if (Array.isArray(a)) 1.776 + this._list = a.concat(); 1.777 + else 1.778 + this._list = []; 1.779 +}; 1.780 + 1.781 +MRUList.prototype = { 1.782 + // ---------- 1.783 + // Function: toString 1.784 + // Prints [List (entry1, entry2, ...)] for debug use 1.785 + toString: function MRUList_toString() { 1.786 + return "[List (" + this._list.join(", ") + ")]"; 1.787 + }, 1.788 + 1.789 + // ---------- 1.790 + // Function: update 1.791 + // Updates/inserts the given entry as the most recently used one in the list. 1.792 + update: function MRUList_update(entry) { 1.793 + this.remove(entry); 1.794 + this._list.unshift(entry); 1.795 + }, 1.796 + 1.797 + // ---------- 1.798 + // Function: remove 1.799 + // Removes the given entry from the list. 1.800 + remove: function MRUList_remove(entry) { 1.801 + let index = this._list.indexOf(entry); 1.802 + if (index > -1) 1.803 + this._list.splice(index, 1); 1.804 + }, 1.805 + 1.806 + // ---------- 1.807 + // Function: peek 1.808 + // Returns the most recently used entry. If a filter exists, gets the most 1.809 + // recently used entry which matches the filter. 1.810 + peek: function MRUList_peek(filter) { 1.811 + let match = null; 1.812 + if (filter && typeof filter == "function") 1.813 + this._list.some(function MRUList_peek_getEntry(entry) { 1.814 + if (filter(entry)) { 1.815 + match = entry 1.816 + return true; 1.817 + } 1.818 + return false; 1.819 + }); 1.820 + else 1.821 + match = this._list.length > 0 ? this._list[0] : null; 1.822 + 1.823 + return match; 1.824 + }, 1.825 +}; 1.826 +