browser/components/tabview/modules/utils.jsm

changeset 0
6474c204b198
     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 +

mercurial