diff --git a/vendor/backbone/LICENSE b/vendor/backbone/LICENSE
new file mode 100644
index 000000000..f79bb0058
--- /dev/null
+++ b/vendor/backbone/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2012 Jeremy Ashkenas, DocumentCloud
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/backbone/README.md b/vendor/backbone/README.md
new file mode 100644
index 000000000..75aa0780c
--- /dev/null
+++ b/vendor/backbone/README.md
@@ -0,0 +1,26 @@
+ ____ __ __
+ /\ _`\ /\ \ /\ \ __
+ \ \ \ \ \ __ ___\ \ \/'\\ \ \____ ___ ___ __ /\_\ ____
+ \ \ _ <' /'__`\ /'___\ \ , < \ \ '__`\ / __`\ /' _ `\ /'__`\ \/\ \ /',__\
+ \ \ \ \ \/\ \ \.\_/\ \__/\ \ \\`\\ \ \ \ \/\ \ \ \/\ \/\ \/\ __/ __ \ \ \/\__, `\
+ \ \____/\ \__/.\_\ \____\\ \_\ \_\ \_,__/\ \____/\ \_\ \_\ \____\/\_\_\ \ \/\____/
+ \/___/ \/__/\/_/\/____/ \/_/\/_/\/___/ \/___/ \/_/\/_/\/____/\/_/\ \_\ \/___/
+ \ \____/
+ \/___/
+ (_'_______________________________________________________________________________'_)
+ (_.———————————————————————————————————————————————————————————————————————————————._)
+
+
+Backbone supplies structure to JavaScript-heavy applications by providing models key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface.
+
+For Docs, License, Tests, pre-packed downloads, and everything else, really, see:
+http://backbonejs.org
+
+To suggest a feature, report a bug, or general discussion:
+http://github.com/documentcloud/backbone/issues/
+
+All contributors are listed here:
+http://github.com/documentcloud/backbone/contributors
+
+Special thanks to Robert Kieffer for the original philosophy behind Backbone.
+http://github.com/broofa
diff --git a/vendor/backbone/backbone.js b/vendor/backbone/backbone.js
new file mode 100644
index 000000000..15a74d10e
--- /dev/null
+++ b/vendor/backbone/backbone.js
@@ -0,0 +1,1461 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `global`
+ // on the server).
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create a local reference to splice.
+ var splice = Array.prototype.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.2';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ Backbone.$ = root.jQuery || root.Zepto || root.ender;
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+
+ // Regular expression used to split event strings
+ var eventSplitter = /\s+/;
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may bind with `on` or remove with `off` callback functions
+ // to an event; `trigger`-ing an event fires all callbacks in succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.on('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ var Events = Backbone.Events = {
+
+ // Bind one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+ var calls, event, list;
+ if (!callback) return this;
+
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ while (event = events.shift()) {
+ list = calls[event] || (calls[event] = []);
+ list.push(callback, context);
+ }
+
+ return this;
+ },
+
+ // Remove one or many callbacks. If `context` is null, removes all callbacks
+ // with that function. If `callback` is null, removes all callbacks for the
+ // event. If `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, list, i;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return this;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+
+ // Loop through the callback list, splicing where appropriate.
+ while (event = events.shift()) {
+ if (!(list = calls[event]) || !(callback || context)) {
+ delete calls[event];
+ continue;
+ }
+
+ for (i = list.length - 2; i >= 0; i -= 2) {
+ if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
+ list.splice(i, 2);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // passed the same arguments as `trigger` is, apart from the event name
+ // (unless you're listening on `"all"`, which will cause your callback to
+ // receive the true name of the event as the first argument).
+ trigger: function(events) {
+ var event, calls, list, i, length, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+
+ rest = [];
+ events = events.split(eventSplitter);
+ for (i = 1, length = arguments.length; i < length; i++) {
+ rest[i - 1] = arguments[i];
+ }
+
+ // For each event, walk through the list of callbacks twice, first to
+ // trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ // Copy callback lists to prevent modification.
+ if (all = calls.all) all = all.slice();
+ if (list = calls[event]) list = list.slice();
+
+ // Execute event callbacks.
+ if (list) {
+ for (i = 0, length = list.length; i < length; i += 2) {
+ list[i].apply(list[i + 1] || this, rest);
+ }
+ }
+
+ // Execute "all" callbacks.
+ if (all) {
+ args = [event].concat(rest);
+ for (i = 0, length = all.length; i < length; i += 2) {
+ all[i].apply(all[i + 1] || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.collection) this.collection = options.collection;
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Proxy `Backbone.sync` by default.
+ sync: function() {
+ return Backbone.sync.apply(this, arguments);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ options = _.extend({}, options, {unset: true});
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ options = _.extend({}, options, {unset: true});
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp, options);
+ model.trigger('sync', model, resp, options);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return this.sync('read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save: function(key, value, options) {
+ var attrs, current, done;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // Do not persist invalid models.
+ if (!attrs && !this.isValid()) return false;
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ done = true;
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) success(model, resp, options);
+ model.trigger('sync', model, resp, options);
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
+
+ // When using `wait`, reset attributes to original values unless
+ // `success` has been called already.
+ if (!done && options.wait) {
+ this.clear(silentOptions);
+ this.set(current, silentOptions);
+ }
+
+ return xhr;
+ },
+
+ // Destroy this model on the server if it was already persisted.
+ // Optimistically removes the model from its collection, if it has one.
+ // If `wait: true` is passed, waits for the server to respond before removal.
+ destroy: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+
+ var destroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ options.success = function(resp) {
+ if (options.wait || model.isNew()) destroy();
+ if (success) success(model, resp, options);
+ if (!model.isNew()) model.trigger('sync', model, resp, options);
+ };
+
+ if (this.isNew()) {
+ options.success();
+ return false;
+ }
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = this.sync('delete', this, options);
+ if (!options.wait) destroy();
+ return xhr;
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url: function() {
+ var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew: function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (attr == null) return !_.isEmpty(this.changed);
+ return _.has(this.changed, attr);
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+ var val, changed = false, old = this._previousAttributes;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (attr == null || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate || !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. If a `comparator` is specified, the Collection will maintain
+ // its models in sort order, as they're added and removed.
+ var Collection = Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.model) this.model = options.model;
+ if (options.comparator !== undefined) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Collection.prototype, Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model: Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON: function(options) {
+ return this.map(function(model){ return model.toJSON(options); });
+ },
+
+ // Proxy `Backbone.sync` by default.
+ sync: function() {
+ return Backbone.sync.apply(this, arguments);
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ dups[i] = models.splice(dups[i], 1)[0];
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+
+ // Merge in duplicate models.
+ if (options.merge) {
+ for (i = 0, length = dups.length; i < length; i++) {
+ if (model = this._byId[dups[i].id]) {
+ model.set(dups[i], options);
+ }
+ }
+ }
+
+ // Sort the collection if appropriate.
+ if (this.comparator && options.at == null) this.sort({silent: true});
+
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Slice out a sub-array of models from the collection.
+ slice: function(begin, end) {
+ return this.models.slice(begin, end);
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of `filter`.
+ where: function(attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return true;
+ });
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
+ // normal circumstances, as the set will maintain sort order as each item
+ // is added.
+ sort: function(options) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp, options);
+ collection.trigger('sync', collection, resp, options);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return this.sync('read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. Add the model to the
+ // collection immediately, unless `wait: true` is passed, in which case we
+ // wait for the server to agree.
+ create: function(model, options) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(model, resp, options) {
+ if (options.wait) coll.add(model, options);
+ if (success) success(model, resp, options);
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new collection with an identical list of models as this one.
+ clone: function() {
+ return new this.constructor(this.models);
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function() {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(attrs, options) {
+ if (attrs instanceof Model) {
+ if (!attrs.collection) attrs.collection = this;
+ return attrs;
+ }
+ options || (options = {});
+ options.collection = this;
+ var model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) return false;
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ if (this == model.collection) {
+ delete model.collection;
+ }
+ model.off('all', this._onModelEvent, this);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that originate
+ // in other collections are ignored.
+ _onModelEvent: function(event, model, collection, options) {
+ if ((event == 'add' || event == 'remove') && collection != this) return;
+ if (event == 'destroy') {
+ this.remove(model, options);
+ }
+ if (model && event === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ if (model.id != null) this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+ });
+
+ // Backbone.Router
+ // -------------------
+
+ // Routers map faux-URLs to actions, and fire events when routes are
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
+ var Router = Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var namedParam = /:\w+/g;
+ var splatParam = /\*\w+/g;
+ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ // Set up all inheritable **Backbone.Router** properties and methods.
+ _.extend(Router.prototype, Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query, num) {
+ // ...
+ // });
+ //
+ route: function(route, name, callback) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) return;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function(options) {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ this.location = options && options.location || root.location;
+ this.history = options && options.history || root.history;
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Cached regex for removing a trailing slash.
+ var trailingSlash = /\/$/;
+
+ // Has the history handling already been started?
+ History.started = false;
+
+ // Set up all inheritable **Backbone.History** properties and methods.
+ _.extend(History.prototype, Events, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(window) {
+ var match = (window || this).location.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || !this._wantsHashChange || forcePushState) {
+ fragment = this.location.pathname;
+ var root = this.options.root.replace(trailingSlash, '');
+ if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ return decodeURIComponent(fragment.replace(routeStripper, ''));
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start: function(options) {
+ if (History.started) throw new Error("Backbone.history has already been started");
+ History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE && this._wantsHashChange) {
+ this.iframe = Backbone.$('').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ Backbone.$(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ Backbone.$(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = this.location;
+ var atRoot = (loc.pathname == this.options.root) && !loc.search;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ this.location.replace(this.options.root + this.location.search + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ this.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ Backbone.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) {
+ current = this.getFragment(this.getHash(this.iframe));
+ }
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+ this.fragment = frag;
+ var url = (frag.indexOf(this.options.root) != 0 ? this.options.root : '') + frag;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this._updateHash(this.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a
+ // history entry on hash-tag change. When replace is true, we don't
+ // want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ return this.location.assign(url);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+
+ // Backbone.View
+ // -------------
+
+ // Creating a Backbone.View creates its initial element outside of the DOM,
+ // if an existing element is not provided...
+ var View = Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+ // Set up all inheritable **Backbone.View** properties and methods.
+ _.extend(View.prototype, Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ // jQuery delegate for element lookup, scoped to DOM elements within the
+ // current view. This should be prefered to global lookups where possible.
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render: function() {
+ return this;
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) Backbone.$(el).attr(attributes);
+ if (content != null) Backbone.$(el).html(content);
+ return el;
+ },
+
+ // Change the view's element (`this.el` property), including event
+ // re-delegation.
+ setElement: function(element, delegate) {
+ if (this.$el) this.undelegateEvents();
+ this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
+ this.el = this.$el[0];
+ if (delegate !== false) this.delegateEvents();
+ return this;
+ },
+
+ // Set callbacks, where `this.events` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // 'click .open': function(e) { ... }
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents: function(events) {
+ if (!(events || (events = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
+ // You usually don't need to use this, but may wish to if you have multiple
+ // Backbone views attached to the same DOM element.
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` properties.
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = _.extend({}, getValue(this, 'attributes'));
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(getValue(this, 'tagName'), attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function(protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // Override this function to change the manner in which Backbone persists
+ // models to the server. You will be passed the type of request, and the
+ // model in question. By default, makes a RESTful Ajax request
+ // to the model's `url()`. Some possible customizations could be:
+ //
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
+ // * Send up the models as XML instead of JSON.
+ // * Persist models via WebSockets instead of Ajax.
+ //
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
+ // instead of `application/json` with the model in a param named `model`.
+ // Useful when interfacing with server-side languages like **PHP** that make
+ // it difficult to read the body of `PUT` requests.
+ Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default options, unless specified.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model);
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.data = params.data ? {model: params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return Backbone.ajax(_.extend(params, options));
+ };
+
+ // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
+ Backbone.ajax = function() {
+ return Backbone.$.ajax.apply(Backbone.$, arguments);
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this);
diff --git a/vendor/backbone/test/collection.js b/vendor/backbone/test/collection.js
new file mode 100644
index 000000000..91c0b11db
--- /dev/null
+++ b/vendor/backbone/test/collection.js
@@ -0,0 +1,669 @@
+$(document).ready(function() {
+
+ var lastRequest = null;
+ var sync = Backbone.sync;
+
+ var a, b, c, d, e, col, otherCol;
+
+ module("Backbone.Collection", {
+
+ setup: function() {
+ a = new Backbone.Model({id: 3, label: 'a'});
+ b = new Backbone.Model({id: 2, label: 'b'});
+ c = new Backbone.Model({id: 1, label: 'c'});
+ d = new Backbone.Model({id: 0, label: 'd'});
+ e = null;
+ col = new Backbone.Collection([a,b,c,d]);
+ otherCol = new Backbone.Collection();
+
+ Backbone.sync = function(method, model, options) {
+ lastRequest = {
+ method: method,
+ model: model,
+ options: options
+ };
+ };
+ },
+
+ teardown: function() {
+ Backbone.sync = sync;
+ }
+
+ });
+
+ test("Collection: new and sort", 7, function() {
+ equal(col.first(), a, "a should be first");
+ equal(col.last(), d, "d should be last");
+ col.comparator = function(a, b) {
+ return a.id > b.id ? -1 : 1;
+ };
+ col.sort();
+ equal(col.first(), a, "a should be first");
+ equal(col.last(), d, "d should be last");
+ col.comparator = function(model) { return model.id; };
+ col.sort();
+ equal(col.first(), d, "d should be first");
+ equal(col.last(), a, "a should be last");
+ equal(col.length, 4);
+ });
+
+ test("Collection: get, getByCid", 3, function() {
+ equal(col.get(0), d);
+ equal(col.get(2), b);
+ equal(col.getByCid(col.first().cid), col.first());
+ });
+
+ test("Collection: get with non-default ids", 2, function() {
+ var col = new Backbone.Collection();
+ var MongoModel = Backbone.Model.extend({
+ idAttribute: '_id'
+ });
+ var model = new MongoModel({_id: 100});
+ col.push(model);
+ equal(col.get(100), model);
+ model.set({_id: 101});
+ equal(col.get(101), model);
+ });
+
+ test("Collection: update index when id changes", 3, function() {
+ var col = new Backbone.Collection();
+ col.add([
+ {id : 0, name : 'one'},
+ {id : 1, name : 'two'}
+ ]);
+ var one = col.get(0);
+ equal(one.get('name'), 'one');
+ one.set({id : 101});
+ equal(col.get(0), null);
+ equal(col.get(101).get('name'), 'one');
+ });
+
+ test("Collection: at", 1, function() {
+ equal(col.at(2), c);
+ });
+
+ test("Collection: pluck", 1, function() {
+ equal(col.pluck('label').join(' '), 'a b c d');
+ });
+
+ test("Collection: add", 11, function() {
+ var added, opts, secondAdded;
+ added = opts = secondAdded = null;
+ e = new Backbone.Model({id: 10, label : 'e'});
+ otherCol.add(e);
+ otherCol.on('add', function() {
+ secondAdded = true;
+ });
+ col.on('add', function(model, collection, options){
+ added = model.get('label');
+ equal(options.index, 4);
+ opts = options;
+ });
+ col.add(e, {amazing: true});
+ equal(added, 'e');
+ equal(col.length, 5);
+ equal(col.last(), e);
+ equal(otherCol.length, 1);
+ equal(secondAdded, null);
+ ok(opts.amazing);
+
+ var f = new Backbone.Model({id: 20, label : 'f'});
+ var g = new Backbone.Model({id: 21, label : 'g'});
+ var h = new Backbone.Model({id: 22, label : 'h'});
+ var atCol = new Backbone.Collection([f, g, h]);
+ equal(atCol.length, 3);
+ atCol.add(e, {at: 1});
+ equal(atCol.length, 4);
+ equal(atCol.at(1), e);
+ equal(atCol.last(), h);
+ });
+
+ test("Collection: add multiple models", 6, function() {
+ var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
+ col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
+ for (var i = 0; i <= 5; i++) {
+ equal(col.at(i).get('at'), i);
+ }
+ });
+
+ test("Collection: add; at should have preference over comparator", 1, function() {
+ var Col = Backbone.Collection.extend({
+ comparator: function(a,b) {
+ return a.id > b.id ? -1 : 1;
+ }
+ });
+
+ var col = new Col([{id: 2}, {id: 3}]);
+ col.add(new Backbone.Model({id: 1}), {at: 1});
+
+ equal(col.pluck('id').join(' '), '3 1 2');
+ });
+
+ test("Collection: can't add model to collection twice", function() {
+ var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
+ equal(col.pluck('id').join(' '), '1 2 3');
+ });
+
+ test("Collection: can't add different model with same id to collection twice", 1, function() {
+ var col = new Backbone.Collection;
+ col.unshift({id: 101});
+ col.add({id: 101});
+ equal(col.length, 1);
+ });
+
+ test("Collection: merge in duplicate models with {merge: true}", 3, function() {
+ var col = new Backbone.Collection;
+ col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
+ col.add({id: 1, name: 'Moses'});
+ equal(col.first().get('name'), 'Moe');
+ col.add({id: 1, name: 'Moses'}, {merge: true});
+ equal(col.first().get('name'), 'Moses');
+ col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
+ equal(col.first().get('name'), 'Tim');
+ });
+
+ test("Collection: add model to multiple collections", 10, function() {
+ var counter = 0;
+ var e = new Backbone.Model({id: 10, label : 'e'});
+ e.on('add', function(model, collection) {
+ counter++;
+ equal(e, model);
+ if (counter > 1) {
+ equal(collection, colF);
+ } else {
+ equal(collection, colE);
+ }
+ });
+ var colE = new Backbone.Collection([]);
+ colE.on('add', function(model, collection) {
+ equal(e, model);
+ equal(colE, collection);
+ });
+ var colF = new Backbone.Collection([]);
+ colF.on('add', function(model, collection) {
+ equal(e, model);
+ equal(colF, collection);
+ });
+ colE.add(e);
+ equal(e.collection, colE);
+ colF.add(e);
+ equal(e.collection, colE);
+ });
+
+ test("Collection: add model with parse", 1, function() {
+ var Model = Backbone.Model.extend({
+ parse: function(obj) {
+ obj.value += 1;
+ return obj;
+ }
+ });
+
+ var Col = Backbone.Collection.extend({model: Model});
+ var col = new Col;
+ col.add({value: 1}, {parse: true});
+ equal(col.at(0).get('value'), 2);
+ });
+
+ test("Collection: add model to collection with sort()-style comparator", 3, function() {
+ var col = new Backbone.Collection;
+ col.comparator = function(a, b) {
+ return a.get('name') < b.get('name') ? -1 : 1;
+ };
+ var tom = new Backbone.Model({name: 'Tom'});
+ var rob = new Backbone.Model({name: 'Rob'});
+ var tim = new Backbone.Model({name: 'Tim'});
+ col.add(tom);
+ col.add(rob);
+ col.add(tim);
+ equal(col.indexOf(rob), 0);
+ equal(col.indexOf(tim), 1);
+ equal(col.indexOf(tom), 2);
+ });
+
+ test("Collection: comparator that depends on `this`", 1, function() {
+ var col = new Backbone.Collection;
+ col.negative = function(num) {
+ return -num;
+ };
+ col.comparator = function(a) {
+ return this.negative(a.id);
+ };
+ col.add([{id: 1}, {id: 2}, {id: 3}]);
+ equal(col.pluck('id').join(' '), '3 2 1');
+ });
+
+ test("Collection: remove", 5, function() {
+ var removed = null;
+ var otherRemoved = null;
+ col.on('remove', function(model, col, options) {
+ removed = model.get('label');
+ equal(options.index, 3);
+ });
+ otherCol.on('remove', function(model, col, options) {
+ otherRemoved = true;
+ });
+ col.remove(d);
+ equal(removed, 'd');
+ equal(col.length, 3);
+ equal(col.first(), a);
+ equal(otherRemoved, null);
+ });
+
+ test("Collection: shift and pop", 2, function() {
+ var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
+ equal(col.shift().get('a'), 'a');
+ equal(col.pop().get('c'), 'c');
+ });
+
+ test("Collection: slice", 2, function() {
+ var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
+ var array = col.slice(1, 3);
+ equal(array.length, 2);
+ equal(array[0].get('b'), 'b');
+ });
+
+ test("Collection: events are unbound on remove", 3, function() {
+ var counter = 0;
+ var dj = new Backbone.Model();
+ var emcees = new Backbone.Collection([dj]);
+ emcees.on('change', function(){ counter++; });
+ dj.set({name : 'Kool'});
+ equal(counter, 1);
+ emcees.reset([]);
+ equal(dj.collection, undefined);
+ dj.set({name : 'Shadow'});
+ equal(counter, 1);
+ });
+
+ test("Collection: remove in multiple collections", 7, function() {
+ var modelData = {
+ id : 5,
+ title : 'Othello'
+ };
+ var passed = false;
+ var e = new Backbone.Model(modelData);
+ var f = new Backbone.Model(modelData);
+ f.on('remove', function() {
+ passed = true;
+ });
+ var colE = new Backbone.Collection([e]);
+ var colF = new Backbone.Collection([f]);
+ ok(e != f);
+ ok(colE.length == 1);
+ ok(colF.length == 1);
+ colE.remove(e);
+ equal(passed, false);
+ ok(colE.length == 0);
+ colF.remove(e);
+ ok(colF.length == 0);
+ equal(passed, true);
+ });
+
+ test("Collection: remove same model in multiple collection", 16, function() {
+ var counter = 0;
+ var e = new Backbone.Model({id: 5, title: 'Othello'});
+ e.on('remove', function(model, collection) {
+ counter++;
+ equal(e, model);
+ if (counter > 1) {
+ equal(collection, colE);
+ } else {
+ equal(collection, colF);
+ }
+ });
+ var colE = new Backbone.Collection([e]);
+ colE.on('remove', function(model, collection) {
+ equal(e, model);
+ equal(colE, collection);
+ });
+ var colF = new Backbone.Collection([e]);
+ colF.on('remove', function(model, collection) {
+ equal(e, model);
+ equal(colF, collection);
+ });
+ equal(colE, e.collection);
+ colF.remove(e);
+ ok(colF.length == 0);
+ ok(colE.length == 1);
+ equal(counter, 1);
+ equal(colE, e.collection);
+ colE.remove(e);
+ equal(null, e.collection);
+ ok(colE.length == 0);
+ equal(counter, 2);
+ });
+
+ test("Collection: model destroy removes from all collections", 3, function() {
+ var e = new Backbone.Model({id: 5, title: 'Othello'});
+ e.sync = function(method, model, options) { options.success({}); };
+ var colE = new Backbone.Collection([e]);
+ var colF = new Backbone.Collection([e]);
+ e.destroy();
+ ok(colE.length == 0);
+ ok(colF.length == 0);
+ equal(undefined, e.collection);
+ });
+
+ test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
+ var e = new Backbone.Model({title: 'Othello'});
+ e.sync = function(method, model, options) { throw "should not be called"; };
+ var colE = new Backbone.Collection([e]);
+ var colF = new Backbone.Collection([e]);
+ e.destroy();
+ ok(colE.length == 0);
+ ok(colF.length == 0);
+ equal(undefined, e.collection);
+ });
+
+ test("Collection: fetch", 4, function() {
+ col.fetch();
+ equal(lastRequest.method, 'read');
+ equal(lastRequest.model, col);
+ equal(lastRequest.options.parse, true);
+
+ col.fetch({parse: false});
+ equal(lastRequest.options.parse, false);
+ });
+
+ test("Collection: create", 4, function() {
+ var model = col.create({label: 'f'}, {wait: true});
+ equal(lastRequest.method, 'create');
+ equal(lastRequest.model, model);
+ equal(model.get('label'), 'f');
+ equal(model.collection, col);
+ });
+
+ test("Collection: create enforces validation", 1, function() {
+ var ValidatingModel = Backbone.Model.extend({
+ validate: function(attrs) {
+ return "fail";
+ }
+ });
+ var ValidatingCollection = Backbone.Collection.extend({
+ model: ValidatingModel
+ });
+ var col = new ValidatingCollection();
+ equal(col.create({"foo":"bar"}), false);
+ });
+
+ test("Collection: a failing create runs the error callback", 1, function() {
+ var ValidatingModel = Backbone.Model.extend({
+ validate: function(attrs) {
+ return "fail";
+ }
+ });
+ var ValidatingCollection = Backbone.Collection.extend({
+ model: ValidatingModel
+ });
+ var flag = false;
+ var callback = function(model, error) { flag = true; };
+ var col = new ValidatingCollection();
+ col.create({"foo":"bar"}, { error: callback });
+ equal(flag, true);
+ });
+
+ test("collection: initialize", 1, function() {
+ var Collection = Backbone.Collection.extend({
+ initialize: function() {
+ this.one = 1;
+ }
+ });
+ var coll = new Collection;
+ equal(coll.one, 1);
+ });
+
+ test("Collection: toJSON", 1, function() {
+ equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
+ });
+
+ test("Collection: where", 6, function() {
+ var coll = new Backbone.Collection([
+ {a: 1},
+ {a: 1},
+ {a: 1, b: 2},
+ {a: 2, b: 2},
+ {a: 3}
+ ]);
+ equal(coll.where({a: 1}).length, 3);
+ equal(coll.where({a: 2}).length, 1);
+ equal(coll.where({a: 3}).length, 1);
+ equal(coll.where({b: 1}).length, 0);
+ equal(coll.where({b: 2}).length, 2);
+ equal(coll.where({a: 1, b: 2}).length, 1);
+ });
+
+ test("Collection: Underscore methods", 13, function() {
+ equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
+ equal(col.any(function(model){ return model.id === 100; }), false);
+ equal(col.any(function(model){ return model.id === 0; }), true);
+ equal(col.indexOf(b), 1);
+ equal(col.size(), 4);
+ equal(col.rest().length, 3);
+ ok(!_.include(col.rest()), a);
+ ok(!_.include(col.rest()), d);
+ ok(!col.isEmpty());
+ ok(!_.include(col.without(d)), d);
+ equal(col.max(function(model){ return model.id; }).id, 3);
+ equal(col.min(function(model){ return model.id; }).id, 0);
+ deepEqual(col.chain()
+ .filter(function(o){ return o.id % 2 === 0; })
+ .map(function(o){ return o.id * 2; })
+ .value(),
+ [4, 0]);
+ });
+
+ test("Collection: reset", 10, function() {
+ var resetCount = 0;
+ var models = col.models;
+ col.on('reset', function() { resetCount += 1; });
+ col.reset([]);
+ equal(resetCount, 1);
+ equal(col.length, 0);
+ equal(col.last(), null);
+ col.reset(models);
+ equal(resetCount, 2);
+ equal(col.length, 4);
+ equal(col.last(), d);
+ col.reset(_.map(models, function(m){ return m.attributes; }));
+ equal(resetCount, 3);
+ equal(col.length, 4);
+ ok(col.last() !== d);
+ ok(_.isEqual(col.last().attributes, d.attributes));
+ });
+
+ test("Collection: reset passes caller options", 3, function() {
+ var Model = Backbone.Model.extend({
+ initialize: function(attrs, options) {
+ this.model_parameter = options.model_parameter;
+ }
+ });
+ var col = new (Backbone.Collection.extend({ model: Model }))();
+ col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
+ equal(col.length, 2);
+ col.each(function(model) {
+ equal(model.model_parameter, 'model parameter');
+ });
+ });
+
+ test("Collection: trigger custom events on models", 1, function() {
+ var fired = null;
+ a.on("custom", function() { fired = true; });
+ a.trigger("custom");
+ equal(fired, true);
+ });
+
+ test("Collection: add does not alter arguments", 2, function(){
+ var attrs = {};
+ var models = [attrs];
+ new Backbone.Collection().add(models);
+ equal(models.length, 1);
+ ok(attrs === models[0]);
+ });
+
+ test("#714: access `model.collection` in a brand new model.", 2, function() {
+ var col = new Backbone.Collection;
+ var Model = Backbone.Model.extend({
+ set: function(attrs) {
+ equal(attrs.prop, 'value');
+ equal(this.collection, col);
+ return this;
+ }
+ });
+ col.model = Model;
+ col.create({prop: 'value'});
+ });
+
+ test("#574, remove its own reference to the .models array.", 2, function() {
+ var col = new Backbone.Collection([
+ {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
+ ]);
+ equal(col.length, 6);
+ col.remove(col.models);
+ equal(col.length, 0);
+ });
+
+ test("#861, adding models to a collection which do not pass validation", 1, function() {
+ raises(function() {
+ var Model = Backbone.Model.extend({
+ validate: function(attrs) {
+ if (attrs.id == 3) return "id can't be 3";
+ }
+ });
+
+ var Collection = Backbone.Collection.extend({
+ model: Model
+ });
+
+ var col = new Collection;
+
+ col.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]);
+ }, function(e) {
+ return e.message === "Can't add an invalid model to a collection";
+ });
+ });
+
+ test("Collection: index with comparator", 4, function() {
+ var counter = 0;
+ var col = new Backbone.Collection([{id: 2}, {id: 4}], {
+ comparator: function(model){ return model.id; }
+ }).on('add', function(model, colleciton, options){
+ if (model.id == 1) {
+ equal(options.index, 0);
+ equal(counter++, 0);
+ }
+ if (model.id == 3) {
+ equal(options.index, 2);
+ equal(counter++, 1);
+ }
+ });
+ col.add([{id: 3}, {id: 1}]);
+ });
+
+ test("Collection: throwing during add leaves consistent state", 4, function() {
+ var col = new Backbone.Collection();
+ col.on('test', function() { ok(false); });
+ col.model = Backbone.Model.extend({
+ validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
+ });
+ var model = new col.model({id: 1, valid: true});
+ raises(function() { col.add([model, {id: 2}]); });
+ model.trigger('test');
+ ok(!col.getByCid(model.cid));
+ ok(!col.get(1));
+ equal(col.length, 0);
+ });
+
+ test("Collection: multiple copies of the same model", 3, function() {
+ var col = new Backbone.Collection();
+ var model = new Backbone.Model();
+ col.add([model, model]);
+ equal(col.length, 1);
+ col.add([{id: 1}, {id: 1}]);
+ equal(col.length, 2);
+ equal(col.last().id, 1);
+ });
+
+ test("#964 - collection.get return inconsistent", 2, function() {
+ var c = new Backbone.Collection();
+ ok(c.get(null) === undefined);
+ ok(c.get() === undefined);
+ });
+
+ test("#1112 - passing options.model sets collection.model", 2, function() {
+ var Model = Backbone.Model.extend({});
+ var c = new Backbone.Collection([{id: 1}], {model: Model});
+ ok(c.model === Model);
+ ok(c.at(0) instanceof Model);
+ });
+
+ test("null and undefined are invalid ids.", 2, function() {
+ var model = new Backbone.Model({id: 1});
+ var collection = new Backbone.Collection([model]);
+ model.set({id: null});
+ ok(!collection.get('null'));
+ model.set({id: 1});
+ model.set({id: undefined});
+ ok(!collection.get('undefined'));
+ });
+
+ test("Collection: falsy comparator", 4, function(){
+ var Col = Backbone.Collection.extend({
+ comparator: function(model){ return model.id; }
+ });
+ var col = new Col();
+ var colFalse = new Col(null, {comparator: false});
+ var colNull = new Col(null, {comparator: null});
+ var colUndefined = new Col(null, {comparator: undefined});
+ ok(col.comparator);
+ ok(!colFalse.comparator);
+ ok(!colNull.comparator);
+ ok(colUndefined.comparator);
+ });
+
+ test("#1355 - `options` is passed to success callbacks", 2, function(){
+ var m = new Backbone.Model({x:1});
+ var col = new Backbone.Collection();
+ var opts = {
+ success: function(collection, resp, options){
+ ok(options);
+ }
+ };
+ col.sync = m.sync = function( method, collection, options ){
+ options.success();
+ };
+ col.fetch(opts);
+ col.create(m, opts);
+ });
+
+ test("#1412 - Trigger 'sync' event.", 2, function() {
+ var collection = new Backbone.Collection([], {
+ model: Backbone.Model.extend({
+ sync: function(method, model, options) {
+ options.success();
+ }
+ })
+ });
+ collection.sync = function(method, model, options) { options.success(); };
+ collection.on('sync', function() { ok(true); });
+ collection.fetch();
+ collection.create({id: 1});
+ });
+
+ test("#1447 - create with wait adds model.", function() {
+ var collection = new Backbone.Collection;
+ var model = new Backbone.Model;
+ model.sync = function(method, model, options){ options.success(); };
+ collection.on('add', function(){ ok(true); });
+ collection.create(model, {wait: true});
+ });
+
+ test("#1448 - add sorts collection after merge.", function() {
+ var collection = new Backbone.Collection([
+ {id: 1, x: 1},
+ {id: 2, x: 2}
+ ]);
+ collection.comparator = function(model){ return model.get('x'); };
+ collection.add({id: 1, x: 3}, {merge: true});
+ deepEqual(collection.pluck('id'), [2, 1]);
+ });
+});
diff --git a/vendor/backbone/test/events.js b/vendor/backbone/test/events.js
new file mode 100644
index 000000000..6d5977432
--- /dev/null
+++ b/vendor/backbone/test/events.js
@@ -0,0 +1,195 @@
+$(document).ready(function() {
+
+ module("Backbone.Events");
+
+ test("Events: on and trigger", 2, function() {
+ var obj = { counter: 0 };
+ _.extend(obj,Backbone.Events);
+ obj.on('event', function() { obj.counter += 1; });
+ obj.trigger('event');
+ equal(obj.counter,1,'counter should be incremented.');
+ obj.trigger('event');
+ obj.trigger('event');
+ obj.trigger('event');
+ obj.trigger('event');
+ equal(obj.counter, 5, 'counter should be incremented five times.');
+ });
+
+ test("Events: binding and triggering multiple events", 4, function() {
+ var obj = { counter: 0 };
+ _.extend(obj,Backbone.Events);
+
+ obj.on('a b c', function() { obj.counter += 1; });
+
+ obj.trigger('a');
+ equal(obj.counter, 1);
+
+ obj.trigger('a b');
+ equal(obj.counter, 3);
+
+ obj.trigger('c');
+ equal(obj.counter, 4);
+
+ obj.off('a c');
+ obj.trigger('a b c');
+ equal(obj.counter, 5);
+ });
+
+ test("Events: trigger all for each event", 3, function() {
+ var a, b, obj = { counter: 0 };
+ _.extend(obj, Backbone.Events);
+ obj.on('all', function(event) {
+ obj.counter++;
+ if (event == 'a') a = true;
+ if (event == 'b') b = true;
+ })
+ .trigger('a b');
+ ok(a);
+ ok(b);
+ equal(obj.counter, 2);
+ });
+
+ test("Events: on, then unbind all functions", 1, function() {
+ var obj = { counter: 0 };
+ _.extend(obj,Backbone.Events);
+ var callback = function() { obj.counter += 1; };
+ obj.on('event', callback);
+ obj.trigger('event');
+ obj.off('event');
+ obj.trigger('event');
+ equal(obj.counter, 1, 'counter should have only been incremented once.');
+ });
+
+ test("Events: bind two callbacks, unbind only one", 2, function() {
+ var obj = { counterA: 0, counterB: 0 };
+ _.extend(obj,Backbone.Events);
+ var callback = function() { obj.counterA += 1; };
+ obj.on('event', callback);
+ obj.on('event', function() { obj.counterB += 1; });
+ obj.trigger('event');
+ obj.off('event', callback);
+ obj.trigger('event');
+ equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+ equal(obj.counterB, 2, 'counterB should have been incremented twice.');
+ });
+
+ test("Events: unbind a callback in the midst of it firing", 1, function() {
+ var obj = {counter: 0};
+ _.extend(obj, Backbone.Events);
+ var callback = function() {
+ obj.counter += 1;
+ obj.off('event', callback);
+ };
+ obj.on('event', callback);
+ obj.trigger('event');
+ obj.trigger('event');
+ obj.trigger('event');
+ equal(obj.counter, 1, 'the callback should have been unbound.');
+ });
+
+ test("Events: two binds that unbind themeselves", 2, function() {
+ var obj = { counterA: 0, counterB: 0 };
+ _.extend(obj,Backbone.Events);
+ var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); };
+ var incrB = function(){ obj.counterB += 1; obj.off('event', incrB); };
+ obj.on('event', incrA);
+ obj.on('event', incrB);
+ obj.trigger('event');
+ obj.trigger('event');
+ obj.trigger('event');
+ equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+ equal(obj.counterB, 1, 'counterB should have only been incremented once.');
+ });
+
+ test("Events: bind a callback with a supplied context", 1, function () {
+ var TestClass = function () {
+ return this;
+ };
+ TestClass.prototype.assertTrue = function () {
+ ok(true, '`this` was bound to the callback');
+ };
+
+ var obj = _.extend({},Backbone.Events);
+ obj.on('event', function () { this.assertTrue(); }, (new TestClass));
+ obj.trigger('event');
+ });
+
+ test("Events: nested trigger with unbind", 1, function () {
+ var obj = { counter: 0 };
+ _.extend(obj, Backbone.Events);
+ var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); };
+ var incr2 = function(){ obj.counter += 1; };
+ obj.on('event', incr1);
+ obj.on('event', incr2);
+ obj.trigger('event');
+ equal(obj.counter, 3, 'counter should have been incremented three times');
+ });
+
+ test("Events: callback list is not altered during trigger", 2, function () {
+ var counter = 0, obj = _.extend({}, Backbone.Events);
+ var incr = function(){ counter++; };
+ obj.on('event', function(){ obj.on('event', incr).on('all', incr); })
+ .trigger('event');
+ equal(counter, 0, 'bind does not alter callback list');
+ obj.off()
+ .on('event', function(){ obj.off('event', incr).off('all', incr); })
+ .on('event', incr)
+ .on('all', incr)
+ .trigger('event');
+ equal(counter, 2, 'unbind does not alter callback list');
+ });
+
+ test("#1282 - 'all' callback list is retrieved after each event.", 1, function() {
+ var counter = 0;
+ var obj = _.extend({}, Backbone.Events);
+ var incr = function(){ counter++; };
+ obj.on('x', function() {
+ obj.on('y', incr).on('all', incr);
+ })
+ .trigger('x y');
+ strictEqual(counter, 2);
+ });
+
+ test("if no callback is provided, `on` is a noop", 0, function() {
+ _.extend({}, Backbone.Events).on('test').trigger('test');
+ });
+
+ test("remove all events for a specific context", 4, function() {
+ var obj = _.extend({}, Backbone.Events);
+ obj.on('x y all', function() { ok(true); });
+ obj.on('x y all', function() { ok(false); }, obj);
+ obj.off(null, null, obj);
+ obj.trigger('x y');
+ });
+
+ test("remove all events for a specific callback", 4, function() {
+ var obj = _.extend({}, Backbone.Events);
+ var success = function() { ok(true); };
+ var fail = function() { ok(false); };
+ obj.on('x y all', success);
+ obj.on('x y all', fail);
+ obj.off(null, fail);
+ obj.trigger('x y');
+ });
+
+ test("off is chainable", 3, function() {
+ var obj = _.extend({}, Backbone.Events);
+ // With no events
+ ok(obj.off() === obj);
+ // When removing all events
+ obj.on('event', function(){}, obj);
+ ok(obj.off() === obj);
+ // When removing some events
+ obj.on('event', function(){}, obj);
+ ok(obj.off('event') === obj);
+ });
+
+ test("#1310 - off does not skip consecutive events", 0, function() {
+ var obj = _.extend({}, Backbone.Events);
+ obj.on('event', function() { ok(false); }, obj);
+ obj.on('event', function() { ok(false); }, obj);
+ obj.off(null, null, obj);
+ obj.trigger('event');
+ });
+
+});
diff --git a/vendor/backbone/test/model.js b/vendor/backbone/test/model.js
new file mode 100644
index 000000000..8689d9754
--- /dev/null
+++ b/vendor/backbone/test/model.js
@@ -0,0 +1,855 @@
+$(document).ready(function() {
+
+ // Variable to catch the last request.
+ var lastRequest = null;
+ // Variable to catch ajax params.
+ var ajaxParams = null;
+ var sync = Backbone.sync;
+ var ajax = Backbone.ajax;
+ var urlRoot = null;
+
+ var proxy = Backbone.Model.extend();
+ var klass = Backbone.Collection.extend({
+ url : function() { return '/collection'; }
+ });
+ var doc, collection;
+
+ module("Backbone.Model", {
+
+ setup: function() {
+ doc = new proxy({
+ id : '1-the-tempest',
+ title : "The Tempest",
+ author : "Bill Shakespeare",
+ length : 123
+ });
+ collection = new klass();
+ collection.add(doc);
+
+ Backbone.sync = function(method, model, options) {
+ lastRequest = {
+ method: method,
+ model: model,
+ options: options
+ };
+ sync.apply(this, arguments);
+ };
+ Backbone.ajax = function(params) { ajaxParams = params; };
+ urlRoot = Backbone.Model.prototype.urlRoot;
+ Backbone.Model.prototype.urlRoot = '/';
+ },
+
+ teardown: function() {
+ Backbone.sync = sync;
+ Backbone.ajax = ajax;
+ Backbone.Model.prototype.urlRoot = urlRoot;
+ }
+
+ });
+
+ test("Model: initialize", 3, function() {
+ var Model = Backbone.Model.extend({
+ initialize: function() {
+ this.one = 1;
+ equal(this.collection, collection);
+ }
+ });
+ var model = new Model({}, {collection: collection});
+ equal(model.one, 1);
+ equal(model.collection, collection);
+ });
+
+ test("Model: initialize with attributes and options", 1, function() {
+ var Model = Backbone.Model.extend({
+ initialize: function(attributes, options) {
+ this.one = options.one;
+ }
+ });
+ var model = new Model({}, {one: 1});
+ equal(model.one, 1);
+ });
+
+ test("Model: initialize with parsed attributes", 1, function() {
+ var Model = Backbone.Model.extend({
+ parse: function(obj) {
+ obj.value += 1;
+ return obj;
+ }
+ });
+ var model = new Model({value: 1}, {parse: true});
+ equal(model.get('value'), 2);
+ });
+
+ test("Model: url", 3, function() {
+ doc.urlRoot = null;
+ equal(doc.url(), '/collection/1-the-tempest');
+ doc.collection.url = '/collection/';
+ equal(doc.url(), '/collection/1-the-tempest');
+ doc.collection = null;
+ raises(function() { doc.url(); });
+ doc.collection = collection;
+ });
+
+ test("Model: url when using urlRoot, and uri encoding", 2, function() {
+ var Model = Backbone.Model.extend({
+ urlRoot: '/collection'
+ });
+ var model = new Model();
+ equal(model.url(), '/collection');
+ model.set({id: '+1+'});
+ equal(model.url(), '/collection/%2B1%2B');
+ });
+
+ test("Model: url when using urlRoot as a function to determine urlRoot at runtime", 2, function() {
+ var Model = Backbone.Model.extend({
+ urlRoot: function() {
+ return '/nested/' + this.get('parent_id') + '/collection';
+ }
+ });
+
+ var model = new Model({parent_id: 1});
+ equal(model.url(), '/nested/1/collection');
+ model.set({id: 2});
+ equal(model.url(), '/nested/1/collection/2');
+ });
+
+ test("Model: clone", 8, function() {
+ var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
+ var b = a.clone();
+ equal(a.get('foo'), 1);
+ equal(a.get('bar'), 2);
+ equal(a.get('baz'), 3);
+ equal(b.get('foo'), a.get('foo'), "Foo should be the same on the clone.");
+ equal(b.get('bar'), a.get('bar'), "Bar should be the same on the clone.");
+ equal(b.get('baz'), a.get('baz'), "Baz should be the same on the clone.");
+ a.set({foo : 100});
+ equal(a.get('foo'), 100);
+ equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
+ });
+
+ test("Model: isNew", 6, function() {
+ var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
+ ok(a.isNew(), "it should be new");
+ a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
+ ok(!a.isNew(), "any defined ID is legal, negative or positive");
+ a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 });
+ ok(!a.isNew(), "any defined ID is legal, including zero");
+ ok( new Backbone.Model({ }).isNew(), "is true when there is no id");
+ ok(!new Backbone.Model({ 'id': 2 }).isNew(), "is false for a positive integer");
+ ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
+ });
+
+ test("Model: get", 2, function() {
+ equal(doc.get('title'), 'The Tempest');
+ equal(doc.get('author'), 'Bill Shakespeare');
+ });
+
+ test("Model: escape", 5, function() {
+ equal(doc.escape('title'), 'The Tempest');
+ doc.set({audience: 'Bill & Bob'});
+ equal(doc.escape('audience'), 'Bill & Bob');
+ doc.set({audience: 'Tim > Joan'});
+ equal(doc.escape('audience'), 'Tim > Joan');
+ doc.set({audience: 10101});
+ equal(doc.escape('audience'), '10101');
+ doc.unset('audience');
+ equal(doc.escape('audience'), '');
+ });
+
+ test("Model: has", 10, function() {
+ var model = new Backbone.Model();
+
+ strictEqual(model.has('name'), false);
+
+ model.set({
+ '0': 0,
+ '1': 1,
+ 'true': true,
+ 'false': false,
+ 'empty': '',
+ 'name': 'name',
+ 'null': null,
+ 'undefined': undefined
+ });
+
+ strictEqual(model.has('0'), true);
+ strictEqual(model.has('1'), true);
+ strictEqual(model.has('true'), true);
+ strictEqual(model.has('false'), true);
+ strictEqual(model.has('empty'), true);
+ strictEqual(model.has('name'), true);
+
+ model.unset('name');
+
+ strictEqual(model.has('name'), false);
+ strictEqual(model.has('null'), false);
+ strictEqual(model.has('undefined'), false);
+ });
+
+ test("Model: set and unset", 8, function() {
+ var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
+ var changeCount = 0;
+ a.on("change:foo", function() { changeCount += 1; });
+ a.set({'foo': 2});
+ ok(a.get('foo') == 2, "Foo should have changed.");
+ ok(changeCount == 1, "Change count should have incremented.");
+ a.set({'foo': 2}); // set with value that is not new shouldn't fire change event
+ ok(a.get('foo') == 2, "Foo should NOT have changed, still 2");
+ ok(changeCount == 1, "Change count should NOT have incremented.");
+
+ a.validate = function(attrs) {
+ equal(attrs.foo, void 0, "don't ignore values when unsetting");
+ };
+ a.unset('foo');
+ equal(a.get('foo'), void 0, "Foo should have changed");
+ delete a.validate;
+ ok(changeCount == 2, "Change count should have incremented for unset.");
+
+ a.unset('id');
+ equal(a.id, undefined, "Unsetting the id should remove the id property.");
+ });
+
+ test("Model: multiple unsets", 1, function() {
+ var i = 0;
+ var counter = function(){ i++; };
+ var model = new Backbone.Model({a: 1});
+ model.on("change:a", counter);
+ model.set({a: 2});
+ model.unset('a');
+ model.unset('a');
+ equal(i, 2, 'Unset does not fire an event for missing attributes.');
+ });
+
+ test("Model: unset and changedAttributes", 2, function() {
+ var model = new Backbone.Model({a: 1});
+ model.unset('a', {silent: true});
+ var changedAttributes = model.changedAttributes();
+ ok('a' in changedAttributes, 'changedAttributes should contain unset properties');
+
+ changedAttributes = model.changedAttributes();
+ ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.');
+ });
+
+ test("Model: using a non-default id attribute.", 5, function() {
+ var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
+ var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
+ equal(model.get('id'), 'eye-dee');
+ equal(model.id, 25);
+ equal(model.isNew(), false);
+ model.unset('_id');
+ equal(model.id, undefined);
+ equal(model.isNew(), true);
+ });
+
+ test("Model: set an empty string", 1, function() {
+ var model = new Backbone.Model({name : "Model"});
+ model.set({name : ''});
+ equal(model.get('name'), '');
+ });
+
+ test("Model: clear", 3, function() {
+ var changed;
+ var model = new Backbone.Model({id: 1, name : "Model"});
+ model.on("change:name", function(){ changed = true; });
+ model.on("change", function() {
+ var changedAttrs = model.changedAttributes();
+ ok('name' in changedAttrs);
+ });
+ model.clear();
+ equal(changed, true);
+ equal(model.get('name'), undefined);
+ });
+
+ test("Model: defaults", 4, function() {
+ var Defaulted = Backbone.Model.extend({
+ defaults: {
+ "one": 1,
+ "two": 2
+ }
+ });
+ var model = new Defaulted({two: null});
+ equal(model.get('one'), 1);
+ equal(model.get('two'), null);
+ Defaulted = Backbone.Model.extend({
+ defaults: function() {
+ return {
+ "one": 3,
+ "two": 4
+ };
+ }
+ });
+ var model = new Defaulted({two: null});
+ equal(model.get('one'), 3);
+ equal(model.get('two'), null);
+ });
+
+ test("Model: change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
+ var model = new Backbone.Model({name : "Tim", age : 10});
+ equal(model.changedAttributes(), false);
+ model.on('change', function() {
+ ok(model.hasChanged('name'), 'name changed');
+ ok(!model.hasChanged('age'), 'age did not');
+ ok(_.isEqual(model.changedAttributes(), {name : 'Rob'}), 'changedAttributes returns the changed attrs');
+ equal(model.previous('name'), 'Tim');
+ ok(_.isEqual(model.previousAttributes(), {name : "Tim", age : 10}), 'previousAttributes is correct');
+ });
+ equal(model.hasChanged(), false);
+ equal(model.hasChanged(undefined), false);
+ model.set({name : 'Rob'}, {silent : true});
+ equal(model.hasChanged(), true);
+ equal(model.hasChanged(undefined), true);
+ equal(model.hasChanged('name'), true);
+ model.change();
+ equal(model.get('name'), 'Rob');
+
+ });
+
+ test("Model: changedAttributes", 3, function() {
+ var model = new Backbone.Model({a: 'a', b: 'b'});
+ equal(model.changedAttributes(), false);
+ equal(model.changedAttributes({a: 'a'}), false);
+ equal(model.changedAttributes({a: 'b'}).a, 'b');
+ });
+
+ test("Model: change with options", 2, function() {
+ var value;
+ var model = new Backbone.Model({name: 'Rob'});
+ model.on('change', function(model, options) {
+ value = options.prefix + model.get('name');
+ });
+ model.set({name: 'Bob'}, {silent: true});
+ model.change({prefix: 'Mr. '});
+ equal(value, 'Mr. Bob');
+ model.set({name: 'Sue'}, {prefix: 'Ms. '});
+ equal(value, 'Ms. Sue');
+ });
+
+ test("Model: change after initialize", 1, function () {
+ var changed = 0;
+ var attrs = {id: 1, label: 'c'};
+ var obj = new Backbone.Model(attrs);
+ obj.on('change', function() { changed += 1; });
+ obj.set(attrs);
+ equal(changed, 0);
+ });
+
+ test("Model: save within change event", 1, function () {
+ var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
+ model.on('change', function () {
+ model.save();
+ ok(_.isEqual(lastRequest.model, model));
+ });
+ model.set({lastName: 'Hicks'});
+ });
+
+ test("Model: validate after save", 1, function() {
+ var lastError, model = new Backbone.Model();
+ model.validate = function(attrs) {
+ if (attrs.admin) return "Can't change admin status.";
+ };
+ model.sync = function(method, model, options) {
+ options.success.call(this, {admin: true});
+ };
+ model.save(null, {error: function(model, error) {
+ lastError = error;
+ }});
+
+ equal(lastError, "Can't change admin status.");
+ });
+
+ test("Model: isValid", 5, function() {
+ var model = new Backbone.Model({valid: true});
+ model.validate = function(attrs) {
+ if (!attrs.valid) return "invalid";
+ };
+ equal(model.isValid(), true);
+ equal(model.set({valid: false}), false);
+ equal(model.isValid(), true);
+ ok(model.set('valid', false, {silent: true}));
+ equal(model.isValid(), false);
+ });
+
+ test("Model: save", 2, function() {
+ doc.save({title : "Henry V"});
+ equal(lastRequest.method, 'update');
+ ok(_.isEqual(lastRequest.model, doc));
+ });
+
+ test("Model: save in positional style", 1, function() {
+ var model = new Backbone.Model();
+ model.sync = function(method, model, options) {
+ options.success();
+ };
+ model.save('title', 'Twelfth Night');
+ equal(model.get('title'), 'Twelfth Night');
+ });
+
+
+
+ test("Model: fetch", 2, function() {
+ doc.fetch();
+ equal(lastRequest.method, 'read');
+ ok(_.isEqual(lastRequest.model, doc));
+ });
+
+ test("Model: destroy", 3, function() {
+ doc.destroy();
+ equal(lastRequest.method, 'delete');
+ ok(_.isEqual(lastRequest.model, doc));
+
+ var newModel = new Backbone.Model;
+ equal(newModel.destroy(), false);
+ });
+
+ test("Model: non-persisted destroy", 1, function() {
+ var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
+ a.sync = function() { throw "should not be called"; };
+ a.destroy();
+ ok(true, "non-persisted model should not call sync");
+ });
+
+ test("Model: validate", 7, function() {
+ var lastError;
+ var model = new Backbone.Model();
+ model.validate = function(attrs) {
+ if (attrs.admin != this.get('admin')) return "Can't change admin status.";
+ };
+ model.on('error', function(model, error) {
+ lastError = error;
+ });
+ var result = model.set({a: 100});
+ equal(result, model);
+ equal(model.get('a'), 100);
+ equal(lastError, undefined);
+ result = model.set({admin: true}, {silent: true});
+ equal(model.get('admin'), true);
+ result = model.set({a: 200, admin: false});
+ equal(lastError, "Can't change admin status.");
+ equal(result, false);
+ equal(model.get('a'), 100);
+ });
+
+ test("Model: validate on unset and clear", 6, function() {
+ var error;
+ var model = new Backbone.Model({name: "One"});
+ model.validate = function(attrs) {
+ if (!attrs.name) {
+ error = true;
+ return "No thanks.";
+ }
+ };
+ model.set({name: "Two"});
+ equal(model.get('name'), 'Two');
+ equal(error, undefined);
+ model.unset('name');
+ equal(error, true);
+ equal(model.get('name'), 'Two');
+ model.clear();
+ equal(model.get('name'), 'Two');
+ delete model.validate;
+ model.clear();
+ equal(model.get('name'), undefined);
+ });
+
+ test("Model: validate with error callback", 8, function() {
+ var lastError, boundError;
+ var model = new Backbone.Model();
+ model.validate = function(attrs) {
+ if (attrs.admin) return "Can't change admin status.";
+ };
+ var callback = function(model, error) {
+ lastError = error;
+ };
+ model.on('error', function(model, error) {
+ boundError = true;
+ });
+ var result = model.set({a: 100}, {error: callback});
+ equal(result, model);
+ equal(model.get('a'), 100);
+ equal(lastError, undefined);
+ equal(boundError, undefined);
+ result = model.set({a: 200, admin: true}, {error: callback});
+ equal(result, false);
+ equal(model.get('a'), 100);
+ equal(lastError, "Can't change admin status.");
+ equal(boundError, undefined);
+ });
+
+ test("Model: defaults always extend attrs (#459)", 2, function() {
+ var Defaulted = Backbone.Model.extend({
+ defaults: {one: 1},
+ initialize : function(attrs, opts) {
+ equal(this.attributes.one, 1);
+ }
+ });
+ var providedattrs = new Defaulted({});
+ var emptyattrs = new Defaulted();
+ });
+
+ test("Model: Inherit class properties", 6, function() {
+ var Parent = Backbone.Model.extend({
+ instancePropSame: function() {},
+ instancePropDiff: function() {}
+ }, {
+ classProp: function() {}
+ });
+ var Child = Parent.extend({
+ instancePropDiff: function() {}
+ });
+
+ var adult = new Parent;
+ var kid = new Child;
+
+ equal(Child.classProp, Parent.classProp);
+ notEqual(Child.classProp, undefined);
+
+ equal(kid.instancePropSame, adult.instancePropSame);
+ notEqual(kid.instancePropSame, undefined);
+
+ notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
+ notEqual(Child.prototype.instancePropDiff, undefined);
+ });
+
+ test("Model: Nested change events don't clobber previous attributes", 4, function() {
+ new Backbone.Model()
+ .on('change:state', function(model, newState) {
+ equal(model.previous('state'), undefined);
+ equal(newState, 'hello');
+ // Fire a nested change event.
+ model.set({other: 'whatever'});
+ })
+ .on('change:state', function(model, newState) {
+ equal(model.previous('state'), undefined);
+ equal(newState, 'hello');
+ })
+ .set({state: 'hello'});
+ });
+
+ test("hasChanged/set should use same comparison", 2, function() {
+ var changed = 0, model = new Backbone.Model({a: null});
+ model.on('change', function() {
+ ok(this.hasChanged('a'));
+ })
+ .on('change:a', function() {
+ changed++;
+ })
+ .set({a: undefined});
+ equal(changed, 1);
+ });
+
+ test("#582, #425, change:attribute callbacks should fire after all changes have occurred", 9, function() {
+ var model = new Backbone.Model;
+
+ var assertion = function() {
+ equal(model.get('a'), 'a');
+ equal(model.get('b'), 'b');
+ equal(model.get('c'), 'c');
+ };
+
+ model.on('change:a', assertion);
+ model.on('change:b', assertion);
+ model.on('change:c', assertion);
+
+ model.set({a: 'a', b: 'b', c: 'c'});
+ });
+
+ test("#871, set with attributes property", 1, function() {
+ var model = new Backbone.Model();
+ model.set({attributes: true});
+ ok(model.has('attributes'));
+ });
+
+ test("set value regardless of equality/change", 1, function() {
+ var model = new Backbone.Model({x: []});
+ var a = [];
+ model.set({x: a});
+ ok(model.get('x') === a);
+ });
+
+ test("unset fires change for undefined attributes", 1, function() {
+ var model = new Backbone.Model({x: undefined});
+ model.on('change:x', function(){ ok(true); });
+ model.unset('x');
+ });
+
+ test("set: undefined values", 1, function() {
+ var model = new Backbone.Model({x: undefined});
+ ok('x' in model.attributes);
+ });
+
+ test("change fires change:attr", 1, function() {
+ var model = new Backbone.Model({x: 1});
+ model.set({x: 2}, {silent: true});
+ model.on('change:x', function(){ ok(true); });
+ model.change();
+ });
+
+ test("hasChanged is false after original values are set", 2, function() {
+ var model = new Backbone.Model({x: 1});
+ model.on('change:x', function(){ ok(false); });
+ model.set({x: 2}, {silent: true});
+ ok(model.hasChanged());
+ model.set({x: 1}, {silent: true});
+ ok(!model.hasChanged());
+ });
+
+ test("save with `wait` succeeds without `validate`", 1, function() {
+ var model = new Backbone.Model();
+ model.save({x: 1}, {wait: true});
+ ok(lastRequest.model === model);
+ });
+
+ test("`hasChanged` for falsey keys", 2, function() {
+ var model = new Backbone.Model();
+ model.set({x: true}, {silent: true});
+ ok(!model.hasChanged(0));
+ ok(!model.hasChanged(''));
+ });
+
+ test("`previous` for falsey keys", 2, function() {
+ var model = new Backbone.Model({0: true, '': true});
+ model.set({0: false, '': false}, {silent: true});
+ equal(model.previous(0), true);
+ equal(model.previous(''), true);
+ });
+
+ test("`save` with `wait` sends correct attributes", 5, function() {
+ var changed = 0;
+ var model = new Backbone.Model({x: 1, y: 2});
+ model.on('change:x', function() { changed++; });
+ model.save({x: 3}, {wait: true});
+ deepEqual(JSON.parse(ajaxParams.data), {x: 3, y: 2});
+ equal(model.get('x'), 1);
+ equal(changed, 0);
+ lastRequest.options.success({});
+ equal(model.get('x'), 3);
+ equal(changed, 1);
+ });
+
+ test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
+ var model = new Backbone.Model;
+ model.save({x: 1}, {wait: true});
+ equal(model.get('x'), void 0);
+ });
+
+ test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function() {
+ var model = new Backbone.Model({x: 1, y: 2});
+ model.sync = function(method, model, options) {
+ options.success();
+ };
+ model.on("change:x", function() { ok(true); });
+ model.save({x: 3}, {wait: true});
+ equal(model.get('x'), 3);
+ });
+
+ test("save with wait validates attributes", 1, function() {
+ var model = new Backbone.Model();
+ model.validate = function() { ok(true); };
+ model.save({x: 1}, {wait: true});
+ });
+
+ test("nested `set` during `'change:attr'`", 2, function() {
+ var events = [];
+ var model = new Backbone.Model();
+ model.on('all', function(event) { events.push(event); });
+ model.on('change', function() {
+ model.set({z: true}, {silent:true});
+ });
+ model.on('change:x', function() {
+ model.set({y: true});
+ });
+ model.set({x: true});
+ deepEqual(events, ['change:y', 'change:x', 'change']);
+ events = [];
+ model.change();
+ deepEqual(events, ['change:z', 'change']);
+ });
+
+ test("nested `change` only fires once", 1, function() {
+ var model = new Backbone.Model();
+ model.on('change', function() {
+ ok(true);
+ model.change();
+ });
+ model.set({x: true});
+ });
+
+ test("no `'change'` event if no changes", 0, function() {
+ var model = new Backbone.Model();
+ model.on('change', function() { ok(false); });
+ model.change();
+ });
+
+ test("nested `set` during `'change'`", 6, function() {
+ var count = 0;
+ var model = new Backbone.Model();
+ model.on('change', function() {
+ switch(count++) {
+ case 0:
+ deepEqual(this.changedAttributes(), {x: true});
+ equal(model.previous('x'), undefined);
+ model.set({y: true});
+ break;
+ case 1:
+ deepEqual(this.changedAttributes(), {y: true});
+ equal(model.previous('x'), true);
+ model.set({z: true});
+ break;
+ case 2:
+ deepEqual(this.changedAttributes(), {z: true});
+ equal(model.previous('y'), true);
+ break;
+ default:
+ ok(false);
+ }
+ });
+ model.set({x: true});
+ });
+
+ test("nested `'change'` with silent", 3, function() {
+ var count = 0;
+ var model = new Backbone.Model();
+ model.on('change:y', function() { ok(true); });
+ model.on('change', function() {
+ switch(count++) {
+ case 0:
+ deepEqual(this.changedAttributes(), {x: true});
+ model.set({y: true}, {silent: true});
+ break;
+ case 1:
+ deepEqual(this.changedAttributes(), {y: true, z: true});
+ break;
+ default:
+ ok(false);
+ }
+ });
+ model.set({x: true});
+ model.set({z: true});
+ });
+
+ test("nested `'change:attr'` with silent", 1, function() {
+ var model = new Backbone.Model();
+ model.on('change:y', function(){ ok(true); });
+ model.on('change', function() {
+ model.set({y: true}, {silent: true});
+ model.set({z: true});
+ });
+ model.set({x: true});
+ });
+
+ test("multiple nested changes with silent", 1, function() {
+ var model = new Backbone.Model();
+ model.on('change:x', function() {
+ model.set({y: 1}, {silent: true});
+ model.set({y: 2});
+ });
+ model.on('change:y', function(model, val) {
+ equal(val, 2);
+ });
+ model.set({x: true});
+ model.change();
+ });
+
+ test("multiple nested changes with silent", 2, function() {
+ var changes = [];
+ var model = new Backbone.Model();
+ model.on('change:b', function(model, val) { changes.push(val); });
+ model.on('change', function() {
+ model.set({b: 1});
+ model.set({b: 2}, {silent: true});
+ });
+ model.set({b: 0});
+ deepEqual(changes, [0, 1, 1]);
+ model.change();
+ deepEqual(changes, [0, 1, 1, 2, 1]);
+ });
+
+ test("nested set multiple times", 1, function() {
+ var model = new Backbone.Model();
+ model.on('change:b', function() {
+ ok(true);
+ });
+ model.on('change:a', function() {
+ model.set({b: true});
+ model.set({b: true});
+ });
+ model.set({a: true});
+ });
+
+ test("Backbone.wrapError triggers `'error'`", 12, function() {
+ var resp = {};
+ var options = {};
+ var model = new Backbone.Model();
+ model.on('error', error);
+ var callback = Backbone.wrapError(null, model, options);
+ callback(model, resp);
+ callback(resp);
+ callback = Backbone.wrapError(error, model, options);
+ callback(model, resp);
+ callback(resp);
+ function error(_model, _resp, _options) {
+ ok(model === _model);
+ ok(resp === _resp);
+ ok(options === _options);
+ }
+ });
+
+ test("#1179 - isValid returns true in the absence of validate.", 1, function() {
+ var model = new Backbone.Model();
+ model.validate = null;
+ ok(model.isValid());
+ });
+
+ test("#1122 - clear does not alter options.", 1, function() {
+ var model = new Backbone.Model();
+ var options = {};
+ model.clear(options);
+ ok(!options.unset);
+ });
+
+ test("#1122 - unset does not alter options.", 1, function() {
+ var model = new Backbone.Model();
+ var options = {};
+ model.unset('x', options);
+ ok(!options.unset);
+ });
+
+ test("#1355 - `options` is passed to success callbacks", 3, function() {
+ var model = new Backbone.Model();
+ var opts = {
+ success: function( model, resp, options ) {
+ ok(options);
+ }
+ };
+ model.sync = function(method, model, options) {
+ options.success();
+ };
+ model.save({id: 1}, opts);
+ model.fetch(opts);
+ model.destroy(opts);
+ });
+
+ test("#1412 - Trigger 'sync' event.", 3, function() {
+ var model = new Backbone.Model({id: 1});
+ model.sync = function(method, model, options) { options.success(); };
+ model.on('sync', function() { ok(true); });
+ model.fetch();
+ model.save();
+ model.destroy();
+ });
+
+ test("#1365 - Destroy: New models execute success callback.", 2, function() {
+ new Backbone.Model()
+ .on('sync', function() { ok(false); })
+ .on('destroy', function(){ ok(true); })
+ .destroy({ success: function(){ ok(true); }});
+ });
+
+ test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
+ var model = new Backbone.Model;
+ model.validate = function(){ return 'invalid'; };
+ model.sync = function(){ ok(false); };
+ strictEqual(model.save(), false);
+ });
+
+});
diff --git a/vendor/backbone/test/noconflict.js b/vendor/backbone/test/noconflict.js
new file mode 100644
index 000000000..64c6d0a20
--- /dev/null
+++ b/vendor/backbone/test/noconflict.js
@@ -0,0 +1,12 @@
+$(document).ready(function() {
+
+ module("Backbone.noConflict");
+
+ test('Backbone.noConflict', 2, function() {
+ var noconflictBackbone = Backbone.noConflict();
+ equal(window.Backbone, undefined, 'Returned window.Backbone');
+ window.Backbone = noconflictBackbone;
+ equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone');
+ });
+
+});
diff --git a/vendor/backbone/test/router.js b/vendor/backbone/test/router.js
new file mode 100644
index 000000000..a4b5b4405
--- /dev/null
+++ b/vendor/backbone/test/router.js
@@ -0,0 +1,321 @@
+$(document).ready(function() {
+
+ var router = null;
+ var location = null;
+ var lastRoute = null;
+ var lastArgs = [];
+
+ function onRoute(router, route, args) {
+ lastRoute = route;
+ lastArgs = args;
+ }
+
+ var Location = function(href) {
+ this.replace(href);
+ };
+
+ _.extend(Location.prototype, {
+
+ replace: function(href) {
+ _.extend(this, _.pick($('', {href: href})[0],
+ 'href',
+ 'hash',
+ 'search',
+ 'fragment',
+ 'pathname'
+ ));
+ // In IE, anchor.pathname does not contain a leading slash though
+ // window.location.pathname does.
+ if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname;
+ },
+
+ toString: function() {
+ return this.href;
+ }
+
+ });
+
+ module("Backbone.Router", {
+
+ setup: function() {
+ location = new Location('http://example.com');
+ Backbone.history = new Backbone.History({location: location});
+ router = new Router({testing: 101});
+ Backbone.history.interval = 9;
+ Backbone.history.start({pushState: false});
+ lastRoute = null;
+ lastArgs = [];
+ Backbone.history.on('route', onRoute);
+ },
+
+ teardown: function() {
+ Backbone.history.stop();
+ Backbone.history.off('route', onRoute);
+ }
+
+ });
+
+ var Router = Backbone.Router.extend({
+
+ count: 0,
+
+ routes: {
+ "noCallback": "noCallback",
+ "counter": "counter",
+ "search/:query": "search",
+ "search/:query/p:page": "search",
+ "contacts": "contacts",
+ "contacts/new": "newContact",
+ "contacts/:id": "loadContact",
+ "splat/*args/end": "splat",
+ "*first/complex-:part/*rest": "complex",
+ ":entity?*args": "query",
+ "*anything": "anything"
+ },
+
+ initialize : function(options) {
+ this.testing = options.testing;
+ this.route('implicit', 'implicit');
+ },
+
+ counter: function() {
+ this.count++;
+ },
+
+ implicit: function() {
+ this.count++;
+ },
+
+ search : function(query, page) {
+ this.query = query;
+ this.page = page;
+ },
+
+ contacts: function(){
+ this.contact = 'index';
+ },
+
+ newContact: function(){
+ this.contact = 'new';
+ },
+
+ loadContact: function(){
+ this.contact = 'load';
+ },
+
+ splat : function(args) {
+ this.args = args;
+ },
+
+ complex : function(first, part, rest) {
+ this.first = first;
+ this.part = part;
+ this.rest = rest;
+ },
+
+ query : function(entity, args) {
+ this.entity = entity;
+ this.queryArgs = args;
+ },
+
+ anything : function(whatever) {
+ this.anything = whatever;
+ }
+
+ });
+
+ test("Router: initialize", 1, function() {
+ equal(router.testing, 101);
+ });
+
+ test("Router: routes (simple)", 4, function() {
+ location.replace('http://example.com#search/news');
+ Backbone.history.checkUrl();
+ equal(router.query, 'news');
+ equal(router.page, undefined);
+ equal(lastRoute, 'search');
+ equal(lastArgs[0], 'news');
+ });
+
+ test("Router: routes (two part)", 2, function() {
+ location.replace('http://example.com#search/nyc/p10');
+ Backbone.history.checkUrl();
+ equal(router.query, 'nyc');
+ equal(router.page, '10');
+ });
+
+ test("Router: routes via navigate", 2, function() {
+ Backbone.history.navigate('search/manhattan/p20', {trigger: true});
+ equal(router.query, 'manhattan');
+ equal(router.page, '20');
+ });
+
+ test("Router: routes via navigate for backwards-compatibility", 2, function() {
+ Backbone.history.navigate('search/manhattan/p20', true);
+ equal(router.query, 'manhattan');
+ equal(router.page, '20');
+ });
+
+ test("Router: route precedence via navigate", 6, function(){
+ // check both 0.9.x and backwards-compatibility options
+ _.each([ { trigger: true }, true ], function( options ){
+ Backbone.history.navigate('contacts', options);
+ equal(router.contact, 'index');
+ Backbone.history.navigate('contacts/new', options);
+ equal(router.contact, 'new');
+ Backbone.history.navigate('contacts/foo', options);
+ equal(router.contact, 'load');
+ });
+ });
+
+ test("loadUrl is not called for identical routes.", 0, function() {
+ Backbone.history.loadUrl = function(){ ok(false); };
+ location.replace('http://example.com#route');
+ Backbone.history.navigate('route');
+ Backbone.history.navigate('/route');
+ Backbone.history.navigate('/route');
+ });
+
+ test("Router: use implicit callback if none provided", 1, function() {
+ router.count = 0;
+ router.navigate('implicit', {trigger: true});
+ equal(router.count, 1);
+ });
+
+ test("Router: routes via navigate with {replace: true}", 1, function() {
+ location.replace('http://example.com#start_here');
+ Backbone.history.checkUrl();
+ location.replace = function(href) {
+ strictEqual(href, new Location('http://example.com#end_here').href);
+ };
+ Backbone.history.navigate('end_here', {replace: true});
+ });
+
+ test("Router: routes (splats)", 1, function() {
+ location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
+ Backbone.history.checkUrl();
+ equal(router.args, 'long-list/of/splatted_99args');
+ });
+
+ test("Router: routes (complex)", 3, function() {
+ location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
+ Backbone.history.checkUrl();
+ equal(router.first, 'one/two/three');
+ equal(router.part, 'part');
+ equal(router.rest, 'four/five/six/seven');
+ });
+
+ test("Router: routes (query)", 5, function() {
+ location.replace('http://example.com#mandel?a=b&c=d');
+ Backbone.history.checkUrl();
+ equal(router.entity, 'mandel');
+ equal(router.queryArgs, 'a=b&c=d');
+ equal(lastRoute, 'query');
+ equal(lastArgs[0], 'mandel');
+ equal(lastArgs[1], 'a=b&c=d');
+ });
+
+ test("Router: routes (anything)", 1, function() {
+ location.replace('http://example.com#doesnt-match-a-route');
+ Backbone.history.checkUrl();
+ equal(router.anything, 'doesnt-match-a-route');
+ });
+
+ test("Router: fires event when router doesn't have callback on it", 1, function() {
+ router.on("route:noCallback", function(){ ok(true); });
+ location.replace('http://example.com#noCallback');
+ Backbone.history.checkUrl();
+ });
+
+ test("#933, #908 - leading slash", 2, function() {
+ location.replace('http://example.com/root/foo');
+
+ Backbone.history.stop();
+ Backbone.history = new Backbone.History({location: location});
+ Backbone.history.start({root: '/root', hashChange: false, silent: true});
+ strictEqual(Backbone.history.getFragment(), 'foo');
+
+ Backbone.history.stop();
+ Backbone.history = new Backbone.History({location: location});
+ Backbone.history.start({root: '/root/', hashChange: false, silent: true});
+ strictEqual(Backbone.history.getFragment(), 'foo');
+ });
+
+ test("#1003 - History is started before navigate is called", 1, function() {
+ var history = new Backbone.History();
+ history.navigate = function(){
+ ok(Backbone.History.started);
+ };
+ Backbone.history.stop();
+ history.start();
+ // If this is not an old IE navigate will not be called.
+ if (!history.iframe) ok(true);
+ });
+
+ test("Router: route callback gets passed decoded values", 3, function() {
+ var route = 'has%2Fslash/complex-has%23hash/has%20space';
+ Backbone.history.navigate(route, {trigger: true});
+ equal(router.first, 'has/slash');
+ equal(router.part, 'has#hash');
+ equal(router.rest, 'has space');
+ });
+
+ test("Router: correctly handles URLs with % (#868)", 3, function() {
+ location.replace('http://example.com#search/fat%3A1.5%25');
+ Backbone.history.checkUrl();
+ location.replace('http://example.com#search/fat');
+ Backbone.history.checkUrl();
+ equal(router.query, 'fat');
+ equal(router.page, undefined);
+ equal(lastRoute, 'search');
+ });
+
+ test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
+ Backbone.history.stop();
+ location.replace('http://example.com/path/name#hash');
+ Backbone.history = new Backbone.History({location: location});
+ Backbone.history.start({hashChange: false});
+ var fragment = Backbone.history.getFragment();
+ strictEqual(fragment, location.pathname.replace(/^\//, ''));
+ });
+
+ test("#1206 - Strip leading slash before location.assign.", 1, function() {
+ Backbone.history.stop();
+ location.replace('http://example.com/root/');
+ Backbone.history = new Backbone.History({location: location});
+ Backbone.history.start({hashChange: false, root: '/root/'});
+ location.assign = function(pathname) {
+ strictEqual(pathname, '/root/fragment');
+ };
+ Backbone.history.navigate('/fragment');
+ });
+
+ test("#1387 - Root fragment without trailing slash.", 1, function() {
+ Backbone.history.stop();
+ location.replace('http://example.com/root');
+ Backbone.history = new Backbone.History({location: location});
+ Backbone.history.start({hashChange: false, root: '/root/', silent: true});
+ strictEqual(Backbone.history.getFragment(), '');
+ });
+
+ test("#1366 - History does not prepend root to fragment.", 2, function() {
+ Backbone.history.stop();
+ location.replace('http://example.com/root/');
+ Backbone.history = new Backbone.History({
+ location: location,
+ history: {
+ pushState: function(state, title, url) {
+ strictEqual(url, '/root/x');
+ }
+ }
+ });
+ Backbone.history.start({
+ root: '/root/',
+ pushState: true,
+ hashChange: false
+ });
+ Backbone.history.navigate('x');
+ strictEqual(Backbone.history.fragment, 'x');
+ });
+
+});
diff --git a/vendor/backbone/test/sync.js b/vendor/backbone/test/sync.js
new file mode 100644
index 000000000..f4afb5c14
--- /dev/null
+++ b/vendor/backbone/test/sync.js
@@ -0,0 +1,160 @@
+$(document).ready(function() {
+
+ var ajax = Backbone.ajax;
+ var lastRequest = null;
+
+ var Library = Backbone.Collection.extend({
+ url : function() { return '/library'; }
+ });
+ var library;
+
+ var attrs = {
+ title : "The Tempest",
+ author : "Bill Shakespeare",
+ length : 123
+ };
+
+ module("Backbone.sync", {
+
+ setup : function() {
+ library = new Library();
+ Backbone.ajax = function(obj) {
+ lastRequest = obj;
+ };
+ library.create(attrs, {wait: false});
+ },
+
+ teardown: function() {
+ Backbone.ajax = ajax;
+ }
+
+ });
+
+ test("sync: read", 4, function() {
+ library.fetch();
+ equal(lastRequest.url, '/library');
+ equal(lastRequest.type, 'GET');
+ equal(lastRequest.dataType, 'json');
+ ok(_.isEmpty(lastRequest.data));
+ });
+
+ test("sync: passing data", 3, function() {
+ library.fetch({data: {a: 'a', one: 1}});
+ equal(lastRequest.url, '/library');
+ equal(lastRequest.data.a, 'a');
+ equal(lastRequest.data.one, 1);
+ });
+
+ test("sync: create", 6, function() {
+ equal(lastRequest.url, '/library');
+ equal(lastRequest.type, 'POST');
+ equal(lastRequest.dataType, 'json');
+ var data = JSON.parse(lastRequest.data);
+ equal(data.title, 'The Tempest');
+ equal(data.author, 'Bill Shakespeare');
+ equal(data.length, 123);
+ });
+
+ test("sync: update", 7, function() {
+ library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
+ equal(lastRequest.url, '/library/1-the-tempest');
+ equal(lastRequest.type, 'PUT');
+ equal(lastRequest.dataType, 'json');
+ var data = JSON.parse(lastRequest.data);
+ equal(data.id, '1-the-tempest');
+ equal(data.title, 'The Tempest');
+ equal(data.author, 'William Shakespeare');
+ equal(data.length, 123);
+ });
+
+ test("sync: update with emulateHTTP and emulateJSON", 7, function() {
+ Backbone.emulateHTTP = Backbone.emulateJSON = true;
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'POST');
+ equal(lastRequest.dataType, 'json');
+ equal(lastRequest.data._method, 'PUT');
+ var data = JSON.parse(lastRequest.data.model);
+ equal(data.id, '2-the-tempest');
+ equal(data.author, 'Tim Shakespeare');
+ equal(data.length, 123);
+ Backbone.emulateHTTP = Backbone.emulateJSON = false;
+ });
+
+ test("sync: update with just emulateHTTP", 6, function() {
+ Backbone.emulateHTTP = true;
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'POST');
+ equal(lastRequest.contentType, 'application/json');
+ var data = JSON.parse(lastRequest.data);
+ equal(data.id, '2-the-tempest');
+ equal(data.author, 'Tim Shakespeare');
+ equal(data.length, 123);
+ Backbone.emulateHTTP = false;
+ });
+
+ test("sync: update with just emulateJSON", 6, function() {
+ Backbone.emulateJSON = true;
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'PUT');
+ equal(lastRequest.contentType, 'application/x-www-form-urlencoded');
+ var data = JSON.parse(lastRequest.data.model);
+ equal(data.id, '2-the-tempest');
+ equal(data.author, 'Tim Shakespeare');
+ equal(data.length, 123);
+ Backbone.emulateJSON = false;
+ });
+
+ test("sync: read model", 3, function() {
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ library.first().fetch();
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'GET');
+ ok(_.isEmpty(lastRequest.data));
+ });
+
+ test("sync: destroy", 3, function() {
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ library.first().destroy({wait: true});
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'DELETE');
+ equal(lastRequest.data, null);
+ });
+
+ test("sync: destroy with emulateHTTP", 3, function() {
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ Backbone.emulateHTTP = Backbone.emulateJSON = true;
+ library.first().destroy();
+ equal(lastRequest.url, '/library/2-the-tempest');
+ equal(lastRequest.type, 'POST');
+ equal(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}');
+ Backbone.emulateHTTP = Backbone.emulateJSON = false;
+ });
+
+ test("sync: urlError", 2, function() {
+ var model = new Backbone.Model();
+ raises(function() {
+ model.fetch();
+ });
+ model.fetch({url: '/one/two'});
+ equal(lastRequest.url, '/one/two');
+ });
+
+ test("#1052 - `options` is optional.", 0, function() {
+ var model = new Backbone.Model();
+ model.url = '/test';
+ Backbone.sync('create', model);
+ });
+
+ test("Backbone.ajax", 1, function() {
+ Backbone.ajax = function(settings){
+ strictEqual(settings.url, '/test');
+ };
+ var model = new Backbone.Model();
+ model.url = '/test';
+ Backbone.sync('create', model);
+ });
+
+});
diff --git a/vendor/backbone/test/vendor/jquery-1.7.1.js b/vendor/backbone/test/vendor/jquery-1.7.1.js
new file mode 100644
index 000000000..8ccd0ea78
--- /dev/null
+++ b/vendor/backbone/test/vendor/jquery-1.7.1.js
@@ -0,0 +1,9266 @@
+/*!
+ * jQuery JavaScript Library v1.7.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Nov 21 21:11:03 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.1",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!memory;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ marginDiv,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = "
a";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = marginDiv = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ conMarginTop, ptlm, vb, style, html,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
+ vb = "visibility:hidden;border:0;";
+ style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
+ html = "
" +
+ "
" +
+ "
";
+
+ container = document.createElement("div");
+ container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "
t
";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Figure out if the W3C box model works as expected
+ div.innerHTML = "";
+ div.style.width = div.style.paddingLeft = "1px";
+ jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+ }
+
+ div.style.cssText = ptlm + vb;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ body.removeChild( container );
+ div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, attr, name,
+ data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
+ attr = this[0].attributes;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ jQuery._data( this[0], "parsedAttrs", true );
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var self = jQuery( this ),
+ args = [ parts[0], value ];
+
+ self.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise();
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.prop );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ jQuery.attr( elem, name, "" );
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( rboolean.test( name ) && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /\bhover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on.call( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /", "" ],
+ legend: [ 1, "" ],
+ thead: [ 1, "
", "
" ],
+ tr: [ 2, "
", "
" ],
+ td: [ 3, "
", "
" ],
+ col: [ 2, "
", "
" ],
+ area: [ 1, "" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize and
+~~~
+
+Optionally, expose Java’s nanosecond timer by adding the `nano` applet to the ``:
+
+~~~ html
+
+~~~
+
+Or enable Chrome’s microsecond timer by using the [command line switch](http://peter.sh/experiments/chromium-command-line-switches/#enable-benchmarking):
+
+ --enable-benchmarking
+
+Via [npm](http://npmjs.org/):
+
+~~~ bash
+npm install benchmark
+~~~
+
+In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
+
+~~~ js
+var Benchmark = require('benchmark');
+~~~
+
+Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
+
+~~~ bash
+npm install microtime
+~~~
+
+In [Narwhal](http://narwhaljs.org/) and [RingoJS v0.7.0-](http://ringojs.org/):
+
+~~~ js
+var Benchmark = require('benchmark').Benchmark;
+~~~
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+~~~ js
+load('benchmark.js');
+~~~
+
+In an AMD loader like [RequireJS](http://requirejs.org/):
+
+~~~ js
+require({
+ 'paths': {
+ 'benchmark': 'path/to/benchmark'
+ }
+},
+['benchmark'], function(Benchmark) {
+ console.log(Benchmark.version);
+});
+
+// or with platform.js
+// https://github.com/bestiejs/platform.js
+require({
+ 'paths': {
+ 'benchmark': 'path/to/benchmark',
+ 'platform': 'path/to/platform'
+ }
+},
+['benchmark', 'platform'], function(Benchmark, platform) {
+ Benchmark.platform = platform;
+ console.log(Benchmark.platform.name);
+});
+~~~
+
+Usage example:
+
+~~~ js
+var suite = new Benchmark.Suite;
+
+// add tests
+suite.add('RegExp#test', function() {
+ /o/.test('Hello World!');
+})
+.add('String#indexOf', function() {
+ 'Hello World!'.indexOf('o') > -1;
+})
+// add listeners
+.on('cycle', function(event) {
+ console.log(String(event.target));
+})
+.on('complete', function() {
+ console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+})
+// run async
+.run({ 'async': true });
+
+// logs:
+// > RegExp#test x 4,161,532 +-0.99% (59 cycles)
+// > String#indexOf x 6,139,623 +-1.00% (131 cycles)
+// > Fastest is String#indexOf
+~~~
+
+## Cloning this repo
+
+To clone this repository including all submodules, using Git 1.6.5 or later:
+
+~~~ bash
+git clone --recursive https://github.com/bestiejs/benchmark.js.git
+cd benchmark.js
+~~~
+
+For older Git versions, just use:
+
+~~~ bash
+git clone https://github.com/bestiejs/benchmark.js.git
+cd benchmark.js
+git submodule update --init
+~~~
+
+Feel free to fork and send pull requests if you see improvements!
+
+## Footnotes
+
+ 1. Benchmark.js has been tested in at least Adobe AIR 2.6, Chrome 5-15, Firefox 1.5-8, IE 6-10, Opera 9.25-11.52, Safari 2-5.1.1, Node.js 0.4.8-0.6.1, Narwhal 0.3.2, RingoJS 0.7-0.8, and Rhino 1.7RC3.
+ ↩
+
+## Authors
+
+* [Mathias Bynens](http://mathiasbynens.be/)
+ [](https://twitter.com/mathias "Follow @mathias on Twitter")
+* [John-David Dalton](http://allyoucanleet.com/)
+ [](https://twitter.com/jdalton "Follow @jdalton on Twitter")
+
+## Contributors
+
+* [Kit Cambridge](http://kitcambridge.github.com/)
+ [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter")
diff --git a/vendor/benchmark.js/benchmark.js b/vendor/benchmark.js/benchmark.js
new file mode 100644
index 000000000..de403d97b
--- /dev/null
+++ b/vendor/benchmark.js/benchmark.js
@@ -0,0 +1,3830 @@
+/*!
+ * Benchmark.js v1.0.0-pre
+ * Copyright 2010-2012 Mathias Bynens
+ * Based on JSLitmus.js, copyright Robert Kieffer
+ * Modified by John-David Dalton
+ * Available under MIT license
+ */
+;(function(window, undefined) {
+ 'use strict';
+
+ /** Used to assign each benchmark an incrimented id */
+ var counter = 0;
+
+ /** Detect DOM document object */
+ var doc = isHostType(window, 'document') && document;
+
+ /** Detect free variable `define` */
+ var freeDefine = typeof define == 'function' &&
+ typeof define.amd == 'object' && define.amd && define;
+
+ /** Detect free variable `exports` */
+ var freeExports = typeof exports == 'object' && exports &&
+ (typeof global == 'object' && global && global == global.global && (window = global), exports);
+
+ /** Detect free variable `require` */
+ var freeRequire = typeof require == 'function' && require;
+
+ /** Used to crawl all properties regardless of enumerability */
+ var getAllKeys = Object.getOwnPropertyNames;
+
+ /** Used to get property descriptors */
+ var getDescriptor = Object.getOwnPropertyDescriptor;
+
+ /** Used in case an object doesn't have its own method */
+ var hasOwnProperty = {}.hasOwnProperty;
+
+ /** Used to check if an object is extensible */
+ var isExtensible = Object.isExtensible || function() { return true; };
+
+ /** Used to access the browser's high resolution timer */
+ var perfObject = isHostType(window, 'performance') && performance;
+
+ /** Used to call the browser's high resolution timer */
+ var perfName = perfObject && (
+ perfObject.now && 'now' ||
+ perfObject.webkitNow && 'webkitNow'
+ );
+
+ /** Used to check if an own property is enumerable */
+ var propertyIsEnumerable = {}.propertyIsEnumerable;
+
+ /** Used to set property descriptors */
+ var setDescriptor = Object.defineProperty;
+
+ /** Used to resolve a value's internal [[Class]] */
+ var toString = {}.toString;
+
+ /** Used to prevent a `removeChild` memory leak in IE < 9 */
+ var trash = doc && doc.createElement('div');
+
+ /** Used to integrity check compiled tests */
+ var uid = 'uid' + (+new Date);
+
+ /** Used to avoid infinite recursion when methods call each other */
+ var calledBy = {};
+
+ /** Used to avoid hz of Infinity */
+ var divisors = {
+ '1': 4096,
+ '2': 512,
+ '3': 64,
+ '4': 8,
+ '5': 0
+ };
+
+ /**
+ * T-Distribution two-tailed critical values for 95% confidence
+ * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
+ */
+ var tTable = {
+ '1': 12.706,'2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
+ '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
+ '13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
+ '19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
+ '25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
+ 'infinity': 1.96
+ };
+
+ /**
+ * Critical Mann-Whitney U-values for 95% confidence
+ * http://www.saburchill.com/IBbiology/stats/003.html
+ */
+ var uTable = {
+ '5': [0, 1, 2],
+ '6': [1, 2, 3, 5],
+ '7': [1, 3, 5, 6, 8],
+ '8': [2, 4, 6, 8, 10, 13],
+ '9': [2, 4, 7, 10, 12, 15, 17],
+ '10': [3, 5, 8, 11, 14, 17, 20, 23],
+ '11': [3, 6, 9, 13, 16, 19, 23, 26, 30],
+ '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37],
+ '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45],
+ '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55],
+ '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64],
+ '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75],
+ '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87],
+ '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99],
+ '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113],
+ '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127],
+ '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142],
+ '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158],
+ '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175],
+ '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192],
+ '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211],
+ '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230],
+ '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250],
+ '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272],
+ '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294],
+ '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317]
+ };
+
+ /**
+ * An object used to flag environments/features.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ var support = {};
+
+ (function() {
+
+ /**
+ * Detect Adobe AIR.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject');
+
+ /**
+ * Detect if `arguments` objects have the correct internal [[Class]] value.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.argumentsClass = isClassOf(arguments, 'Arguments');
+
+ /**
+ * Detect if in a browser environment.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.browser = doc && isHostType(window, 'navigator');
+
+ /**
+ * Detect if strings support accessing characters by index.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.charByIndex =
+ // IE 8 supports indexes on string literals but not string objects
+ ('x'[0] + Object('x')[0]) == 'xx';
+
+ /**
+ * Detect if strings have indexes as own properties.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.charByOwnIndex =
+ // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on
+ // strings but don't detect them as own properties
+ support.charByIndex && hasKey('x', '0');
+
+ /**
+ * Detect if Java is enabled/exposed.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.java = isClassOf(window.java, 'JavaPackage');
+
+ /**
+ * Detect if the Timers API exists.
+ *
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout');
+
+ /**
+ * Detect if functions support decompilation.
+ *
+ * @name decompilation
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ try {
+ // Safari 2.x removes commas in object literals
+ // from Function#toString results
+ // http://webk.it/11609
+ // Firefox 3.6 and Opera 9.25 strip grouping
+ // parentheses from Function#toString results
+ // http://bugzil.la/559438
+ support.decompilation = Function(
+ 'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')'
+ )()(0).x === '1';
+ } catch(e) {
+ support.decompilation = false;
+ }
+
+ /**
+ * Detect ES5+ property descriptor API.
+ *
+ * @name descriptors
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ try {
+ var o = {};
+ support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
+ } catch(e) {
+ support.descriptors = false;
+ }
+
+ /**
+ * Detect ES5+ Object.getOwnPropertyNames().
+ *
+ * @name getAllKeys
+ * @memberOf Benchmark.support
+ * @type Boolean
+ */
+ try {
+ support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype));
+ } catch(e) {
+ support.getAllKeys = false;
+ }
+ }());
+
+ /**
+ * Timer object used by `clock()` and `Deferred#resolve`.
+ *
+ * @private
+ * @type Object
+ */
+ var timer = {
+
+ /**
+ * The timer namespace object or constructor.
+ *
+ * @private
+ * @memberOf timer
+ * @type Function|Object
+ */
+ 'ns': Date,
+
+ /**
+ * Starts the deferred timer.
+ *
+ * @private
+ * @memberOf timer
+ * @param {Object} deferred The deferred instance.
+ */
+ 'start': null, // lazy defined in `clock()`
+
+ /**
+ * Stops the deferred timer.
+ *
+ * @private
+ * @memberOf timer
+ * @param {Object} deferred The deferred instance.
+ */
+ 'stop': null // lazy defined in `clock()`
+ };
+
+ /** Shortcut for inverse results */
+ var noArgumentsClass = !support.argumentsClass,
+ noCharByIndex = !support.charByIndex,
+ noCharByOwnIndex = !support.charByOwnIndex;
+
+ /** Math shortcuts */
+ var abs = Math.abs,
+ floor = Math.floor,
+ max = Math.max,
+ min = Math.min,
+ pow = Math.pow,
+ sqrt = Math.sqrt;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The Benchmark constructor.
+ *
+ * @constructor
+ * @param {String} name A name to identify the benchmark.
+ * @param {Function|String} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage (the `new` operator is optional)
+ * var bench = new Benchmark(fn);
+ *
+ * // or using a name first
+ * var bench = new Benchmark('foo', fn);
+ *
+ * // or with options
+ * var bench = new Benchmark('foo', fn, {
+ *
+ * // displayed by Benchmark#toString if `name` is not available
+ * 'id': 'xyz',
+ *
+ * // called when the benchmark starts running
+ * 'onStart': onStart,
+ *
+ * // called after each run cycle
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the benchmark completes running
+ * 'onComplete': onComplete,
+ *
+ * // compiled/called before the test loop
+ * 'setup': setup,
+ *
+ * // compiled/called after the test loop
+ * 'teardown': teardown
+ * });
+ *
+ * // or name and options
+ * var bench = new Benchmark('foo', {
+ *
+ * // a flag to indicate the benchmark is deferred
+ * 'defer': true,
+ *
+ * // benchmark test function
+ * 'fn': function(deferred) {
+ * // call resolve() when the deferred test is finished
+ * deferred.resolve();
+ * }
+ * });
+ *
+ * // or options only
+ * var bench = new Benchmark({
+ *
+ * // benchmark name
+ * 'name': 'foo',
+ *
+ * // benchmark test as a string
+ * 'fn': '[1,2,3,4].sort()'
+ * });
+ *
+ * // a test's `this` binding is set to the benchmark instance
+ * var bench = new Benchmark('foo', function() {
+ * 'My name is '.concat(this.name); // My name is foo
+ * });
+ */
+ function Benchmark(name, fn, options) {
+ var me = this;
+
+ // allow instance creation without the `new` operator
+ if (me == null || me.constructor != Benchmark) {
+ return new Benchmark(name, fn, options);
+ }
+ // juggle arguments
+ if (isClassOf(name, 'Object')) {
+ // 1 argument (options)
+ options = name;
+ }
+ else if (isClassOf(name, 'Function')) {
+ // 2 arguments (fn, options)
+ options = fn;
+ fn = name;
+ }
+ else if (isClassOf(fn, 'Object')) {
+ // 2 arguments (name, options)
+ options = fn;
+ fn = null;
+ me.name = name;
+ }
+ else {
+ // 3 arguments (name, fn [, options])
+ me.name = name;
+ }
+ setOptions(me, options);
+ me.id || (me.id = ++counter);
+ me.fn == null && (me.fn = fn);
+ me.stats = deepClone(me.stats);
+ me.times = deepClone(me.times);
+ }
+
+ /**
+ * The Deferred constructor.
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {Object} clone The cloned benchmark instance.
+ */
+ function Deferred(clone) {
+ var me = this;
+ if (me == null || me.constructor != Deferred) {
+ return new Deferred(clone);
+ }
+ me.benchmark = clone;
+ clock(me);
+ }
+
+ /**
+ * The Event constructor.
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {String|Object} type The event type.
+ */
+ function Event(type) {
+ var me = this;
+ return (me == null || me.constructor != Event)
+ ? new Event(type)
+ : (type instanceof Event)
+ ? type
+ : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type);
+ }
+
+ /**
+ * The Suite constructor.
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {String} name A name to identify the suite.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage (the `new` operator is optional)
+ * var suite = new Benchmark.Suite;
+ *
+ * // or using a name first
+ * var suite = new Benchmark.Suite('foo');
+ *
+ * // or with options
+ * var suite = new Benchmark.Suite('foo', {
+ *
+ * // called when the suite starts running
+ * 'onStart': onStart,
+ *
+ * // called between running benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the suite completes running
+ * 'onComplete': onComplete
+ * });
+ */
+ function Suite(name, options) {
+ var me = this;
+
+ // allow instance creation without the `new` operator
+ if (me == null || me.constructor != Suite) {
+ return new Suite(name, options);
+ }
+ // juggle arguments
+ if (isClassOf(name, 'Object')) {
+ // 1 argument (options)
+ options = name;
+ } else {
+ // 2 arguments (name [, options])
+ me.name = name;
+ }
+ setOptions(me, options);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Note: Some array methods have been implemented in plain JavaScript to avoid
+ * bugs in IE, Opera, Rhino, and Mobile Safari.
+ *
+ * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()`
+ * functions that fail to remove the last element, `object[0]`, of
+ * array-like-objects even though the `length` property is set to `0`.
+ * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+ * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+ *
+ * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()`
+ * generically to augment the `arguments` object will pave the value at index 0
+ * without incrimenting the other values's indexes.
+ * https://github.com/documentcloud/underscore/issues/9
+ *
+ * Rhino and environments it powers, like Narwhal and RingoJS, may have
+ * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and
+ * `unshift()` functions that make sparse arrays non-sparse by assigning the
+ * undefined indexes a value of undefined.
+ * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd
+ */
+
+ /**
+ * Creates an array containing the elements of the host array followed by the
+ * elements of each argument in order.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Array} The new array.
+ */
+ function concat() {
+ var value,
+ j = -1,
+ length = arguments.length,
+ result = slice.call(this),
+ index = result.length;
+
+ while (++j < length) {
+ value = arguments[j];
+ if (isClassOf(value, 'Array')) {
+ for (var k = 0, l = value.length; k < l; k++, index++) {
+ if (k in value) {
+ result[index] = value[k];
+ }
+ }
+ } else {
+ result[index++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Utility function used by `shift()`, `splice()`, and `unshift()`.
+ *
+ * @private
+ * @param {Number} start The index to start inserting elements.
+ * @param {Number} deleteCount The number of elements to delete from the insert point.
+ * @param {Array} elements The elements to insert.
+ * @returns {Array} An array of deleted elements.
+ */
+ function insert(start, deleteCount, elements) {
+ // `result` should have its length set to the `deleteCount`
+ // see https://bugs.ecmascript.org/show_bug.cgi?id=332
+ var deleteEnd = start + deleteCount,
+ elementCount = elements ? elements.length : 0,
+ index = start - 1,
+ length = start + elementCount,
+ object = this,
+ result = Array(deleteCount),
+ tail = slice.call(object, deleteEnd);
+
+ // delete elements from the array
+ while (++index < deleteEnd) {
+ if (index in object) {
+ result[index - start] = object[index];
+ delete object[index];
+ }
+ }
+ // insert elements
+ index = start - 1;
+ while (++index < length) {
+ object[index] = elements[index - start];
+ }
+ // append tail elements
+ start = index--;
+ length = max(0, (object.length >>> 0) - deleteCount + elementCount);
+ while (++index < length) {
+ if ((index - start) in tail) {
+ object[index] = tail[index - start];
+ } else {
+ delete object[index];
+ }
+ }
+ // delete excess elements
+ deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
+ while (deleteCount--) {
+ delete object[length + deleteCount];
+ }
+ object.length = length;
+ return result;
+ }
+
+ /**
+ * Rearrange the host array's elements in reverse order.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Array} The reversed array.
+ */
+ function reverse() {
+ var upperIndex,
+ value,
+ index = -1,
+ object = Object(this),
+ length = object.length >>> 0,
+ middle = floor(length / 2);
+
+ if (length > 1) {
+ while (++index < middle) {
+ upperIndex = length - index - 1;
+ value = upperIndex in object ? object[upperIndex] : uid;
+ if (index in object) {
+ object[upperIndex] = object[index];
+ } else {
+ delete object[upperIndex];
+ }
+ if (value != uid) {
+ object[index] = value;
+ } else {
+ delete object[index];
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Removes the first element of the host array and returns it.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Mixed} The first element of the array.
+ */
+ function shift() {
+ return insert.call(this, 0, 1)[0];
+ }
+
+ /**
+ * Creates an array of the host array's elements from the start index up to,
+ * but not including, the end index.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Number} start The starting index.
+ * @param {Number} end The end index.
+ * @returns {Array} The new array.
+ */
+ function slice(start, end) {
+ var index = -1,
+ object = Object(this),
+ length = object.length >>> 0,
+ result = [];
+
+ start = toInteger(start);
+ start = start < 0 ? max(length + start, 0) : min(start, length);
+ start--;
+ end = end == null ? length : toInteger(end);
+ end = end < 0 ? max(length + end, 0) : min(end, length);
+
+ while ((++index, ++start) < end) {
+ if (start in object) {
+ result[index] = object[start];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Allows removing a range of elements and/or inserting elements into the
+ * host array.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Number} start The start index.
+ * @param {Number} deleteCount The number of elements to delete.
+ * @param {Mixed} [val1, val2, ...] values to insert at the `start` index.
+ * @returns {Array} An array of removed elements.
+ */
+ function splice(start, deleteCount) {
+ var object = Object(this),
+ length = object.length >>> 0;
+
+ start = toInteger(start);
+ start = start < 0 ? max(length + start, 0) : min(start, length);
+ deleteCount = min(max(toInteger(deleteCount), 0), length - start);
+ return insert.call(object, start, deleteCount, slice.call(arguments, 2));
+ }
+
+ /**
+ * Converts the specified `value` to an integer.
+ *
+ * @private
+ * @param {Mixed} value The value to convert.
+ * @returns {Number} The resulting integer.
+ */
+ function toInteger(value) {
+ value = +value;
+ return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1);
+ }
+
+ /**
+ * Appends arguments to the host array.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Number} The new length.
+ */
+ function unshift() {
+ var object = Object(this);
+ insert.call(object, 0, 0, arguments);
+ return object.length;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic `Function#bind` like method.
+ *
+ * @private
+ * @param {Function} fn The function to be bound to `thisArg`.
+ * @param {Mixed} thisArg The `this` binding for the given function.
+ * @returns {Function} The bound function.
+ */
+ function bind(fn, thisArg) {
+ return function() { fn.apply(thisArg, arguments); };
+ }
+
+ /**
+ * Creates a function from the given arguments string and body.
+ *
+ * @private
+ * @param {String} args The comma separated function arguments.
+ * @param {String} body The function body.
+ * @returns {Function} The new function.
+ */
+ function createFunction() {
+ // lazy define
+ createFunction = function(args, body) {
+ var result,
+ anchor = freeDefine ? define.amd : Benchmark,
+ prop = uid + 'createFunction';
+
+ runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}');
+ result = anchor[prop];
+ delete anchor[prop];
+ return result;
+ };
+ // fix JaegerMonkey bug
+ // http://bugzil.la/639720
+ createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function;
+ return createFunction.apply(null, arguments);
+ }
+
+ /**
+ * Delay the execution of a function based on the benchmark's `delay` property.
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @param {Object} fn The function to execute.
+ */
+ function delay(bench, fn) {
+ bench._timerId = setTimeout(fn, bench.delay * 1e3);
+ }
+
+ /**
+ * Destroys the given element.
+ *
+ * @private
+ * @param {Element} element The element to destroy.
+ */
+ function destroyElement(element) {
+ trash.appendChild(element);
+ trash.innerHTML = '';
+ }
+
+ /**
+ * Iterates over an object's properties, executing the `callback` for each.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function executed per own property.
+ * @param {Object} options The options object.
+ * @returns {Object} Returns the object iterated over.
+ */
+ function forProps() {
+ var forShadowed,
+ skipSeen,
+ forArgs = true,
+ shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf'];
+
+ (function(enumFlag, key) {
+ // must use a non-native constructor to catch the Safari 2 issue
+ function Klass() { this.valueOf = 0; };
+ Klass.prototype.valueOf = 0;
+ // check various for-in bugs
+ for (key in new Klass) {
+ enumFlag += key == 'valueOf' ? 1 : 0;
+ }
+ // check if `arguments` objects have non-enumerable indexes
+ for (key in arguments) {
+ key == '0' && (forArgs = false);
+ }
+ // Safari 2 iterates over shadowed properties twice
+ // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/
+ skipSeen = enumFlag == 2;
+ // IE < 9 incorrectly makes an object's properties non-enumerable if they have
+ // the same name as other non-enumerable properties in its prototype chain.
+ forShadowed = !enumFlag;
+ }(0));
+
+ // lazy define
+ forProps = function(object, callback, options) {
+ options || (options = {});
+
+ var result = object;
+ object = Object(object);
+
+ var ctor,
+ key,
+ keys,
+ skipCtor,
+ done = !result,
+ which = options.which,
+ allFlag = which == 'all',
+ index = -1,
+ iteratee = object,
+ length = object.length,
+ ownFlag = allFlag || which == 'own',
+ seen = {},
+ skipProto = isClassOf(object, 'Function'),
+ thisArg = options.bind;
+
+ if (thisArg !== undefined) {
+ callback = bind(callback, thisArg);
+ }
+ // iterate all properties
+ if (allFlag && support.getAllKeys) {
+ for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) {
+ key = keys[index];
+ if (callback(object[key], key, object) === false) {
+ break;
+ }
+ }
+ }
+ // else iterate only enumerable properties
+ else {
+ for (key in object) {
+ // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ // (if the prototype or a property on the prototype has been set)
+ // incorrectly set a function's `prototype` property [[Enumerable]] value
+ // to `true`. Because of this we standardize on skipping the `prototype`
+ // property of functions regardless of their [[Enumerable]] value.
+ if ((done =
+ !(skipProto && key == 'prototype') &&
+ !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) &&
+ (!ownFlag || ownFlag && hasKey(object, key)) &&
+ callback(object[key], key, object) === false)) {
+ break;
+ }
+ }
+ // in IE < 9 strings don't support accessing characters by index
+ if (!done && (forArgs && isArguments(object) ||
+ ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') &&
+ (iteratee = noCharByIndex ? object.split('') : object)))) {
+ while (++index < length) {
+ if ((done =
+ callback(iteratee[index], String(index), object) === false)) {
+ break;
+ }
+ }
+ }
+ if (!done && forShadowed) {
+ // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing
+ // property and the `constructor` property of a prototype defaults to
+ // non-enumerable, we manually skip the `constructor` property when we
+ // think we are iterating over a `prototype` object.
+ ctor = object.constructor;
+ skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor;
+ for (index = 0; index < 7; index++) {
+ key = shadowed[index];
+ if (!(skipCtor && key == 'constructor') &&
+ hasKey(object, key) &&
+ callback(object[key], key, object) === false) {
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ };
+ return forProps.apply(null, arguments);
+ }
+
+ /**
+ * Gets the name of the first argument from a function's source.
+ *
+ * @private
+ * @param {Function} fn The function.
+ * @returns {String} The argument name.
+ */
+ function getFirstArgument(fn) {
+ return (!hasKey(fn, 'toString') &&
+ (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
+ }
+
+ /**
+ * Computes the arithmetic mean of a sample.
+ *
+ * @private
+ * @param {Array} sample The sample.
+ * @returns {Number} The mean.
+ */
+ function getMean(sample) {
+ return reduce(sample, function(sum, x) {
+ return sum + x;
+ }) / sample.length || 0;
+ }
+
+ /**
+ * Gets the source code of a function.
+ *
+ * @private
+ * @param {Function} fn The function.
+ * @param {String} altSource A string used when a function's source code is unretrievable.
+ * @returns {String} The function's source code.
+ */
+ function getSource(fn, altSource) {
+ var result = altSource;
+ if (isStringable(fn)) {
+ result = String(fn);
+ } else if (support.decompilation) {
+ // escape the `{` for Firefox 1
+ result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1];
+ }
+ return (result || '').replace(/^\s+|\s+$/g, '');
+ }
+
+ /**
+ * Checks if a value is an `arguments` object.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`.
+ */
+ function isArguments() {
+ // lazy define
+ isArguments = function(value) {
+ return toString.call(value) == '[object Arguments]';
+ };
+ if (noArgumentsClass) {
+ isArguments = function(value) {
+ return hasKey(value, 'callee') &&
+ !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee'));
+ };
+ }
+ return isArguments(arguments[0]);
+ }
+
+ /**
+ * Checks if an object is of the specified class.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @param {String} name The name of the class.
+ * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`.
+ */
+ function isClassOf(value, name) {
+ return value != null && toString.call(value) == '[object ' + name + ']';
+ }
+
+ /**
+ * Host objects can return type values that are different from their actual
+ * data type. The objects we are concerned with usually return non-primitive
+ * types of object, function, or unknown.
+ *
+ * @private
+ * @param {Mixed} object The owner of the property.
+ * @param {String} property The property to check.
+ * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
+ */
+ function isHostType(object, property) {
+ var type = object != null ? typeof object[property] : 'number';
+ return !/^(?:boolean|number|string|undefined)$/.test(type) &&
+ (type == 'object' ? !!object[property] : true);
+ }
+
+ /**
+ * Checks if the specified `value` is an object created by the `Object`
+ * constructor assuming objects created by the `Object` constructor have no
+ * inherited enumerable properties and assuming there are no `Object.prototype`
+ * extensions.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if `value` is an object, else `false`.
+ */
+ function isObject(value) {
+ var ctor,
+ result = !!value && toString.call(value) == '[object Object]';
+
+ if (result && noArgumentsClass) {
+ // avoid false positives for `arguments` objects in IE < 9
+ result = !isArguments(value);
+ }
+ if (result) {
+ // IE < 9 presents nodes like `Object` objects:
+ // IE < 8 are missing the node's constructor property
+ // IE 8 node constructors are typeof "object"
+ ctor = value.constructor;
+ // check if the constructor is `Object` as `Object instanceof Object` is `true`
+ if ((result = isClassOf(ctor, 'Function') && ctor instanceof ctor)) {
+ // An object's own properties are iterated before inherited properties.
+ // If the last iterated key belongs to an object's own property then
+ // there are no inherited enumerable properties.
+ forProps(value, function(subValue, subKey) { result = subKey; });
+ result = result === true || hasKey(value, result);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if a value can be safely coerced to a string.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the value can be coerced, else `false`.
+ */
+ function isStringable(value) {
+ return hasKey(value, 'toString') || isClassOf(value, 'String');
+ }
+
+ /**
+ * Wraps a function and passes `this` to the original function as the
+ * first argument.
+ *
+ * @private
+ * @param {Function} fn The function to be wrapped.
+ * @returns {Function} The new function.
+ */
+ function methodize(fn) {
+ return function() {
+ var args = [this];
+ args.push.apply(args, arguments);
+ return fn.apply(null, args);
+ };
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @private
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * A wrapper around require() to suppress `module missing` errors.
+ *
+ * @private
+ * @param {String} id The module id.
+ * @returns {Mixed} The exported module or `null`.
+ */
+ function req(id) {
+ try {
+ var result = freeExports && freeRequire(id);
+ } catch(e) { }
+ return result || null;
+ }
+
+ /**
+ * Runs a snippet of JavaScript via script injection.
+ *
+ * @private
+ * @param {String} code The code to run.
+ */
+ function runScript(code) {
+ var anchor = freeDefine ? define.amd : Benchmark,
+ script = doc.createElement('script'),
+ sibling = doc.getElementsByTagName('script')[0],
+ parent = sibling.parentNode,
+ prop = uid + 'runScript',
+ prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();';
+
+ // Firefox 2.0.0.2 cannot use script injection as intended because it executes
+ // asynchronously, but that's OK because script injection is only used to avoid
+ // the previously commented JaegerMonkey bug.
+ try {
+ // remove the inserted script *before* running the code to avoid differences
+ // in the expected script element count/order of the document.
+ script.appendChild(doc.createTextNode(prefix + code));
+ anchor[prop] = function() { destroyElement(script); };
+ } catch(e) {
+ parent = parent.cloneNode(false);
+ sibling = null;
+ script.text = code;
+ }
+ parent.insertBefore(script, sibling);
+ delete anchor[prop];
+ }
+
+ /**
+ * A helper function for setting options/event handlers.
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @param {Object} [options={}] Options object.
+ */
+ function setOptions(bench, options) {
+ options = extend({}, bench.constructor.options, options);
+ bench.options = forOwn(options, function(value, key) {
+ if (value != null) {
+ // add event listeners
+ if (/^on[A-Z]/.test(key)) {
+ forEach(key.split(' '), function(key) {
+ bench.on(key.slice(2).toLowerCase(), value);
+ });
+ } else if (!hasKey(bench, key)) {
+ bench[key] = deepClone(value);
+ }
+ }
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Handles cycling/completing the deferred benchmark.
+ *
+ * @memberOf Benchmark.Deferred
+ */
+ function resolve() {
+ var me = this,
+ clone = me.benchmark,
+ bench = clone._original;
+
+ if (bench.aborted) {
+ // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete
+ me.teardown();
+ clone.running = false;
+ cycle(me);
+ }
+ else if (++me.cycles < clone.count) {
+ // continue the test loop
+ if (support.timeout) {
+ // use setTimeout to avoid a call stack overflow if called recursively
+ setTimeout(function() { clone.compiled.call(me, timer); }, 0);
+ } else {
+ clone.compiled.call(me, timer);
+ }
+ }
+ else {
+ timer.stop(me);
+ me.teardown();
+ delay(clone, function() { cycle(me); });
+ }
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A deep clone utility.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Mixed} value The value to clone.
+ * @returns {Mixed} The cloned value.
+ */
+ function deepClone(value) {
+ var accessor,
+ circular,
+ clone,
+ ctor,
+ descriptor,
+ extensible,
+ key,
+ length,
+ markerKey,
+ parent,
+ result,
+ source,
+ subIndex,
+ data = { 'value': value },
+ index = 0,
+ marked = [],
+ queue = { 'length': 0 },
+ unmarked = [];
+
+ /**
+ * An easily detectable decorator for cloned values.
+ */
+ function Marker(object) {
+ this.raw = object;
+ }
+
+ /**
+ * The callback used by `forProps()`.
+ */
+ function forPropsCallback(subValue, subKey) {
+ // exit early to avoid cloning the marker
+ if (subValue && subValue.constructor == Marker) {
+ return;
+ }
+ // add objects to the queue
+ if (subValue === Object(subValue)) {
+ queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value };
+ }
+ // assign non-objects
+ else {
+ try {
+ // will throw an error in strict mode if the property is read-only
+ clone[subKey] = subValue;
+ } catch(e) { }
+ }
+ }
+
+ /**
+ * Gets an available marker key for the given object.
+ */
+ function getMarkerKey(object) {
+ // avoid collisions with existing keys
+ var result = uid;
+ while (object[result] && object[result].constructor != Marker) {
+ result += 1;
+ }
+ return result;
+ }
+
+ do {
+ key = data.key;
+ parent = data.parent;
+ source = data.source;
+ clone = value = source ? source[key] : data.value;
+ accessor = circular = descriptor = false;
+
+ // create a basic clone to filter out functions, DOM elements, and
+ // other non `Object` objects
+ if (value === Object(value)) {
+ // use custom deep clone function if available
+ if (isClassOf(value.deepClone, 'Function')) {
+ clone = value.deepClone();
+ } else {
+ ctor = value.constructor;
+ switch (toString.call(value)) {
+ case '[object Array]':
+ clone = new ctor(value.length);
+ break;
+
+ case '[object Boolean]':
+ clone = new ctor(value == true);
+ break;
+
+ case '[object Date]':
+ clone = new ctor(+value);
+ break;
+
+ case '[object Object]':
+ isObject(value) && (clone = new ctor);
+ break;
+
+ case '[object Number]':
+ case '[object String]':
+ clone = new ctor(value);
+ break;
+
+ case '[object RegExp]':
+ clone = ctor(value.source,
+ (value.global ? 'g' : '') +
+ (value.ignoreCase ? 'i' : '') +
+ (value.multiline ? 'm' : ''));
+ }
+ }
+ // continue clone if `value` doesn't have an accessor descriptor
+ // http://es5.github.com/#x8.10.1
+ if (clone && clone != value &&
+ !(descriptor = source && support.descriptors && getDescriptor(source, key),
+ accessor = descriptor && (descriptor.get || descriptor.set))) {
+ // use an existing clone (circular reference)
+ if ((extensible = isExtensible(value))) {
+ markerKey = getMarkerKey(value);
+ if (value[markerKey]) {
+ circular = clone = value[markerKey].raw;
+ }
+ } else {
+ // for frozen/sealed objects
+ for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) {
+ data = unmarked[subIndex];
+ if (data.object === value) {
+ circular = clone = data.clone;
+ break;
+ }
+ }
+ }
+ if (!circular) {
+ // mark object to allow quickly detecting circular references and tie it to its clone
+ if (extensible) {
+ value[markerKey] = new Marker(clone);
+ marked.push({ 'key': markerKey, 'object': value });
+ } else {
+ // for frozen/sealed objects
+ unmarked.push({ 'clone': clone, 'object': value });
+ }
+ // iterate over object properties
+ forProps(value, forPropsCallback, { 'which': 'all' });
+ }
+ }
+ }
+ if (parent) {
+ // for custom property descriptors
+ if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) {
+ if ('value' in descriptor) {
+ descriptor.value = clone;
+ }
+ setDescriptor(parent, key, descriptor);
+ }
+ // for default property descriptors
+ else {
+ parent[key] = clone;
+ }
+ } else {
+ result = clone;
+ }
+ } while ((data = queue[index++]));
+
+ // remove markers
+ for (index = 0, length = marked.length; index < length; index++) {
+ data = marked[index];
+ delete data.object[data.key];
+ }
+ return result;
+ }
+
+ /**
+ * An iteration utility for arrays and objects.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} thisArg The `this` binding for the callback.
+ * @returns {Array|Object} Returns the object iterated over.
+ */
+ function each(object, callback, thisArg) {
+ var result = object;
+ object = Object(object);
+
+ var fn = callback,
+ index = -1,
+ length = object.length,
+ isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)),
+ isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'),
+ isConvertable = isSnapshot || isSplittable || 'item' in object,
+ origObject = object;
+
+ // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists
+ if (length === length >>> 0) {
+ if (isConvertable) {
+ // the third argument of the callback is the original non-array object
+ callback = function(value, index) {
+ return fn.call(this, value, index, origObject);
+ };
+ // in IE < 9 strings don't support accessing characters by index
+ if (isSplittable) {
+ object = object.split('');
+ } else {
+ object = [];
+ while (++index < length) {
+ // in Safari 2 `index in object` is always `false` for NodeLists
+ object[index] = isSnapshot ? result.snapshotItem(index) : result[index];
+ }
+ }
+ }
+ forEach(object, callback, thisArg);
+ } else {
+ forOwn(object, callback, thisArg);
+ }
+ return result;
+ }
+
+ /**
+ * Copies enumerable properties from the source(s) object to the destination object.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Object} destination The destination object.
+ * @param {Object} [source={}] The source object.
+ * @returns {Object} The destination object.
+ */
+ function extend(destination, source) {
+ // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]`
+ // http://code.google.com/p/v8/issues/detail?id=839
+ var result = destination;
+ delete arguments[0];
+
+ forEach(arguments, function(source) {
+ forProps(source, function(value, key) {
+ result[key] = value;
+ });
+ });
+ return result;
+ }
+
+ /**
+ * A generic `Array#filter` like method.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function|String} callback The function/alias called per iteration.
+ * @param {Mixed} thisArg The `this` binding for the callback.
+ * @returns {Array} A new array of values that passed callback filter.
+ * @example
+ *
+ * // get odd numbers
+ * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+ * return n % 2;
+ * }); // -> [1, 3, 5];
+ *
+ * // get fastest benchmarks
+ * Benchmark.filter(benches, 'fastest');
+ *
+ * // get slowest benchmarks
+ * Benchmark.filter(benches, 'slowest');
+ *
+ * // get benchmarks that completed without erroring
+ * Benchmark.filter(benches, 'successful');
+ */
+ function filter(array, callback, thisArg) {
+ var result;
+
+ if (callback == 'successful') {
+ // callback to exclude those that are errored, unrun, or have hz of Infinity
+ callback = function(bench) { return bench.cycles && isFinite(bench.hz); };
+ }
+ else if (callback == 'fastest' || callback == 'slowest') {
+ // get successful, sort by period + margin of error, and filter fastest/slowest
+ result = filter(array, 'successful').sort(function(a, b) {
+ a = a.stats; b = b.stats;
+ return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1);
+ });
+ result = filter(result, function(bench) {
+ return result[0].compare(bench) == 0;
+ });
+ }
+ return result || reduce(array, function(result, value, index) {
+ return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result;
+ }, []);
+ }
+
+ /**
+ * A generic `Array#forEach` like method.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} thisArg The `this` binding for the callback.
+ * @returns {Array} Returns the array iterated over.
+ */
+ function forEach(array, callback, thisArg) {
+ var index = -1,
+ length = (array = Object(array)).length >>> 0;
+
+ if (thisArg !== undefined) {
+ callback = bind(callback, thisArg);
+ }
+ while (++index < length) {
+ if (index in array &&
+ callback(array[index], index, array) === false) {
+ break;
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Iterates over an object's own properties, executing the `callback` for each.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function executed per own property.
+ * @param {Mixed} thisArg The `this` binding for the callback.
+ * @returns {Object} Returns the object iterated over.
+ */
+ function forOwn(object, callback, thisArg) {
+ return forProps(object, callback, { 'bind': thisArg, 'which': 'own' });
+ }
+
+ /**
+ * Converts a number to a more readable comma-separated string representation.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Number} number The number to convert.
+ * @returns {String} The more readable string representation.
+ */
+ function formatNumber(number) {
+ number = String(number).split('.');
+ return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
+ (number[1] ? '.' + number[1] : '');
+ }
+
+ /**
+ * Checks if an object has the specified key as a direct property.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Object} object The object to check.
+ * @param {String} key The key to check for.
+ * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+ */
+ function hasKey() {
+ // lazy define for worst case fallback (not as accurate)
+ hasKey = function(object, key) {
+ var parent = object != null && (object.constructor || Object).prototype;
+ return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
+ };
+ // for modern browsers
+ if (isClassOf(hasOwnProperty, 'Function')) {
+ hasKey = function(object, key) {
+ return object != null && hasOwnProperty.call(object, key);
+ };
+ }
+ // for Safari 2
+ else if ({}.__proto__ == Object.prototype) {
+ hasKey = function(object, key) {
+ var result = false;
+ if (object != null) {
+ object = Object(object);
+ object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
+ }
+ return result;
+ };
+ }
+ return hasKey.apply(this, arguments);
+ }
+
+ /**
+ * A generic `Array#indexOf` like method.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=0] The index to start searching from.
+ * @returns {Number} The index of the matched value or `-1`.
+ */
+ function indexOf(array, value, fromIndex) {
+ var index = toInteger(fromIndex),
+ length = (array = Object(array)).length >>> 0;
+
+ index = (index < 0 ? max(0, length + index) : index) - 1;
+ while (++index < length) {
+ if (index in array && value === array[index]) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Modify a string by replacing named tokens with matching object property values.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {String} string The string to modify.
+ * @param {Object} object The template object.
+ * @returns {String} The modified string.
+ */
+ function interpolate(string, object) {
+ forOwn(object, function(value, key) {
+ // escape regexp special characters in `key`
+ string = string.replace(RegExp('#\\{' + key.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') + '\\}', 'g'), value);
+ });
+ return string;
+ }
+
+ /**
+ * Invokes a method on all items in an array.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} benches Array of benchmarks to iterate over.
+ * @param {String|Object} name The name of the method to invoke OR options object.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} A new array of values returned from each method invoked.
+ * @example
+ *
+ * // invoke `reset` on all benchmarks
+ * Benchmark.invoke(benches, 'reset');
+ *
+ * // invoke `emit` with arguments
+ * Benchmark.invoke(benches, 'emit', 'complete', listener);
+ *
+ * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+ * Benchmark.invoke(benches, {
+ *
+ * // invoke the `run` method
+ * 'name': 'run',
+ *
+ * // pass a single argument
+ * 'args': true,
+ *
+ * // treat as queue, removing benchmarks from front of `benches` until empty
+ * 'queued': true,
+ *
+ * // called before any benchmarks have been invoked.
+ * 'onStart': onStart,
+ *
+ * // called between invoking benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called after all benchmarks have been invoked.
+ * 'onComplete': onComplete
+ * });
+ */
+ function invoke(benches, name) {
+ var args,
+ bench,
+ queued,
+ index = -1,
+ eventProps = { 'currentTarget': benches },
+ options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop },
+ result = map(benches, function(bench) { return bench; });
+
+ /**
+ * Invokes the method of the current object and if synchronous, fetches the next.
+ */
+ function execute() {
+ var listeners,
+ async = isAsync(bench);
+
+ if (async) {
+ // use `getNext` as the first listener
+ bench.on('complete', getNext);
+ listeners = bench.events.complete;
+ listeners.splice(0, 0, listeners.pop());
+ }
+ // execute method
+ result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined;
+ // if synchronous return true until finished
+ return !async && getNext();
+ }
+
+ /**
+ * Fetches the next bench or executes `onComplete` callback.
+ */
+ function getNext(event) {
+ var cycleEvent,
+ last = bench,
+ async = isAsync(last);
+
+ if (async) {
+ last.off('complete', getNext);
+ last.emit('complete');
+ }
+ // emit "cycle" event
+ eventProps.type = 'cycle';
+ eventProps.target = last;
+ cycleEvent = Event(eventProps);
+ options.onCycle.call(benches, cycleEvent);
+
+ // choose next benchmark if not exiting early
+ if (!cycleEvent.aborted && raiseIndex() !== false) {
+ bench = queued ? benches[0] : result[index];
+ if (isAsync(bench)) {
+ delay(bench, execute);
+ }
+ else if (async) {
+ // resume execution if previously asynchronous but now synchronous
+ while (execute()) { }
+ }
+ else {
+ // continue synchronous execution
+ return true;
+ }
+ } else {
+ // emit "complete" event
+ eventProps.type = 'complete';
+ options.onComplete.call(benches, Event(eventProps));
+ }
+ // When used as a listener `event.aborted = true` will cancel the rest of
+ // the "complete" listeners because they were already called above and when
+ // used as part of `getNext` the `return false` will exit the execution while-loop.
+ if (event) {
+ event.aborted = true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if invoking `Benchmark#run` with asynchronous cycles.
+ */
+ function isAsync(object) {
+ // avoid using `instanceof` here because of IE memory leak issues with host objects
+ var async = args[0] && args[0].async;
+ return Object(object).constructor == Benchmark && name == 'run' &&
+ ((async == null ? object.options.async : async) && support.timeout || object.defer);
+ }
+
+ /**
+ * Raises `index` to the next defined index or returns `false`.
+ */
+ function raiseIndex() {
+ var length = result.length;
+ if (queued) {
+ // if queued remove the previous bench and subsequent skipped non-entries
+ do {
+ ++index > 0 && shift.call(benches);
+ } while ((length = benches.length) && !('0' in benches));
+ }
+ else {
+ while (++index < length && !(index in result)) { }
+ }
+ // if we reached the last index then return `false`
+ return (queued ? length : index < length) ? index : (index = false);
+ }
+
+ // juggle arguments
+ if (isClassOf(name, 'String')) {
+ // 2 arguments (array, name)
+ args = slice.call(arguments, 2);
+ } else {
+ // 2 arguments (array, options)
+ options = extend(options, name);
+ name = options.name;
+ args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args];
+ queued = options.queued;
+ }
+
+ // start iterating over the array
+ if (raiseIndex() !== false) {
+ // emit "start" event
+ bench = result[index];
+ eventProps.type = 'start';
+ eventProps.target = bench;
+ options.onStart.call(benches, Event(eventProps));
+
+ // end early if the suite was aborted in an "onStart" listener
+ if (benches.aborted && benches.constructor == Suite && name == 'run') {
+ // emit "cycle" event
+ eventProps.type = 'cycle';
+ options.onCycle.call(benches, Event(eventProps));
+ // emit "complete" event
+ eventProps.type = 'complete';
+ options.onComplete.call(benches, Event(eventProps));
+ }
+ // else start
+ else {
+ if (isAsync(bench)) {
+ delay(bench, execute);
+ } else {
+ while (execute()) { }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a string of joined array values or object key-value pairs.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array|Object} object The object to operate on.
+ * @param {String} [separator1=','] The separator used between key-value pairs.
+ * @param {String} [separator2=': '] The separator used between keys and values.
+ * @returns {String} The joined result.
+ */
+ function join(object, separator1, separator2) {
+ var result = [],
+ length = (object = Object(object)).length,
+ arrayLike = length === length >>> 0;
+
+ separator2 || (separator2 = ': ');
+ each(object, function(value, key) {
+ result.push(arrayLike ? value : key + separator2 + value);
+ });
+ return result.join(separator1 || ',');
+ }
+
+ /**
+ * A generic `Array#map` like method.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} thisArg The `this` binding for the callback.
+ * @returns {Array} A new array of values returned by the callback.
+ */
+ function map(array, callback, thisArg) {
+ return reduce(array, function(result, value, index) {
+ result[index] = callback.call(thisArg, value, index, array);
+ return result;
+ }, Array(Object(array).length >>> 0));
+ }
+
+ /**
+ * Retrieves the value of a specified property from all items in an array.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {String} property The property to pluck.
+ * @returns {Array} A new array of property values.
+ */
+ function pluck(array, property) {
+ return map(array, function(object) {
+ return object == null ? undefined : object[property];
+ });
+ }
+
+ /**
+ * A generic `Array#reduce` like method.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} accumulator Initial value of the accumulator.
+ * @returns {Mixed} The accumulator.
+ */
+ function reduce(array, callback, accumulator) {
+ var noaccum = arguments.length < 3;
+ forEach(array, function(value, index) {
+ accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array);
+ });
+ return accumulator;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Aborts all benchmarks in the suite.
+ *
+ * @name abort
+ * @memberOf Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function abortSuite() {
+ var event,
+ me = this,
+ resetting = calledBy.resetSuite;
+
+ if (me.running) {
+ event = Event('abort');
+ me.emit(event);
+ if (!event.cancelled || resetting) {
+ // avoid infinite recursion
+ calledBy.abortSuite = true;
+ me.reset();
+ delete calledBy.abortSuite;
+
+ if (!resetting) {
+ me.aborted = true;
+ invoke(me, 'abort');
+ }
+ }
+ }
+ return me;
+ }
+
+ /**
+ * Adds a test to the benchmark suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {String} name A name to identify the benchmark.
+ * @param {Function|String} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * suite.add(fn);
+ *
+ * // or using a name first
+ * suite.add('foo', fn);
+ *
+ * // or with options
+ * suite.add('foo', fn, {
+ * 'onCycle': onCycle,
+ * 'onComplete': onComplete
+ * });
+ */
+ function add(name, fn, options) {
+ var me = this,
+ bench = Benchmark(name, fn, options),
+ event = Event({ 'type': 'add', 'target': bench });
+
+ if (me.emit(event), !event.cancelled) {
+ me.push(bench);
+ }
+ return me;
+ }
+
+ /**
+ * Creates a new suite with cloned benchmarks.
+ *
+ * @name clone
+ * @memberOf Benchmark.Suite
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new suite instance.
+ */
+ function cloneSuite(options) {
+ var me = this,
+ result = new me.constructor(extend({}, me.options, options));
+
+ // copy own properties
+ forOwn(me, function(value, key) {
+ if (!hasKey(result, key)) {
+ result[key] = value && isClassOf(value.clone, 'Function')
+ ? value.clone()
+ : deepClone(value);
+ }
+ });
+ return result;
+ }
+
+ /**
+ * An `Array#filter` like method.
+ *
+ * @name filter
+ * @memberOf Benchmark.Suite
+ * @param {Function|String} callback The function/alias called per iteration.
+ * @returns {Object} A new suite of benchmarks that passed callback filter.
+ */
+ function filterSuite(callback) {
+ var me = this,
+ result = new me.constructor;
+
+ result.push.apply(result, filter(me, callback));
+ return result;
+ }
+
+ /**
+ * Resets all benchmarks in the suite.
+ *
+ * @name reset
+ * @memberOf Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function resetSuite() {
+ var event,
+ me = this,
+ aborting = calledBy.abortSuite;
+
+ if (me.running && !aborting) {
+ // no worries, `resetSuite()` is called within `abortSuite()`
+ calledBy.resetSuite = true;
+ me.abort();
+ delete calledBy.resetSuite;
+ }
+ // reset if the state has changed
+ else if ((me.aborted || me.running) &&
+ (me.emit(event = Event('reset')), !event.cancelled)) {
+ me.running = false;
+ if (!aborting) {
+ invoke(me, 'reset');
+ }
+ }
+ return me;
+ }
+
+ /**
+ * Runs the suite.
+ *
+ * @name run
+ * @memberOf Benchmark.Suite
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The suite instance.
+ * @example
+ *
+ * // basic usage
+ * suite.run();
+ *
+ * // or with options
+ * suite.run({ 'async': true, 'queued': true });
+ */
+ function runSuite(options) {
+ var me = this;
+
+ me.reset();
+ me.running = true;
+ options || (options = {});
+
+ invoke(me, {
+ 'name': 'run',
+ 'args': options,
+ 'queued': options.queued,
+ 'onStart': function(event) {
+ me.emit(event);
+ },
+ 'onCycle': function(event) {
+ var bench = event.target;
+ if (bench.error) {
+ me.emit({ 'type': 'error', 'target': bench });
+ }
+ me.emit(event);
+ event.aborted = me.aborted;
+ },
+ 'onComplete': function(event) {
+ me.running = false;
+ me.emit(event);
+ }
+ });
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Executes all registered listeners of the specified event type.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {String|Object} type The event type or object.
+ * @returns {Mixed} Returns the return value of the last listener executed.
+ */
+ function emit(type) {
+ var listeners,
+ me = this,
+ event = Event(type),
+ events = me.events,
+ args = (arguments[0] = event, arguments);
+
+ event.currentTarget || (event.currentTarget = me);
+ event.target || (event.target = me);
+ delete event.result;
+
+ if (events && (listeners = hasKey(events, event.type) && events[event.type])) {
+ forEach(listeners.slice(), function(listener) {
+ if ((event.result = listener.apply(me, args)) === false) {
+ event.cancelled = true;
+ }
+ return !event.aborted;
+ });
+ }
+ return event.result;
+ }
+
+ /**
+ * Returns an array of event listeners for a given type that can be manipulated
+ * to add or remove listeners.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ * @returns {Array} The listeners array.
+ */
+ function listeners(type) {
+ var me = this,
+ events = me.events || (me.events = {});
+
+ return hasKey(events, type) ? events[type] : (events[type] = []);
+ }
+
+ /**
+ * Unregisters a listener for the specified event type(s),
+ * or unregisters all listeners for the specified event type(s),
+ * or unregisters all listeners for all event types.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {String} [type] The event type.
+ * @param {Function} [listener] The function to unregister.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // unregister a listener for an event type
+ * bench.off('cycle', listener);
+ *
+ * // unregister a listener for multiple event types
+ * bench.off('start cycle', listener);
+ *
+ * // unregister all listeners for an event type
+ * bench.off('cycle');
+ *
+ * // unregister all listeners for multiple event types
+ * bench.off('start cycle complete');
+ *
+ * // unregister all listeners for all event types
+ * bench.off();
+ */
+ function off(type, listener) {
+ var me = this,
+ events = me.events;
+
+ events && each(type ? type.split(' ') : events, function(listeners, type) {
+ var index;
+ if (typeof listeners == 'string') {
+ type = listeners;
+ listeners = hasKey(events, type) && events[type];
+ }
+ if (listeners) {
+ if (listener) {
+ index = indexOf(listeners, listener);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ } else {
+ listeners.length = 0;
+ }
+ }
+ });
+ return me;
+ }
+
+ /**
+ * Registers a listener for the specified event type(s).
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ * @param {Function} listener The function to register.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // register a listener for an event type
+ * bench.on('cycle', listener);
+ *
+ * // register a listener for multiple event types
+ * bench.on('start cycle', listener);
+ */
+ function on(type, listener) {
+ var me = this,
+ events = me.events || (me.events = {});
+
+ forEach(type.split(' '), function(type) {
+ (hasKey(events, type)
+ ? events[type]
+ : (events[type] = [])
+ ).push(listener);
+ });
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Aborts the benchmark without recording times.
+ *
+ * @memberOf Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function abort() {
+ var event,
+ me = this,
+ resetting = calledBy.reset;
+
+ if (me.running) {
+ event = Event('abort');
+ me.emit(event);
+ if (!event.cancelled || resetting) {
+ // avoid infinite recursion
+ calledBy.abort = true;
+ me.reset();
+ delete calledBy.abort;
+
+ if (support.timeout) {
+ clearTimeout(me._timerId);
+ delete me._timerId;
+ }
+ if (!resetting) {
+ me.aborted = true;
+ me.running = false;
+ }
+ }
+ }
+ return me;
+ }
+
+ /**
+ * Creates a new benchmark using the same test and options.
+ *
+ * @memberOf Benchmark
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new benchmark instance.
+ * @example
+ *
+ * var bizarro = bench.clone({
+ * 'name': 'doppelganger'
+ * });
+ */
+ function clone(options) {
+ var me = this,
+ result = new me.constructor(extend({}, me, options));
+
+ // correct the `options` object
+ result.options = extend({}, me.options, options);
+
+ // copy own custom properties
+ forOwn(me, function(value, key) {
+ if (!hasKey(result, key)) {
+ result[key] = deepClone(value);
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Determines if a benchmark is faster than another.
+ *
+ * @memberOf Benchmark
+ * @param {Object} other The benchmark to compare.
+ * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
+ */
+ function compare(other) {
+ var critical,
+ zStat,
+ me = this,
+ sample1 = me.stats.sample,
+ sample2 = other.stats.sample,
+ size1 = sample1.length,
+ size2 = sample2.length,
+ maxSize = max(size1, size2),
+ minSize = min(size1, size2),
+ u1 = getU(sample1, sample2),
+ u2 = getU(sample2, sample1),
+ u = min(u1, u2);
+
+ function getScore(xA, sampleB) {
+ return reduce(sampleB, function(total, xB) {
+ return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5);
+ }, 0);
+ }
+
+ function getU(sampleA, sampleB) {
+ return reduce(sampleA, function(total, xA) {
+ return total + getScore(xA, sampleB);
+ }, 0);
+ }
+
+ function getZ(u) {
+ return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12);
+ }
+
+ // exit early if comparing the same benchmark
+ if (me == other) {
+ return 0;
+ }
+ // reject the null hyphothesis the two samples come from the
+ // same population (i.e. have the same median) if...
+ if (size1 + size2 > 30) {
+ // ...the z-stat is greater than 1.96 or less than -1.96
+ // http://www.statisticslectures.com/topics/mannwhitneyu/
+ zStat = getZ(u);
+ return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0;
+ }
+ // ...the U value is less than or equal the critical U value
+ // http://www.geoib.com/mann-whitney-u-test.html
+ critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3];
+ return u <= critical ? (u == u1 ? 1 : -1) : 0;
+ }
+
+ /**
+ * Reset properties and abort if running.
+ *
+ * @memberOf Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function reset() {
+ var data,
+ event,
+ me = this,
+ index = 0,
+ changes = { 'length': 0 },
+ queue = { 'length': 0 };
+
+ if (me.running && !calledBy.abort) {
+ // no worries, `reset()` is called within `abort()`
+ calledBy.reset = true;
+ me.abort();
+ delete calledBy.reset;
+ }
+ else {
+ // a non-recursive solution to check if properties have changed
+ // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4
+ data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) };
+ do {
+ forOwn(data.source, function(value, key) {
+ var changed,
+ destination = data.destination,
+ currValue = destination[key];
+
+ if (value && typeof value == 'object') {
+ if (isClassOf(value, 'Array')) {
+ // check if an array value has changed to a non-array value
+ if (!isClassOf(currValue, 'Array')) {
+ changed = currValue = [];
+ }
+ // or has changed its length
+ if (currValue.length != value.length) {
+ changed = currValue = currValue.slice(0, value.length);
+ currValue.length = value.length;
+ }
+ }
+ // check if an object has changed to a non-object value
+ else if (!currValue || typeof currValue != 'object') {
+ changed = currValue = {};
+ }
+ // register a changed object
+ if (changed) {
+ changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue };
+ }
+ queue[queue.length++] = { 'destination': currValue, 'source': value };
+ }
+ // register a changed primitive
+ else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) {
+ changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value };
+ }
+ });
+ }
+ while ((data = queue[index++]));
+
+ // if changed emit the `reset` event and if it isn't cancelled reset the benchmark
+ if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) {
+ forEach(changes, function(data) {
+ data.destination[data.key] = data.value;
+ });
+ }
+ }
+ return me;
+ }
+
+ /**
+ * Displays relevant benchmark information when coerced to a string.
+ *
+ * @name toString
+ * @memberOf Benchmark
+ * @returns {String} A string representation of the benchmark instance.
+ */
+ function toStringBench() {
+ var me = this,
+ error = me.error,
+ hz = me.hz,
+ id = me.id,
+ stats = me.stats,
+ size = stats.sample.length,
+ pm = support.java ? '+/-' : '\xb1',
+ result = me.name || (isNaN(id) ? id : '');
+
+ if (error) {
+ result += ': ' + join(error);
+ } else {
+ result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
+ stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Clocks the time taken to execute a test per cycle (secs).
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @returns {Number} The time taken.
+ */
+ function clock() {
+ var applet,
+ options = Benchmark.options,
+ template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid },
+ timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }];
+
+ // lazy define for hi-res timers
+ clock = function(clone) {
+ var deferred;
+ if (clone instanceof Deferred) {
+ deferred = clone;
+ clone = deferred.benchmark;
+ }
+
+ var bench = clone._original,
+ fn = bench.fn,
+ fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '',
+ stringable = isStringable(fn);
+
+ var source = {
+ 'setup': getSource(bench.setup, preprocess('m$.setup()')),
+ 'fn': getSource(fn, preprocess('f$(' + fnArg + ')')),
+ 'fnArg': fnArg,
+ 'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
+ };
+
+ var compiled = bench.compiled,
+ count = bench.count = clone.count,
+ decompilable = support.decompilation || stringable,
+ id = bench.id,
+ isEmpty = !(source.fn || stringable),
+ name = bench.name || (typeof id == 'number' ? '' : id),
+ ns = timer.ns,
+ result = 0;
+
+ // init `minTime` if needed
+ clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime);
+
+ // repair nanosecond timer
+ // (some Chrome builds erase the `ns` variable after millions of executions)
+ if (applet) {
+ try {
+ ns.nanoTime();
+ } catch(e) {
+ // use non-element to avoid issues with libs that augment them
+ ns = timer.ns = new applet.Packages.nano;
+ }
+ }
+
+ if (!compiled) {
+ // compile in setup/teardown functions and the test loop
+ compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
+ preprocess(deferred
+ ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
+ // when `deferred.cycles` is `0` then...
+ 'if(!d$.cycles){' +
+ // set `deferred.fn`
+ 'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
+ // set `deferred.teardown`
+ 'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
+ // execute the benchmark's `setup`
+ 'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
+ // start timer
+ 't$.start(d$);' +
+ // execute `deferred.fn` and return a dummy object
+ '}d$.fn();return{}'
+
+ : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
+ 'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
+ source
+ ));
+
+ try {
+ if (isEmpty) {
+ // Firefox may remove dead code from Function#toString results
+ // http://bugzil.la/536085
+ throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
+ }
+ else if (!deferred) {
+ // pretest to determine if compiled code is exits early, usually by a
+ // rogue `return` statement, by checking for a return object with the uid
+ bench.count = 1;
+ compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
+ bench.count = count;
+ }
+ } catch(e) {
+ compiled = null;
+ clone.error = e || new Error(String(e));
+ bench.count = count;
+ }
+ // fallback when a test exits early or errors during pretest
+ if (decompilable && !compiled && !deferred && !isEmpty) {
+ compiled = createFunction(preprocess('t$'), interpolate(
+ preprocess(
+ (clone.error && !stringable
+ ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
+ : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
+ ) +
+ ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
+ 'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
+ ),
+ source
+ ));
+
+ try {
+ // pretest one more time to check for errors
+ bench.count = 1;
+ compiled.call(bench, timer);
+ bench.compiled = compiled;
+ bench.count = count;
+ delete clone.error;
+ }
+ catch(e) {
+ bench.count = count;
+ if (clone.error) {
+ compiled = null;
+ } else {
+ bench.compiled = compiled;
+ clone.error = e || new Error(String(e));
+ }
+ }
+ }
+ }
+ // assign `compiled` to `clone` before calling in case a deferred benchmark
+ // immediately calls `deferred.resolve()`
+ clone.compiled = compiled;
+ // if no errors run the full test loop
+ if (!clone.error) {
+ result = compiled.call(deferred || bench, timer).elapsed;
+ }
+ return result;
+ };
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Gets the current timer's minimum resolution (secs).
+ */
+ function getRes(unit) {
+ var measured,
+ begin,
+ count = 30,
+ divisor = 1e3,
+ ns = timer.ns,
+ sample = [];
+
+ // get average smallest measurable time
+ while (count--) {
+ if (unit == 'us') {
+ divisor = 1e6;
+ if (ns.stop) {
+ ns.start();
+ while (!(measured = ns.microseconds())) { }
+ } else if (perfName) {
+ divisor = 1e3;
+ measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns);
+ } else {
+ begin = ns();
+ while (!(measured = ns() - begin)) { }
+ }
+ }
+ else if (unit == 'ns') {
+ divisor = 1e9;
+ begin = ns.nanoTime();
+ while (!(measured = ns.nanoTime() - begin)) { }
+ }
+ else {
+ begin = new ns;
+ while (!(measured = new ns - begin)) { }
+ }
+ // check for broken timers (nanoTime may have issues)
+ // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/
+ if (measured > 0) {
+ sample.push(measured);
+ } else {
+ sample.push(Infinity);
+ break;
+ }
+ }
+ // convert to seconds
+ return getMean(sample) / divisor;
+ }
+
+ /**
+ * Replaces all occurrences of `$` with a unique number and
+ * template tokens with content.
+ */
+ function preprocess(code) {
+ return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid));
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // detect nanosecond support from a Java applet
+ each(doc && doc.applets || [], function(element) {
+ return !(timer.ns = applet = 'nanoTime' in element && element);
+ });
+
+ // check type in case Safari returns an object instead of a number
+ try {
+ if (typeof timer.ns.nanoTime() == 'number') {
+ timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+ }
+ } catch(e) { }
+
+ // detect Chrome's microsecond timer:
+ // enable benchmarking via the --enable-benchmarking command
+ // line switch in at least Chrome 7 to use chrome.Interval
+ try {
+ if ((timer.ns = new (window.chrome || window.chromium).Interval)) {
+ timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+ }
+ } catch(e) { }
+
+ // detect `performance.now` microsecond resolution timer
+ if ((timer.ns = perfName && perfObject)) {
+ timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+ }
+
+ // detect Node's microtime module:
+ // npm install microtime
+ if ((timer.ns = (req('microtime') || { 'now': 0 }).now)) {
+ timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+ }
+
+ // pick timer with highest resolution
+ timer = reduce(timers, function(timer, other) {
+ return other.res < timer.res ? other : timer;
+ });
+
+ // remove unused applet
+ if (timer.unit != 'ns' && applet) {
+ applet = destroyElement(applet);
+ }
+ // error if there are no working timers
+ if (timer.res == Infinity) {
+ throw new Error('Benchmark.js was unable to find a working timer.');
+ }
+ // use API of chosen timer
+ if (timer.unit == 'ns') {
+ extend(template, {
+ 'begin': 's$=n$.nanoTime()',
+ 'end': 'r$=(n$.nanoTime()-s$)/1e9'
+ });
+ }
+ else if (timer.unit == 'us') {
+ if (timer.ns.stop) {
+ extend(template, {
+ 'begin': 's$=n$.start()',
+ 'end': 'r$=n$.microseconds()/1e6'
+ });
+ } else if (perfName) {
+ extend(template, {
+ 'begin': 's$=n$.' + perfName + '()',
+ 'end': 'r$=(n$.' + perfName + '()-s$)/1e3'
+ });
+ } else {
+ extend(template, {
+ 'begin': 's$=n$()',
+ 'end': 'r$=(n$()-s$)/1e6'
+ });
+ }
+ }
+
+ // define `timer` methods
+ timer.start = createFunction(preprocess('o$'),
+ preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$'));
+
+ timer.stop = createFunction(preprocess('o$'),
+ preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$'));
+
+ // resolve time span required to achieve a percent uncertainty of at most 1%
+ // http://spiff.rit.edu/classes/phys273/uncert/uncert.html
+ options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05));
+ return clock.apply(null, arguments);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Computes stats on benchmark results.
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @param {Object} options The options object.
+ */
+ function compute(bench, options) {
+ options || (options = {});
+
+ var async = options.async,
+ elapsed = 0,
+ initCount = bench.initCount,
+ minSamples = bench.minSamples,
+ queue = [],
+ sample = bench.stats.sample;
+
+ /**
+ * Adds a number of clones to the queue.
+ */
+ function enqueue(count) {
+ while (count--) {
+ queue.push(bench.clone({
+ '_original': bench,
+ 'events': {
+ 'abort': [update],
+ 'cycle': [update],
+ 'error': [update],
+ 'start': [update]
+ }
+ }));
+ }
+ }
+
+ /**
+ * Updates the clone/original benchmarks to keep their data in sync.
+ */
+ function update(event) {
+ var clone = this,
+ type = event.type;
+
+ if (bench.running) {
+ if (type == 'start') {
+ // Note: `clone.minTime` prop is inited in `clock()`
+ clone.count = bench.initCount;
+ }
+ else {
+ if (type == 'error') {
+ bench.error = clone.error;
+ }
+ if (type == 'abort') {
+ bench.abort();
+ bench.emit('cycle');
+ } else {
+ event.currentTarget = event.target = bench;
+ bench.emit(event);
+ }
+ }
+ } else if (bench.aborted) {
+ // clear abort listeners to avoid triggering bench's abort/cycle again
+ clone.events.abort.length = 0;
+ clone.abort();
+ }
+ }
+
+ /**
+ * Determines if more clones should be queued or if cycling should stop.
+ */
+ function evaluate(event) {
+ var critical,
+ df,
+ mean,
+ moe,
+ rme,
+ sd,
+ sem,
+ variance,
+ clone = event.target,
+ done = bench.aborted,
+ now = +new Date,
+ size = sample.push(clone.times.period),
+ maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime,
+ times = bench.times,
+ varOf = function(sum, x) { return sum + pow(x - mean, 2); };
+
+ // exit early for aborted or unclockable tests
+ if (done || clone.hz == Infinity) {
+ maxedOut = !(size = sample.length = queue.length = 0);
+ }
+
+ if (!done) {
+ // sample mean (estimate of the population mean)
+ mean = getMean(sample);
+ // sample variance (estimate of the population variance)
+ variance = reduce(sample, varOf, 0) / (size - 1) || 0;
+ // sample standard deviation (estimate of the population standard deviation)
+ sd = sqrt(variance);
+ // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
+ sem = sd / sqrt(size);
+ // degrees of freedom
+ df = size - 1;
+ // critical value
+ critical = tTable[Math.round(df) || 1] || tTable.infinity;
+ // margin of error
+ moe = sem * critical;
+ // relative margin of error
+ rme = (moe / mean) * 100 || 0;
+
+ extend(bench.stats, {
+ 'deviation': sd,
+ 'mean': mean,
+ 'moe': moe,
+ 'rme': rme,
+ 'sem': sem,
+ 'variance': variance
+ });
+
+ // Abort the cycle loop when the minimum sample size has been collected
+ // and the elapsed time exceeds the maximum time allowed per benchmark.
+ // We don't count cycle delays toward the max time because delays may be
+ // increased by browsers that clamp timeouts for inactive tabs.
+ // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs
+ if (maxedOut) {
+ // reset the `initCount` in case the benchmark is rerun
+ bench.initCount = initCount;
+ bench.running = false;
+ done = true;
+ times.elapsed = (now - times.timeStamp) / 1e3;
+ }
+ if (bench.hz != Infinity) {
+ bench.hz = 1 / mean;
+ times.cycle = mean * bench.count;
+ times.period = mean;
+ }
+ }
+ // if time permits, increase sample size to reduce the margin of error
+ if (queue.length < 2 && !maxedOut) {
+ enqueue(1);
+ }
+ // abort the invoke cycle when done
+ event.aborted = done;
+ }
+
+ // init queue and begin
+ enqueue(minSamples);
+ invoke(queue, {
+ 'name': 'run',
+ 'args': { 'async': async },
+ 'queued': true,
+ 'onCycle': evaluate,
+ 'onComplete': function() { bench.emit('complete'); }
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Cycles a benchmark until a run `count` can be established.
+ *
+ * @private
+ * @param {Object} clone The cloned benchmark instance.
+ * @param {Object} options The options object.
+ */
+ function cycle(clone, options) {
+ options || (options = {});
+
+ var deferred;
+ if (clone instanceof Deferred) {
+ deferred = clone;
+ clone = clone.benchmark;
+ }
+
+ var clocked,
+ cycles,
+ divisor,
+ event,
+ minTime,
+ period,
+ async = options.async,
+ bench = clone._original,
+ count = clone.count,
+ times = clone.times;
+
+ // continue, if not aborted between cycles
+ if (clone.running) {
+ // `minTime` is set to `Benchmark.options.minTime` in `clock()`
+ cycles = ++clone.cycles;
+ clocked = deferred ? deferred.elapsed : clock(clone);
+ minTime = clone.minTime;
+
+ if (cycles > bench.cycles) {
+ bench.cycles = cycles;
+ }
+ if (clone.error) {
+ event = Event('error');
+ event.message = clone.error;
+ clone.emit(event);
+ if (!event.cancelled) {
+ clone.abort();
+ }
+ }
+ }
+
+ // continue, if not errored
+ if (clone.running) {
+ // time taken to complete last test cycle
+ bench.times.cycle = times.cycle = clocked;
+ // seconds per operation
+ period = bench.times.period = times.period = clocked / count;
+ // ops per second
+ bench.hz = clone.hz = 1 / period;
+ // avoid working our way up to this next time
+ bench.initCount = clone.initCount = count;
+ // do we need to do another cycle?
+ clone.running = clocked < minTime;
+
+ if (clone.running) {
+ // tests may clock at `0` when `initCount` is a small number,
+ // to avoid that we set its count to something a bit higher
+ if (!clocked && (divisor = divisors[clone.cycles]) != null) {
+ count = floor(4e6 / divisor);
+ }
+ // calculate how many more iterations it will take to achive the `minTime`
+ if (count <= clone.count) {
+ count += Math.ceil((minTime - clocked) / period);
+ }
+ clone.running = count != Infinity;
+ }
+ }
+ // should we exit early?
+ event = Event('cycle');
+ clone.emit(event);
+ if (event.aborted) {
+ clone.abort();
+ }
+ // figure out what to do next
+ if (clone.running) {
+ // start a new cycle
+ clone.count = count;
+ if (deferred) {
+ clone.compiled.call(deferred, timer);
+ } else if (async) {
+ delay(clone, function() { cycle(clone, options); });
+ } else {
+ cycle(clone);
+ }
+ }
+ else {
+ // fix TraceMonkey bug associated with clock fallbacks
+ // http://bugzil.la/509069
+ if (support.browser) {
+ runScript(uid + '=1;delete ' + uid);
+ }
+ // done
+ clone.emit('complete');
+ }
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Runs the benchmark.
+ *
+ * @memberOf Benchmark
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * bench.run();
+ *
+ * // or with options
+ * bench.run({ 'async': true });
+ */
+ function run(options) {
+ var me = this,
+ event = Event('start');
+
+ // set `running` to `false` so `reset()` won't call `abort()`
+ me.running = false;
+ me.reset();
+ me.running = true;
+
+ me.count = me.initCount;
+ me.times.timeStamp = +new Date;
+ me.emit(event);
+
+ if (!event.cancelled) {
+ options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout };
+
+ // for clones created within `compute()`
+ if (me._original) {
+ if (me.defer) {
+ Deferred(me);
+ } else {
+ cycle(me, options);
+ }
+ }
+ // for original benchmarks
+ else {
+ compute(me, options);
+ }
+ }
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // Firefox 1 erroneously defines variable and argument names of functions on
+ // the function itself as non-configurable properties with `undefined` values.
+ // The bugginess continues as the `Benchmark` constructor has an argument
+ // named `options` and Firefox 1 will not assign a value to `Benchmark.options`,
+ // making it non-writable in the process, unless it is the first property
+ // assigned by for-in loop of `extend()`.
+ extend(Benchmark, {
+
+ /**
+ * The default options copied by benchmark instances.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'options': {
+
+ /**
+ * A flag to indicate that benchmark cycles will execute asynchronously
+ * by default.
+ *
+ * @memberOf Benchmark.options
+ * @type Boolean
+ */
+ 'async': false,
+
+ /**
+ * A flag to indicate that the benchmark clock is deferred.
+ *
+ * @memberOf Benchmark.options
+ * @type Boolean
+ */
+ 'defer': false,
+
+ /**
+ * The delay between test cycles (secs).
+ * @memberOf Benchmark.options
+ * @type Number
+ */
+ 'delay': 0.005,
+
+ /**
+ * Displayed by Benchmark#toString when a `name` is not available
+ * (auto-generated if absent).
+ *
+ * @memberOf Benchmark.options
+ * @type String
+ */
+ 'id': undefined,
+
+ /**
+ * The default number of times to execute a test on a benchmark's first cycle.
+ *
+ * @memberOf Benchmark.options
+ * @type Number
+ */
+ 'initCount': 1,
+
+ /**
+ * The maximum time a benchmark is allowed to run before finishing (secs).
+ * Note: Cycle delays aren't counted toward the maximum time.
+ *
+ * @memberOf Benchmark.options
+ * @type Number
+ */
+ 'maxTime': 5,
+
+ /**
+ * The minimum sample size required to perform statistical analysis.
+ *
+ * @memberOf Benchmark.options
+ * @type Number
+ */
+ 'minSamples': 5,
+
+ /**
+ * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
+ *
+ * @memberOf Benchmark.options
+ * @type Number
+ */
+ 'minTime': 0,
+
+ /**
+ * The name of the benchmark.
+ *
+ * @memberOf Benchmark.options
+ * @type String
+ */
+ 'name': undefined,
+
+ /**
+ * An event listener called when the benchmark is aborted.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onAbort': undefined,
+
+ /**
+ * An event listener called when the benchmark completes running.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onComplete': undefined,
+
+ /**
+ * An event listener called after each run cycle.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onCycle': undefined,
+
+ /**
+ * An event listener called when a test errors.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onError': undefined,
+
+ /**
+ * An event listener called when the benchmark is reset.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onReset': undefined,
+
+ /**
+ * An event listener called when the benchmark starts running.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onStart': undefined
+ },
+
+ /**
+ * Platform object with properties describing things like browser name,
+ * version, and operating system.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'platform': req('platform') || window.platform || {
+
+ /**
+ * The platform description.
+ *
+ * @memberOf Benchmark.platform
+ * @type String
+ */
+ 'description': window.navigator && navigator.userAgent || null,
+
+ /**
+ * The name of the browser layout engine.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'layout': null,
+
+ /**
+ * The name of the product hosting the browser.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'product': null,
+
+ /**
+ * The name of the browser/environment.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'name': null,
+
+ /**
+ * The name of the product's manufacturer.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'manufacturer': null,
+
+ /**
+ * The name of the operating system.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'os': null,
+
+ /**
+ * The alpha/beta release indicator.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'prerelease': null,
+
+ /**
+ * The browser/environment version.
+ *
+ * @memberOf Benchmark.platform
+ * @type String|Null
+ */
+ 'version': null,
+
+ /**
+ * Return platform description when the platform object is coerced to a string.
+ *
+ * @memberOf Benchmark.platform
+ * @type Function
+ * @returns {String} The platform description.
+ */
+ 'toString': function() {
+ return this.description || '';
+ }
+ },
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type String
+ */
+ 'version': '1.0.0-pre',
+
+ // an object of environment/feature detection flags
+ 'support': support,
+
+ // clone objects
+ 'deepClone': deepClone,
+
+ // iteration utility
+ 'each': each,
+
+ // augment objects
+ 'extend': extend,
+
+ // generic Array#filter
+ 'filter': filter,
+
+ // generic Array#forEach
+ 'forEach': forEach,
+
+ // generic own property iteration utility
+ 'forOwn': forOwn,
+
+ // converts a number to a comma-separated string
+ 'formatNumber': formatNumber,
+
+ // generic Object#hasOwnProperty
+ // (trigger hasKey's lazy define before assigning it to Benchmark)
+ 'hasKey': (hasKey(Benchmark, ''), hasKey),
+
+ // generic Array#indexOf
+ 'indexOf': indexOf,
+
+ // template utility
+ 'interpolate': interpolate,
+
+ // invokes a method on each item in an array
+ 'invoke': invoke,
+
+ // generic Array#join for arrays and objects
+ 'join': join,
+
+ // generic Array#map
+ 'map': map,
+
+ // retrieves a property value from each item in an array
+ 'pluck': pluck,
+
+ // generic Array#reduce
+ 'reduce': reduce
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Benchmark.prototype, {
+
+ /**
+ * The number of times a test was executed.
+ *
+ * @memberOf Benchmark
+ * @type Number
+ */
+ 'count': 0,
+
+ /**
+ * The number of cycles performed while benchmarking.
+ *
+ * @memberOf Benchmark
+ * @type Number
+ */
+ 'cycles': 0,
+
+ /**
+ * The number of executions per second.
+ *
+ * @memberOf Benchmark
+ * @type Number
+ */
+ 'hz': 0,
+
+ /**
+ * The compiled test function.
+ *
+ * @memberOf Benchmark
+ * @type Function|String
+ */
+ 'compiled': undefined,
+
+ /**
+ * The error object if the test failed.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'error': undefined,
+
+ /**
+ * The test to benchmark.
+ *
+ * @memberOf Benchmark
+ * @type Function|String
+ */
+ 'fn': undefined,
+
+ /**
+ * A flag to indicate if the benchmark is aborted.
+ *
+ * @memberOf Benchmark
+ * @type Boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the benchmark is running.
+ *
+ * @memberOf Benchmark
+ * @type Boolean
+ */
+ 'running': false,
+
+ /**
+ * Compiled into the test and executed immediately **before** the test loop.
+ *
+ * @memberOf Benchmark
+ * @type Function|String
+ * @example
+ *
+ * // basic usage
+ * var bench = Benchmark({
+ * 'setup': function() {
+ * var c = this.count,
+ * element = document.getElementById('container');
+ * while (c--) {
+ * element.appendChild(document.createElement('div'));
+ * }
+ * },
+ * 'fn': function() {
+ * element.removeChild(element.lastChild);
+ * }
+ * });
+ *
+ * // compiles to something like:
+ * var c = this.count,
+ * element = document.getElementById('container');
+ * while (c--) {
+ * element.appendChild(document.createElement('div'));
+ * }
+ * var start = new Date;
+ * while (count--) {
+ * element.removeChild(element.lastChild);
+ * }
+ * var end = new Date - start;
+ *
+ * // or using strings
+ * var bench = Benchmark({
+ * 'setup': '\
+ * var a = 0;\n\
+ * (function() {\n\
+ * (function() {\n\
+ * (function() {',
+ * 'fn': 'a += 1;',
+ * 'teardown': '\
+ * }())\n\
+ * }())\n\
+ * }())'
+ * });
+ *
+ * // compiles to something like:
+ * var a = 0;
+ * (function() {
+ * (function() {
+ * (function() {
+ * var start = new Date;
+ * while (count--) {
+ * a += 1;
+ * }
+ * var end = new Date - start;
+ * }())
+ * }())
+ * }())
+ */
+ 'setup': noop,
+
+ /**
+ * Compiled into the test and executed immediately **after** the test loop.
+ *
+ * @memberOf Benchmark
+ * @type Function|String
+ */
+ 'teardown': noop,
+
+ /**
+ * An object of stats including mean, margin or error, and standard deviation.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'stats': {
+
+ /**
+ * The margin of error.
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'moe': 0,
+
+ /**
+ * The relative margin of error (expressed as a percentage of the mean).
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'rme': 0,
+
+ /**
+ * The standard error of the mean.
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'sem': 0,
+
+ /**
+ * The sample standard deviation.
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'deviation': 0,
+
+ /**
+ * The sample arithmetic mean.
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'mean': 0,
+
+ /**
+ * The array of sampled periods.
+ *
+ * @memberOf Benchmark#stats
+ * @type Array
+ */
+ 'sample': [],
+
+ /**
+ * The sample variance.
+ *
+ * @memberOf Benchmark#stats
+ * @type Number
+ */
+ 'variance': 0
+ },
+
+ /**
+ * An object of timing data including cycle, elapsed, period, start, and stop.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'times': {
+
+ /**
+ * The time taken to complete the last cycle (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type Number
+ */
+ 'cycle': 0,
+
+ /**
+ * The time taken to complete the benchmark (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type Number
+ */
+ 'elapsed': 0,
+
+ /**
+ * The time taken to execute the test once (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type Number
+ */
+ 'period': 0,
+
+ /**
+ * A timestamp of when the benchmark started (ms).
+ *
+ * @memberOf Benchmark#times
+ * @type Number
+ */
+ 'timeStamp': 0
+ },
+
+ // aborts benchmark (does not record times)
+ 'abort': abort,
+
+ // creates a new benchmark using the same test and options
+ 'clone': clone,
+
+ // compares benchmark's hertz with another
+ 'compare': compare,
+
+ // executes listeners
+ 'emit': emit,
+
+ // get listeners
+ 'listeners': listeners,
+
+ // unregister listeners
+ 'off': off,
+
+ // register listeners
+ 'on': on,
+
+ // reset benchmark properties
+ 'reset': reset,
+
+ // runs the benchmark
+ 'run': run,
+
+ // pretty print benchmark info
+ 'toString': toStringBench
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Deferred.prototype, {
+
+ /**
+ * The deferred benchmark instance.
+ *
+ * @memberOf Benchmark.Deferred
+ * @type Object
+ */
+ 'benchmark': null,
+
+ /**
+ * The number of deferred cycles performed while benchmarking.
+ *
+ * @memberOf Benchmark.Deferred
+ * @type Number
+ */
+ 'cycles': 0,
+
+ /**
+ * The time taken to complete the deferred benchmark (secs).
+ *
+ * @memberOf Benchmark.Deferred
+ * @type Number
+ */
+ 'elapsed': 0,
+
+ /**
+ * A timestamp of when the deferred benchmark started (ms).
+ *
+ * @memberOf Benchmark.Deferred
+ * @type Number
+ */
+ 'timeStamp': 0,
+
+ // cycles/completes the deferred benchmark
+ 'resolve': resolve
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Event.prototype, {
+
+ /**
+ * A flag to indicate if the emitters listener iteration is aborted.
+ *
+ * @memberOf Benchmark.Event
+ * @type Boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the default action is cancelled.
+ *
+ * @memberOf Benchmark.Event
+ * @type Boolean
+ */
+ 'cancelled': false,
+
+ /**
+ * The object whose listeners are currently being processed.
+ *
+ * @memberOf Benchmark.Event
+ * @type Object
+ */
+ 'currentTarget': undefined,
+
+ /**
+ * The return value of the last executed listener.
+ *
+ * @memberOf Benchmark.Event
+ * @type Mixed
+ */
+ 'result': undefined,
+
+ /**
+ * The object to which the event was originally emitted.
+ *
+ * @memberOf Benchmark.Event
+ * @type Object
+ */
+ 'target': undefined,
+
+ /**
+ * A timestamp of when the event was created (ms).
+ *
+ * @memberOf Benchmark.Event
+ * @type Number
+ */
+ 'timeStamp': 0,
+
+ /**
+ * The event type.
+ *
+ * @memberOf Benchmark.Event
+ * @type String
+ */
+ 'type': ''
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The default options copied by suite instances.
+ *
+ * @static
+ * @memberOf Benchmark.Suite
+ * @type Object
+ */
+ Suite.options = {
+
+ /**
+ * The name of the suite.
+ *
+ * @memberOf Benchmark.Suite.options
+ * @type String
+ */
+ 'name': undefined
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Suite.prototype, {
+
+ /**
+ * The number of benchmarks in the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @type Number
+ */
+ 'length': 0,
+
+ /**
+ * A flag to indicate if the suite is aborted.
+ *
+ * @memberOf Benchmark.Suite
+ * @type Boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the suite is running.
+ *
+ * @memberOf Benchmark.Suite
+ * @type Boolean
+ */
+ 'running': false,
+
+ /**
+ * An `Array#forEach` like method.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @returns {Object} The suite iterated over.
+ */
+ 'forEach': methodize(forEach),
+
+ /**
+ * An `Array#indexOf` like method.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Mixed} value The value to search for.
+ * @returns {Number} The index of the matched value or `-1`.
+ */
+ 'indexOf': methodize(indexOf),
+
+ /**
+ * Invokes a method on all benchmarks in the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {String|Object} name The name of the method to invoke OR options object.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} A new array of values returned from each method invoked.
+ */
+ 'invoke': methodize(invoke),
+
+ /**
+ * Converts the suite of benchmarks to a string.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {String} [separator=','] A string to separate each element of the array.
+ * @returns {String} The string.
+ */
+ 'join': [].join,
+
+ /**
+ * An `Array#map` like method.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @returns {Array} A new array of values returned by the callback.
+ */
+ 'map': methodize(map),
+
+ /**
+ * Retrieves the value of a specified property from all benchmarks in the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {String} property The property to pluck.
+ * @returns {Array} A new array of property values.
+ */
+ 'pluck': methodize(pluck),
+
+ /**
+ * Removes the last benchmark from the suite and returns it.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Mixed} The removed benchmark.
+ */
+ 'pop': [].pop,
+
+ /**
+ * Appends benchmarks to the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @returns {Number} The suite's new length.
+ */
+ 'push': [].push,
+
+ /**
+ * Sorts the benchmarks of the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Function} [compareFn=null] A function that defines the sort order.
+ * @returns {Object} The sorted suite.
+ */
+ 'sort': [].sort,
+
+ /**
+ * An `Array#reduce` like method.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} accumulator Initial value of the accumulator.
+ * @returns {Mixed} The accumulator.
+ */
+ 'reduce': methodize(reduce),
+
+ // aborts all benchmarks in the suite
+ 'abort': abortSuite,
+
+ // adds a benchmark to the suite
+ 'add': add,
+
+ // creates a new suite with cloned benchmarks
+ 'clone': cloneSuite,
+
+ // executes listeners of a specified type
+ 'emit': emit,
+
+ // creates a new suite of filtered benchmarks
+ 'filter': filterSuite,
+
+ // get listeners
+ 'listeners': listeners,
+
+ // unregister listeners
+ 'off': off,
+
+ // register listeners
+ 'on': on,
+
+ // resets all benchmarks in the suite
+ 'reset': resetSuite,
+
+ // runs all benchmarks in the suite
+ 'run': runSuite,
+
+ // array methods
+ 'concat': concat,
+
+ 'reverse': reverse,
+
+ 'shift': shift,
+
+ 'slice': slice,
+
+ 'splice': splice,
+
+ 'unshift': unshift
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Deferred, Event and Suite
+ extend(Benchmark, {
+ 'Deferred': Deferred,
+ 'Event': Event,
+ 'Suite': Suite
+ });
+
+ // expose Benchmark
+ // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // define as an anonymous module so, through path mapping, it can be aliased
+ define(function() {
+ return Benchmark;
+ });
+ }
+ // check for `exports` after `define` in case a build optimizer adds an `exports` object
+ else if (freeExports) {
+ // in Node.js or RingoJS v0.8.0+
+ if (typeof module == 'object' && module && module.exports == freeExports) {
+ (module.exports = Benchmark).Benchmark = Benchmark;
+ }
+ // in Narwhal or RingoJS v0.7.0-
+ else {
+ freeExports.Benchmark = Benchmark;
+ }
+ }
+ // in a browser or Rhino
+ else {
+ // use square bracket notation so Closure Compiler won't munge `Benchmark`
+ // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
+ window['Benchmark'] = Benchmark;
+ }
+
+ // trigger clock's lazy define early to avoid a security error
+ if (support.air) {
+ clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } });
+ }
+}(this));
diff --git a/vendor/benchmark.js/nano.jar b/vendor/benchmark.js/nano.jar
new file mode 100644
index 000000000..b5778403b
Binary files /dev/null and b/vendor/benchmark.js/nano.jar differ
diff --git a/vendor/docdown/LICENSE.txt b/vendor/docdown/LICENSE.txt
new file mode 100644
index 000000000..dadad22fa
--- /dev/null
+++ b/vendor/docdown/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright 2011-2012 John-David Dalton
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/docdown/README.md b/vendor/docdown/README.md
new file mode 100644
index 000000000..5b0b38c4b
--- /dev/null
+++ b/vendor/docdown/README.md
@@ -0,0 +1,44 @@
+# Docdown v1.0.0-pre
+
+A simple JSDoc to Markdown documentation generator.
+
+## Documentation
+
+The documentation for Docdown can be viewed here: [/doc/README.md](https://github.com/jdalton/docdown/blob/master/doc/README.md#readme)
+
+For a list of upcoming features, check out our [roadmap](https://github.com/jdalton/docdown/wiki/Roadmap).
+
+## Installation and usage
+
+Usage example:
+
+~~~ php
+require("docdown.php");
+
+// generate Markdown
+$markdown = docdown(array(
+ "path" => $filepath,
+ "url" => "https://github.com/username/project/blob/master/my.js"
+));
+~~~
+
+## Cloning this repo
+
+To clone this repository just use:
+
+~~~ bash
+git clone https://github.com/docdown/docdown.git
+cd docdown
+~~~
+
+Feel free to fork and send pull requests if you see improvements!
+
+## Author
+
+* [John-David Dalton](http://allyoucanleet.com/)
+ [](https://twitter.com/jdalton "Follow @jdalton on Twitter")
+
+## Contributors
+
+* [Mathias Bynens](http://mathiasbynens.be/)
+ [](https://twitter.com/mathias "Follow @mathias on Twitter")
diff --git a/vendor/docdown/docdown.php b/vendor/docdown/docdown.php
new file mode 100644
index 000000000..6a960d49e
--- /dev/null
+++ b/vendor/docdown/docdown.php
@@ -0,0 +1,38 @@
+
+ * Available under MIT license
+ */
+require(dirname(__FILE__) . '/src/DocDown/Generator.php');
+
+/**
+ * Generates Markdown from JSDoc entries in a given file.
+ *
+ * @param {Array} [$options=array()] The options array.
+ * @returns {String} The generated Markdown.
+ * @example
+ *
+ * // specify a file path
+ * $markdown = docdown(array(
+ * // path to js file
+ * 'path' => $filepath,
+ * // url used to reference line numbers in code
+ * 'url' => 'https://github.com/username/project/blob/master/my.js'
+ * ));
+ *
+ * // or pass raw js
+ * $markdown = docdown(array(
+ * // raw JavaScript source
+ * 'source' => $rawJS,
+ * // documentation title
+ * 'title' => 'My API Documentation',
+ * // url used to reference line numbers in code
+ * 'url' => 'https://github.com/username/project/blob/master/my.js'
+ * ));
+ */
+function docdown( $options = array() ) {
+ $gen = new Generator($options);
+ return $gen->generate();
+}
+?>
\ No newline at end of file
diff --git a/vendor/docdown/src/DocDown/Entry.php b/vendor/docdown/src/DocDown/Entry.php
new file mode 100644
index 000000000..aacd9b860
--- /dev/null
+++ b/vendor/docdown/src/DocDown/Entry.php
@@ -0,0 +1,304 @@
+entry = $entry;
+ $this->lang = $lang;
+ $this->source = str_replace(PHP_EOL, "\n", $source);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Extracts the documentation entries from source code.
+ *
+ * @static
+ * @memberOf Entry
+ * @param {String} $source The source code.
+ * @returns {Array} The array of entries.
+ */
+ public static function getEntries( $source ) {
+ preg_match_all('#/\*\*(?![-!])[\s\S]*?\*/\s*[^\n]+#', $source, $result);
+ return array_pop($result);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if the entry is a function reference.
+ *
+ * @private
+ * @memberOf Entry
+ * @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
+ */
+ private function isFunction() {
+ return !!(
+ $this->isCtor() ||
+ count($this->getParams()) ||
+ count($this->getReturns()) ||
+ preg_match('/\*\s*@function\b/', $this->entry)
+ );
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Extracts the function call from the entry.
+ *
+ * @memberOf Entry
+ * @returns {String} The function call.
+ */
+ public function getCall() {
+ preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
+ if ($result = array_pop($result)) {
+ $result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
+ }
+ // resolve name
+ // avoid $this->getName() because it calls $this->getCall()
+ preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $name);
+ if (count($name)) {
+ $name = trim($name[1]);
+ } else {
+ $name = $result;
+ }
+ // compile function call syntax
+ if ($this->isFunction()) {
+ // compose parts
+ $result = array($result);
+ $params = $this->getParams();
+ foreach ($params as $param) {
+ $result[] = $param[1];
+ }
+ // format
+ $result = $name .'('. implode(array_slice($result, 1), ', ') .')';
+ $result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
+ }
+ return $result ? $result : $name;
+ }
+
+ /**
+ * Extracts the entry description.
+ *
+ * @memberOf Entry
+ * @returns {String} The entry description.
+ */
+ public function getDesc() {
+ preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+ if (count($result)) {
+ $type = $this->getType();
+ $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+ $result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
+ }
+ return $result;
+ }
+
+ /**
+ * Extracts the entry `example` data.
+ *
+ * @memberOf Entry
+ * @returns {String} The entry `example` data.
+ */
+ public function getExample() {
+ preg_match('#\*\s*@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+ if (count($result)) {
+ $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
+ $result = '~~~ ' . $this->lang . "\n" . $result . "\n~~~";
+ }
+ return $result;
+ }
+
+ /**
+ * Resolves the line number of the entry.
+ *
+ * @memberOf Entry
+ * @returns {Number} The line number.
+ */
+ public function getLineNumber() {
+ preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
+ return count(array_pop($lines)) + 1;
+ }
+
+ /**
+ * Extracts the entry `member` data.
+ *
+ * @memberOf Entry
+ * @param {Number} $index The index of the array value to return.
+ * @returns {Array|String} The entry `member` data.
+ */
+ public function getMembers( $index = null ) {
+ preg_match('#\*\s*@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
+ if (count($result)) {
+ $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+ $result = preg_split('/,\s*/', $result);
+ }
+ return $index !== null ? @$result[$index] : $result;
+ }
+
+ /**
+ * Extracts the entry `name` data.
+ *
+ * @memberOf Entry
+ * @returns {String} The entry `name` data.
+ */
+ public function getName() {
+ preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $result);
+ if (count($result)) {
+ $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+ } else {
+ $result = array_shift(explode('(', $this->getCall()));
+ }
+ return $result;
+ }
+
+ /**
+ * Extracts the entry `param` data.
+ *
+ * @memberOf Entry
+ * @param {Number} $index The index of the array value to return.
+ * @returns {Array} The entry `param` data.
+ */
+ public function getParams( $index = null ) {
+ preg_match_all('#\*\s*@param\s+\{([^}]+)\}\s+(\[[^]]+\]|[$\w]+)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
+ if (count($result = array_filter(array_slice($result, 1)))) {
+ // repurpose array
+ foreach ($result as $param) {
+ foreach ($param as $key => $value) {
+ if (!is_array($result[0][$key])) {
+ $result[0][$key] = array();
+ }
+ $result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $value));
+ }
+ }
+ $result = $result[0];
+ }
+ return $index !== null ? @$result[$index] : $result;
+ }
+
+ /**
+ * Extracts the entry `returns` data.
+ *
+ * @memberOf Entry
+ * @returns {String} The entry `returns` data.
+ */
+ public function getReturns() {
+ preg_match('#\*\s*@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+ if (count($result)) {
+ $result = array_map('trim', array_slice($result, 1));
+ $result[0] = str_replace('|', ', ', $result[0]);
+ $result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
+ }
+ return $result;
+ }
+
+ /**
+ * Extracts the entry `type` data.
+ *
+ * @memberOf Entry
+ * @returns {String} The entry `type` data.
+ */
+ public function getType() {
+ preg_match('#\*\s*@type\s+([^\n]+)#', $this->entry, $result);
+ if (count($result)) {
+ $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+ } else {
+ $result = $this->isFunction() ? 'Function' : 'Unknown';
+ }
+ return $result;
+ }
+
+ /**
+ * Checks if an entry is a constructor.
+ *
+ * @memberOf Entry
+ * @returns {Boolean} Returns true if a constructor, else false.
+ */
+ public function isCtor() {
+ return !!preg_match('/\*\s*@constructor\b/', $this->entry);
+ }
+
+ /**
+ * Checks if an entry *is* assigned to a prototype.
+ *
+ * @memberOf Entry
+ * @returns {Boolean} Returns true if assigned to a prototype, else false.
+ */
+ public function isPlugin() {
+ return !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
+ }
+
+ /**
+ * Checks if an entry is private.
+ *
+ * @memberOf Entry
+ * @returns {Boolean} Returns true if private, else false.
+ */
+ public function isPrivate() {
+ return !!preg_match('/\*\s*@private\b/', $this->entry) || strrpos($this->entry, '@') === false;
+ }
+
+ /**
+ * Checks if an entry is *not* assigned to a prototype.
+ *
+ * @memberOf Entry
+ * @returns {Boolean} Returns true if not assigned to a prototype, else false.
+ */
+ public function isStatic() {
+ $public = !$this->isPrivate();
+ $result = $public && !!preg_match('/\*\s*@static\b/', $this->entry);
+
+ // set in cases where it isn't explicitly stated
+ if ($public && !$result) {
+ if ($parent = array_pop(preg_split('/[#.]/', $this->getMembers(0)))) {
+ foreach (Entry::getEntries($this->source) as $entry) {
+ $entry = new Entry($entry, $this->source);
+ if ($entry->getName() == $parent) {
+ $result = !$entry->isCtor();
+ break;
+ }
+ }
+ } else {
+ $result = true;
+ }
+ }
+ return $result;
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendor/docdown/src/DocDown/Generator.php b/vendor/docdown/src/DocDown/Generator.php
new file mode 100644
index 000000000..768273f5f
--- /dev/null
+++ b/vendor/docdown/src/DocDown/Generator.php
@@ -0,0 +1,391 @@
+options = $options;
+ $this->source = str_replace(PHP_EOL, "\n", $options['source']);
+ $this->entries = Entry::getEntries($this->source);
+
+ foreach ($this->entries as $index => $value) {
+ $this->entries[$index] = new Entry($value, $this->source, $options['lang']);
+ }
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Performs common string formatting operations.
+ *
+ * @private
+ * @static
+ * @memberOf Generator
+ * @param {String} $string The string to format.
+ * @returns {String} The formatted string.
+ */
+ private static function format($string) {
+ // mark numbers as code and italicize parentheses
+ return trim(preg_replace('/(^|\s)(\([^)]+\))/', '$1*$2*',
+ preg_replace('/ (-?\d+(?:.\d+)?)(?!\.[^\n])/', ' `$1`', $string)));
+ }
+
+ /**
+ * Modify a string by replacing named tokens with matching assoc. array values.
+ *
+ * @private
+ * @static
+ * @memberOf Generator
+ * @param {String} $string The string to modify.
+ * @param {Array|Object} $object The template object.
+ * @returns {String} The modified string.
+ */
+ private static function interpolate($string, $object) {
+ preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
+ $tokens = array_unique(array_pop($tokens));
+
+ foreach ($tokens as $token) {
+ $pattern = '/#\{' . $token . '\}/';
+ $replacement = '';
+
+ if (is_object($object)) {
+ preg_match('/\(([^)]+?)\)$/', $token, $args);
+ $args = preg_split('/,\s*/', array_pop($args));
+ $method = 'get' . ucfirst(str_replace('/\([^)]+?\)$/', '', $token));
+
+ if (method_exists($object, $method)) {
+ $replacement = (string) call_user_func_array(array($object, $method), $args);
+ } else if (isset($object->{$token})) {
+ $replacement = (string) $object->{$token};
+ }
+ } else if (isset($object[$token])) {
+ $replacement = (string) $object[$token];
+ }
+ $string = preg_replace($pattern, trim($replacement), $string);
+ }
+ return Generator::format($string);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Resolves the entry's hash used to navigate the documentation.
+ *
+ * @private
+ * @memberOf Generator
+ * @param {Number|Object} $entry The entry object.
+ * @param {String} $member The name of the member.
+ * @returns {String} The url hash.
+ */
+ private function getHash( $entry, $member = '' ) {
+ $entry = is_numeric($entry) ? $this->entries[$entry] : $entry;
+ $member = !$member ? $entry->getMembers(0) : $member;
+ $result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
+ $result = preg_replace('/\(\[|\[\]/', '', $result);
+ $result = preg_replace('/[ =\'"{}.()\]]/', '', $result);
+ $result = preg_replace('/[[#,]/', '-', $result);
+ return strtolower($result);
+ }
+
+ /**
+ * Resolves the entry's url for the specific line number.
+ *
+ * @private
+ * @memberOf Generator
+ * @param {Number|Object} $entry The entry object.
+ * @returns {String} The url.
+ */
+ private function getLineUrl( $entry ) {
+ $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
+ return $this->options['url'] . '#L' . $entry->getLineNumber();
+ }
+
+ /**
+ * Extracts the character used to separate the entry's name from its member.
+ *
+ * @private
+ * @memberOf Generator
+ * @param {Number|Object} $entry The entry object.
+ * @returns {String} The separator.
+ */
+ private function getSeparator( $entry ) {
+ $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
+ return $entry->isPlugin() ? '.prototype.' : '.';
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Generates Markdown from JSDoc entries.
+ *
+ * @memberOf Generator
+ * @returns {String} The rendered Markdown.
+ */
+ public function generate() {
+ $api = array();
+ $compiling = false;
+ $openTag = "\n\n";
+ $closeTag = "\n\n";
+ $result = array('# ' . $this->options['title']);
+
+ // initialize $api array
+ foreach ($this->entries as $entry) {
+
+ if (!$entry->isPrivate()) {
+ $name = $entry->getName();
+ $members = $entry->getMembers();
+ $members = count($members) ? $members : array('');
+
+ foreach ($members as $member) {
+ // create api category arrays
+ if (!isset($api[$member]) && $member) {
+ $api[$member] = new Entry('', '', $entry->lang);
+ $api[$member]->static = array();
+ $api[$member]->plugin = array();
+ }
+ // append entry to api category
+ if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
+ !preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) {
+ $member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
+ $entry->static = @$api[$member] ? $api[$member]->static : array();
+ $entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
+ $api[$member] = $entry;
+ }
+ else if ($entry->isStatic()) {
+ $api[$member]->static[] = $entry;
+ } else if (!$entry->isCtor()) {
+ $api[$member]->plugin[] = $entry;
+ }
+ }
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // custom sort for root level entries
+ // TODO: see how well it handles deeper namespace traversal
+ function sortCompare($a, $b) {
+ $score = array( 'a' => 0, 'b' => 0);
+ foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
+ // capitalized keys that represent constructor properties are last
+ if (preg_match('/[#.][A-Z]/', $value)) {
+ $score[$key] = 0;
+ }
+ // lowercase keys with prototype properties are next to last
+ else if (preg_match('/#[a-z]/', $value)) {
+ $score[$key] = 1;
+ }
+ // lowercase keys with static properties next to first
+ else if (preg_match('/\.[a-z]/', $value)) {
+ $score[$key] = 2;
+ }
+ // lowercase keys with no properties are first
+ else if (preg_match('/^[^#.]+$/', $value)) {
+ $score[$key] = 3;
+ }
+ }
+ $score = $score['b'] - $score['a'];
+ return $score ? $score : strcasecmp($a, $b);
+ }
+
+ uksort($api, 'sortCompare');
+
+ // sort static and plugin sub-entries
+ foreach ($api as $entry) {
+ foreach (array('static', 'plugin') as $kind) {
+ $sortBy = array( 'a' => array(), 'b' => array(), 'c' => array() );
+ foreach ($entry->{$kind} as $subentry) {
+ $name = $subentry->getName();
+ // functions w/o ALL-CAPs names are last
+ $sortBy['a'][] = $subentry->getType() == 'Function' && !preg_match('/^[A-Z_]+$/', $name);
+ // ALL-CAPs properties first
+ $sortBy['b'][] = preg_match('/^[A-Z_]+$/', $name);
+ // lowercase alphanumeric sort
+ $sortBy['c'][] = strtolower($name);
+ }
+ array_multisort($sortBy['a'], SORT_ASC, $sortBy['b'], SORT_DESC, $sortBy['c'], SORT_ASC, $entry->{$kind});
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // compile TOC
+ $result[] = $openTag;
+
+ foreach ($api as $key => $entry) {
+ $entry->hash = $this->getHash($entry);
+ $entry->href = $this->getLineUrl($entry);
+
+ $member = $entry->getMembers(0);
+ $member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
+
+ $entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
+
+ $compiling = $compiling ? ($result[] = $closeTag) : true;
+
+ // add root entry
+ array_push(
+ $result,
+ $openTag, '## ' . (count($result) == 2 ? '' : '') . '`' . $member . '`',
+ Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
+ );
+
+ // add static and plugin sub-entries
+ foreach (array('static', 'plugin') as $kind) {
+ if ($kind == 'plugin' && count($entry->plugin)) {
+ array_push(
+ $result,
+ $closeTag,
+ $openTag,
+ '## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
+ );
+ }
+ foreach ($entry->{$kind} as $subentry) {
+ $subentry->hash = $this->getHash($subentry);
+ $subentry->href = $this->getLineUrl($subentry);
+ $subentry->member = $member;
+ $subentry->separator = $this->getSeparator($subentry);
+ $result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
+ }
+ }
+ }
+
+ array_push($result, $closeTag, $closeTag);
+
+ /*------------------------------------------------------------------------*/
+
+ // compile content
+ $compiling = false;
+ $result[] = $openTag;
+
+ foreach ($api as $entry) {
+ // add root entry
+ $member = $entry->member . $entry->getName();
+ $compiling = $compiling ? ($result[] = $closeTag) : true;
+
+ array_push($result, $openTag, '## `' . $member . '`');
+
+ foreach (array($entry, 'static', 'plugin') as $kind) {
+ $subentries = is_string($kind) ? $entry->{$kind} : array($kind);
+
+ // title
+ if ($kind != 'static' && $entry->getType() != 'Object' &&
+ count($subentries) && $subentries[0] != $kind) {
+ if ($kind == 'plugin') {
+ $result[] = $closeTag;
+ }
+ array_push(
+ $result,
+ $openTag,
+ '## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
+ );
+ }
+
+ // body
+ foreach ($subentries as $subentry) {
+ // description
+ array_push(
+ $result,
+ $openTag,
+ Generator::interpolate("### `#{member}#{separator}#{call}`\n# [Ⓢ](#{href} \"View in source\") [Ⓣ][1]\n\n#{desc}", $subentry)
+ );
+
+ // @param
+ if (count($params = $subentry->getParams())) {
+ array_push($result, '', '#### Arguments');
+ foreach ($params as $index => $param) {
+ $result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
+ 'desc' => $param[2],
+ 'name' => $param[1],
+ 'num' => $index + 1,
+ 'type' => $param[0]
+ ));
+ }
+ }
+ // @returns
+ if (count($returns = $subentry->getReturns())) {
+ array_push(
+ $result, '',
+ '#### Returns',
+ Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
+ );
+ }
+ // @example
+ if ($example = $subentry->getExample()) {
+ array_push($result, '', '#### Example', $example);
+ }
+ array_push($result, "\n* * *", $closeTag);
+ }
+ }
+ }
+
+ // close tags add TOC link reference
+ array_push($result, $closeTag, $closeTag, '', ' [1]: #toc "Jump back to the TOC."');
+
+ // cleanup whitespace
+ return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendor/firebug-lite/changelog.txt b/vendor/firebug-lite/changelog.txt
new file mode 100644
index 000000000..8d35837e1
--- /dev/null
+++ b/vendor/firebug-lite/changelog.txt
@@ -0,0 +1,1049 @@
+###################################################################################################
+ 1.4.0 - 2011-09-23 - Revision: 11967
+###################################################################################################
+
+Overview:
+ Issue 4776: [Firebug lite] CSS Media Types
+ Issue 4777: [Firebug lite] Specificity of CSS Rules
+ Issue 3760: [Firebug lite] CommandLine throws syntax error if there's a comment in the expression
+ Issue 3326: [Firebug lite] CSS Rule Line Number
+ Issue 3262: [Firebug Lite] CSS specificity is not being calculated properly
+ Issue 4239: [Firebug Lite] Using ie7-js library crashes IE8
+ Issue 4472: [Firebug Lite] Ajax headers deleted on IE
+
+ Issue 4606: [Firebug Lite] Console is not working properly in recent versions of FF
+ Issue 4587: [Firebug Lite] Opera shows security warning when using the Inspect tool
+ Issue 4432: [Firebug Lite] HTML is mixed-up with functions
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+
+ - cssAnalyzer code refactored
+ - cssParser (powered by sergeche's Simple CSS Parser https://github.com/sergeche/webkit-css)
+
+-------------------------------------------------------------------------------
+XHR
+-------------------------------------------------------------------------------
+ - XHR calls made by Firebug Lite internally are not visible in the Console log anymore
+ - added FBL.getNativeXHRObject()
+
+-------------------------------------------------------------------------------
+Other
+-------------------------------------------------------------------------------
+ - Store module (powered by marcuswestin's library https://github.com/marcuswestin/store.js)
+
+ - console injection fallback now works as expected, creating a "firebug" object when there's
+ a "console" object already and overrideConsole option is set to false.
+
+ - jsonViewer will not try to evaluate the contents of the requested file if the content-type
+ is set to "text/plain"
+
+ - new getLocation() function
+ - better expression evaluation
+
+-------------------------------------------------------------------------------
+FBTest
+-------------------------------------------------------------------------------
+ - more robust Unit Test Framework
+ - updated QUnit to the latest version
+ - FBTest.click() now works in IE
+ - FBTest.getPanel() now returns also Side Panels
+ - updated Test Cases to use Sebastian's template
+ - Firebug Lite script is automatically inserted by the Test Runner, allowing you to easily
+ test the same file against different versions
+
+ - Improvements in the Test Runner
+ - included total number of tests passed and failed, and total execution time
+ - included link to run the test page again
+ - included link to open the test page it in another tab
+ - included link to view the test page in fullscreen mode
+ - improved the progress output of the tests, including the title of that test (if any
+ is found in the document.title)
+
+ - new Test Runner Toolbar:
+ - allows selecting different Test Lists
+ - allows selecting different builds given the Version/Channel/Mode
+
+ - Properties added to FBTest
+ - config
+ - delayDuration
+ - waitInterval
+ - waitTimeout
+
+ - Methods added to FBTest
+ - FBTest.triggerEvent()
+ - FBTest.getXHRObject()
+ - FBTest.loadScript()
+ - FBTest.installFirebug()
+ - FBTest.getFirebugConfig()
+ - FBTest.getFirebugLocation()
+ - FBTest.getTestListLocation()
+ - FBTest.loadTestList()
+ - FBTest.getURLParamaters()
+ - FBTest.delay()
+ - FBTest.wait()
+
+
+###################################################################################################
+ 1.4.0b1 - 2011-08-18 - Revision: 11337
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Issues
+-------------------------------------------------------------------------------
+ - Issue 4606: Firebug Lite: Console is not working properly in recent versions of FF
+ - Issue 4587: Firebug Lite: Opera shows security warning when using the Inspect tool
+ - Issue 4432: Firebug Lite: HTML is mixed-up with functions
+
+-------------------------------------------------------------------------------
+Internal fixes
+-------------------------------------------------------------------------------
+ - overrideConsole option now works as expected
+ - Fixed problem with console injection in recent versions of FF
+ - Fixed problem with resizing in-page chrome (iframe) in recent versions of FF
+ - Fixed visual glitch with Menus in high resolution monitors
+ - Fixed source line number height in high resolution monitors
+ - Fixed mini-chrome (lower-right icon) size glitch when the cache is empty
+ - Bookmarklet now works as expected when visiting getfirebug.com
+
+-------------------------------------------------------------------------------
+Chrome extension
+-------------------------------------------------------------------------------
+ - Improved activation failure messages in Chrome extension
+
+-------------------------------------------------------------------------------
+Internal changes
+-------------------------------------------------------------------------------
+ - internal directory organization and code refactoring to facilitate future
+ merging with Firebug code base (now code written for lite and the code
+ borrowed from Firebug are in different directories)
+
+
+###################################################################################################
+ 1.3.2 - 2011-03-22 - Revision: 9760
+###################################################################################################
+
+Overview:
+ - Issue 3422: Firebug Lite breaks Google Instant Search
+ - Issue 3504: Firebug lite: jQuery.ajax call fails in IE
+ - Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended
+ - Issue 3554: Firebug Lite should use local images when loaded locally
+ - Issue 3166: Listen to F12 key in for Google Chrome when inactive
+ - Issue 3579: Use context menu to Inspect Element in Firebug Lite Chrome Extension
+ - infoTips for CSS properties such as color and image
+
+-------------------------------------------------------------------------------
+Addition
+-------------------------------------------------------------------------------
+ - infoTips for CSS properties such as color and image
+
+-------------------------------------------------------------------------------
+Bugfixes
+-------------------------------------------------------------------------------
+ - Issue 3422: Firebug Lite breaks Google Instant Search
+ - Issue 3504: Firebug lite: jQuery.ajax call fails in IE
+ - Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended
+ - Issue 3554: Firebug Lite should use local images when loaded locally
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - Isolated most of Lite-specific code (not part/adaptation of Firebug's
+ original source) into a single directory (/content/firebug/lite/).
+ - Created a simple server-side proxy plugin to be used in conjunction with
+ Firebug Lite in order to overcome the cross-domain limitations of JavaScript
+ - Unifying Firebug Lite internal cache system (to be used in sourceCache).
+ As a bonus, the unified model allows Firebug Lite to adapt its cache when
+ new elements are inserted into the document, which means that now it will
+ be easier to use the Inspector in dynamically created content.
+
+-------------------------------------------------------------------------------
+Chrome extension improvements
+-------------------------------------------------------------------------------
+ - Issue 3166: Listen to F12 key in for Google Chrome when inactive
+ - Issue 3579: Use context menu to Inspect Element in Firebug Lite Chrome Extension
+ - Code refactored (chrome extension specific code isolated in a single file/module)
+ - Better message handling (two-way communication between the application/page,
+ content script and background page)
+ - Activation refactoring. The application is loaded how assynchronously during
+ activation and the activation can be started now by the BrowserAction/Icon,
+ the F12/ctrl+F12 key, or the context meny "Inspect with Firebug Lite" option.
+ It is possible also to activate-deactivate-reactivate without reloading
+ the page now.
+
+
+###################################################################################################
+ 1.3.1 - 2010-09-07 - Revision: 7759
+###################################################################################################
+
+Overview:
+ - Issue 3272: Install Google Chrome extension results in a 404 error
+ - Issue 3384: Just two inadvertent globals across the Firebug Lite files
+ - Issue 3318: Firebug Lite dies if you hide the UI when the large command line is open
+ - Issue 3181: Firebug Lite Missing XHR methods/properties
+ - Issue 3262: CSS specificity is not being calculated properly.
+ - Issue 3038: Empty (null) styles when adding CSS styles in Firebug Lite
+ - Normalizing syntax (missing semicolons)
+ - Added basic JsDoc comment markup
+
+
+###################################################################################################
+ 1.3.1b2 - 2010-07-26 - Revision: 7413
+###################################################################################################
+
+Overview:
+ - Issue 3224: Firebug Lite shows error when trying to read some external stylesheets
+ - Issue 3181: Missing XHR methods/properties
+ - Custom Net response viewers (XML and JSON viewers)
+ - Port of HTML viewer used in XHR representations
+ - Port of jsonViewer used in XHR representations
+ - Port of xmlViewer used in XHR representations
+
+-------------------------------------------------------------------------------
+XHR
+-------------------------------------------------------------------------------
+ - Compatibility with XMLHttpRequest 2 specification
+ - Issue 3181: Missing XHR methods/properties
+ - XHR representation is properly updated when the request is aborted
+ - Adjusting spy.mimeType according XHR response so we can detect when to
+ use custom response viewers (like HTML, XML and JSON viewers)
+
+-------------------------------------------------------------------------------
+jsonViewer
+-------------------------------------------------------------------------------
+ - Port of jsonViewer used in XHR representations
+
+-------------------------------------------------------------------------------
+xmlViewer
+-------------------------------------------------------------------------------
+ - Port of xmlViewer used in XHR representations
+
+-------------------------------------------------------------------------------
+Net
+-------------------------------------------------------------------------------
+ - Custom Net response viewers (XML and JSON viewers)
+ - Port of HTML viewer used in XHR representations
+
+-------------------------------------------------------------------------------
+Spy
+-------------------------------------------------------------------------------
+ - dispatching "initTabBody" event to Firebug.NetMonitor.NetInfoBody listeners
+ so custom response viewers can be properly initialized
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+ - Included warnings when some external stylesheets could not be loaded
+ - Issue 3224: Firebug Lite shows error when trying to read some external stylesheets
+
+-------------------------------------------------------------------------------
+Inspector
+-------------------------------------------------------------------------------
+ - Avoid error when the element is not attached a document
+
+-------------------------------------------------------------------------------
+Domplate
+-------------------------------------------------------------------------------
+ - Removing the temporary fix to RegExp problem Google Chrome 5 once it
+ is now fixed (and the temporary fix breaks the latest version).
+
+-------------------------------------------------------------------------------
+Firebug
+-------------------------------------------------------------------------------
+ - Firebug.Rep.getTitle now works for some special cases in IE
+
+-------------------------------------------------------------------------------
+Command Line
+-------------------------------------------------------------------------------
+ - Firebug.Console no longer uses Firebug.Console.LOG_COMMAND to identify
+ console calls as in the old Console panel version.
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - Added new experimental getDOMMember function to detect user members
+ (properties/functions) of several builtin objects such as window,
+ document, location, and instances of Element and other DOM objects
+
+
+###################################################################################################
+ 1.3.1b1 - 2010-06-29 - Revision: 7198
+###################################################################################################
+
+Overview:
+ - Issue 2958: Unable to add CSS to an element that has no style rules
+ - Issue 3165: Styling problem with nested expandable groups
+ - Issue 3178: Bookmarklet does not support XML+XSLT documents
+ - Context menu support for Style and CSS Panels
+ - Using double click to insert new CSS rule (instead of mouse down)
+
+
+-------------------------------------------------------------------------------
+i18n
+-------------------------------------------------------------------------------
+ - Unified all localization-related functions inside i18n.js
+ - Implemented $STRF (required for context menus)
+
+-------------------------------------------------------------------------------
+GUI
+-------------------------------------------------------------------------------
+ - Improved GUI Menu component to support content menus
+
+-------------------------------------------------------------------------------
+Editor
+-------------------------------------------------------------------------------
+ - Fixed timing issues when calling input.focus() and input.select()
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - Panels now will stop editing when clicking on any non-editable element
+
+-------------------------------------------------------------------------------
+UI
+-------------------------------------------------------------------------------
+ - Issue 3165: Styling problem with nested expandable groups
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+ - Issue 2958: Unable to add CSS to an element that has no style rules
+ - Using double click to insert new CSS rule (instead of mouse down)
+ - IE support for new features being used (context menu, double click, etc)
+
+-------------------------------------------------------------------------------
+Firebug
+-------------------------------------------------------------------------------
+ - Implemented panel.onContextMenu()
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - Ported lib.hasProperties()
+ - Fixed IE mouse button detection for "dblclick" events
+ - Port of lib.parseJSONString()
+ - Making the development mode work online without requiring pre-configuration
+ - Enabled the bookmarlet update detection
+
+-------------------------------------------------------------------------------
+HTML
+-------------------------------------------------------------------------------
+ - Added the old representations back to Firebug Lite source (Firebug.Reps)
+
+-------------------------------------------------------------------------------
+Console
+-------------------------------------------------------------------------------
+ - Fixed the broken console.dirxml() function
+
+-------------------------------------------------------------------------------
+CommandLine
+-------------------------------------------------------------------------------
+ - Fixed the broken dirxml() command line shortcut
+
+-------------------------------------------------------------------------------
+Bookmarklet
+-------------------------------------------------------------------------------
+ - Issue 3178: Bookmarklet does not support XML+XSLT documents
+
+
+###################################################################################################
+ 1.3.1a2 - 2010-06-24 - Revision: 7125
+###################################################################################################
+
+Overview:
+ - Major performance improvements in the inline editor
+ - Major performance improvement in Chrome.keyCodeListen
+ - Issue 3118: Long lines in XHR response
+ - Issue 2981: Switching from CSS tab and back causes an error
+ - Fix bug in Google Chrome 5 which causes representation of "object links"
+ not being properly styled
+
+
+-------------------------------------------------------------------------------
+Editor
+-------------------------------------------------------------------------------
+ - autocompletion cycling (with UP/DOWN keys) now works as expected for partially
+ typed words in IE6+, Safari/Google Chrome, and Opera
+
+ - Major improvement in editor's autocomplete performance (was too slow on IE)
+ - No more problems with autocomplete when typing fast (timing issues)
+ - ignoring the inline editor spell checking in Safari/Google Chrome
+
+-------------------------------------------------------------------------------
+Domplate
+-------------------------------------------------------------------------------
+ - Fix bug in Google Chrome 5 which causes representation of "object links"
+ not being properly styled
+
+-------------------------------------------------------------------------------
+UI
+-------------------------------------------------------------------------------
+ - Issue 3118: Long lines in XHR response
+ - Added round corner support in the XHR tabs to Chrome and Opera
+ - Resetting user agent styles for tables which was creating a small glitch
+ (undesired blank space) between the toolbar and the panel content
+
+-------------------------------------------------------------------------------
+Extensions
+-------------------------------------------------------------------------------
+ - Testing Firediff extension support
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+ - Issue 2981: Switching from CSS tab and back causes an error
+ - Destroying the inline editor when the panel is destroyed or hidden
+ - Properly dispatching some CSS change events to listeners
+
+-------------------------------------------------------------------------------
+Firebug
+-------------------------------------------------------------------------------
+ - Port of Firebug.Listener
+ - Firebug.Module now inherits from Firebug.Listener as in Firebug
+ - Experimental context menu support
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - Improved window key code event listener. Only one "keydown" event will be
+ attached to the window, and the onKeyCodeListen() function will delegate
+ which listeners should be called according to the event.keyCode fired.
+
+ - Fixing bug in the persistent mode (related to the new console panel)
+ - Improving the accuracy of the delay time calculated in the persist process.
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - Fixed problem in Lib.dispatch which was preventing some listeners to be called.
+
+
+
+###################################################################################################
+ 1.3.0 - 2010-05-24 - Revision: 6859
+###################################################################################################
+
+ - Refactored code
+ - Console e Css old modules deleted (not used anymore)
+ - Test modules deleted (moved to 1.4 branch)
+ - Comparison modules deleted (a copy of Domplate and DOM which was
+ used to compare the Firebug and Firebug Lite sources)
+
+ - New distribution location and file name:
+ - https://getfirebug.com/firebug-lite.js (compressed)
+ - https://getfirebug.com/firebug-lite-debug.js (uncompressed, trace)
+ - https://getfirebug.com/firebug-lite-beta.js (beta channel)
+
+ - Added "debug" URL option
+
+ - Updated "classic" and "light" skins
+
+ - Improvements in the debug mode (it exposes the FBL library, and forces
+ the UI element to be visible at HTML panel)
+
+ - Fixed frameCounters variable leaking to global namespace
+
+ - Firebug.extend() method added to support Firebug Lite extensions
+
+ - Fixed the missing command line API dir()
+ - Fixed the missing command line api dirxml()
+ - Fixed the missing console.firebuglite property in the console object
+ - Fixed problem when loading an extension before the UI finish loading
+
+
+###################################################################################################
+ 1.3.0b2 - 2010-05-06 - Revision: 6695
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Console
+-------------------------------------------------------------------------------
+ - The Console Panel now uses the same rendering engine (domplate) and object
+ representation (Reps) used in Firebug
+
+ - Console now has clickable objects links, which will lead you to the related
+ panel, HTML if is an element, or the DOM panel if is an object
+
+ - console.dir() now uses the same rich representation as in the DOM panel, with
+ items which can be collapsed, and links which can be clicked.
+
+ - console.trace() now uses rich representation, with clickable links, and will
+ show the file name and line number for some browsers when found at the stacktrace
+
+ - console.count() now works as in Firebug
+ - console.group() now can be collapsed, using the same representation as in Firebug
+ - console.groupCollapsed() added to the console object
+
+ - new offline log messages handler (messages called before Firebug Lite UI finish
+ rendering), able to support clickable links and advanced representations
+ like the XHR watcher
+
+ - ability to listen offline XHR messages
+
+-------------------------------------------------------------------------------
+XHR
+-------------------------------------------------------------------------------
+ - Fixed Issue 2977: XHR POST and URL parameters in the console
+ - Fixed Issue 2840: Firebug Lite 1.3b doesn't handle synchronous XHR requests
+ - Fixed Issue 2846: Firebug Lite 1.3b doesn't show XHR request made before the main
+ document is loaded
+
+ - Fixed issue with the spinning XHR gif that wasn't being hidden sometimes in IE
+ - Fixed bug when there is no responseHeaders in IE
+ - Properly handling error when something goes wrong (like access restriction error)
+
+-------------------------------------------------------------------------------
+Chrome extension
+-------------------------------------------------------------------------------
+ - Fixed problem with restricted pages. the method used to load the bookmarlet
+ when no content script is available no longer works in recent versions of
+ Google Chrome, so now an alert box appears indicating that the extension
+ can't work on that page
+
+ - Fixed problem when trying to activate Firebug Lite in a page which was open
+ before Firebug extension itself being enabled. Now it shows an alert box
+ asking the user to reload the page to complete the activation
+
+ - Fixed problem in Google Chrome 5 which was caused by not using the proper
+ encoding (UTF-8) at the content script file
+
+ - Fixed problem with popup. when the popup was opened, the bug icon was
+ becoming gray, falsely indicating that it was deactivated
+
+ - Fixed problem with synchronization between Firebug Lite state and the
+ browser icon state
+
+ - Fixed problem with UI images not loading in Mac and Linux (was related
+ to a bug in a third-party compression tool called Izarc)
+
+ - Ignoring the FirebugChannel element in the HTML panel visualization
+
+ - The core of the extension now uses the exact the same source as found
+ at getfirebug.com, and no longer needs to be built with a different
+ URL location for the images
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - Fixed problem with cookies not available in XML+XSL documents
+ - Fixed bug at lib.findLocation() in IE, when using deep relative paths
+ - Basic extension system support
+ - Basic support for the next generation HTML panel (fully editable, cross-frame)
+
+-------------------------------------------------------------------------------
+Net
+-------------------------------------------------------------------------------
+ - Ported Firebug.NetMonitor.NetInfoPostData representation
+ - Fixed problem with the styling of XHR post tab in Google Chrome
+ - Fixed problem with the styling of XHR params tab in IE
+
+-------------------------------------------------------------------------------
+ConsoleInjector
+-------------------------------------------------------------------------------
+ - ported the consoleInjector module from Firebug
+
+-------------------------------------------------------------------------------
+Reps
+-------------------------------------------------------------------------------
+ - Fixed problem with the Element's representation and attribute names
+ - Adjusted the StackFrame representation to be used with the new console.trace()
+
+-------------------------------------------------------------------------------
+HTML
+-------------------------------------------------------------------------------
+ - Fixed styling problem with source code inside HTML tree (script tag).
+ The line numbers were positioned at the top of the panel.
+
+-------------------------------------------------------------------------------
+Repository
+-------------------------------------------------------------------------------
+ - Added issues test cases directory to the repository
+
+
+
+###################################################################################################
+ 1.3.0b1 - 2010-02-05 - Revision: 6012
+###################################################################################################
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+ - Implemented a more robust stylesheet scanner (will scan imported stylesheets)
+ - Implemented a cascading styles analyser (will detect which CSS rules are applied
+ to a particular element, in the proper cascading order)
+
+ - Ported css.js file from Firebug, including the following features:
+ - live edit of CSS properties
+ - enable/disable CSS properties on-the-fly
+ - Cascading visualization
+ - Inheritance visualization (with overriden properties marked)
+
+ - Ported the CSS Panel
+ - Ported the Style Panel
+ - Ported the Computed Panel divided in categories (in a separated tab)
+ - Fixed the problem with external stylesheets (now shows a "Access restricted" message).
+
+-------------------------------------------------------------------------------
+Editor
+-------------------------------------------------------------------------------
+ - Autocomplete feature with UP/DOWN keys
+ - "Complete as you type" feature in most browsers (not working in Opera yet)
+ - Increment/decrement with UP/DOWN, PAGE-UP/PAGE-DOWN
+ - Navigation with TAB/SHIFT+TAB
+ - Fixed the CSS of editor to work in all browsers
+ - Pretty inline editor support in IE6
+ - Fixed problem with inline editor in Safari/Chrome/IE: special keys doesn't
+ trigger the onkeypress event, making some changes in the editor not apply
+ to the CSS property.
+
+-------------------------------------------------------------------------------
+Console
+-------------------------------------------------------------------------------
+ - Strings are properly rendered in console.* calls
+
+-------------------------------------------------------------------------------
+CommandLine
+-------------------------------------------------------------------------------
+ - Fixed Issue 2764: Fix problem with commandLine API and jQuery's $ shortcut.
+
+-------------------------------------------------------------------------------
+Script
+-------------------------------------------------------------------------------
+ - Don't show the Firebug Lite source code in the script list
+ - Refactored Script panel
+ - Fixed potential memory leak
+ - Using the Warning template in the Script panel when failing to load external scripts.
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - When running as Chrome extension, all images used in the interface are
+ stored in the extension directory, hugely improving the perceived loading
+ time for GUI operations, specially in the startup.
+
+ - Implemented the chrome.deactivate() method
+
+-------------------------------------------------------------------------------
+GUI
+-------------------------------------------------------------------------------
+ - Added the "off" button to the UI
+ - Updated "minimize" and "detach" buttons with new images used in Firebug 1.4+
+ - Fixed problem with panel initialization that was breaking the scroll
+ position persistence of the panels.
+
+-------------------------------------------------------------------------------
+Domplate
+-------------------------------------------------------------------------------
+ - Added domplate tag.insertBefore method
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - Added KeyEvent constants
+ - Added bindFixed method
+ - Added Whitespace and Entity conversions methods
+ - Added String escaping methods
+ - Added CSS methods
+ - Added DOM queries methods
+
+ - Fixed lib.collapse() method to work in IE6 (that doesn't support the "[collapsed]"
+ CSS selector that was used to match the element)
+
+ - Implemented a cross-browser lib.selectInputRange() and lib.getInputCaretPosition()
+ to support text selection and caret position detection in editor module
+
+ - Making instanceOf() work also for non HTML elements (elements without ownerDocument
+ property), to avoid the use of the instanceof operator, that may cause error in other
+ browsers when the Class is not defined in the global namespace.
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - Ported editor.js module from Firebug
+ - Ported a simplified version of tabContext.js
+ - Implemented a more robust Cache system that will be used internally
+ - Implemented a message dispatching method to communicate with the Chrome extension
+
+
+###################################################################################################
+ 1.3.0a5 - 2010-01-16 - Revision: 5719
+###################################################################################################
+
+-------------------------------------------------------------------------------
+CommandLine
+-------------------------------------------------------------------------------
+ - Large Command Line
+ - Refactoring CommandLine module for better readability and encapsulation (commandHistory
+ is now a private variable in CommandLine module)
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - Fix problem in iframe creation that was blocking the UI creation via
+ bookmarlet in IE, for some pages.
+
+ - Allow Firebug Lite UI to load in "windowless mode", without creating an
+ iframe. This is necessary to make the bookmarlet run in cases where it
+ is not possible to create an iframe.
+
+ - Refactoring Chrome module for better readability
+
+-------------------------------------------------------------------------------
+User Interface
+-------------------------------------------------------------------------------
+ - refined the layout of buttons (added an image background)
+ - refined the layout of log groups
+
+-------------------------------------------------------------------------------
+Context
+-------------------------------------------------------------------------------
+ - Better context evaluation (commands with multiple lines are now properly evaluated)
+ - context.evaluate() properly executes and returns the value of expressions with
+ multiple commands (be it multilined or not).
+
+-------------------------------------------------------------------------------
+Style
+-------------------------------------------------------------------------------
+ - Basic editing feature of inline styles (Style panel)
+
+-------------------------------------------------------------------------------
+HTML
+-------------------------------------------------------------------------------
+ - properly format inline style of elements in IE to lower-case in HTML panel
+
+-------------------------------------------------------------------------------
+Lib
+-------------------------------------------------------------------------------
+ - fixed visibility detection and visibility representation of elements
+ - Fixed problems in IE with some event functions like isLeftClick(), isRightClick(),
+ and others. IE has a different pattern for identifying mouse buttons.
+
+-------------------------------------------------------------------------------
+Console
+-------------------------------------------------------------------------------
+ - Added the "category" of error in the error messages (like "Type Error", "Syntax Error", etc).
+ - ported the consoleInjetor.js file that will be used with the new console (console2.js)
+ - ported the console.js file from Firebug codebase (not enabled yet). This will replace
+ the current implementation of the Console panel in the 1.3 final version.
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - new XHR watcher (with response and headers tabs)
+ - fixed variable "root" leaking to global namespace (domplate.js)
+ - improved development build functions
+
+
+###################################################################################################
+ 1.3.0a4 - 2009-12-31 - Revision: 5505
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - Improved the performance of the application initial loading time
+ - Improved the performance of the popup loading time
+ - Refactored the chrome synchronization mechanism
+ - Implemented synchronization of the persistent popup
+ - Fixed isFunction() problem with IE when dealing with external objects
+
+ - Improved the memory consumption. Now each panel only uses resources (listen
+ for events, etc) when is currently selected.
+
+ - Implemented the "Duck Type Detection" system, to make possible identify
+ native classes (Document, Element, etc) in IE, and therefore, generate
+ the appropriate visual representation.
+
+-------------------------------------------------------------------------------
+User Interface
+-------------------------------------------------------------------------------
+ - Moved all UI components to a separated gui.js file.
+ - Implemented the Menu class (with normal, checkbox, radiobutton, group
+ and separator items), that will be used in options menus at 1.3 verson
+ and in contexts menus at 1.4 version.
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - StatusBar
+ - SidePanel size and positioning
+
+ - Long sequence of elements (like toolbar/statusbar buttons) don't "bleed"
+ anymore (appears outside its container) when the chrome has small dimensions
+
+ - Large panels now triggers automatically the appropriate scrollbars
+ (some huge scripts with long lines wans't triggering the horizontal scrollbar)
+
+-------------------------------------------------------------------------------
+Console
+-------------------------------------------------------------------------------
+ - Fixed problem in console.time() and console.timeEnd().
+ - Implemented the console.trace (thanks dongryphon for the contribution!)
+
+-------------------------------------------------------------------------------
+Inspector
+-------------------------------------------------------------------------------
+ - Implemented the border in the BoxModel Highlight
+
+-------------------------------------------------------------------------------
+HTML
+-------------------------------------------------------------------------------
+ - Internet Explorer and inline styles representation (thanks christophe.blin
+ for the contribution!)
+
+ - Implemented a basic sidePanel synchronization to test the overall
+ performance of the rendering when inspecting elements
+
+-------------------------------------------------------------------------------
+DOM
+-------------------------------------------------------------------------------
+ - Ported the main part of the original DOM Panel in Firebug
+ - Ported the DOM "views path" mechanism (click and "go into" DOM objects)
+ - Improved the performance of the initial rendering
+ - Implemented a basic DOM Panel subclass used in as HTML side panel
+
+-------------------------------------------------------------------------------
+Script
+-------------------------------------------------------------------------------
+ - Implemented the basics of the Script panel, with some code ported from
+ the Firebug Lite 1.2 version.
+
+ - Better number of lines detection
+
+-------------------------------------------------------------------------------
+CSS
+-------------------------------------------------------------------------------
+ - Implemented the basics of the CSS panel, with some code ported from
+ the Firebug Lite 1.2 version.
+
+ - Adjusted the rules and property names styles to lowercase
+
+-------------------------------------------------------------------------------
+Domplate
+-------------------------------------------------------------------------------
+ - Removed the dependency on global variables (domplate, DomplateTag)
+ - Adjusted the code so it can run in external contexts (persistent mode)
+
+
+
+###################################################################################################
+ 1.3.0a3 - 2009-09-13 - Revision: 4882
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - Better implementation of the chrome synchronization (detach and reattach methods)
+ - Improvements the location detection
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - XML+XSL and XHTML support
+ - Synchronization messages ("detach" and "reattach") are now dispatched to all panels
+ - Fixed problem with Chrome synchronization in Opera
+ - Fixed weird bug in layout in IE (horizontal splitter was disappearing sometimes)
+
+-------------------------------------------------------------------------------
+Inspector
+-------------------------------------------------------------------------------
+ - Reimplemented the IE auto margin size calculator
+ - Reimplemented the pointsToPixels function
+ - Reimplemented the pixelsPerInch calculator
+ - Outline Inspector is now "cropped" to avoid triggering the scrollbars
+
+-------------------------------------------------------------------------------
+Bookmarlet
+-------------------------------------------------------------------------------
+ - More robust and maintainable bookmarlet
+
+-------------------------------------------------------------------------------
+Domplate
+-------------------------------------------------------------------------------
+ - Ported the Domplate rendering engine
+
+-------------------------------------------------------------------------------
+Reps
+-------------------------------------------------------------------------------
+ - Ported the visual representation rules of objects (Reps module)
+
+-------------------------------------------------------------------------------
+Persist
+-------------------------------------------------------------------------------
+ - Reimplemented the application core to support in the future persisted
+ Chromes, that is, UI windows that stays alive when the user reloads or
+ changes the page (considering that the following page is in the same domain).
+
+
+
+###################################################################################################
+ 1.3.0a2 - 2009-08-03 - Revision: 3847
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Core Changes
+-------------------------------------------------------------------------------
+ - Context Class implemented to allow inspect different windows (contexts)
+
+ - better settings handling:
+ - modes: BookmarletMode, PersistentMode, TraceMode, DevelopmentMode
+ - skin: xp, classic, light
+
+ - all components were revised to better handling memory consumption.
+ create()/destroy() methods (called when something is created) and
+ initialize()/shutdown() when something is activated, or made visible.
+
+ - console.log calls are now captured even when the UI is not loaded
+ - better location detection
+ - library initialization reimplemented to support future persistent applications
+
+-------------------------------------------------------------------------------
+User Interface Changes
+-------------------------------------------------------------------------------
+ - Created "xp" and "classic" skins. The old skin was based in a Firefox
+ non-default theme.
+
+ - HTML and CSS revised to render properly in different browsers, running
+ on different compatibility modes (quirks mode, standards mode).
+
+-------------------------------------------------------------------------------
+Chrome Changes
+-------------------------------------------------------------------------------
+ - better positioning calculation, when running on different compatibility
+ modes (quirks mode, standards mode).
+
+ - better window size, scrollSize and scollPosition calculations, when
+ running on different compatibility modes (quirks mode, standards mode).
+
+ - element:hover now works also in IE7 & IE8 when in quirks mode.
+
+ - resize chrome performance (buffered frame-skip technique)
+
+ - mini-chrome implemented
+
+-------------------------------------------------------------------------------
+Core Additions
+-------------------------------------------------------------------------------
+ - FBTrace - internal logging system
+
+ - DOM methods:
+ - createElement()
+ - createGlobalElement()
+
+ - Event methods:
+ - bind()
+ - cancelEvent()
+ - addGlobalEvent()
+ - removeGlobalEvent()
+ - dispatch()
+ - disableTextSelection()
+
+ - className methods:
+ - addClass()
+ - removeClass()
+ - hasClass()
+ - toggleClass()
+
+-------------------------------------------------------------------------------
+Chrome Additions
+-------------------------------------------------------------------------------
+ - Controller Class
+ - Module Class
+ - Panel Class
+ - PanelBar Class
+ - Button Class (normal and toggle buttons)
+
+ - FBTrace Panel
+
+
+
+
+###################################################################################################
+ 1.3.0a1 - 2009-05-03 - Revision: 2729
+###################################################################################################
+
+-------------------------------------------------------------------------------
+Inspector
+-------------------------------------------------------------------------------
+ - Inspect function implemented.
+
+ - onInspecting highlight element in HTML Tree behaviour implemented.
+ When inspecting, the elements are being highlighted, and the scroll
+ is being changed to make the element visible in the tree.
+
+-------------------------------------------------------------------------------
+Core
+-------------------------------------------------------------------------------
+ - Problem with scope in event handlers. All functions that need to access
+ the "shared scope" must be assigned to a local variable.
+
+ var onClick = function onClick(e)
+ {
+ ...
+
+ - Revised "extend" and "append" functions
+
+ - problem with the new Firebug for FF3, it seems that it doesn't allow
+ extending the console namespace anymore.
+
+ - CommandLineAPI --> $, $$, dir, dirxml...
+
+ - Fixed bug in getLocation function, the relative path calculation wasn't
+ working in all cases.
+
+ - Fixed bug in commandLine. Commands that doesn't return a value (if, for,
+ while) wasn't being properly executed.
+
+-------------------------------------------------------------------------------
+Events
+-------------------------------------------------------------------------------
+ - Opera problem with the TAB key in commandLine
+
+ - Better handling of the F12 key press, which wasn't being properly
+ attached to the Chrome Frame window.
+
+-------------------------------------------------------------------------------
+Chrome
+-------------------------------------------------------------------------------
+ - Problem with multiple iframes and the resizing of the Chrome, that
+ tries to add events on them.
+
+ - Fixed problem in IE when resizing the Chrome, when the relative position
+ of the mouse wasnt being computed in all frames of the document,
+ resulting in strange flickerings when resizing it.
+
+ - Fixed problem in Opera when resizing the Chrome.
+
+ - Problem when resizing with the fbVSplitter, when it reaches the side of
+ the screen. Problem with negative pixel numbers.
+
+ - fbVSplitter is bigger than the frame in firefox. Problem with mouse scroll.
+
+ - isScrolledToBottom is not working in Firefox, it seems that this is
+ happening because the scrollable panel is some pixels higher than
+ it should be.
+
+-------------------------------------------------------------------------------
+Inspector
+-------------------------------------------------------------------------------
+ - Selected element in HTML tree isn't being highlighted (boxmodel)
+
+ - BoxModel functions entirely revised. Now the position, size, padding
+ and margin are being computed correctly, in all units: pt, px, em, ex
+ and % (need to test more deeply the percentage values).
+
+-------------------------------------------------------------------------------
+commandLine
+-------------------------------------------------------------------------------
+ - better handling of scope of commandLine.eval(), if you type "this" it will
+ refer to the CommandLine module, and it should refer to "window" instead
+
+
+
+
+###################################################################################################
+ 1.3.0a0 - 2009-01-24 - Revision: 1765
+###################################################################################################
+
+1.3.0 prototype
\ No newline at end of file
diff --git a/vendor/firebug-lite/license.txt b/vendor/firebug-lite/license.txt
new file mode 100644
index 000000000..ba43b7514
--- /dev/null
+++ b/vendor/firebug-lite/license.txt
@@ -0,0 +1,30 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2007, Parakey Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Parakey Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Parakey Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/firebug-lite/skin/xp/blank.gif b/vendor/firebug-lite/skin/xp/blank.gif
new file mode 100644
index 000000000..6865c9604
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/blank.gif differ
diff --git a/vendor/firebug-lite/skin/xp/buttonBg.png b/vendor/firebug-lite/skin/xp/buttonBg.png
new file mode 100644
index 000000000..f367b427e
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/buttonBg.png differ
diff --git a/vendor/firebug-lite/skin/xp/buttonBgHover.png b/vendor/firebug-lite/skin/xp/buttonBgHover.png
new file mode 100644
index 000000000..cd37a0d52
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/buttonBgHover.png differ
diff --git a/vendor/firebug-lite/skin/xp/debugger.css b/vendor/firebug-lite/skin/xp/debugger.css
new file mode 100644
index 000000000..4a64d2664
--- /dev/null
+++ b/vendor/firebug-lite/skin/xp/debugger.css
@@ -0,0 +1,331 @@
+/* See license.txt for terms of usage */
+
+.panelNode-script {
+ overflow: hidden;
+ font-family: monospace;
+}
+
+/************************************************************************************************/
+
+.scriptTooltip {
+ position: fixed;
+ z-index: 2147483647;
+ padding: 2px 3px;
+ border: 1px solid #CBE087;
+ background: LightYellow;
+ font-family: monospace;
+ color: #000000;
+}
+
+/************************************************************************************************/
+
+.sourceBox {
+ /* TODO: xxxpedro problem with sourceBox and scrolling elements */
+ /*overflow: scroll; /* see issue 1479 */
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.sourceRow {
+ white-space: nowrap;
+ -moz-user-select: text;
+}
+
+.sourceRow.hovered {
+ background-color: #EEEEEE;
+}
+
+/************************************************************************************************/
+
+.sourceLine {
+ -moz-user-select: none;
+ margin-right: 10px;
+ border-right: 1px solid #CCCCCC;
+ padding: 0px 4px 0 20px;
+ background: #EEEEEE no-repeat 2px 0px;
+ color: #888888;
+ white-space: pre;
+ font-family: monospace; /* see issue 2953 */
+}
+
+.noteInToolTip { /* below sourceLine, so it overrides it */
+ background-color: #FFD472;
+}
+
+.useA11y .sourceBox .sourceViewport:focus .sourceLine {
+ background-color: #FFFFC0;
+ color: navy;
+ border-right: 1px solid black;
+}
+
+.useA11y .sourceBox .sourceViewport:focus {
+ outline: none;
+}
+
+.a11y1emSize {
+ width: 1em;
+ height: 1em;
+ position: absolute;
+}
+
+.useA11y .panelStatusLabel:focus {
+ outline-offset: -2px !important;
+ }
+
+.sourceBox > .sourceRow > .sourceLine {
+ cursor: pointer;
+}
+
+.sourceLine:hover {
+ text-decoration: none;
+}
+
+.sourceRowText {
+ white-space: pre;
+}
+
+.sourceRow[exe_line="true"] {
+ outline: 1px solid #D9D9B6;
+ margin-right: 1px;
+ background-color: lightgoldenrodyellow;
+}
+
+.sourceRow[executable="true"] > .sourceLine {
+ content: "-";
+ color: #4AA02C; /* Spring Green */
+ font-weight: bold;
+}
+
+.sourceRow[exe_line="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/exe.png);
+ color: #000000;
+}
+
+.sourceRow[breakpoint="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+.sourceRow[breakpoint="true"][condition="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/breakpointCondition.png);
+}
+
+.sourceRow[breakpoint="true"][disabledBreakpoint="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/breakpointDisabled.png);
+}
+
+.sourceRow[breakpoint="true"][exe_line="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/breakpointExe.png);
+}
+
+.sourceRow[breakpoint="true"][exe_line="true"][disabledBreakpoint="true"] > .sourceLine {
+ background-image: url(chrome://firebug/skin/breakpointDisabledExe.png);
+}
+
+.sourceLine.editing {
+ background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+/************************************************************************************************/
+
+.conditionEditor {
+ z-index: 2147483647;
+ position: absolute;
+ margin-top: 0;
+ left: 2px;
+ width: 90%;
+}
+
+.conditionEditorInner {
+ position: relative;
+ top: -26px;
+ height: 0;
+}
+
+.conditionCaption {
+ margin-bottom: 2px;
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ font-size: 11px;
+ color: #226679;
+}
+
+.conditionInput {
+ width: 100%;
+ border: 1px solid #0096C0;
+ font-family: monospace;
+ font-size: inherit;
+}
+
+.conditionEditorInner1 {
+ padding-left: 37px;
+ background: url(condBorders.png) repeat-y;
+}
+
+.conditionEditorInner2 {
+ padding-right: 25px;
+ background: url(condBorders.png) repeat-y 100% 0;
+}
+
+.conditionEditorTop1 {
+ background: url(condCorners.png) no-repeat 100% 0;
+ margin-left: 37px;
+ height: 35px;
+}
+
+.conditionEditorTop2 {
+ position: relative;
+ left: -37px;
+ width: 37px;
+ height: 35px;
+ background: url(condCorners.png) no-repeat;
+}
+
+.conditionEditorBottom1 {
+ background: url(condCorners.png) no-repeat 100% 100%;
+ margin-left: 37px;
+ height: 33px;
+}
+
+.conditionEditorBottom2 {
+ position: relative; left: -37px;
+ width: 37px;
+ height: 33px;
+ background: url(condCorners.png) no-repeat 0 100%;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.upsideDown {
+ margin-top: 2px;
+}
+
+.upsideDown .conditionEditorInner {
+ top: -8px;
+}
+
+.upsideDown .conditionEditorInner1 {
+ padding-left: 33px;
+ background: url(condBordersUps.png) repeat-y;
+}
+
+.upsideDown .conditionEditorInner2 {
+ padding-right: 25px;
+ background: url(condBordersUps.png) repeat-y 100% 0;
+}
+
+.upsideDown .conditionEditorTop1 {
+ background: url(condCornersUps.png) no-repeat 100% 0;
+ margin-left: 33px;
+ height: 25px;
+}
+
+.upsideDown .conditionEditorTop2 {
+ position: relative;
+ left: -33px;
+ width: 33px;
+ height: 25px;
+ background: url(condCornersUps.png) no-repeat;
+}
+
+.upsideDown .conditionEditorBottom1 {
+ background: url(condCornersUps.png) no-repeat 100% 100%;
+ margin-left: 33px;
+ height: 43px;
+}
+
+.upsideDown .conditionEditorBottom2 {
+ position: relative;
+ left: -33px;
+ width: 33px;
+ height: 43px;
+ background: url(condCornersUps.png) no-repeat 0 100%;
+}
+
+/************************************************************************************************/
+
+.breakpointsGroupListBox {
+ overflow: hidden;
+}
+
+.breakpointBlockHead {
+ position: relative;
+ padding-top: 4px;
+}
+
+.breakpointBlockHead > .checkbox {
+ margin-right: 4px;
+}
+
+.breakpointBlockHead > .objectLink-sourceLink {
+ top: 4px;
+ right: 20px;
+ background-color: #FFFFFF; /* issue 3308 */
+}
+
+.breakpointBlockHead > .closeButton {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+}
+
+.breakpointCheckbox {
+ margin-top: 0;
+ vertical-align: top;
+}
+
+.breakpointName {
+ margin-left: 4px;
+ font-weight: bold;
+}
+
+.breakpointRow[aria-checked="false"] > .breakpointBlockHead > *,
+.breakpointRow[aria-checked="false"] > .breakpointCode {
+ opacity: 0.5;
+}
+
+.breakpointRow[aria-checked="false"] .breakpointCheckbox,
+.breakpointRow[aria-checked="false"] .objectLink-sourceLink,
+.breakpointRow[aria-checked="false"] .closeButton,
+.breakpointRow[aria-checked="false"] .breakpointMutationType {
+ opacity: 1.0 !important;
+}
+
+.breakpointCode {
+ overflow: hidden;
+ white-space: nowrap;
+ padding-left: 24px;
+ padding-bottom: 2px;
+ border-bottom: 1px solid #D7D7D7;
+ font-family: monospace;
+ color: DarkGreen;
+}
+
+.breakpointCondition {
+ white-space: nowrap;
+ padding-left: 24px;
+ padding-bottom: 2px;
+ border-bottom: 1px solid #D7D7D7;
+ font-family: monospace;
+ color: Gray;
+}
+
+.breakpointBlock-breakpoints > .groupHeader {
+ display: none;
+}
+
+.breakpointBlock-monitors > .breakpointCode {
+ padding: 0;
+}
+
+.breakpointBlock-errorBreakpoints .breakpointCheckbox,
+.breakpointBlock-monitors .breakpointCheckbox {
+ display: none;
+}
+
+.breakpointHeader {
+ margin: 0 !important;
+ border-top: none !important;
+}
diff --git a/vendor/firebug-lite/skin/xp/detach.png b/vendor/firebug-lite/skin/xp/detach.png
new file mode 100644
index 000000000..0ddb9a176
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/detach.png differ
diff --git a/vendor/firebug-lite/skin/xp/detachHover.png b/vendor/firebug-lite/skin/xp/detachHover.png
new file mode 100644
index 000000000..e41927291
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/detachHover.png differ
diff --git a/vendor/firebug-lite/skin/xp/disable.gif b/vendor/firebug-lite/skin/xp/disable.gif
new file mode 100644
index 000000000..dd9eb0e3e
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/disable.gif differ
diff --git a/vendor/firebug-lite/skin/xp/disable.png b/vendor/firebug-lite/skin/xp/disable.png
new file mode 100644
index 000000000..c28bcdf24
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/disable.png differ
diff --git a/vendor/firebug-lite/skin/xp/disableHover.gif b/vendor/firebug-lite/skin/xp/disableHover.gif
new file mode 100644
index 000000000..70565a83c
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/disableHover.gif differ
diff --git a/vendor/firebug-lite/skin/xp/disableHover.png b/vendor/firebug-lite/skin/xp/disableHover.png
new file mode 100644
index 000000000..26fe37542
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/disableHover.png differ
diff --git a/vendor/firebug-lite/skin/xp/down.png b/vendor/firebug-lite/skin/xp/down.png
new file mode 100644
index 000000000..acbbd30c0
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/down.png differ
diff --git a/vendor/firebug-lite/skin/xp/downActive.png b/vendor/firebug-lite/skin/xp/downActive.png
new file mode 100644
index 000000000..f4312b2ff
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/downActive.png differ
diff --git a/vendor/firebug-lite/skin/xp/downHover.png b/vendor/firebug-lite/skin/xp/downHover.png
new file mode 100644
index 000000000..8144e6378
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/downHover.png differ
diff --git a/vendor/firebug-lite/skin/xp/errorIcon-sm.png b/vendor/firebug-lite/skin/xp/errorIcon-sm.png
new file mode 100644
index 000000000..0c377e307
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/errorIcon-sm.png differ
diff --git a/vendor/firebug-lite/skin/xp/errorIcon.gif b/vendor/firebug-lite/skin/xp/errorIcon.gif
new file mode 100644
index 000000000..8ee8116a5
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/errorIcon.gif differ
diff --git a/vendor/firebug-lite/skin/xp/errorIcon.png b/vendor/firebug-lite/skin/xp/errorIcon.png
new file mode 100644
index 000000000..2d75261bb
Binary files /dev/null and b/vendor/firebug-lite/skin/xp/errorIcon.png differ
diff --git a/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
new file mode 100644
index 000000000..b5dd5dde1
--- /dev/null
+++ b/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
@@ -0,0 +1,817 @@
+.fbBtnPressed {
+ background: #ECEBE3;
+ padding: 3px 6px 2px 7px !important;
+ margin: 1px 0 0 1px;
+ _margin: 1px -1px 0 1px;
+ border: 1px solid #ACA899 !important;
+ border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;
+}
+
+.fbToolbarButtons {
+ display: none;
+}
+
+#fbStatusBarBox {
+ display: none;
+}
+
+/************************************************************************************************
+ Error Popup
+*************************************************************************************************/
+#fbErrorPopup {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ height: 19px;
+ width: 75px;
+ background: url(sprite.png) #f1f2ee 0 0;
+ z-index: 999;
+}
+
+#fbErrorPopupContent {
+ position: absolute;
+ right: 0;
+ top: 1px;
+ height: 18px;
+ width: 75px;
+ _width: 74px;
+ border-left: 1px solid #aca899;
+}
+
+#fbErrorIndicator {
+ position: absolute;
+ top: 2px;
+ right: 5px;
+}
+
+
+
+
+
+
+
+
+
+
+.fbBtnInspectActive {
+ background: #aaa;
+ color: #fff !important;
+}
+
+/************************************************************************************************
+ General
+*************************************************************************************************/
+html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+body {
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ background: #fff;
+}
+
+.clear {
+ clear: both;
+}
+
+/************************************************************************************************
+ Mini Chrome
+*************************************************************************************************/
+#fbMiniChrome {
+ display: none;
+ right: 0;
+ height: 27px;
+ background: url(sprite.png) #f1f2ee 0 0;
+ margin-left: 1px;
+}
+
+#fbMiniContent {
+ display: block;
+ position: relative;
+ left: -1px;
+ right: 0;
+ top: 1px;
+ height: 25px;
+ border-left: 1px solid #aca899;
+}
+
+#fbToolbarSearch {
+ float: right;
+ border: 1px solid #ccc;
+ margin: 0 5px 0 0;
+ background: #fff url(search.png) no-repeat 4px 2px;
+ padding-left: 20px;
+ font-size: 11px;
+}
+
+#fbToolbarErrors {
+ float: right;
+ margin: 1px 4px 0 0;
+ font-size: 11px;
+}
+
+#fbLeftToolbarErrors {
+ float: left;
+ margin: 7px 0px 0 5px;
+ font-size: 11px;
+}
+
+.fbErrors {
+ padding-left: 20px;
+ height: 14px;
+ background: url(errorIcon.png) no-repeat;
+ color: #f00;
+ font-weight: bold;
+}
+
+#fbMiniErrors {
+ display: inline;
+ display: none;
+ float: right;
+ margin: 5px 2px 0 5px;
+}
+
+#fbMiniIcon {
+ float: right;
+ margin: 3px 4px 0;
+ height: 20px;
+ width: 20px;
+ float: right;
+ background: url(sprite.png) 0 -135px;
+ cursor: pointer;
+}
+
+
+/************************************************************************************************
+ Master Layout
+*************************************************************************************************/
+#fbChrome {
+ position: fixed;
+ overflow: hidden;
+ height: 100%;
+ width: 100%;
+ border-collapse: collapse;
+ background: #fff;
+}
+
+#fbTop {
+ height: 49px;
+}
+
+#fbToolbar {
+ position: absolute;
+ z-index: 5;
+ width: 100%;
+ top: 0;
+ background: url(sprite.png) #f1f2ee 0 0;
+ height: 27px;
+ font-size: 11px;
+ overflow: hidden;
+}
+
+#fbPanelBarBox {
+ top: 27px;
+ position: absolute;
+ z-index: 8;
+ width: 100%;
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ height: 22px;
+}
+
+#fbContent {
+ height: 100%;
+ vertical-align: top;
+}
+
+#fbBottom {
+ height: 18px;
+ background: #fff;
+}
+
+/************************************************************************************************
+ Sub-Layout
+*************************************************************************************************/
+
+/* fbToolbar
+*************************************************************************************************/
+#fbToolbarIcon {
+ float: left;
+ padding: 4px 5px 0;
+}
+
+#fbToolbarIcon a {
+ display: block;
+ height: 20px;
+ width: 20px;
+ background: url(sprite.png) 0 -135px;
+ text-decoration: none;
+ cursor: default;
+}
+
+#fbToolbarButtons {
+ float: left;
+ padding: 4px 2px 0 5px;
+}
+
+#fbToolbarButtons a {
+ text-decoration: none;
+ display: block;
+ float: left;
+ color: #000;
+ padding: 4px 8px 4px;
+ cursor: default;
+}
+
+#fbToolbarButtons a:hover {
+ color: #333;
+ padding: 3px 7px 3px;
+ border: 1px solid #fff;
+ border-bottom: 1px solid #bbb;
+ border-right: 1px solid #bbb;
+}
+
+#fbStatusBarBox {
+ position: relative;
+ top: 5px;
+ line-height: 19px;
+ cursor: default;
+}
+
+.fbToolbarSeparator{
+ overflow: hidden;
+ border: 1px solid;
+ border-color: transparent #fff transparent #777;
+ _border-color: #eee #fff #eee #777;
+ height: 7px;
+ margin: 10px 6px 0 0;
+ float: left;
+}
+
+.fbStatusBar span {
+ color: #808080;
+ padding: 0 4px 0 0;
+}
+
+.fbStatusBar span a {
+ text-decoration: none;
+ color: black;
+}
+
+.fbStatusBar span a:hover {
+ color: blue;
+ cursor: pointer;
+}
+
+
+#fbWindowButtons {
+ position: absolute;
+ white-space: nowrap;
+ right: 0;
+ top: 0;
+ height: 17px;
+ _width: 50px;
+ padding: 5px 0 5px 5px;
+ z-index: 6;
+ background: url(sprite.png) #f1f2ee 0 0;
+}
+
+/* fbPanelBarBox
+*************************************************************************************************/
+
+#fbPanelBar1 {
+ width: 255px; /* fixed width to avoid tabs breaking line */
+ z-index: 8;
+ left: 0;
+ white-space: nowrap;
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ position: absolute;
+ left: 4px;
+}
+
+#fbPanelBar2Box {
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ position: absolute;
+ height: 22px;
+ width: 300px; /* fixed width to avoid tabs breaking line */
+ z-index: 9;
+ right: 0;
+}
+
+#fbPanelBar2 {
+ position: absolute;
+ width: 290px; /* fixed width to avoid tabs breaking line */
+ height: 22px;
+ padding-left: 10px;
+}
+
+/* body
+*************************************************************************************************/
+.fbPanel {
+ display: none;
+}
+
+#fbPanelBox1, #fbPanelBox2 {
+ max-height: inherit;
+ height: 100%;
+ font-size: 11px;
+}
+
+#fbPanelBox2 {
+ background: #fff;
+}
+
+#fbPanelBox2 {
+ width: 300px;
+ background: #fff;
+}
+
+#fbPanel2 {
+ padding-left: 6px;
+ background: #fff;
+}
+
+.hide {
+ overflow: hidden !important;
+ position: fixed !important;
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* fbBottom
+*************************************************************************************************/
+
+#fbCommand {
+ height: 18px;
+}
+
+#fbCommandBox {
+ position: absolute;
+ width: 100%;
+ height: 18px;
+ bottom: 0;
+ overflow: hidden;
+ z-index: 9;
+ background: #fff;
+ border: 0;
+ border-top: 1px solid #ccc;
+}
+
+#fbCommandIcon {
+ position: absolute;
+ color: #00f;
+ top: 2px;
+ left: 7px;
+ display: inline;
+ font: 11px Monaco, monospace;
+ z-index: 10;
+}
+
+#fbCommandLine {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ left: 0;
+ border: 0;
+ margin: 0;
+ padding: 2px 0 2px 32px;
+ font: 11px Monaco, monospace;
+ z-index: 9;
+}
+
+div.fbFitHeight {
+ overflow: auto;
+ _position: absolute;
+}
+
+
+/************************************************************************************************
+ Layout Controls
+*************************************************************************************************/
+
+/* fbToolbar buttons
+*************************************************************************************************/
+#fbWindowButtons a {
+ font-size: 1px;
+ width: 16px;
+ height: 16px;
+ display: block;
+ float: right;
+ margin-right: 4px;
+ text-decoration: none;
+ cursor: default;
+}
+
+#fbWindow_btClose {
+ background: url(sprite.png) 0 -119px;
+}
+
+#fbWindow_btClose:hover {
+ background: url(sprite.png) -16px -119px;
+}
+
+#fbWindow_btDetach {
+ background: url(sprite.png) -32px -119px;
+}
+
+#fbWindow_btDetach:hover {
+ background: url(sprite.png) -48px -119px;
+}
+
+/* fbPanelBarBox tabs
+*************************************************************************************************/
+.fbTab {
+ text-decoration: none;
+ display: none;
+ float: left;
+ width: auto;
+ float: left;
+ cursor: default;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ height: 22px;
+ color: #565656;
+}
+
+.fbPanelBar span {
+ display: block;
+ float: left;
+}
+
+.fbPanelBar .fbTabL,.fbPanelBar .fbTabR {
+ height: 22px;
+ width: 8px;
+}
+
+.fbPanelBar .fbTabText {
+ padding: 4px 1px 0;
+}
+
+a.fbTab:hover {
+ background: url(sprite.png) 0 -73px;
+}
+
+a.fbTab:hover .fbTabL {
+ background: url(sprite.png) -16px -96px;
+}
+
+a.fbTab:hover .fbTabR {
+ background: url(sprite.png) -24px -96px;
+}
+
+.fbSelectedTab {
+ background: url(sprite.png) #f1f2ee 0 -50px !important;
+ color: #000;
+}
+
+.fbSelectedTab .fbTabL {
+ background: url(sprite.png) 0 -96px !important;
+}
+
+.fbSelectedTab .fbTabR {
+ background: url(sprite.png) -8px -96px !important;
+}
+
+/* splitters
+*************************************************************************************************/
+#fbHSplitter {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 5px;
+ overflow: hidden;
+ cursor: n-resize !important;
+ background: url(pixel_transparent.gif);
+ z-index: 9;
+}
+
+#fbHSplitter.fbOnMovingHSplitter {
+ height: 100%;
+ z-index: 100;
+}
+
+.fbVSplitter {
+ background: #ece9d8;
+ color: #000;
+ border: 1px solid #716f64;
+ border-width: 0 1px;
+ border-left-color: #aca899;
+ width: 4px;
+ cursor: e-resize;
+ overflow: hidden;
+ right: 294px;
+ text-decoration: none;
+ z-index: 9;
+ position: absolute;
+ height: 100%;
+ top: 27px;
+ _width: 6px;
+}
+
+/************************************************************************************************/
+div.lineNo {
+ font: 11px Monaco, monospace;
+ float: left;
+ display: inline;
+ position: relative;
+ margin: 0;
+ padding: 0 5px 0 20px;
+ background: #eee;
+ color: #888;
+ border-right: 1px solid #ccc;
+ text-align: right;
+}
+
+pre.nodeCode {
+ font: 11px Monaco, monospace;
+ margin: 0;
+ padding-left: 10px;
+ overflow: hidden;
+ /*
+ _width: 100%;
+ /**/
+}
+
+/************************************************************************************************/
+.nodeControl {
+ margin-top: 3px;
+ margin-left: -14px;
+ float: left;
+ width: 9px;
+ height: 9px;
+ overflow: hidden;
+ cursor: default;
+ background: url(tree_open.gif);
+ _float: none;
+ _display: inline;
+ _position: absolute;
+}
+
+div.nodeMaximized {
+ background: url(tree_close.gif);
+}
+
+div.objectBox-element {
+ padding: 1px 3px;
+}
+.objectBox-selector{
+ cursor: default;
+}
+
+.selectedElement{
+ background: highlight;
+ /* background: url(roundCorner.svg); Opera */
+ color: #fff !important;
+}
+.selectedElement span{
+ color: #fff !important;
+}
+
+/* Webkit CSS Hack - bug in "highlight" named color */
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ .selectedElement{
+ background: #316AC5;
+ color: #fff !important;
+ }
+}
+
+/************************************************************************************************/
+/************************************************************************************************/
+.logRow * {
+ font-size: 11px;
+}
+
+.logRow {
+ position: relative;
+ border-bottom: 1px solid #D7D7D7;
+ padding: 2px 4px 1px 6px;
+ background-color: #FFFFFF;
+}
+
+.logRow-command {
+ font-family: Monaco, monospace;
+ color: blue;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectBox-function,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ font-family: Monaco, monospace;
+}
+
+.objectBox-null {
+ padding: 0 2px;
+ border: 1px solid #666666;
+ background-color: #888888;
+ color: #FFFFFF;
+}
+
+.objectBox-string {
+ color: red;
+ white-space: pre;
+}
+
+.objectBox-number {
+ color: #000088;
+}
+
+.objectBox-function {
+ color: DarkGreen;
+}
+
+.objectBox-object {
+ color: DarkGreen;
+ font-weight: bold;
+ font-family: Lucida Grande, sans-serif;
+}
+
+.objectBox-array {
+ color: #000;
+}
+
+/************************************************************************************************/
+.logRow-info,.logRow-error,.logRow-warning {
+ background: #fff no-repeat 2px 2px;
+ padding-left: 20px;
+ padding-bottom: 3px;
+}
+
+.logRow-info {
+ background-image: url(infoIcon.png);
+}
+
+.logRow-warning {
+ background-color: cyan;
+ background-image: url(warningIcon.png);
+}
+
+.logRow-error {
+ background-color: LightYellow;
+ background-image: url(errorIcon.png);
+ color: #f00;
+}
+
+.errorMessage {
+ vertical-align: top;
+ color: #f00;
+}
+
+.objectBox-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ color: #0000FF;
+}
+
+/************************************************************************************************/
+.logRow-group {
+ background: #EEEEEE;
+ border-bottom: none;
+}
+
+.logGroup {
+ background: #EEEEEE;
+}
+
+.logGroupBox {
+ margin-left: 24px;
+ border-top: 1px solid #D7D7D7;
+ border-left: 1px solid #D7D7D7;
+}
+
+/************************************************************************************************/
+.selectorTag,.selectorId,.selectorClass {
+ font-family: Monaco, monospace;
+ font-weight: normal;
+}
+
+.selectorTag {
+ color: #0000FF;
+}
+
+.selectorId {
+ color: DarkBlue;
+}
+
+.selectorClass {
+ color: red;
+}
+
+/************************************************************************************************/
+.objectBox-element {
+ font-family: Monaco, monospace;
+ color: #000088;
+}
+
+.nodeChildren {
+ padding-left: 26px;
+}
+
+.nodeTag {
+ color: blue;
+ cursor: pointer;
+}
+
+.nodeValue {
+ color: #FF0000;
+ font-weight: normal;
+}
+
+.nodeText,.nodeComment {
+ margin: 0 2px;
+ vertical-align: top;
+}
+
+.nodeText {
+ color: #333333;
+ font-family: Monaco, monospace;
+}
+
+.nodeComment {
+ color: DarkGreen;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeHidden, .nodeHidden * {
+ color: #888888;
+}
+
+.nodeHidden .nodeTag {
+ color: #5F82D9;
+}
+
+.nodeHidden .nodeValue {
+ color: #D86060;
+}
+
+.selectedElement .nodeHidden, .selectedElement .nodeHidden * {
+ color: SkyBlue !important;
+}
+
+
+/************************************************************************************************/
+.log-object {
+ /*
+ _position: relative;
+ _height: 100%;
+ /**/
+}
+
+.property {
+ position: relative;
+ clear: both;
+ height: 15px;
+}
+
+.propertyNameCell {
+ vertical-align: top;
+ float: left;
+ width: 28%;
+ position: absolute;
+ left: 0;
+ z-index: 0;
+}
+
+.propertyValueCell {
+ float: right;
+ width: 68%;
+ background: #fff;
+ position: absolute;
+ padding-left: 5px;
+ display: table-cell;
+ right: 0;
+ z-index: 1;
+ /*
+ _position: relative;
+ /**/
+}
+
+.propertyName {
+ font-weight: bold;
+}
+
+.FirebugPopup {
+ height: 100% !important;
+}
+
+.FirebugPopup #fbWindowButtons {
+ display: none !important;
+}
+
+.FirebugPopup #fbHSplitter {
+ display: none !important;
+}
diff --git a/vendor/firebug-lite/skin/xp/firebug.IE6.css b/vendor/firebug-lite/skin/xp/firebug.IE6.css
new file mode 100644
index 000000000..14f8aa87e
--- /dev/null
+++ b/vendor/firebug-lite/skin/xp/firebug.IE6.css
@@ -0,0 +1,20 @@
+/************************************************************************************************/
+#fbToolbarSearch {
+ background-image: url(search.gif) !important;
+}
+/************************************************************************************************/
+.fbErrors {
+ background-image: url(errorIcon.gif) !important;
+}
+/************************************************************************************************/
+.logRow-info {
+ background-image: url(infoIcon.gif) !important;
+}
+
+.logRow-warning {
+ background-image: url(warningIcon.gif) !important;
+}
+
+.logRow-error {
+ background-image: url(errorIcon.gif) !important;
+}
diff --git a/vendor/firebug-lite/skin/xp/firebug.css b/vendor/firebug-lite/skin/xp/firebug.css
new file mode 100644
index 000000000..cc33761c4
--- /dev/null
+++ b/vendor/firebug-lite/skin/xp/firebug.css
@@ -0,0 +1,3147 @@
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Loose */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*
+.netInfoResponseHeadersTitle, netInfoResponseHeadersBody {
+ display: none;
+}
+/**/
+
+.obscured {
+ left: -999999px !important;
+}
+
+/* IE6 need a separated rule, otherwise it will not recognize it */
+.collapsed {
+ display: none;
+}
+
+[collapsed="true"] {
+ display: none;
+}
+
+#fbCSS {
+ padding: 0 !important;
+}
+
+.cssPropDisable {
+ float: left;
+ display: block;
+ width: 2em;
+ cursor: default;
+}
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* panelBase */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/************************************************************************************************/
+
+.infoTip {
+ z-index: 2147483647;
+ position: fixed;
+ padding: 2px 3px;
+ border: 1px solid #CBE087;
+ background: LightYellow;
+ font-family: Monaco, monospace;
+ color: #000000;
+ display: none;
+ white-space: nowrap;
+ pointer-events: none;
+}
+
+.infoTip[active="true"] {
+ display: block;
+}
+
+.infoTipLoading {
+ width: 16px;
+ height: 16px;
+ background: url(chrome://firebug/skin/loading_16.gif) no-repeat;
+}
+
+.infoTipImageBox {
+ font-size: 11px;
+ min-width: 100px;
+ text-align: center;
+}
+
+.infoTipCaption {
+ font-size: 11px;
+ font: Monaco, monospace;
+}
+
+.infoTipLoading > .infoTipImage,
+.infoTipLoading > .infoTipCaption {
+ display: none;
+}
+
+/************************************************************************************************/
+
+h1.groupHeader {
+ padding: 2px 4px;
+ margin: 0 0 4px 0;
+ border-top: 1px solid #CCCCCC;
+ border-bottom: 1px solid #CCCCCC;
+ background: #eee url(group.gif) repeat-x;
+ font-size: 11px;
+ font-weight: bold;
+ _position: relative;
+}
+
+/************************************************************************************************/
+
+.inlineEditor,
+.fixedWidthEditor {
+ z-index: 2147483647;
+ position: absolute;
+ display: none;
+}
+
+.inlineEditor {
+ margin-left: -6px;
+ margin-top: -3px;
+ /*
+ _margin-left: -7px;
+ _margin-top: -5px;
+ /**/
+}
+
+.textEditorInner,
+.fixedWidthEditor {
+ margin: 0 0 0 0 !important;
+ padding: 0;
+ border: none !important;
+ font: inherit;
+ text-decoration: inherit;
+ background-color: #FFFFFF;
+}
+
+.fixedWidthEditor {
+ border-top: 1px solid #888888 !important;
+ border-bottom: 1px solid #888888 !important;
+}
+
+.textEditorInner {
+ position: relative;
+ top: -7px;
+ left: -5px;
+
+ outline: none;
+ resize: none;
+
+ /*
+ _border: 1px solid #999 !important;
+ _padding: 1px !important;
+ _filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="#55404040");
+ /**/
+}
+
+.textEditorInner1 {
+ padding-left: 11px;
+ background: url(textEditorBorders.png) repeat-y;
+ _background: url(textEditorBorders.gif) repeat-y;
+ _overflow: hidden;
+}
+
+.textEditorInner2 {
+ position: relative;
+ padding-right: 2px;
+ background: url(textEditorBorders.png) repeat-y 100% 0;
+ _background: url(textEditorBorders.gif) repeat-y 100% 0;
+ _position: fixed;
+}
+
+.textEditorTop1 {
+ background: url(textEditorCorners.png) no-repeat 100% 0;
+ margin-left: 11px;
+ height: 10px;
+ _background: url(textEditorCorners.gif) no-repeat 100% 0;
+ _overflow: hidden;
+}
+
+.textEditorTop2 {
+ position: relative;
+ left: -11px;
+ width: 11px;
+ height: 10px;
+ background: url(textEditorCorners.png) no-repeat;
+ _background: url(textEditorCorners.gif) no-repeat;
+}
+
+.textEditorBottom1 {
+ position: relative;
+ background: url(textEditorCorners.png) no-repeat 100% 100%;
+ margin-left: 11px;
+ height: 12px;
+ _background: url(textEditorCorners.gif) no-repeat 100% 100%;
+}
+
+.textEditorBottom2 {
+ position: relative;
+ left: -11px;
+ width: 11px;
+ height: 12px;
+ background: url(textEditorCorners.png) no-repeat 0 100%;
+ _background: url(textEditorCorners.gif) no-repeat 0 100%;
+}
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* CSS */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/* See license.txt for terms of usage */
+
+.panelNode-css {
+ overflow-x: hidden;
+}
+
+.cssSheet > .insertBefore {
+ height: 1.5em;
+}
+
+.cssRule {
+ position: relative;
+ margin: 0;
+ padding: 1em 0 0 6px;
+ font-family: Monaco, monospace;
+ color: #000000;
+}
+
+.cssRule:first-child {
+ padding-top: 6px;
+}
+
+.cssElementRuleContainer {
+ position: relative;
+}
+
+.cssHead {
+ padding-right: 150px;
+}
+
+.cssProp {
+ /*padding-left: 2em;*/
+}
+
+.cssPropName {
+ color: DarkGreen;
+}
+
+.cssPropValue {
+ margin-left: 8px;
+ color: DarkBlue;
+}
+
+.cssOverridden span {
+ text-decoration: line-through;
+}
+
+.cssInheritedRule {
+}
+
+.cssInheritLabel {
+ margin-right: 0.5em;
+ font-weight: bold;
+}
+
+.cssRule .objectLink-sourceLink {
+ top: 0;
+}
+
+.cssProp.editGroup:hover {
+ background: url(disable.png) no-repeat 2px 1px;
+ _background: url(disable.gif) no-repeat 2px 1px;
+}
+
+.cssProp.editGroup.editing {
+ background: none;
+}
+
+.cssProp.disabledStyle {
+ background: url(disableHover.png) no-repeat 2px 1px;
+ _background: url(disableHover.gif) no-repeat 2px 1px;
+ opacity: 1;
+ color: #CCCCCC;
+}
+
+.disabledStyle .cssPropName,
+.disabledStyle .cssPropValue {
+ color: #CCCCCC;
+}
+
+.cssPropValue.editing + .cssSemi,
+.inlineExpander + .cssSemi {
+ display: none;
+}
+
+.cssPropValue.editing {
+ white-space: nowrap;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.stylePropName {
+ font-weight: bold;
+ padding: 0 4px 4px 4px;
+ width: 50%;
+}
+
+.stylePropValue {
+ width: 50%;
+}
+/*
+.useA11y .a11yCSSView .focusRow:focus {
+ outline: none;
+ background-color: transparent
+ }
+
+ .useA11y .a11yCSSView .focusRow:focus .cssSelector,
+ .useA11y .a11yCSSView .focusRow:focus .cssPropName,
+ .useA11y .a11yCSSView .focusRow:focus .cssPropValue,
+ .useA11y .a11yCSSView .computedStyleRow:focus,
+ .useA11y .a11yCSSView .groupHeader:focus {
+ outline: 2px solid #FF9933;
+ outline-offset: -2px;
+ background-color: #FFFFD6;
+ }
+
+ .useA11y .a11yCSSView .groupHeader:focus {
+ outline-offset: -2px;
+ }
+/**/
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Net */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/* See license.txt for terms of usage */
+
+.panelNode-net {
+ overflow-x: hidden;
+}
+
+.netTable {
+ width: 100%;
+}
+
+/************************************************************************************************/
+
+.hideCategory-undefined .category-undefined,
+.hideCategory-html .category-html,
+.hideCategory-css .category-css,
+.hideCategory-js .category-js,
+.hideCategory-image .category-image,
+.hideCategory-xhr .category-xhr,
+.hideCategory-flash .category-flash,
+.hideCategory-txt .category-txt,
+.hideCategory-bin .category-bin {
+ display: none;
+}
+
+/************************************************************************************************/
+
+.netHeadRow {
+ background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF;
+}
+
+.netHeadCol {
+ border-bottom: 1px solid #CCCCCC;
+ padding: 2px 4px 2px 18px;
+ font-weight: bold;
+}
+
+.netHeadLabel {
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+/************************************************************************************************/
+/* Header for Net panel table */
+
+.netHeaderRow {
+ height: 16px;
+}
+
+.netHeaderCell {
+ cursor: pointer;
+ -moz-user-select: none;
+ border-bottom: 1px solid #9C9C9C;
+ padding: 0 !important;
+ font-weight: bold;
+ background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x;
+ white-space: nowrap;
+}
+
+.netHeaderRow > .netHeaderCell:first-child > .netHeaderCellBox {
+ padding: 2px 14px 2px 18px;
+}
+
+.netHeaderCellBox {
+ padding: 2px 14px 2px 10px;
+ border-left: 1px solid #D9D9D9;
+ border-right: 1px solid #9C9C9C;
+}
+
+.netHeaderCell:hover:active {
+ background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x;
+}
+
+.netHeaderSorted {
+ background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;
+}
+
+.netHeaderSorted > .netHeaderCellBox {
+ border-right-color: #6B7C93;
+ background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;
+}
+
+.netHeaderSorted.sortedAscending > .netHeaderCellBox {
+ background-image: url(chrome://firebug/skin/arrowUp.png);
+}
+
+.netHeaderSorted:hover:active {
+ background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;
+}
+
+/************************************************************************************************/
+/* Breakpoints */
+
+.panelNode-net .netRowHeader {
+ display: block;
+}
+
+.netRowHeader {
+ cursor: pointer;
+ display: none;
+ height: 15px;
+ margin-right: 0 !important;
+}
+
+/* Display brekpoint disc */
+.netRow .netRowHeader {
+ background-position: 5px 1px;
+}
+
+.netRow[breakpoint="true"] .netRowHeader {
+ background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader {
+ background-image: url(chrome://firebug/skin/breakpointDisabled.png);
+}
+
+.netRow.category-xhr:hover .netRowHeader {
+ background-color: #F6F6F6;
+}
+
+#netBreakpointBar {
+ max-width: 38px;
+}
+
+#netHrefCol > .netHeaderCellBox {
+ border-left: 0px;
+}
+
+.netRow .netRowHeader {
+ width: 3px;
+}
+
+.netInfoRow .netRowHeader {
+ display: table-cell;
+}
+
+/************************************************************************************************/
+/* Column visibility */
+
+.netTable[hiddenCols~=netHrefCol] TD[id="netHrefCol"],
+.netTable[hiddenCols~=netHrefCol] TD.netHrefCol,
+.netTable[hiddenCols~=netStatusCol] TD[id="netStatusCol"],
+.netTable[hiddenCols~=netStatusCol] TD.netStatusCol,
+.netTable[hiddenCols~=netDomainCol] TD[id="netDomainCol"],
+.netTable[hiddenCols~=netDomainCol] TD.netDomainCol,
+.netTable[hiddenCols~=netSizeCol] TD[id="netSizeCol"],
+.netTable[hiddenCols~=netSizeCol] TD.netSizeCol,
+.netTable[hiddenCols~=netTimeCol] TD[id="netTimeCol"],
+.netTable[hiddenCols~=netTimeCol] TD.netTimeCol {
+ display: none;
+}
+
+/************************************************************************************************/
+
+.netRow {
+ background: LightYellow;
+}
+
+.netRow.loaded {
+ background: #FFFFFF;
+}
+
+.netRow.loaded:hover {
+ background: #EFEFEF;
+}
+
+.netCol {
+ padding: 0;
+ vertical-align: top;
+ border-bottom: 1px solid #EFEFEF;
+ white-space: nowrap;
+ height: 17px;
+}
+
+.netLabel {
+ width: 100%;
+}
+
+.netStatusCol {
+ padding-left: 10px;
+ color: rgb(128, 128, 128);
+}
+
+.responseError > .netStatusCol {
+ color: red;
+}
+
+.netDomainCol {
+ padding-left: 5px;
+}
+
+.netSizeCol {
+ text-align: right;
+ padding-right: 10px;
+}
+
+.netHrefLabel {
+ -moz-box-sizing: padding-box;
+ overflow: hidden;
+ z-index: 10;
+ position: absolute;
+ padding-left: 18px;
+ padding-top: 1px;
+ max-width: 15%;
+ font-weight: bold;
+}
+
+.netFullHrefLabel {
+ display: none;
+ -moz-user-select: none;
+ padding-right: 10px;
+ padding-bottom: 3px;
+ max-width: 100%;
+ background: #FFFFFF;
+ z-index: 200;
+}
+
+.netHrefCol:hover > .netFullHrefLabel {
+ display: block;
+}
+
+.netRow.loaded:hover .netCol > .netFullHrefLabel {
+ background-color: #EFEFEF;
+}
+
+.useA11y .a11yShowFullLabel {
+ display: block;
+ background-image: none !important;
+ border: 1px solid #CBE087;
+ background-color: LightYellow;
+ font-family: Monaco, monospace;
+ color: #000000;
+ font-size: 10px;
+ z-index: 2147483647;
+}
+
+.netSizeLabel {
+ padding-left: 6px;
+}
+
+.netStatusLabel,
+.netDomainLabel,
+.netSizeLabel,
+.netBar {
+ padding: 1px 0 2px 0 !important;
+}
+
+.responseError {
+ color: red;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.hasHeaders .netHrefLabel:hover {
+ cursor: pointer;
+ color: blue;
+ text-decoration: underline;
+}
+
+/************************************************************************************************/
+
+.netLoadingIcon {
+ position: absolute;
+ border: 0;
+ margin-left: 14px;
+ width: 16px;
+ height: 16px;
+ background: transparent no-repeat 0 0;
+ background-image: url(chrome://firebug/skin/loading_16.gif);
+ display:inline-block;
+}
+
+.loaded .netLoadingIcon {
+ display: none;
+}
+
+/************************************************************************************************/
+
+.netBar, .netSummaryBar {
+ position: relative;
+ border-right: 50px solid transparent;
+}
+
+.netResolvingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #FFFFFF url(chrome://firebug/skin/netBarResolving.gif) repeat-x;
+ z-index:60;
+}
+
+.netConnectingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #FFFFFF url(chrome://firebug/skin/netBarConnecting.gif) repeat-x;
+ z-index:50;
+}
+
+.netBlockingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #FFFFFF url(chrome://firebug/skin/netBarWaiting.gif) repeat-x;
+ z-index:40;
+}
+
+.netSendingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #FFFFFF url(chrome://firebug/skin/netBarSending.gif) repeat-x;
+ z-index:30;
+}
+
+.netWaitingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #FFFFFF url(chrome://firebug/skin/netBarResponded.gif) repeat-x;
+ z-index:20;
+ min-width: 1px;
+}
+
+.netReceivingBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ background: #38D63B url(chrome://firebug/skin/netBarLoading.gif) repeat-x;
+ z-index:10;
+}
+
+.netWindowLoadBar,
+.netContentLoadBar {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: red;
+ z-index: 70;
+ opacity: 0.5;
+ display: none;
+ margin-bottom:-1px;
+}
+
+.netContentLoadBar {
+ background-color: Blue;
+}
+
+.netTimeLabel {
+ -moz-box-sizing: padding-box;
+ position: absolute;
+ top: 1px;
+ left: 100%;
+ padding-left: 6px;
+ color: #444444;
+ min-width: 16px;
+}
+
+/*
+ * Timing info tip is reusing net timeline styles to display the same
+ * colors for individual request phases. Notice that the info tip must
+ * respect also loaded and fromCache styles that also modify the
+ * actual color. These are used both on the same element in case
+ * of the tooltip.
+ */
+.loaded .netReceivingBar,
+.loaded.netReceivingBar {
+ background: #B6B6B6 url(chrome://firebug/skin/netBarLoaded.gif) repeat-x;
+ border-color: #B6B6B6;
+}
+
+.fromCache .netReceivingBar,
+.fromCache.netReceivingBar {
+ background: #D6D6D6 url(chrome://firebug/skin/netBarCached.gif) repeat-x;
+ border-color: #D6D6D6;
+}
+
+.netSummaryRow .netTimeLabel,
+.loaded .netTimeLabel {
+ background: transparent;
+}
+
+/************************************************************************************************/
+/* Time Info tip */
+
+.timeInfoTip {
+ width: 150px;
+ height: 40px
+}
+
+.timeInfoTipBar,
+.timeInfoTipEventBar {
+ position: relative;
+ display: block;
+ margin: 0;
+ opacity: 1;
+ height: 15px;
+ width: 4px;
+}
+
+.timeInfoTipEventBar {
+ width: 1px !important;
+}
+
+.timeInfoTipCell.startTime {
+ padding-right: 8px;
+}
+
+.timeInfoTipCell.elapsedTime {
+ text-align: right;
+ padding-right: 8px;
+}
+
+/************************************************************************************************/
+/* Size Info tip */
+
+.sizeInfoLabelCol {
+ font-weight: bold;
+ padding-right: 10px;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+}
+
+.sizeInfoSizeCol {
+ font-weight: bold;
+}
+
+.sizeInfoDetailCol {
+ color: gray;
+ text-align: right;
+}
+
+.sizeInfoDescCol {
+ font-style: italic;
+}
+
+/************************************************************************************************/
+/* Summary */
+
+.netSummaryRow .netReceivingBar {
+ background: #BBBBBB;
+ border: none;
+}
+
+.netSummaryLabel {
+ color: #222222;
+}
+
+.netSummaryRow {
+ background: #BBBBBB !important;
+ font-weight: bold;
+}
+
+.netSummaryRow .netBar {
+ border-right-color: #BBBBBB;
+}
+
+.netSummaryRow > .netCol {
+ border-top: 1px solid #999999;
+ border-bottom: 2px solid;
+ -moz-border-bottom-colors: #EFEFEF #999999;
+ padding-top: 1px;
+ padding-bottom: 2px;
+}
+
+.netSummaryRow > .netHrefCol:hover {
+ background: transparent !important;
+}
+
+.netCountLabel {
+ padding-left: 18px;
+}
+
+.netTotalSizeCol {
+ text-align: right;
+ padding-right: 10px;
+}
+
+.netTotalTimeCol {
+ text-align: right;
+}
+
+.netCacheSizeLabel {
+ position: absolute;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+}
+
+/************************************************************************************************/
+
+.netLimitRow {
+ background: rgb(255, 255, 225) !important;
+ font-weight:normal;
+ color: black;
+ font-weight:normal;
+}
+
+.netLimitLabel {
+ padding-left: 18px;
+}
+
+.netLimitRow > .netCol {
+ border-bottom: 2px solid;
+ -moz-border-bottom-colors: #EFEFEF #999999;
+ vertical-align: middle !important;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+.netLimitButton {
+ font-size: 11px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+/************************************************************************************************/
+
+.netInfoCol {
+ border-top: 1px solid #EEEEEE;
+ background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF;
+}
+
+.netInfoBody {
+ margin: 10px 0 4px 10px;
+}
+
+.netInfoTabs {
+ position: relative;
+ padding-left: 17px;
+}
+
+.netInfoTab {
+ position: relative;
+ top: -3px;
+ margin-top: 10px;
+ padding: 4px 6px;
+ border: 1px solid transparent;
+ border-bottom: none;
+ _border: none;
+ font-weight: bold;
+ color: #565656;
+ cursor: pointer;
+}
+
+/*.netInfoTab:hover {
+ cursor: pointer;
+}*/
+
+/* replaced by .netInfoTabSelected for IE6 support
+.netInfoTab[selected="true"] {
+ cursor: default !important;
+ border: 1px solid #D7D7D7 !important;
+ border-bottom: none !important;
+ -moz-border-radius: 4px 4px 0 0;
+ background-color: #FFFFFF;
+}
+/**/
+.netInfoTabSelected {
+ cursor: default !important;
+ border: 1px solid #D7D7D7 !important;
+ border-bottom: none !important;
+ -moz-border-radius: 4px 4px 0 0;
+ -webkit-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+ background-color: #FFFFFF;
+}
+
+.logRow-netInfo.error .netInfoTitle {
+ color: red;
+}
+
+.logRow-netInfo.loading .netInfoResponseText {
+ font-style: italic;
+ color: #888888;
+}
+
+.loading .netInfoResponseHeadersTitle {
+ display: none;
+}
+
+.netInfoResponseSizeLimit {
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ padding-top: 10px;
+ font-size: 11px;
+}
+
+.netInfoText {
+ display: none;
+ margin: 0;
+ border: 1px solid #D7D7D7;
+ border-right: none;
+ padding: 8px;
+ background-color: #FFFFFF;
+ font-family: Monaco, monospace;
+ white-space: pre-wrap;
+ /*overflow-x: auto; HTML is damaged in case of big (2-3MB) responses */
+}
+
+/* replaced by .netInfoTextSelected for IE6 support
+.netInfoText[selected="true"] {
+ display: block;
+}
+/**/
+.netInfoTextSelected {
+ display: block;
+}
+
+.netInfoParamName {
+ padding-right: 10px;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-weight: bold;
+ vertical-align: top;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.netInfoPostText .netInfoParamName {
+ width: 1px; /* Google Chrome need this otherwise the first column of
+ the post variables table will be larger than expected */
+}
+
+.netInfoParamValue {
+ width: 100%;
+}
+
+.netInfoHeadersText,
+.netInfoPostText,
+.netInfoPutText {
+ padding-top: 0;
+}
+
+.netInfoHeadersGroup,
+.netInfoPostParams,
+.netInfoPostSource {
+ margin-bottom: 4px;
+ border-bottom: 1px solid #D7D7D7;
+ padding-top: 8px;
+ padding-bottom: 2px;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-weight: bold;
+ color: #565656;
+}
+
+.netInfoPostParamsTable,
+.netInfoPostPartsTable,
+.netInfoPostJSONTable,
+.netInfoPostXMLTable,
+.netInfoPostSourceTable {
+ margin-bottom: 10px;
+ width: 100%;
+}
+
+.netInfoPostContentType {
+ color: #bdbdbd;
+ padding-left: 50px;
+ font-weight: normal;
+}
+
+.netInfoHtmlPreview {
+ border: 0;
+ width: 100%;
+ height:100%;
+}
+
+/************************************************************************************************/
+/* Request & Response Headers */
+
+.netHeadersViewSource {
+ color: #bdbdbd;
+ margin-left: 200px;
+ font-weight: normal;
+}
+
+.netHeadersViewSource:hover {
+ color: blue;
+ cursor: pointer;
+}
+
+/************************************************************************************************/
+
+.netActivationRow,
+.netPageSeparatorRow {
+ background: rgb(229, 229, 229) !important;
+ font-weight: normal;
+ color: black;
+}
+
+.netActivationLabel {
+ background: url(chrome://firebug/skin/infoIcon.png) no-repeat 3px 2px;
+ padding-left: 22px;
+}
+
+/************************************************************************************************/
+
+.netPageSeparatorRow {
+ height: 5px !important;
+}
+
+.netPageSeparatorLabel {
+ padding-left: 22px;
+ height: 5px !important;
+}
+
+.netPageRow {
+ background-color: rgb(255, 255, 255);
+}
+
+.netPageRow:hover {
+ background: #EFEFEF;
+}
+
+.netPageLabel {
+ padding: 1px 0 2px 18px !important;
+ font-weight: bold;
+}
+
+/************************************************************************************************/
+
+.netActivationRow > .netCol {
+ border-bottom: 2px solid;
+ -moz-border-bottom-colors: #EFEFEF #999999;
+ padding-top: 2px;
+ padding-bottom: 3px;
+}
+/*
+.useA11y .panelNode-net .a11yFocus:focus,
+.useA11y .panelNode-net .focusRow:focus {
+ outline-offset: -2px;
+ background-color: #FFFFD6 !important;
+}
+
+.useA11y .panelNode-net .netHeaderCell:focus,
+.useA11y .panelNode-net :focus .netHeaderCell,
+.useA11y .panelNode-net :focus .netReceivingBar,
+.useA11y .netSummaryRow :focus .netBar,
+.useA11y .netSummaryRow:focus .netBar {
+ background-color: #FFFFD6;
+ background-image: none;
+ border-color: #FFFFD6;
+}
+/**/
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Windows */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/************************************************************************************************/
+/* Twisties */
+
+.twisty,
+.logRow-errorMessage > .hasTwisty > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty,
+.logRow-spy .spyHead .spyTitle,
+.logGroup > .logRow,
+.memberRow.hasChildren > .memberLabelCell > .memberLabel,
+.hasHeaders .netHrefLabel,
+.netPageRow > .netCol > .netPageTitle {
+ background-image: url(tree_open.gif);
+ background-repeat: no-repeat;
+ background-position: 2px 2px;
+ min-height: 12px;
+}
+
+.logRow-errorMessage > .hasTwisty.opened > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty.opened,
+.logRow-spy.opened .spyHead .spyTitle,
+.logGroup.opened > .logRow,
+.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel,
+.nodeBox.highlightOpen > .nodeLabel > .twisty,
+.nodeBox.open > .nodeLabel > .twisty,
+.netRow.opened > .netCol > .netHrefLabel,
+.netPageRow.opened > .netCol > .netPageTitle {
+ background-image: url(tree_close.gif);
+}
+
+.twisty {
+ background-position: 4px 4px;
+}
+
+
+
+/************************************************************************************************/
+/* Twisties IE6 */
+
+/* IE6 has problems with > operator, and multiple classes */
+
+* html .logRow-spy .spyHead .spyTitle,
+* html .logGroup .logGroupLabel,
+* html .hasChildren .memberLabelCell .memberLabel,
+* html .hasHeaders .netHrefLabel {
+ background-image: url(tree_open.gif);
+ background-repeat: no-repeat;
+ background-position: 2px 2px;
+}
+
+* html .opened .spyHead .spyTitle,
+* html .opened .logGroupLabel,
+* html .opened .memberLabelCell .memberLabel {
+ background-image: url(tree_close.gif);
+ background-repeat: no-repeat;
+ background-position: 2px 2px;
+}
+
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Console */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/* See license.txt for terms of usage */
+
+.panelNode-console {
+ overflow-x: hidden;
+}
+
+.objectLink {
+ text-decoration: none;
+}
+
+.objectLink:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.logRow {
+ position: relative;
+ margin: 0;
+ border-bottom: 1px solid #D7D7D7;
+ padding: 2px 4px 1px 6px;
+ background-color: #FFFFFF;
+ overflow: hidden !important; /* IE need this to avoid disappearing bug with collapsed logs */
+}
+
+.useA11y .logRow:focus {
+ border-bottom: 1px solid #000000 !important;
+ outline: none !important;
+ background-color: #FFFFAD !important;
+}
+
+.useA11y .logRow:focus a.objectLink-sourceLink {
+ background-color: #FFFFAD;
+}
+
+.useA11y .a11yFocus:focus, .useA11y .objectBox:focus {
+ outline: 2px solid #FF9933;
+ background-color: #FFFFAD;
+}
+
+.useA11y .objectBox-null:focus, .useA11y .objectBox-undefined:focus{
+ background-color: #888888 !important;
+}
+
+.useA11y .logGroup.opened > .logRow {
+ border-bottom: 1px solid #ffffff;
+}
+
+.logGroup {
+ background: url(group.gif) repeat-x #FFFFFF;
+ padding: 0 !important;
+ border: none !important;
+}
+
+.logGroupBody {
+ display: none;
+ margin-left: 16px;
+ border-left: 1px solid #D7D7D7;
+ border-top: 1px solid #D7D7D7;
+ background: #FFFFFF;
+}
+
+.logGroup > .logRow {
+ background-color: transparent !important;
+ font-weight: bold;
+}
+
+.logGroup.opened > .logRow {
+ border-bottom: none;
+}
+
+.logGroup.opened > .logGroupBody {
+ display: block;
+}
+
+/*****************************************************************************************/
+
+.logRow-command > .objectBox-text {
+ font-family: Monaco, monospace;
+ color: #0000FF;
+ white-space: pre-wrap;
+}
+
+.logRow-info,
+.logRow-warn,
+.logRow-error,
+.logRow-assert,
+.logRow-warningMessage,
+.logRow-errorMessage {
+ padding-left: 22px;
+ background-repeat: no-repeat;
+ background-position: 4px 2px;
+}
+
+.logRow-assert,
+.logRow-warningMessage,
+.logRow-errorMessage {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.logRow-info,
+.logRow-info .objectLink-sourceLink {
+ background-color: #FFFFFF;
+}
+
+.logRow-warn,
+.logRow-warningMessage,
+.logRow-warn .objectLink-sourceLink,
+.logRow-warningMessage .objectLink-sourceLink {
+ background-color: cyan;
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage,
+.logRow-error .objectLink-sourceLink,
+.logRow-errorMessage .objectLink-sourceLink {
+ background-color: LightYellow;
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage {
+ color: #FF0000;
+}
+
+.logRow-info {
+ /*background-image: url(chrome://firebug/skin/infoIcon.png);*/
+}
+
+.logRow-warn,
+.logRow-warningMessage {
+ /*background-image: url(chrome://firebug/skin/warningIcon.png);*/
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage {
+ /*background-image: url(chrome://firebug/skin/errorIcon.png);*/
+}
+
+/*****************************************************************************************/
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ font-family: Monaco, monospace;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectLink-textNode {
+ white-space: pre-wrap;
+}
+
+.objectBox-number,
+.objectLink-styleRule,
+.objectLink-element,
+.objectLink-textNode {
+ color: #000088;
+}
+
+.objectBox-string {
+ color: #FF0000;
+}
+
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ color: DarkGreen;
+}
+
+.objectBox-null,
+.objectBox-undefined {
+ padding: 0 2px;
+ border: 1px solid #666666;
+ background-color: #888888;
+ color: #FFFFFF;
+}
+
+.objectBox-exception {
+ padding: 0 2px 0 18px;
+ /*background: url(chrome://firebug/skin/errorIcon-sm.png) no-repeat 0 0;*/
+ color: red;
+}
+
+.objectLink-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ color: #0000FF;
+}
+
+/************************************************************************************************/
+
+.errorTitle {
+ margin-top: 0px;
+ margin-bottom: 1px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+.errorTrace {
+ margin-left: 17px;
+}
+
+.errorSourceBox {
+ margin: 2px 0;
+}
+
+.errorSource-none {
+ display: none;
+}
+
+.errorSource-syntax > .errorBreak {
+ visibility: hidden;
+}
+
+.errorSource {
+ cursor: pointer;
+ font-family: Monaco, monospace;
+ color: DarkGreen;
+}
+
+.errorSource:hover {
+ text-decoration: underline;
+}
+
+.errorBreak {
+ cursor: pointer;
+ display: none;
+ margin: 0 6px 0 0;
+ width: 13px;
+ height: 14px;
+ vertical-align: bottom;
+ /*background: url(chrome://firebug/skin/breakpoint.png) no-repeat;*/
+ opacity: 0.1;
+}
+
+.hasBreakSwitch .errorBreak {
+ display: inline;
+}
+
+.breakForError .errorBreak {
+ opacity: 1;
+}
+
+.assertDescription {
+ margin: 0;
+}
+
+/************************************************************************************************/
+
+.logRow-profile > .logRow > .objectBox-text {
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ color: #000000;
+}
+
+.logRow-profile > .logRow > .objectBox-text:last-child {
+ color: #555555;
+ font-style: italic;
+}
+
+.logRow-profile.opened > .logRow {
+ padding-bottom: 4px;
+}
+
+.profilerRunning > .logRow {
+ /*background: transparent url(chrome://firebug/skin/loading_16.gif) no-repeat 2px 0 !important;*/
+ padding-left: 22px !important;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.profileSizer {
+ width:100%;
+ overflow-x:auto;
+ overflow-y: scroll;
+}
+
+.profileTable {
+ border-bottom: 1px solid #D7D7D7;
+ padding: 0 0 4px 0;
+}
+
+.profileTable tr[odd="1"] {
+ background-color: #F5F5F5;
+ vertical-align:middle;
+}
+
+.profileTable a {
+ vertical-align:middle;
+}
+
+.profileTable td {
+ padding: 1px 4px 0 4px;
+}
+
+.headerCell {
+ cursor: pointer;
+ -moz-user-select: none;
+ border-bottom: 1px solid #9C9C9C;
+ padding: 0 !important;
+ font-weight: bold;
+ /*background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x;*/
+}
+
+.headerCellBox {
+ padding: 2px 4px;
+ border-left: 1px solid #D9D9D9;
+ border-right: 1px solid #9C9C9C;
+}
+
+.headerCell:hover:active {
+ /*background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x;*/
+}
+
+.headerSorted {
+ /*background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;*/
+}
+
+.headerSorted > .headerCellBox {
+ border-right-color: #6B7C93;
+ /*background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;*/
+}
+
+.headerSorted.sortedAscending > .headerCellBox {
+ /*background-image: url(chrome://firebug/skin/arrowUp.png);*/
+}
+
+.headerSorted:hover:active {
+ /*background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;*/
+}
+
+.linkCell {
+ text-align: right;
+}
+
+.linkCell > .objectLink-sourceLink {
+ position: static;
+}
+
+/*****************************************************************************************/
+
+.logRow-stackTrace {
+ padding-top: 0;
+ background: #f8f8f8;
+}
+
+.logRow-stackTrace > .objectBox-stackFrame {
+ position: relative;
+ padding-top: 2px;
+}
+
+/************************************************************************************************/
+
+.objectLink-object {
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ color: DarkGreen;
+ white-space: pre-wrap;
+}
+
+/* xxxpedro reps object representation .................................... */
+.objectProp-object {
+ color: DarkGreen;
+}
+
+.objectProps {
+ color: #000;
+ font-weight: normal;
+}
+
+.objectPropName {
+ /*font-style: italic;*/
+ color: #777;
+}
+
+/*
+.objectProps .objectProp-string,
+.objectProps .objectProp-number,
+.objectProps .objectProp-object
+{
+ font-style: italic;
+}
+/**/
+
+.objectProps .objectProp-string
+{
+ /*font-family: Monaco, monospace;*/
+ color: #f55;
+}
+.objectProps .objectProp-number
+{
+ /*font-family: Monaco, monospace;*/
+ color: #55a;
+}
+.objectProps .objectProp-object
+{
+ /*font-family: Lucida Grande,sans-serif;*/
+ color: #585;
+}
+/* xxxpedro reps object representation .................................... */
+
+/************************************************************************************************/
+
+.selectorTag,
+.selectorId,
+.selectorClass {
+ font-family: Monaco, monospace;
+ font-weight: normal;
+}
+
+.selectorTag {
+ color: #0000FF;
+}
+
+.selectorId {
+ color: DarkBlue;
+}
+
+.selectorClass {
+ color: red;
+}
+
+.selectorHidden > .selectorTag {
+ color: #5F82D9;
+}
+
+.selectorHidden > .selectorId {
+ color: #888888;
+}
+
+.selectorHidden > .selectorClass {
+ color: #D86060;
+}
+
+.selectorValue {
+ font-family: Lucida Grande, sans-serif;
+ font-style: italic;
+ color: #555555;
+}
+
+/*****************************************************************************************/
+
+.panelNode.searching .logRow {
+ display: none;
+}
+
+.logRow.matched {
+ display: block !important;
+}
+
+.logRow.matching {
+ position: absolute;
+ left: -1000px;
+ top: -1000px;
+ max-width: 0;
+ max-height: 0;
+ overflow: hidden;
+}
+
+/*****************************************************************************************/
+
+.objectLeftBrace,
+.objectRightBrace,
+.objectEqual,
+.objectComma,
+.arrayLeftBracket,
+.arrayRightBracket,
+.arrayComma {
+ font-family: Monaco, monospace;
+}
+
+.objectLeftBrace,
+.objectRightBrace,
+.arrayLeftBracket,
+.arrayRightBracket {
+ font-weight: bold;
+}
+
+.objectLeftBrace,
+.arrayLeftBracket {
+ margin-right: 4px;
+}
+
+.objectRightBrace,
+.arrayRightBracket {
+ margin-left: 4px;
+}
+
+/*****************************************************************************************/
+
+.logRow-dir {
+ padding: 0;
+}
+
+/************************************************************************************************/
+
+/*
+.logRow-errorMessage > .hasTwisty > .errorTitle,
+.logRow-spy .spyHead .spyTitle,
+.logGroup > .logRow
+*/
+.logRow-errorMessage .hasTwisty .errorTitle,
+.logRow-spy .spyHead .spyTitle,
+.logGroup .logRow {
+ cursor: pointer;
+ padding-left: 18px;
+ background-repeat: no-repeat;
+ background-position: 3px 3px;
+}
+
+.logRow-errorMessage > .hasTwisty > .errorTitle {
+ background-position: 2px 3px;
+}
+
+.logRow-errorMessage > .hasTwisty > .errorTitle:hover,
+.logRow-spy .spyHead .spyTitle:hover,
+.logGroup > .logRow:hover {
+ text-decoration: underline;
+}
+
+/*****************************************************************************************/
+
+.logRow-spy {
+ padding: 0 !important;
+}
+
+.logRow-spy,
+.logRow-spy .objectLink-sourceLink {
+ background: url(group.gif) repeat-x #FFFFFF;
+ padding-right: 4px;
+ right: 0;
+}
+
+.logRow-spy.opened {
+ padding-bottom: 4px;
+ border-bottom: none;
+}
+
+.spyTitle {
+ color: #000000;
+ font-weight: bold;
+ -moz-box-sizing: padding-box;
+ overflow: hidden;
+ z-index: 100;
+ padding-left: 18px;
+}
+
+.spyCol {
+ padding: 0;
+ white-space: nowrap;
+ height: 16px;
+}
+
+.spyTitleCol:hover > .objectLink-sourceLink,
+.spyTitleCol:hover > .spyTime,
+.spyTitleCol:hover > .spyStatus,
+.spyTitleCol:hover > .spyTitle {
+ display: none;
+}
+
+.spyFullTitle {
+ display: none;
+ -moz-user-select: none;
+ max-width: 100%;
+ background-color: Transparent;
+}
+
+.spyTitleCol:hover > .spyFullTitle {
+ display: block;
+}
+
+.spyStatus {
+ padding-left: 10px;
+ color: rgb(128, 128, 128);
+}
+
+.spyTime {
+ margin-left:4px;
+ margin-right:4px;
+ color: rgb(128, 128, 128);
+}
+
+.spyIcon {
+ margin-right: 4px;
+ margin-left: 4px;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ background: transparent no-repeat 0 0;
+ display: none;
+}
+
+.loading .spyHead .spyRow .spyIcon {
+ background-image: url(loading_16.gif);
+ display: block;
+}
+
+.logRow-spy.loaded:not(.error) .spyHead .spyRow .spyIcon {
+ width: 0;
+ margin: 0;
+}
+
+.logRow-spy.error .spyHead .spyRow .spyIcon {
+ background-image: url(errorIcon-sm.png);
+ display: block;
+ background-position: 2px 2px;
+}
+
+.logRow-spy .spyHead .netInfoBody {
+ display: none;
+}
+
+.logRow-spy.opened .spyHead .netInfoBody {
+ margin-top: 10px;
+ display: block;
+}
+
+.logRow-spy.error .spyTitle,
+.logRow-spy.error .spyStatus,
+.logRow-spy.error .spyTime {
+ color: red;
+}
+
+.logRow-spy.loading .spyResponseText {
+ font-style: italic;
+ color: #888888;
+}
+
+/************************************************************************************************/
+
+.caption {
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-weight: bold;
+ color: #444444;
+}
+
+.warning {
+ padding: 10px;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-weight: bold;
+ color: #888888;
+}
+
+
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* DOM */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/* See license.txt for terms of usage */
+
+.panelNode-dom {
+ overflow-x: hidden !important;
+}
+
+.domTable {
+ font-size: 1em;
+ width: 100%;
+ table-layout: fixed;
+ background: #fff;
+}
+
+.domTableIE {
+ width: auto;
+}
+
+.memberLabelCell {
+ padding: 2px 0 2px 0;
+ vertical-align: top;
+}
+
+.memberValueCell {
+ padding: 1px 0 1px 5px;
+ display: block;
+ overflow: hidden;
+}
+
+.memberLabel {
+ display: block;
+ cursor: default;
+ -moz-user-select: none;
+ overflow: hidden;
+ /*position: absolute;*/
+ padding-left: 18px;
+ /*max-width: 30%;*/
+ /*white-space: nowrap;*/
+ background-color: #FFFFFF;
+ text-decoration: none;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.memberRow.hasChildren .memberLabelCell .memberLabel:hover {
+ cursor: pointer;
+ color: blue;
+ text-decoration: underline;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.userLabel {
+ color: #000000;
+ font-weight: bold;
+}
+
+.userClassLabel {
+ color: #E90000;
+ font-weight: bold;
+}
+
+.userFunctionLabel {
+ color: #025E2A;
+ font-weight: bold;
+}
+
+.domLabel {
+ color: #000000;
+}
+
+.domFunctionLabel {
+ color: #025E2A;
+}
+
+.ordinalLabel {
+ color: SlateBlue;
+ font-weight: bold;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+.scopesRow {
+ padding: 2px 18px;
+ background-color: LightYellow;
+ border-bottom: 5px solid #BEBEBE;
+ color: #666666;
+}
+.scopesLabel {
+ background-color: LightYellow;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.watchEditCell {
+ padding: 2px 18px;
+ background-color: LightYellow;
+ border-bottom: 1px solid #BEBEBE;
+ color: #666666;
+}
+
+.editor-watchNewRow,
+.editor-memberRow {
+ font-family: Monaco, monospace !important;
+}
+
+.editor-memberRow {
+ padding: 1px 0 !important;
+}
+
+.editor-watchRow {
+ padding-bottom: 0 !important;
+}
+
+.watchRow > .memberLabelCell {
+ font-family: Monaco, monospace;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.watchRow > .memberLabelCell > .memberLabel {
+ background-color: transparent;
+}
+
+.watchRow > .memberValueCell {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+.watchRow > .memberLabelCell,
+.watchRow > .memberValueCell {
+ background-color: #F5F5F5;
+ border-bottom: 1px solid #BEBEBE;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.watchToolbox {
+ z-index: 2147483647;
+ position: absolute;
+ right: 0;
+ padding: 1px 2px;
+}
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* FROM ORIGINAL FIREBUG */
+
+
+
+
+/************************************************************************************************
+ CSS Not organized
+*************************************************************************************************/
+#fbConsole {
+ overflow-x: hidden !important;
+}
+
+#fbCSS {
+ font: 1em Monaco, monospace;
+ padding: 0 7px;
+}
+
+#fbstylesheetButtons select, #fbScriptButtons select {
+ font: 11px Lucida Grande, Tahoma, sans-serif;
+ margin-top: 1px;
+ padding-left: 3px;
+ background: #fafafa;
+ border: 1px inset #fff;
+ width: 220px;
+ outline: none;
+}
+
+.Selector { margin-top:10px }
+.CSSItem {margin-left: 4% }
+.CSSText { padding-left:20px; }
+.CSSProperty { color:#005500; }
+.CSSValue { padding-left:5px; color:#000088; }
+
+
+/************************************************************************************************
+ Not organized
+*************************************************************************************************/
+
+#fbHTMLStatusBar {
+ display: inline;
+}
+
+.fbToolbarButtons {
+ display: none;
+}
+
+.fbStatusSeparator{
+ display: block;
+ float: left;
+ padding-top: 4px;
+}
+
+#fbStatusBarBox {
+ display: none;
+}
+
+#fbToolbarContent {
+ display: block;
+ position: absolute;
+ _position: absolute;
+ top: 0;
+ padding-top: 4px;
+ height: 23px;
+ clip: rect(0, 2048px, 27px, 0);
+}
+
+.fbTabMenuTarget {
+ display: none !important;
+ float: left;
+ width: 10px;
+ height: 10px;
+ margin-top: 6px;
+ background: url(tabMenuTarget.png);
+}
+
+.fbTabMenuTarget:hover {
+ background: url(tabMenuTargetHover.png);
+}
+
+.fbShadow {
+ float: left;
+ background: url(shadowAlpha.png) no-repeat bottom right !important;
+ background: url(shadow2.gif) no-repeat bottom right;
+ margin: 10px 0 0 10px !important;
+ margin: 10px 0 0 5px;
+}
+
+.fbShadowContent {
+ display: block;
+ position: relative;
+ background-color: #fff;
+ border: 1px solid #a9a9a9;
+ top: -6px;
+ left: -6px;
+}
+
+.fbMenu {
+ display: none;
+ position: absolute;
+ font-size: 11px;
+ line-height: 13px;
+ z-index: 2147483647;
+}
+
+.fbMenuContent {
+ padding: 2px;
+}
+
+.fbMenuSeparator {
+ display: block;
+ position: relative;
+ padding: 1px 18px 0;
+ text-decoration: none;
+ color: #000;
+ cursor: default;
+ background: #ACA899;
+ margin: 4px 0;
+}
+
+.fbMenuOption
+{
+ display: block;
+ position: relative;
+ padding: 2px 18px;
+ text-decoration: none;
+ color: #000;
+ cursor: default;
+}
+
+.fbMenuOption:hover
+{
+ color: #fff;
+ background: #316AC5;
+}
+
+.fbMenuGroup {
+ background: transparent url(tabMenuPin.png) no-repeat right 0;
+}
+
+.fbMenuGroup:hover {
+ background: #316AC5 url(tabMenuPin.png) no-repeat right -17px;
+}
+
+.fbMenuGroupSelected {
+ color: #fff;
+ background: #316AC5 url(tabMenuPin.png) no-repeat right -17px;
+}
+
+.fbMenuChecked {
+ background: transparent url(tabMenuCheckbox.png) no-repeat 4px 0;
+}
+
+.fbMenuChecked:hover {
+ background: #316AC5 url(tabMenuCheckbox.png) no-repeat 4px -17px;
+}
+
+.fbMenuRadioSelected {
+ background: transparent url(tabMenuRadio.png) no-repeat 4px 0;
+}
+
+.fbMenuRadioSelected:hover {
+ background: #316AC5 url(tabMenuRadio.png) no-repeat 4px -17px;
+}
+
+.fbMenuShortcut {
+ padding-right: 85px;
+}
+
+.fbMenuShortcutKey {
+ position: absolute;
+ right: 0;
+ top: 2px;
+ width: 77px;
+}
+
+#fbFirebugMenu {
+ top: 22px;
+ left: 0;
+}
+
+.fbMenuDisabled {
+ color: #ACA899 !important;
+}
+
+#fbFirebugSettingsMenu {
+ left: 245px;
+ top: 99px;
+}
+
+#fbConsoleMenu {
+ top: 42px;
+ left: 48px;
+}
+
+.fbIconButton {
+ display: block;
+}
+
+.fbIconButton {
+ display: block;
+}
+
+.fbIconButton {
+ display: block;
+ float: left;
+ height: 20px;
+ width: 20px;
+ color: #000;
+ margin-right: 2px;
+ text-decoration: none;
+ cursor: default;
+}
+
+.fbIconButton:hover {
+ position: relative;
+ top: -1px;
+ left: -1px;
+ margin-right: 0;
+ _margin-right: 1px;
+ color: #333;
+ border: 1px solid #fff;
+ border-bottom: 1px solid #bbb;
+ border-right: 1px solid #bbb;
+}
+
+.fbIconPressed {
+ position: relative;
+ margin-right: 0;
+ _margin-right: 1px;
+ top: 0 !important;
+ left: 0 !important;
+ height: 19px;
+ color: #333 !important;
+ border: 1px solid #bbb !important;
+ border-bottom: 1px solid #cfcfcf !important;
+ border-right: 1px solid #ddd !important;
+}
+
+
+
+/************************************************************************************************
+ Error Popup
+*************************************************************************************************/
+#fbErrorPopup {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ height: 19px;
+ width: 75px;
+ background: url(sprite.png) #f1f2ee 0 0;
+ z-index: 999;
+}
+
+#fbErrorPopupContent {
+ position: absolute;
+ right: 0;
+ top: 1px;
+ height: 18px;
+ width: 75px;
+ _width: 74px;
+ border-left: 1px solid #aca899;
+}
+
+#fbErrorIndicator {
+ position: absolute;
+ top: 2px;
+ right: 5px;
+}
+
+
+
+
+
+
+
+
+
+
+.fbBtnInspectActive {
+ background: #aaa;
+ color: #fff !important;
+}
+
+/************************************************************************************************
+ General
+*************************************************************************************************/
+.fbBody {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ background: #fff;
+}
+
+.clear {
+ clear: both;
+}
+
+/************************************************************************************************
+ Mini Chrome
+*************************************************************************************************/
+#fbMiniChrome {
+ display: none;
+ right: 0;
+ height: 27px;
+ background: url(sprite.png) #f1f2ee 0 0;
+ margin-left: 1px;
+}
+
+#fbMiniContent {
+ display: block;
+ position: relative;
+ left: -1px;
+ right: 0;
+ top: 1px;
+ height: 25px;
+ border-left: 1px solid #aca899;
+}
+
+#fbToolbarSearch {
+ float: right;
+ border: 1px solid #ccc;
+ margin: 0 5px 0 0;
+ background: #fff url(search.png) no-repeat 4px 2px !important;
+ background: #fff url(search.gif) no-repeat 4px 2px;
+ padding-left: 20px;
+ font-size: 11px;
+}
+
+#fbToolbarErrors {
+ float: right;
+ margin: 1px 4px 0 0;
+ font-size: 11px;
+}
+
+#fbLeftToolbarErrors {
+ float: left;
+ margin: 7px 0px 0 5px;
+ font-size: 11px;
+}
+
+.fbErrors {
+ padding-left: 20px;
+ height: 14px;
+ background: url(errorIcon.png) no-repeat !important;
+ background: url(errorIcon.gif) no-repeat;
+ color: #f00;
+ font-weight: bold;
+}
+
+#fbMiniErrors {
+ display: inline;
+ display: none;
+ float: right;
+ margin: 5px 2px 0 5px;
+}
+
+#fbMiniIcon {
+ float: right;
+ margin: 3px 4px 0;
+ height: 20px;
+ width: 20px;
+ float: right;
+ background: url(sprite.png) 0 -135px;
+ cursor: pointer;
+}
+
+
+/************************************************************************************************
+ Master Layout
+*************************************************************************************************/
+#fbChrome {
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ position: absolute;
+ _position: static;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ border-collapse: collapse;
+ border-spacing: 0;
+ background: #fff;
+ overflow: hidden;
+}
+
+#fbChrome > tbody > tr > td {
+ padding: 0;
+}
+
+#fbTop {
+ height: 49px;
+}
+
+#fbToolbar {
+ background: url(sprite.png) #f1f2ee 0 0;
+ height: 27px;
+ font-size: 11px;
+ line-height: 13px;
+}
+
+#fbPanelBarBox {
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ height: 22px;
+}
+
+#fbContent {
+ height: 100%;
+ vertical-align: top;
+}
+
+#fbBottom {
+ height: 18px;
+ background: #fff;
+}
+
+/************************************************************************************************
+ Sub-Layout
+*************************************************************************************************/
+
+/* fbToolbar
+*************************************************************************************************/
+#fbToolbarIcon {
+ float: left;
+ padding: 0 5px 0;
+}
+
+#fbToolbarIcon a {
+ background: url(sprite.png) 0 -135px;
+}
+
+#fbToolbarButtons {
+ padding: 0 2px 0 5px;
+}
+
+#fbToolbarButtons {
+ padding: 0 2px 0 5px;
+}
+/*
+#fbStatusBarBox a {
+ text-decoration: none;
+ display: block;
+ float: left;
+ color: #000;
+ padding: 4px 5px;
+ margin: 0 0 0 1px;
+ cursor: default;
+}
+
+#fbStatusBarBox a:hover {
+ color: #333;
+ padding: 3px 4px;
+ border: 1px solid #fff;
+ border-bottom: 1px solid #bbb;
+ border-right: 1px solid #bbb;
+}
+/**/
+
+.fbButton {
+ text-decoration: none;
+ display: block;
+ float: left;
+ color: #000;
+ padding: 4px 6px 4px 7px;
+ cursor: default;
+}
+
+.fbButton:hover {
+ color: #333;
+ background: #f5f5ef url(buttonBg.png);
+ padding: 3px 5px 3px 6px;
+ border: 1px solid #fff;
+ border-bottom: 1px solid #bbb;
+ border-right: 1px solid #bbb;
+}
+
+.fbBtnPressed {
+ background: #e3e3db url(buttonBgHover.png) !important;
+ padding: 3px 4px 2px 6px !important;
+ margin: 1px 0 0 1px !important;
+ border: 1px solid #ACA899 !important;
+ border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;
+}
+
+#fbStatusBarBox {
+ top: 4px;
+ cursor: default;
+}
+
+.fbToolbarSeparator {
+ overflow: hidden;
+ border: 1px solid;
+ border-color: transparent #fff transparent #777;
+ _border-color: #eee #fff #eee #777;
+ height: 7px;
+ margin: 6px 3px;
+ float: left;
+}
+
+.fbBtnSelected {
+ font-weight: bold;
+}
+
+.fbStatusBar {
+ color: #aca899;
+}
+
+.fbStatusBar a {
+ text-decoration: none;
+ color: black;
+}
+
+.fbStatusBar a:hover {
+ color: blue;
+ cursor: pointer;
+}
+
+
+#fbWindowButtons {
+ position: absolute;
+ white-space: nowrap;
+ right: 0;
+ top: 0;
+ height: 17px;
+ width: 48px;
+ padding: 5px;
+ z-index: 6;
+ background: url(sprite.png) #f1f2ee 0 0;
+}
+
+/* fbPanelBarBox
+*************************************************************************************************/
+
+#fbPanelBar1 {
+ width: 1024px; /* fixed width to avoid tabs breaking line */
+ z-index: 8;
+ left: 0;
+ white-space: nowrap;
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ position: absolute;
+ left: 4px;
+}
+
+#fbPanelBar2Box {
+ background: url(sprite.png) #dbd9c9 0 -27px;
+ position: absolute;
+ height: 22px;
+ width: 300px; /* fixed width to avoid tabs breaking line */
+ z-index: 9;
+ right: 0;
+}
+
+#fbPanelBar2 {
+ position: absolute;
+ width: 290px; /* fixed width to avoid tabs breaking line */
+ height: 22px;
+ padding-left: 4px;
+}
+
+/* body
+*************************************************************************************************/
+.fbPanel {
+ display: none;
+}
+
+#fbPanelBox1, #fbPanelBox2 {
+ max-height: inherit;
+ height: 100%;
+ font-size: 1em;
+}
+
+#fbPanelBox2 {
+ background: #fff;
+}
+
+#fbPanelBox2 {
+ width: 300px;
+ background: #fff;
+}
+
+#fbPanel2 {
+ margin-left: 6px;
+ background: #fff;
+}
+
+#fbLargeCommandLine {
+ display: none;
+ position: absolute;
+ z-index: 9;
+ top: 27px;
+ right: 0;
+ width: 294px;
+ height: 201px;
+ border-width: 0;
+ margin: 0;
+ padding: 2px 0 0 2px;
+ resize: none;
+ outline: none;
+ font-size: 11px;
+ overflow: auto;
+ border-top: 1px solid #B9B7AF;
+ _right: -1px;
+ _border-left: 1px solid #fff;
+}
+
+#fbLargeCommandButtons {
+ display: none;
+ background: #ECE9D8;
+ bottom: 0;
+ right: 0;
+ width: 294px;
+ height: 21px;
+ padding-top: 1px;
+ position: fixed;
+ border-top: 1px solid #ACA899;
+ z-index: 9;
+}
+
+#fbSmallCommandLineIcon {
+ background: url(down.png) no-repeat;
+ position: absolute;
+ right: 2px;
+ bottom: 3px;
+
+ z-index: 99;
+}
+
+#fbSmallCommandLineIcon:hover {
+ background: url(downHover.png) no-repeat;
+}
+
+.hide {
+ overflow: hidden !important;
+ position: fixed !important;
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* fbBottom
+*************************************************************************************************/
+
+#fbCommand {
+ height: 18px;
+}
+
+#fbCommandBox {
+ position: fixed;
+ _position: absolute;
+ width: 100%;
+ height: 18px;
+ bottom: 0;
+ overflow: hidden;
+ z-index: 9;
+ background: #fff;
+ border: 0;
+ border-top: 1px solid #ccc;
+}
+
+#fbCommandIcon {
+ position: absolute;
+ color: #00f;
+ top: 2px;
+ left: 6px;
+ display: inline;
+ font: 11px Monaco, monospace;
+ z-index: 10;
+}
+
+#fbCommandLine {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ left: 0;
+ border: 0;
+ margin: 0;
+ padding: 2px 0 2px 32px;
+ font: 11px Monaco, monospace;
+ z-index: 9;
+ outline: none;
+}
+
+#fbLargeCommandLineIcon {
+ background: url(up.png) no-repeat;
+ position: absolute;
+ right: 1px;
+ bottom: 1px;
+ z-index: 10;
+}
+
+#fbLargeCommandLineIcon:hover {
+ background: url(upHover.png) no-repeat;
+}
+
+div.fbFitHeight {
+ overflow: auto;
+ position: relative;
+}
+
+
+/************************************************************************************************
+ Layout Controls
+*************************************************************************************************/
+
+/* fbToolbar buttons
+*************************************************************************************************/
+.fbSmallButton {
+ overflow: hidden;
+ width: 16px;
+ height: 16px;
+ display: block;
+ text-decoration: none;
+ cursor: default;
+}
+
+#fbWindowButtons .fbSmallButton {
+ float: right;
+}
+
+#fbWindow_btClose {
+ background: url(min.png);
+}
+
+#fbWindow_btClose:hover {
+ background: url(minHover.png);
+}
+
+#fbWindow_btDetach {
+ background: url(detach.png);
+}
+
+#fbWindow_btDetach:hover {
+ background: url(detachHover.png);
+}
+
+#fbWindow_btDeactivate {
+ background: url(off.png);
+}
+
+#fbWindow_btDeactivate:hover {
+ background: url(offHover.png);
+}
+
+
+/* fbPanelBarBox tabs
+*************************************************************************************************/
+.fbTab {
+ text-decoration: none;
+ display: none;
+ float: left;
+ width: auto;
+ float: left;
+ cursor: default;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ line-height: 13px;
+ font-weight: bold;
+ height: 22px;
+ color: #565656;
+}
+
+.fbPanelBar span {
+ /*display: block; TODO: safe to remove this? */
+ float: left;
+}
+
+.fbPanelBar .fbTabL,.fbPanelBar .fbTabR {
+ height: 22px;
+ width: 8px;
+}
+
+.fbPanelBar .fbTabText {
+ padding: 4px 1px 0;
+}
+
+a.fbTab:hover {
+ background: url(sprite.png) 0 -73px;
+}
+
+a.fbTab:hover .fbTabL {
+ background: url(sprite.png) -16px -96px;
+}
+
+a.fbTab:hover .fbTabR {
+ background: url(sprite.png) -24px -96px;
+}
+
+.fbSelectedTab {
+ background: url(sprite.png) #f1f2ee 0 -50px !important;
+ color: #000;
+}
+
+.fbSelectedTab .fbTabL {
+ background: url(sprite.png) 0 -96px !important;
+}
+
+.fbSelectedTab .fbTabR {
+ background: url(sprite.png) -8px -96px !important;
+}
+
+/* splitters
+*************************************************************************************************/
+#fbHSplitter {
+ position: fixed;
+ _position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 5px;
+ overflow: hidden;
+ cursor: n-resize !important;
+ background: url(pixel_transparent.gif);
+ z-index: 9;
+}
+
+#fbHSplitter.fbOnMovingHSplitter {
+ height: 100%;
+ z-index: 100;
+}
+
+.fbVSplitter {
+ background: #ece9d8;
+ color: #000;
+ border: 1px solid #716f64;
+ border-width: 0 1px;
+ border-left-color: #aca899;
+ width: 4px;
+ cursor: e-resize;
+ overflow: hidden;
+ right: 294px;
+ text-decoration: none;
+ z-index: 10;
+ position: absolute;
+ height: 100%;
+ top: 27px;
+}
+
+/************************************************************************************************/
+div.lineNo {
+ font: 1em/1.4545em Monaco, monospace;
+ position: relative;
+ float: left;
+ top: 0;
+ left: 0;
+ margin: 0 5px 0 0;
+ padding: 0 5px 0 10px;
+ background: #eee;
+ color: #888;
+ border-right: 1px solid #ccc;
+ text-align: right;
+}
+
+.sourceBox {
+ position: absolute;
+}
+
+.sourceCode {
+ font: 1em Monaco, monospace;
+ overflow: hidden;
+ white-space: pre;
+ display: inline;
+}
+
+/************************************************************************************************/
+.nodeControl {
+ margin-top: 3px;
+ margin-left: -14px;
+ float: left;
+ width: 9px;
+ height: 9px;
+ overflow: hidden;
+ cursor: default;
+ background: url(tree_open.gif);
+ _float: none;
+ _display: inline;
+ _position: absolute;
+}
+
+div.nodeMaximized {
+ background: url(tree_close.gif);
+}
+
+div.objectBox-element {
+ padding: 1px 3px;
+}
+.objectBox-selector{
+ cursor: default;
+}
+
+.selectedElement{
+ background: highlight;
+ /* background: url(roundCorner.svg); Opera */
+ color: #fff !important;
+}
+.selectedElement span{
+ color: #fff !important;
+}
+
+/* IE6 need this hack */
+* html .selectedElement {
+ position: relative;
+}
+
+/* Webkit CSS Hack - bug in "highlight" named color */
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ .selectedElement{
+ background: #316AC5;
+ color: #fff !important;
+ }
+}
+
+/************************************************************************************************/
+/************************************************************************************************/
+.logRow * {
+ font-size: 1em;
+}
+
+/* TODO: remove this? */
+/* TODO: xxxpedro - IE need this in windowless mode (cnn.com) check if the issue is related to
+position. if so, override it at chrome.js initialization when creating the div */
+.logRow {
+ position: relative;
+ border-bottom: 1px solid #D7D7D7;
+ padding: 2px 4px 1px 6px;
+ zbackground-color: #FFFFFF;
+}
+/**/
+
+.logRow-command {
+ font-family: Monaco, monospace;
+ color: blue;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectBox-function,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ font-family: Monaco, monospace;
+}
+
+.objectBox-null {
+ padding: 0 2px;
+ border: 1px solid #666666;
+ background-color: #888888;
+ color: #FFFFFF;
+}
+
+.objectBox-string {
+ color: red;
+
+ /* TODO: xxxpedro make long strings break line */
+ /*white-space: pre; */
+}
+
+.objectBox-number {
+ color: #000088;
+}
+
+.objectBox-function {
+ color: DarkGreen;
+}
+
+.objectBox-object {
+ color: DarkGreen;
+ font-weight: bold;
+ font-family: Lucida Grande, sans-serif;
+}
+
+.objectBox-array {
+ color: #000;
+}
+
+/************************************************************************************************/
+.logRow-info,.logRow-error,.logRow-warn {
+ background: #fff no-repeat 2px 2px;
+ padding-left: 20px;
+ padding-bottom: 3px;
+}
+
+.logRow-info {
+ background-image: url(infoIcon.png) !important;
+ background-image: url(infoIcon.gif);
+}
+
+.logRow-warn {
+ background-color: cyan;
+ background-image: url(warningIcon.png) !important;
+ background-image: url(warningIcon.gif);
+}
+
+.logRow-error {
+ background-color: LightYellow;
+ background-image: url(errorIcon.png) !important;
+ background-image: url(errorIcon.gif);
+ color: #f00;
+}
+
+.errorMessage {
+ vertical-align: top;
+ color: #f00;
+}
+
+.objectBox-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ color: #0000FF;
+}
+
+/************************************************************************************************/
+/*
+//TODO: remove this when console2 is finished
+*/
+/*
+.logRow-group {
+ background: #EEEEEE;
+ border-bottom: none;
+}
+
+.logGroup {
+ background: #EEEEEE;
+}
+
+.logGroupBox {
+ margin-left: 24px;
+ border-top: 1px solid #D7D7D7;
+ border-left: 1px solid #D7D7D7;
+}/**/
+
+/************************************************************************************************/
+.selectorTag,.selectorId,.selectorClass {
+ font-family: Monaco, monospace;
+ font-weight: normal;
+}
+
+.selectorTag {
+ color: #0000FF;
+}
+
+.selectorId {
+ color: DarkBlue;
+}
+
+.selectorClass {
+ color: red;
+}
+
+/************************************************************************************************/
+.objectBox-element {
+ font-family: Monaco, monospace;
+ color: #000088;
+}
+
+.nodeChildren {
+ padding-left: 26px;
+}
+
+.nodeTag {
+ color: blue;
+ cursor: pointer;
+}
+
+.nodeValue {
+ color: #FF0000;
+ font-weight: normal;
+}
+
+.nodeText,.nodeComment {
+ margin: 0 2px;
+ vertical-align: top;
+}
+
+.nodeText {
+ color: #333333;
+ font-family: Monaco, monospace;
+}
+
+.nodeComment {
+ color: DarkGreen;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeHidden, .nodeHidden * {
+ color: #888888;
+}
+
+.nodeHidden .nodeTag {
+ color: #5F82D9;
+}
+
+.nodeHidden .nodeValue {
+ color: #D86060;
+}
+
+.selectedElement .nodeHidden, .selectedElement .nodeHidden * {
+ color: SkyBlue !important;
+}
+
+
+/************************************************************************************************/
+.log-object {
+ /*
+ _position: relative;
+ _height: 100%;
+ /**/
+}
+
+.property {
+ position: relative;
+ clear: both;
+ height: 15px;
+}
+
+.propertyNameCell {
+ vertical-align: top;
+ float: left;
+ width: 28%;
+ position: absolute;
+ left: 0;
+ z-index: 0;
+}
+
+.propertyValueCell {
+ float: right;
+ width: 68%;
+ background: #fff;
+ position: absolute;
+ padding-left: 5px;
+ display: table-cell;
+ right: 0;
+ z-index: 1;
+ /*
+ _position: relative;
+ /**/
+}
+
+.propertyName {
+ font-weight: bold;
+}
+
+.FirebugPopup {
+ height: 100% !important;
+}
+
+.FirebugPopup #fbWindowButtons {
+ display: none !important;
+}
+
+.FirebugPopup #fbHSplitter {
+ display: none !important;
+}
diff --git a/vendor/firebug-lite/skin/xp/firebug.html b/vendor/firebug-lite/skin/xp/firebug.html
new file mode 100644
index 000000000..22960919a
--- /dev/null
+++ b/vendor/firebug-lite/skin/xp/firebug.html
@@ -0,0 +1,215 @@
+
+
+
+
+Firebug Lite
+
+
+
+
+
+
+
([\s\S]+)<\/p>\s+<\/body>$/.exec(source);
+ if (match)
+ source = match[1];
+
+ console.log(source);
+ });
+ }
+};
+
+// ************************************************************************************************
+
+Firebug.Lite.Proxy.fetchResourceDisabledMessage =
+ "/* Firebug Lite resource fetching is disabled.\n" +
+ "To enabled it set the Firebug Lite option \"disableResourceFetching\" to \"false\".\n" +
+ "More info at http://getfirebug.com/firebuglite#Options */";
+
+var fetchResource = function(url)
+{
+ if (Firebug.disableResourceFetching)
+ {
+ var source = sourceMap[url] = Firebug.Lite.Proxy.fetchResourceDisabledMessage;
+ return source;
+ }
+
+ if (sourceMap.hasOwnProperty(url))
+ return sourceMap[url];
+
+ // Getting the native XHR object so our calls won't be logged in the Console Panel
+ var xhr = FBL.getNativeXHRObject();
+ xhr.open("get", url, false);
+ xhr.send();
+
+ var source = sourceMap[url] = xhr.responseText;
+ return source;
+};
+
+var fetchProxyResource = function(url)
+{
+ if (sourceMap.hasOwnProperty(url))
+ return sourceMap[url];
+
+ var proxyURL = Env.Location.baseDir + "plugin/proxy/proxy.php?url=" + encodeURIComponent(url);
+ var response = fetchResource(proxyURL);
+
+ try
+ {
+ var data = eval("(" + response + ")");
+ }
+ catch(E)
+ {
+ return "ERROR: Firebug Lite Proxy plugin returned an invalid response.";
+ }
+
+ var source = data ? data.contents : "";
+ return source;
+};
+
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite.Style =
+{
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite.Script = function(window)
+{
+ this.fileName = null;
+ this.isValid = null;
+ this.baseLineNumber = null;
+ this.lineExtent = null;
+ this.tag = null;
+
+ this.functionName = null;
+ this.functionSource = null;
+};
+
+Firebug.Lite.Script.prototype =
+{
+ isLineExecutable: function(){},
+ pcToLine: function(){},
+ lineToPc: function(){},
+
+ toString: function()
+ {
+ return "Firebug.Lite.Script";
+ }
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+
+Firebug.Lite.Browser = function(window)
+{
+ this.contentWindow = window;
+ this.contentDocument = window.document;
+ this.currentURI =
+ {
+ spec: window.location.href
+ };
+};
+
+Firebug.Lite.Browser.prototype =
+{
+ toString: function()
+ {
+ return "Firebug.Lite.Browser";
+ }
+};
+
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+/*
+ http://www.JSON.org/json2.js
+ 2010-03-20
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+// ************************************************************************************************
+
+var JSON = window.JSON || {};
+
+// ************************************************************************************************
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+
+// ************************************************************************************************
+// registration
+
+FBL.JSON = JSON;
+
+// ************************************************************************************************
+}());
+
+/* See license.txt for terms of usage */
+
+(function(){
+// ************************************************************************************************
+
+/* Copyright (c) 2010-2011 Marcus Westin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+var store = (function(){
+ var api = {},
+ win = window,
+ doc = win.document,
+ localStorageName = 'localStorage',
+ globalStorageName = 'globalStorage',
+ namespace = '__firebug__storejs__',
+ storage
+
+ api.disabled = false
+ api.set = function(key, value) {}
+ api.get = function(key) {}
+ api.remove = function(key) {}
+ api.clear = function() {}
+ api.transact = function(key, transactionFn) {
+ var val = api.get(key)
+ if (typeof val == 'undefined') { val = {} }
+ transactionFn(val)
+ api.set(key, val)
+ }
+
+ api.serialize = function(value) {
+ return JSON.stringify(value)
+ }
+ api.deserialize = function(value) {
+ if (typeof value != 'string') { return undefined }
+ return JSON.parse(value)
+ }
+
+ // Functions to encapsulate questionable FireFox 3.6.13 behavior
+ // when about.config::dom.storage.enabled === false
+ // See https://github.com/marcuswestin/store.js/issues#issue/13
+ function isLocalStorageNameSupported() {
+ try { return (localStorageName in win && win[localStorageName]) }
+ catch(err) { return false }
+ }
+
+ function isGlobalStorageNameSupported() {
+ try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) }
+ catch(err) { return false }
+ }
+
+ if (isLocalStorageNameSupported()) {
+ storage = win[localStorageName]
+ api.set = function(key, val) { storage.setItem(key, api.serialize(val)) }
+ api.get = function(key) { return api.deserialize(storage.getItem(key)) }
+ api.remove = function(key) { storage.removeItem(key) }
+ api.clear = function() { storage.clear() }
+
+ } else if (isGlobalStorageNameSupported()) {
+ storage = win[globalStorageName][win.location.hostname]
+ api.set = function(key, val) { storage[key] = api.serialize(val) }
+ api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) }
+ api.remove = function(key) { delete storage[key] }
+ api.clear = function() { for (var key in storage ) { delete storage[key] } }
+
+ } else if (doc.documentElement.addBehavior) {
+ var storage = doc.createElement('div')
+ function withIEStorage(storeFunction) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0)
+ args.unshift(storage)
+ // See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx
+ // and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx
+ // TODO: xxxpedro doc.body is not always available so we must use doc.documentElement.
+ // We need to make sure this change won't affect the behavior of this library.
+ doc.documentElement.appendChild(storage)
+ storage.addBehavior('#default#userData')
+ storage.load(localStorageName)
+ var result = storeFunction.apply(api, args)
+ doc.documentElement.removeChild(storage)
+ return result
+ }
+ }
+ api.set = withIEStorage(function(storage, key, val) {
+ storage.setAttribute(key, api.serialize(val))
+ storage.save(localStorageName)
+ })
+ api.get = withIEStorage(function(storage, key) {
+ return api.deserialize(storage.getAttribute(key))
+ })
+ api.remove = withIEStorage(function(storage, key) {
+ storage.removeAttribute(key)
+ storage.save(localStorageName)
+ })
+ api.clear = withIEStorage(function(storage) {
+ var attributes = storage.XMLDocument.documentElement.attributes
+ storage.load(localStorageName)
+ for (var i=0, attr; attr = attributes[i]; i++) {
+ storage.removeAttribute(attr.name)
+ }
+ storage.save(localStorageName)
+ })
+ }
+
+ try {
+ api.set(namespace, namespace)
+ if (api.get(namespace) != namespace) { api.disabled = true }
+ api.remove(namespace)
+ } catch(e) {
+ api.disabled = true
+ }
+
+ return api
+})();
+
+if (typeof module != 'undefined') { module.exports = store }
+
+
+// ************************************************************************************************
+// registration
+
+FBL.Store = store;
+
+// ************************************************************************************************
+})();
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /**@scope s_selector*/ function() { with (FBL) {
+// ************************************************************************************************
+
+/*
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
+});
+
+/**
+ * @name Firebug.Selector
+ * @namespace
+ */
+
+/**
+ * @exports Sizzle as Firebug.Selector
+ */
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || selector);
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: " + expr;
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+/**#@+ @ignore */
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+// Provide a fallback method if it does not work
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "
';
+
+ /**
+ * The public API for creating and running tests
+ */
+ window.JSLitmus = {
+ /** The list of all tests that have been registered with JSLitmus.test */
+ _tests: [],
+ /** The queue of tests that need to be run */
+ _queue: [],
+
+ /**
+ * The parsed query parameters the current page URL. This is provided as a
+ * convenience for test functions - it's not used by JSLitmus proper
+ */
+ params: {},
+
+ /**
+ * Initialize
+ */
+ _init: function() {
+ // Parse query params into JSLitmus.params[] hash
+ var match = (location + '').match(/([^?#]*)(#.*)?$/);
+ if (match) {
+ var pairs = match[1].split('&');
+ for (var i = 0; i < pairs.length; i++) {
+ var pair = pairs[i].split('=');
+ if (pair.length > 1) {
+ var key = pair.shift();
+ var value = pair.length > 1 ? pair.join('=') : pair[0];
+ this.params[key] = value;
+ }
+ }
+ }
+
+ // Write out the stylesheet. We have to do this here because IE
+ // doesn't honor sheets written after the document has loaded.
+ document.write(STYLESHEET);
+
+ // Setup the rest of the UI once the document is loaded
+ if (window.addEventListener) {
+ window.addEventListener('load', this._setup, false);
+ } else if (document.addEventListener) {
+ document.addEventListener('load', this._setup, false);
+ } else if (window.attachEvent) {
+ window.attachEvent('onload', this._setup);
+ }
+
+ return this;
+ },
+
+ /**
+ * Set up the UI
+ */
+ _setup: function() {
+ var el = jsl.$('jslitmus_container');
+ if (!el) document.body.appendChild(el = document.createElement('div'));
+
+ el.innerHTML = MARKUP;
+
+ // Render the UI for all our tests
+ for (var i=0; i < JSLitmus._tests.length; i++)
+ JSLitmus.renderTest(JSLitmus._tests[i]);
+ },
+
+ /**
+ * (Re)render all the test results
+ */
+ renderAll: function() {
+ for (var i = 0; i < JSLitmus._tests.length; i++)
+ JSLitmus.renderTest(JSLitmus._tests[i]);
+ JSLitmus.renderChart();
+ },
+
+ /**
+ * (Re)render the chart graphics
+ */
+ renderChart: function() {
+ var url = JSLitmus.chartUrl();
+ jsl.$('chart_link').href = url;
+ jsl.$('chart_image').src = url;
+ jsl.$('chart').style.display = '';
+
+ // Update the tiny URL
+ jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
+ },
+
+ /**
+ * (Re)render the results for a specific test
+ */
+ renderTest: function(test) {
+ // Make a new row if needed
+ if (!test._row) {
+ var trow = jsl.$('test_row_template');
+ if (!trow) return;
+
+ test._row = trow.cloneNode(true);
+ test._row.style.display = '';
+ test._row.id = '';
+ test._row.onclick = function() {JSLitmus._queueTest(test);};
+ test._row.title = 'Run ' + test.name + ' test';
+ trow.parentNode.appendChild(test._row);
+ test._row.cells[0].innerHTML = test.name;
+ }
+
+ var cell = test._row.cells[1];
+ var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
+
+ if (test.error) {
+ cns.push('test_error');
+ cell.innerHTML =
+ '
' + test.error + '
' +
+ '
' +
+ jsl.join(test.error, ': ', '
') +
+ '
';
+ } else {
+ if (test.running) {
+ cns.push('test_running');
+ cell.innerHTML = 'running';
+ } else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
+ cns.push('test_pending');
+ cell.innerHTML = 'pending';
+ } else if (test.count) {
+ cns.push('test_done');
+ var hz = test.getHz(jsl.$('test_normalize').checked);
+ cell.innerHTML = hz != Infinity ? hz : '∞';
+ } else {
+ cell.innerHTML = 'ready';
+ }
+ }
+ cell.className = cns.join(' ');
+ },
+
+ /**
+ * Create a new test
+ */
+ test: function(name, f) {
+ // Create the Test object
+ var test = new Test(name, f);
+ JSLitmus._tests.push(test);
+
+ // Re-render if the test state changes
+ test.onChange = JSLitmus.renderTest;
+
+ // Run the next test if this one finished
+ test.onStop = function(test) {
+ if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
+ JSLitmus.currentTest = null;
+ JSLitmus._nextTest();
+ };
+
+ // Render the new test
+ this.renderTest(test);
+ },
+
+ /**
+ * Add all tests to the run queue
+ */
+ runAll: function(e) {
+ e = e || window.event;
+ var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
+ for (var i = 0; i < len; i++) {
+ JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
+ }
+ },
+
+ /**
+ * Remove all tests from the run queue. The current test has to finish on
+ * it's own though
+ */
+ stop: function() {
+ while (JSLitmus._queue.length) {
+ var test = JSLitmus._queue.shift();
+ JSLitmus.renderTest(test);
+ }
+ },
+
+ /**
+ * Run the next test in the run queue
+ */
+ _nextTest: function() {
+ if (!JSLitmus.currentTest) {
+ var test = JSLitmus._queue.shift();
+ if (test) {
+ jsl.$('stop_button').disabled = false;
+ JSLitmus.currentTest = test;
+ test.run();
+ JSLitmus.renderTest(test);
+ if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
+ } else {
+ jsl.$('stop_button').disabled = true;
+ JSLitmus.renderChart();
+ }
+ }
+ },
+
+ /**
+ * Add a test to the run queue
+ */
+ _queueTest: function(test) {
+ if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
+ JSLitmus._queue.push(test);
+ JSLitmus.renderTest(test);
+ JSLitmus._nextTest();
+ },
+
+ /**
+ * Generate a Google Chart URL that shows the data for all tests
+ */
+ chartUrl: function() {
+ var n = JSLitmus._tests.length, markers = [], data = [];
+ var d, min = 0, max = -1e10;
+ var normalize = jsl.$('test_normalize').checked;
+
+ // Gather test data
+ for (var i=0; i < JSLitmus._tests.length; i++) {
+ var test = JSLitmus._tests[i];
+ if (test.count) {
+ var hz = test.getHz(normalize);
+ var v = hz != Infinity ? hz : 0;
+ data.push(v);
+ markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
+ markers.length + ',10');
+ max = Math.max(v, max);
+ }
+ }
+ if (markers.length <= 0) return null;
+
+ // Build chart title
+ var title = document.getElementsByTagName('title');
+ title = (title && title.length) ? title[0].innerHTML : null;
+ var chart_title = [];
+ if (title) chart_title.push(title);
+ chart_title.push('Ops/sec (' + platform + ')');
+
+ // Build labels
+ var labels = [jsl.toLabel(min), jsl.toLabel(max)];
+
+ var w = 250, bw = 15;
+ var bs = 5;
+ var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
+
+ var params = {
+ chtt: escape(chart_title.join('|')),
+ chts: '000000,10',
+ cht: 'bhg', // chart type
+ chd: 't:' + data.join(','), // data set
+ chds: min + ',' + max, // max/min of data
+ chxt: 'x', // label axes
+ chxl: '0:|' + labels.join('|'), // labels
+ chsp: '0,1',
+ chm: markers.join('|'), // test names
+ chbh: [bw, 0, bs].join(','), // bar widths
+ // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
+ chs: w + 'x' + h
+ };
+ return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
+ }
+ };
+
+ JSLitmus._init();
+})();
\ No newline at end of file
diff --git a/vendor/underscore/test/vendor/qunit.css b/vendor/underscore/test/vendor/qunit.css
new file mode 100644
index 000000000..5684a4485
--- /dev/null
+++ b/vendor/underscore/test/vendor/qunit.css
@@ -0,0 +1,236 @@
+/**
+ * QUnit v1.8.0 - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 15px 15px 0 0;
+ -moz-border-radius: 15px 15px 0 0;
+ -webkit-border-top-right-radius: 15px;
+ -webkit-border-top-left-radius: 15px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-header label {
+ display: inline-block;
+ padding-left: 0.5em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests ol {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+
+ box-shadow: inset 0px 2px 13px #999;
+ -moz-box-shadow: inset 0px 2px 13px #999;
+ -webkit-box-shadow: inset 0px 2px 13px #999;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ margin: 0.5em;
+ padding: 0.4em 0.5em 0.4em 0.5em;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #5E740B;
+ background-color: #fff;
+ border-left: 26px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 26px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 15px 15px;
+ -moz-border-radius: 0 0 15px 15px;
+ -webkit-border-bottom-right-radius: 15px;
+ -webkit-border-bottom-left-radius: 15px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/vendor/underscore/test/vendor/qunit.js b/vendor/underscore/test/vendor/qunit.js
new file mode 100644
index 000000000..c1570c252
--- /dev/null
+++ b/vendor/underscore/test/vendor/qunit.js
@@ -0,0 +1,1863 @@
+/**
+ * QUnit v1.8.0 - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function( window ) {
+
+var QUnit,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ defined = {
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch( e ) {
+ return false;
+ }
+ }())
+};
+
+function Test( settings ) {
+ extend( this, settings );
+ this.assertions = [];
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ init: function() {
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.name;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ testNumber: this.testNumber });
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if ( this.module !== config.previousModule ) {
+ if ( config.previousModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ } else if ( config.autorun ) {
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment );
+
+ runLoggingCallbacks( "testStart", QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ // allow utility functions to access the current test environment
+ // TODO why??
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment );
+ return;
+ }
+ try {
+ this.testEnvironment.setup.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+ }
+ },
+ run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: " + this.name;
+ }
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ if ( config.notrycatch ) {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ return;
+ }
+
+ try {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ if ( config.notrycatch ) {
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+ }
+ }
+ checkPollution();
+ },
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected == null ) {
+ QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+ } else if ( this.expected != null && this.expected != this.assertions.length ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected == null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var assertion, a, b, i, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
+
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ ol = document.createElement( "ol" );
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
+
+ li = document.createElement( "li" );
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+ }
+ }
+
+ if ( bad === 0 ) {
+ ol.style.display = "none";
+ }
+
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
+ b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
+
+ addEvent(b, "click", function() {
+ var next = b.nextSibling.nextSibling,
+ display = next.style.display;
+ next.style.display = display === "none" ? "block" : "none";
+ });
+
+ addEvent(b, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ testNumber: test.testNumber });
+ }
+ });
+
+ // `li` initialized at top of scope
+ li = id( this.id );
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ a = li.firstChild;
+ li.appendChild( b );
+ li.appendChild ( a );
+ li.appendChild( ol );
+
+ } else {
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ runLoggingCallbacks( "testDone", QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ }
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ config.currentModule = name;
+ config.currentModuleTestEnviroment = testEnvironment;
+ },
+
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test,
+ name = "" + escapeInnerText( testName ) + "";
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ name = "" + config.currentModule + ": " + name;
+ }
+
+ test = new Test({
+ name: name,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnviroment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
+ return;
+ }
+
+ test.queue();
+ },
+
+ // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ config.current.expected = asserts;
+ },
+
+ start: function( count ) {
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ window.setTimeout(function() {
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ config.blocking = false;
+ process( true );
+ }, 13);
+ } else {
+ config.blocking = false;
+ process( true );
+ }
+ },
+
+ stop: function( count ) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = window.setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// Asssert helpers
+// All of these must call either QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+QUnit.assert = {
+ /**
+ * Asserts rough true-ish result.
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+
+ var source,
+ details = {
+ result: result,
+ message: msg
+ };
+
+ msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+ msg = "" + msg + "";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "
Source:
" + escapeInnerText( source ) + "
";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
+ config.current.assertions.push({
+ result: result,
+ message: msg
+ });
+ },
+
+ /**
+ * Assert that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+ */
+ equal: function( actual, expected, message ) {
+ QUnit.push( expected == actual, actual, expected, message );
+ },
+
+ notEqual: function( actual, expected, message ) {
+ QUnit.push( expected != actual, actual, expected, message );
+ },
+
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
+ },
+
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
+ },
+
+ raises: function( block, expected, message ) {
+ var actual,
+ ok = false;
+
+ if ( typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ config.current.ignoreGlobalErrors = true;
+ try {
+ block.call( config.current.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ config.current.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ // expected is a regexp
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( actual );
+ // expected is a constructor
+ } else if ( actual instanceof expected ) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if ( expected.call( {}, actual ) === true ) {
+ ok = true;
+ }
+ }
+
+ QUnit.push( ok, actual, null, message );
+ }
+};
+
+// @deprecated: Kept assertion helpers in root for backwards compatibility
+extend( QUnit, QUnit.assert );
+
+/**
+ * @deprecated: Kept for backwards compatibility
+ * next step: remove entirely
+ */
+QUnit.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+ function F() {}
+ F.prototype = QUnit;
+ QUnit = new F();
+ // Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // when enabled, show only failing tests
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
+ hidepassed: false,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ urlConfig: [ "noglobals", "notrycatch" ],
+
+ // logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ if ( params[ 0 ] ) {
+ for ( i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ // Exact match of the module name
+ config.module = urlParams.module;
+
+ config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+ extend( window, QUnit );
+
+ // Expose QUnit object
+ window.QUnit = QUnit;
+}
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date(),
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 0
+ });
+
+ var tests, banner, result,
+ qunit = id( "qunit" );
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "